首先交代一下环境,通常,这类问题随着编译环境、平台类型、版本等的更新变得不那么相同。
运行系统:Mac OS Big Sur:11.5.2
编码平台:Qt 5.15.2、Qt Creator 4.14.0
FFmpeg版本:4.2
测试片源基本信息:
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
creation_time : 2018-07-25T20:11:32.000000Z
Duration: 00:30:59.92, start: 0.000000, bitrate: 531 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1280x720 [SAR 1:1 DAR 16:9], 402 kb/s, 24 fps, 24 tbr, 90k tbn, 48 tbc (default)
Metadata:
creation_time : 2018-07-25T20:11:32.000000Z
handler_name : ISO Media file produced by Google Inc. Created on: 07/25/2018.
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 125 kb/s (default)
Metadata:
creation_time : 2018-07-25T20:11:32.000000Z
handler_name : ISO Media file produced by Google Inc. Created on: 07/25/2018.
按照函数字面意思,我们习惯上先调用avcodec_send_packet,发送packet给ffmpeg解码,然后再从ffmpeg的解码队列中获取解码后的帧数据(AVFrame)。
实际上,在很多网上的demo包括我自己的demo上也是这么实现的。
但是,这次把代码移植到QT上之后,出现了问题:
先调用avcodec_send_packet再调用avcodec_receive_frame的方式,出现无法调用avcodec_send_packet
将音频的AVPacket发送给ffmepg解码的情况,而视频能够正常解码。
avcodec_send_packet调用的异常码为:EAGAIN
源码中对该异常码的解释是:Resource temporarily unavailable
input is not accepted in the current state - user must read output with avcodec_receive_frame() (once all output is read, the packet should be resent, and the call will not fail with EAGAIN).
当前状态输入未被接收,用户必须通过avcodec_receive_frame函数读取输出。(一旦输出被读取,AVPacket需要重传,就不会出现EAGAIN异常。
说的比较明白,在这个状态下,需要先调用avcodec_receive_frame才行。
原来我的调用顺序是:
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
AVCodecContext *cc;
for (; ; ) {
ret = av_read_frame(formateContext, pkt); // AVPacket的内部空间会随着解封装自然增长
if (ret != 0) {
av_seek_frame(formateContext, videoStream, 0, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
continue;
}
if (pkt->stream_index == videoStream) {
cc = vcodec_ctx;
} else if (pkt->stream_index == audioStream) {
cc = acodec_ctx;
} else {
continue;
}
// 发送AVPacket到解码,音频流在这里出现了发送失败的情况(EAGAIN)。视频流正常
ret = avcodec_send_packet(cc, pkt);
if (ret != 0) {
qCritical() << pkt->stream_index << " avcodec_send_packet failed: " << av_err2str(ret);
continue;
}
av_packet_unref(pkt); // 释放AVPacket中的引用计数,防止不必要的内存占用
while(true) { // 循环获取AVFrame
ret = avcodec_receive_frame(vcodec_ctx, frame); // 获取解码数据
if (ret != 0) {
qCritical() << pkt->stream_index << "avcodec_receive_frame failed:" << av_err2str(ret);
break;
} else {
qCritical() << pkt->stream_index << " avcodec_receive_frame success: pts = " << frame->pts;
}
}
}
该段代码出现了音频流调用avcodec_send_packet发送失败的情况(EAGAIN)。视频流正常。
按照异常码提供的信息,将代码改为如下:
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
AVCodecContext *cc;
for (; ; ) {
ret = av_read_frame(formateContext, pkt); // AVPacket的内部空间会随着解封装自然增长
if (ret != 0) {
av_seek_frame(formateContext, videoStream, 0, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
continue;
}
if (pkt->stream_index == videoStream) {
cc = vcodec_ctx;
} else if (pkt->stream_index == audioStream) {
cc = acodec_ctx;
} else {
continue;
}
while(true) {
ret = avcodec_receive_frame(vcodec_ctx, frame);
if (ret != 0) {
// qCritical() << pkt->stream_index << "avcodec_receive_frame failed:" << av_err2str(ret);
break;
} else {
qCritical() << pkt->stream_index << " avcodec_receive_frame success: pts = " << frame->pts;
}
}
ret = avcodec_send_packet(cc, pkt);
if (ret != 0) {
// qCritical() << pkt->stream_index << " avcodec_send_packet failed: " << av_err2str(ret);
continue;
}
av_packet_unref(pkt); // 释放AVPacket中的引用计数,防止不必要的内存占用
}
就可以正常解码音视频了。
实际上,avcodec_send_packet和avcodec_receive_frame谁先调用,并不会影响的解码顺序。我猜测,在调用音频调用avcodec_send_packet时,之所以会出现EAGAIN异常,可能是AVFrame没有从FFMpeg的输出队列中取出完导致的。更多的证据,暂时还没有时间去找。
既然问题已经解决,先不继续深入了,还有更多任务等着呢。记录一下。