avcodec send_packet和receive_frame

下面是解码的过程代码,对输入给解码器的pkt桢类型进行判断,关键桢打印出is key frame,解码出来的桢根据pict_type打印桢类型出I/P/B桢类型,从这里也可以看出来,没解码之前,AVPacket只能得到是否关键帧,要知道桢类型,必须在解码后。

完整代码可以从github上获取

c 复制代码
    /* Read packets from input file and decode them */
    while (av_read_frame(fmt_ctx, pkt) >= 0) {
        if (pkt->stream_index == video_stream_idx) {

            if (pkt->flags & AV_PKT_FLAG_KEY) {
                av_log(NULL, AV_LOG_INFO, " in   is key frame!\n");
            } else {
                av_log(NULL, AV_LOG_INFO, " in is't key frame!\n");
            }

            /* Send packet to decoder */
            ret = avcodec_send_packet(codec_ctx, pkt);
            if (ret < 0) {
                fprintf(stderr, "Error sending packet to decoder\n");
                exit(1);
            }

            /* Receive frame from decoder */
            while (ret >= 0) {
                ret = avcodec_receive_frame(codec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    fprintf(stderr, "Error receiving frame from decoder\n");
                    exit(1);
                }

                if (frame->pict_type == AV_PICTURE_TYPE_I) {
                    av_log(NULL, AV_LOG_INFO, "  out   is I frame!\n");
                } else if (frame->pict_type == AV_PICTURE_TYPE_P) {
                    av_log(NULL, AV_LOG_INFO, "  out   is P frame!\n");
                } else if (frame->pict_type == AV_PICTURE_TYPE_B) {
                    av_log(NULL, AV_LOG_INFO, "  out   is B frame!\n");
                }

                /* Write YUV data to output file */
                fwrite(frame->data[0], 1, codec_ctx->width * codec_ctx->height, outfile);
                fwrite(frame->data[1], 1, codec_ctx->width * codec_ctx->height / 4, outfile);
                fwrite(frame->data[2], 1, codec_ctx->width * codec_ctx->height / 4, outfile);
            }
        }
        av_packet_unref(pkt);
    }

FFmpeg解码流程

avcodec_send_packet先对avpkt进行ref操作,然后发送给bsf,然后判断avci->buffer_frame->buf为NULL,就调用decode_receive_frame_internal进行解码,进入decode_simple_internal后,ff_decode_get_packet会先从bsf中获取packet,然后调用解码器解码函数进行解码。

avcodec_receive_frame中也会先判断avci->buffer_frame->buf[0],如果不为NULL,说明前面send_packet的时候已经解码出来了,这次调用只需要进行ref操作。如果avci->buffer_frame->buf[0]为NULL,调用decode_receive_frame_internal解码,和前面send_packet中的调用流程一样。

bash 复制代码
avcodec_send_packet
  -> av_packet_ref(avci->buffer_pkt, avpkt)
  -> av_bsf_send_packet(avci->bsf, avci->buffer_pkt)
  -> !avci->buffer_frame->buf[0]
      -> decode_receive_frame_internal
          -> decode_simple_receive_frame(avctx, frame)
             -> decode_simple_internal
                -> ff_decode_get_packet
                   -> av_bsf_receive_packet
                      -> ff_bsf(ctx->filter)->filter(ctx, pkt)
                -> h264_decode_frame
                    got_frame -> return

avcodec_receive_frame # 循环receive,直到返回EAGAIN
  -> avci->buffer_frame->buf[0] # 前面已经解码出来
     -> av_frame_move_ref(frame, avci->buffer_frame) #返回继续解码
  -> !avci->buffer_frame->buf[0] #前面没有解码
     -> decode_receive_frame_internal
        -> decode_simple_receive_frame(avctx, frame)
           -> decode_simple_internal
              -> ff_decode_get_packet
                 -> av_bsf_receive_packet
                    -> ff_bsf(ctx->filter)->filter(ctx, pkt)
              -> h264_decode_frame
                   got_frame -> return

解码含B桢的视频

解码含B桢的视频,通过ffprobe把前面几桢的frame信息输出到xml,可以看到桢的分布如下:

bash 复制代码
ffprobe -show_frames -select_streams v -of xml ~/video/b-frame.mp4 > videoframes.info
xml 复制代码
        <frame key_frame="1" pts="0"  pkt_dts="0" pkt_dts_time="0.000000" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="I">
            <side_data_list>
                <side_data type="H.26[45] User Data Unregistered SEI message">
                    <side_datum key="side_data_type" value="H.26[45] User Data Unregistered SEI message"/>
                </side_data>
            </side_data_list>
        </frame>
        <frame key_frame="0" pkt_dts="512" pkt_dts_time="0.040000" pkt_size="921" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="1024" pkt_dts_time="0.080000" pkt_size="250" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="1536" pkt_dts_time="0.120000" pkt_size="2663" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="P"  />
        <frame key_frame="0" pkt_dts="2048" pkt_dts_time="0.160000" pkt_size="400" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="2560" pkt_dts_time="0.200000" pkt_size="247" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="3072" pkt_dts_time="0.240000" pkt_size="774" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="P"  />
        <frame key_frame="0" pkt_dts="3584" pkt_dts_time="0.280000" pkt_size="353" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="4096" pkt_dts_time="0.320000" pkt_size="197" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="4608" pkt_dts_time="0.360000" pkt_size="206" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="P"  />
        <frame key_frame="0" pkt_dts="5120" pkt_dts_time="0.400000" pkt_size="332" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="5632" pkt_dts_time="0.440000" pkt_size="15012" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="B"  />
        <frame key_frame="0" pkt_dts="6144" pkt_dts_time="0.480000" pkt_size="20861" width="1920" height="1080"  pix_fmt="yuv420p" pict_type="P"  />

从前面的xml中可以看到输入桢的次序是:

bash 复制代码
I B B P B B P B B P
cs 复制代码
 in   is key frame! # in I
 in is't key frame! # in B
 in is't key frame! # in B

 # 进三桢I B B解码出一帧I
  out   is I frame! #     out I

# 然后进一帧P解码出一帧B
 in is't key frame! # in P
  out   is B frame! #     out B

# 然后进一帧B解码出一帧B
 in is't key frame! # in B
  out   is B frame! #     out B

# 然后进一帧B解码出一帧P
 in is't key frame! # in B
  out   is P frame! #     out P

# 然后进一帧P解码出一帧B
 in is't key frame! # in P
   out   is B frame! #     out B

# 然后进一帧B解码出一帧B
 in is't key frame! # in B
  out   is B frame! #     out B

解码不含B桢的视频

通过debug和log可以看出,不含B桢的视频是进一帧出一帧,avcodec_send_packet之后,decoder实际上会被调用一次解码出来一帧,avcodec_receive_frame中调用ff_decode_receive_frame也是判断avci->buffer_frame->buf[0]是否为NULL,不为NULL时,av_frame_move_ref返回,不会再走decode_receive_frame_internal解码流程。

bash 复制代码
 in   is key frame!
  out   is I frame!
 in   is key frame!
  out   is I frame!
 in   is key frame!
  out   is I frame!
 in   is key frame!

所以使用avcodec_send_packet/avcodec_receive_frameavcodec_send_packet之后需要将frame及时取出来,不然第三次avcodec_send_packet之后,判断avci->buffer_pkt的地方就会返回EAGAIN。

c 复制代码
        if (!AVPACKET_IS_EMPTY(avci->buffer_pkt))
            return AVERROR(EAGAIN);

原因是这样的:

第一个avcodec_send_packet进来,av_packet_ref(avci->buffer_pkt, avpkt)后,调用decode_receive_frame_internal解码,正常返回。

第二个avcodec_send_packet进来,av_packet_ref(avci->buffer_pkt, avpkt)后,因为avci->buffer_frame->buf[0]不为NULL,没有调用decode_receive_frame_internal解码消耗掉。

c 复制代码
    if (consumed >= pkt->size || ret < 0) {
       av_packet_unref(pkt);

消耗掉以后就会在decode_simple_internal中调用av_packet_unref

第三个avcodec_send_packet进来,因为前面没有解码消耗,avci->buffer_pkt->data不为NULL,AVPACKET_IS_EMPTY判断失败,返回EAGAIN。

所以在使用FFmpeg解码时候,avcodec_send_packetavcodec_receive_frame得在一起出现,receive_frame需要及时的将frame从avci->buffer_frame中取出。

  • 对于没有B桢的视频,没有及时取走后,因为第二个packet没有被解码,第三个以上的buf进来都会报EAGAIN
  • 对于有B桢的视频,进去几桢之后,没有及时取走,后面就不会再调用decode解码,前面的IBBPBB的桢排布,在第五个buf进来的时候也会报EAGAIN
相关推荐
橘子味的茶二16 小时前
ffmpeg内存模型
ffmpeg
TPCloud16 小时前
windows 11编译安装ffmpeg(包含ffplay)
windows·ffmpeg·源码安装·mysys
runing_an_min1 天前
ffmpeg视频滤镜:缓入缓出-fade
ffmpeg·音视频·fade·缓出·缓入
ssslar2 天前
FFMPEG录屏(22)--- Linux 下基于X11枚举所有显示屏,并获取大小和截图等信息
linux·运维·ffmpeg
MonkeyKing_sunyuhua2 天前
FFmpeg 怎么裁剪m4a的音频,从一个时间点开始,裁剪15秒钟的视频
ffmpeg·音视频
DO_Community2 天前
教程:FFmpeg结合GPU实现720p至4K视频转换
ffmpeg·音视频
x66ccff2 天前
使用NVIDIA GPU加速FFmpeg视频压制:完全指南
ffmpeg·音视频
冷眼Σ(-᷅_-᷄๑)3 天前
如何使用ffmpeg命令行进行录屏
ffmpeg
Lary_Rock3 天前
ubuntu22.04 安装ffmpeg
ffmpeg
m0_623171553 天前
Windows搭建流媒体服务并使用ffmpeg推流播放rtsp和rtmp流
windows·ffmpeg