avcodec send_packet和receive_frame

news/2024/7/10 19:17:08 标签: ffmpeg

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

完整代码可以从github上获取

    /* 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中的调用流程一样。

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,可以看到桢的分布如下:

ffprobe -show_frames -select_streams v -of xml ~/video/b-frame.mp4 > videoframes.info

        <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中可以看到输入桢的次序是:

I B B P B B P B B P
 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解码流程。

 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。

        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解码消耗掉。

    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

http://www.niftyadmin.cn/n/5327378.html

相关文章

系列三、Spring Security中自定义用户名/密码

一、Spring Security中自定义用户名/密码 1.1、自定义用户名/密码 1.1.1、配置文件中配置 spring.security.user.nameroot spring.security.user.password123456 1.1.2、定义基于内存的用户 /*** Author : 一叶浮萍归大海* Date: 2024/1/11 21:50* Description:*/ Configu…

R语言【paleobioDB】——pbdb_map_occur():通过化石分布记录,创建一个RasterLayer对象,和一个显示取样力度的图

Package paleobioDB version 0.7.0 paleobioDB 包在2020年已经停止更新&#xff0c;该包依赖PBDB v1 API。 可以选择在Index of /src/contrib/Archive/paleobioDB (r-project.org)下载安装包后&#xff0c;执行本地安装。 Usage pbdb_map_occur (data, res5, col.int"whi…

Vulnhub-Raven-1

一、信息收集 端口扫描 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.7p1 Debian 5deb8u4 (protocol 2.0) | ssh-hostkey: | 1024 26:81:c1:f3:5e:01:ef:93:49:3d:91:1e:ae:8b:3c:fc (DSA) |_ 256 0e:85:71:a8:a2:c3:08:69:9c:91:c0:3f:84:18:df:…

Golang 替换数字卡码54题

文章目录 替换数字 54卡码网直接操作读取的[]byte 替换数字 54卡码网 本题为卡码网的54题&#xff0c;起为acm模式的答题 这里我们不在输入获得的[]byte类型上直接修改&#xff0c;而是保存在新建的[]byte上 golang小知识&#xff1a; 单引号’&#xff0c;表示byte类型或rune…

相比其他关系型数据库,亚信安慧AntDB JDBC驱动特性有哪些不同之处

关键字&#xff1a;JDBC&#xff0c;双引擎语法&#xff0c;安全加强&#xff0c;批量更新 使用Java语言进行各类应用程序的快速开发成为目前比较主要且流行的开发方式。JDBC是 Java 语言中用来连接和操作关系型数据库的 API&#xff0c;在业务程序与关系型数据库通信时&#…

3.0.0 网络安全技术

一、端口安全 1、端口隔离 1.1 简介 以太交换网络中为了实现报文之间的二层隔离&#xff0c;用户通常将*不同的端口*加入*不同的VLAN*&#xff0c;实现二层广播域的隔离。只通过VLAN实现报文二层隔离&#xff0c;会浪费有限的VLAN资源&#xff0c;同时也只能实现基础的隔离操…

深度学习记录--Train/dev/test sets

为什么需要训练集、验证集(简单交叉验证集)和测试集&#xff1f; 为了创建高效的神经网络&#xff0c;需要不断进行训练(迭代) 一个神经网络的产生 从最开始的想法idea开始&#xff0c;然后付诸于代码code&#xff0c;根据结果验证反过来对一开始的想法idea进行修正&#xf…

经典算法-模拟退火算法的python实现

经典算法-模拟退火算法的python实现 模拟退火算法基本思想 模拟退火算法来源于固体退火原理&#xff0c;将固体加温至充分高&#xff0c;再让其徐徐冷却。加温时&#xff0c;固体内部粒子随温度升高变为无序状&#xff0c;内能增大&#xff0c;而缓慢冷却时粒子又逐渐趋有序。…