avcodec_receive_frame 和 avcodec_send_packet的调用顺序

avcodec_receive_frame 和 avcodec_send_packet的调用顺序

环境

首先交代一下环境,通常,这类问题随着编译环境、平台类型、版本等的更新变得不那么相同。

运行系统: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的输出队列中取出完导致的。更多的证据,暂时还没有时间去找。

既然问题已经解决,先不继续深入了,还有更多任务等着呢。记录一下。

你可能感兴趣的:(ffmpeg,ffmpeg,EAGAIN)