标注 avcodec_send_packet 和 avcodec_receive_frame 函数


author: hjjdebug

date: 2025年 12月 03日 星期三 10:01:14 CST

descrip: 标注 avcodec_send_packet 和 avcodec_receive_frame 函数


文章目录

  • [1 avcodec_send_packet 函数](#1 avcodec_send_packet 函数)
  • [2 avcodec_receive_frame 函数](#2 avcodec_receive_frame 函数)
  • [3 ff_decode_receive_frame 函数](#3 ff_decode_receive_frame 函数)
  • [第0层: audio_get_buffer 代码注释](#第0层: audio_get_buffer 代码注释)
  • [第1层: avcodec_default_get_buffer2](#第1层: avcodec_default_get_buffer2)
  • [第2层: ff_get_buffer](#第2层: ff_get_buffer)
  • [第3层: mp_decode_frame](#第3层: mp_decode_frame)
  • [第4层: decode_frame](#第4层: decode_frame)
  • [第5层: decode_simple_internal](#第5层: decode_simple_internal)
  • [第7层 decode_receive_frame_internal](#第7层 decode_receive_frame_internal)

avcodec_send_packet, avcodec_receive_frame 是ffmpeg 解码的核心调用函数
基于ffmpeg6.1, 好像比ffmpeg4 更简洁了.

为了简洁,以下保留了关键代码,忽略部分容错,打印,健康性测试等不重要代码.

1 avcodec_send_packet 函数

cpp 复制代码
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

	//avpkt 有效, 则先释放掉内部的保留的pkt avci->buffer_pkt
    if (avpkt && (avpkt->data || avpkt->side_data_elems)) 
	{
		//用内部的pkt 去引用外部的 avpkt
        ret = av_packet_ref(avci->buffer_pkt, avpkt);
        if (ret < 0)
            return ret;
    } 
	//要确保内部的 buffer_frame 是空的
    if (!avci->buffer_frame->buf[0] ) 
	{
		// 仅一个关键函数, 把frame 接受到内部的frame(avci->buffer_frame)
        ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    return 0;
}

通过代码标注,感觉是如此简单,

send 一个 packet, 用内部函数接受frame 并保存到avci->buffer_frame 就可以了

2 avcodec_receive_frame 函数

cpp 复制代码
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    av_frame_unref(frame); //先释放掉旧frame, 准备接受新frame
    if (av_codec_is_decoder(avctx->codec)) //查看codec 是否是decoder, 这个简单,查看codec属性就可以了
        return ff_decode_receive_frame(avctx, frame); //解码frame
    return ff_encode_receive_frame(avctx, frame);     //编码frame
}

简单的包装函数,调用 ff_decode_receive_frame 即可.

3 ff_decode_receive_frame 函数

cpp 复制代码
int ff_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;
    if (avci->buffer_frame->buf[0])  //如果内部frame有效, 把内部frame直接移动过去就可以了.
	{
        av_frame_move_ref(frame, avci->buffer_frame);
    } 
	else 
	{
        ret = decode_receive_frame_internal(avctx, frame); //否则调用内部接受frame函数到frame
        if (ret < 0)
            return ret;
    }

    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        ret = apply_cropping(avctx, frame); // 视频要对frame 做cropping
        if (ret < 0)
            goto fail;
    }

    avctx->frame_num++;
    return 0;
fail:
    av_frame_unref(frame);
    return ret;
}

看到了吧, 因为send_packet的时候 已经把frame接受到avci->buffer_frame,

所以此时只是把avci->buffer_frame移动到输出参数frame 就可以了.

如果内部frame 为空,与avcodec_send_packet 一样会调用 decode_receive_frame_internal 接受到frame

事情是如此简单.

由此知道 decode_receive_frame_internal 才是关键函数.

且慢, 真的如此简单吗?

我这里给一个mpeg2 音频包解码的例子, 来分析一下 decode_receive_frame_internal 的底层工作原理.

看一下下面的调用栈, 别怕,我们只需要搞清楚工作原理,并不分析其具体算法. 只要关心4-7就够了. 顺便了解一下其它.

cpp 复制代码
 0 in audio_get_buffer of libavcodec/get_buffer.c:206
 1 in avcodec_default_get_buffer2 of libavcodec/get_buffer.c:278
 2 in ff_get_buffer of libavcodec/decode.c:1672
 3 in mp_decode_frame of libavcodec/mpegaudiodec_template.c:1523
 4 in decode_frame of libavcodec/mpegaudiodec_template.c:1601
 5 in decode_simple_internal of libavcodec/decode.c:430
 6 in decode_simple_receive_frame of libavcodec/decode.c:609
 7 in decode_receive_frame_internal of libavcodec/decode.c:637
 8 in avcodec_send_packet of libavcodec/decode.c:734
 9 in decode of decode_audio.c:79
10 in main of decode_audio.c:191

第0层: audio_get_buffer 代码注释

根据planes 个数来分配内存, 从缓冲池中(pool->pools[0])来申请内存,保存到AVBufferRef 指针

为frame->data[i], frame->extended_data[i] 赋值, 考虑了平面数多于8个的情况.

我们知道,双声道,planar模式需要3个平面, 6.1声道,planar模式需要7个平面, 但还有更多的16声道音频,

22声道音频. 多于8个的要放到frame->extended_buf 中, 虽然我们最常见的还是2声道

最底层的操作总是分配内存, 计算机的工作离不开内存

cpp 复制代码
static int audio_get_buffer(AVCodecContext *avctx, AVFrame *frame)
{
//FramePool有4个bufferPool  AVBufferPool *pools[4]; int linesize[4]
    FramePool *pool = avctx->internal->pool; 
    int planes = pool->planes;  //对于本测试, 是单声道音频,只有一个plane
    int i;

    frame->linesize[0] = pool->linesize[0]; // 只需要用第一个bufferPoll的linesize.  

    if (planes > AV_NUM_DATA_POINTERS) { //平面数超过8个时,分配扩展缓冲,虽然很少用到.
        frame->extended_data = av_calloc(planes, sizeof(*frame->extended_data));
        frame->nb_extended_buf = planes - AV_NUM_DATA_POINTERS;
        frame->extended_buf  = av_calloc(frame->nb_extended_buf,
                                          sizeof(*frame->extended_buf));
        if (!frame->extended_data || !frame->extended_buf) {
            av_freep(&frame->extended_data);
            av_freep(&frame->extended_buf);
            return AVERROR(ENOMEM);
        }
    } else {
        frame->extended_data = frame->data; //否则让扩展数据就指向frame 数据
        av_assert0(frame->nb_extended_buf == 0);
    }

	//我工作平面从缓冲池分配内存
    for (i = 0; i < FFMIN(planes, AV_NUM_DATA_POINTERS); i++) {
        frame->buf[i] = av_buffer_pool_get(pool->pools[0]);
        if (!frame->buf[i])
            goto fail;
        frame->extended_data[i] = frame->data[i] = frame->buf[i]->data;
    }
    for (i = 0; i < frame->nb_extended_buf; i++) { //带扩展缓冲的情况
        frame->extended_buf[i] = av_buffer_pool_get(pool->pools[0]);
        if (!frame->extended_buf[i])
            goto fail;
        frame->extended_data[i + AV_NUM_DATA_POINTERS] = frame->extended_buf[i]->data;
    }
    return 0;
fail:
    av_frame_unref(frame);
    return AVERROR(ENOMEM);
}

第1层: avcodec_default_get_buffer2

这是个散转结构.

为frame申请内存缓冲区, 根据codec_context的类型,申请硬件缓冲区,视频缓冲区或者音频缓冲区

详细代码.

cpp 复制代码
int avcodec_default_get_buffer2(AVCodecContext *avctx, AVFrame *frame, int flags)
{
    int ret;
    if (avctx->hw_frames_ctx) { //如果有硬件加速
        ret = av_hwframe_get_buffer(avctx->hw_frames_ctx, frame, 0);
        frame->width  = avctx->coded_width;
        frame->height = avctx->coded_height;
        return ret;
    }
    if ((ret = update_frame_pool(avctx, frame)) < 0)  //更新frame_pool
        return ret;

    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        return video_get_buffer(avctx, frame);
    case AVMEDIA_TYPE_AUDIO:
        return audio_get_buffer(avctx, frame); //为frame 申请内存储存audio 数据,上面分析
    default:
        return -1;
    }
}

第2层: ff_get_buffer

// 准备内存缓冲区及其它一些辅助代码

cpp 复制代码
int ff_get_buffer(AVCodecContext *avctx, AVFrame *frame, int flags)
{
    int override_dimensions = 1;
    int ret;
    ret = ff_decode_frame_props(avctx, frame); //解码frame属性
    if (ret < 0) goto fail;

    avctx->sw_pix_fmt = avctx->pix_fmt;

    ret = avctx->get_buffer2(avctx, frame, flags); //分配内存
    if (ret < 0) goto fail;

    validate_avframe_allocation(avctx, frame); //检查frame成员的合法性

    ret = ff_attach_decode_data(frame); //连接解码数据
    if (ret < 0) goto fail;

end:
    return ret;
}

第3层: mp_decode_frame

// 把解出来的数据块进行组装成frame ,

第4层: decode_frame

// 这才是具体干活的地方. 不同的decoder会调用到不同的解码frame算法

研究算法的人需要关注3层, 4层, 这是具体的实现类,

接口类是不干活的,最多也就是copy一下数据, 而实现类才是干活的地方.

如果你想了解frame 中的参数的来历及数据的来历,就要研究这些代码了, 例如此例mppeg2数据的解码过程

本播就只是点到为止了. 我们重点分析的是框架.

第5层: decode_simple_internal

//又回到了框架上,在libavcode/decode.c 中

核心包装代码, 这里实现向具体的实现类调用. 技巧就是通过具体的codec 对象实现调用跳转.

cpp 复制代码
static inline int decode_simple_internal(AVCodecContext *avctx, AVFrame *frame, int64_t *discarded_samples)
{
    AVCodecInternal   *avci = avctx->internal;
    AVPacket     *const pkt = avci->in_pkt;
    const FFCodec *const codec = ffcodec(avctx->codec);
    int got_frame, consumed;
    int ret;

    got_frame = 0;

    if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) {
        consumed = ff_thread_decode_frame(avctx, frame, &got_frame, pkt); //如果单独开线程
    } 
	else 
	{
        consumed = codec->cb.decode(avctx, frame, &got_frame, pkt);  // 这里向具体实现类调用解码获取frame 函数
        if (!(codec->caps_internal & FF_CODEC_CAP_SETS_PKT_DTS))
            frame->pkt_dts = pkt->dts;
    }

    if (consumed < 0)
        ret = consumed;
    if (consumed >= 0 && avctx->codec->type == AVMEDIA_TYPE_VIDEO)
        consumed = pkt->size;

    if (!ret)
        av_assert0(frame->buf[0]);
    if (ret == AVERROR(EAGAIN))
        ret = 0;

    if (consumed >= pkt->size || ret < 0)  //已经消耗完了pkt中的数据,直接释放包,返回.
	{
        av_packet_unref(pkt);  
    } 
	else 
	{
        pkt->data                += consumed;  //pkt 中的size要减少,指针要前移,应对一个pkt解多个frame 的情况.
        pkt->size                -= consumed;
        pkt->pts                  = AV_NOPTS_VALUE;
        pkt->dts                  = AV_NOPTS_VALUE;
        if (!(codec->caps_internal & FF_CODEC_CAP_SETS_FRAME_PROPS)) 
		{
            avci->last_pkt_props->pts = AV_NOPTS_VALUE;
            avci->last_pkt_props->dts = AV_NOPTS_VALUE;
        }
    }

    return ret;
}

第6层 decode_simple_receive_frame 函数

简单调用包装函数,如果没有得到frame, 就继续调用decode_simple_internal()

cpp 复制代码
static int decode_simple_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    int ret;
    int64_t discarded_samples = 0;

    while (!frame->buf[0]) { //如果没有获取到frame, 要反复调用 decode_simple_internal
        if (discarded_samples > avctx->max_samples)
            return AVERROR(EAGAIN);
        ret = decode_simple_internal(avctx, frame, &discarded_samples);
        if (ret < 0)
            return ret;
    }

    return 0;
}

第7层 decode_receive_frame_internal

区分了到底是直接调用接口类来接受frame, 还是通过通用包装接口decode_simple_receive_frame来调用,

并且还有接受到frame后的一些后处理代码.

cpp 复制代码
static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    const FFCodec *const codec = ffcodec(avctx->codec);
    int ret, ok;

    if (codec->cb_type == FF_CODEC_CB_TYPE_RECEIVE_FRAME) {
        ret = codec->cb.receive_frame(avctx, frame);  //到底是直接调用实现类还是通过simple调用
        emms_c();
        if (!ret) {
            if (avctx->codec->type == AVMEDIA_TYPE_VIDEO)
                ret = (frame->flags & AV_FRAME_FLAG_DISCARD) ? AVERROR(EAGAIN) : 0;
            else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
                int64_t discarded_samples = 0;
                ret = discard_samples(avctx, frame, &discarded_samples);
            }
        }
    } else
        ret = decode_simple_receive_frame(avctx, frame);

    if (ret == AVERROR_EOF)
        avci->draining_done = 1;

    /* preserve ret */
    ok = detect_colorspace(avctx, frame);
    if (ok < 0) {
        av_frame_unref(frame);
        return ok;
    }

    if (!ret) { // 正常返回之后
        if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
            if (!frame->width)
                frame->width = avctx->width;
            if (!frame->height)
                frame->height = avctx->height;
        } else
            frame->flags |= AV_FRAME_FLAG_KEY;

        ret = fill_frame_props(avctx, frame); //填充frame 属性
        if (ret < 0) {
            av_frame_unref(frame);
            return ret;
        }
		//填充frame 的其它成员变量
        frame->key_frame = !!(frame->flags & AV_FRAME_FLAG_KEY);
        frame->best_effort_timestamp = guess_correct_pts(avctx,
                                                         frame->pts,
                                                         frame->pkt_dts);

        frame->pkt_duration = frame->duration;

        if (frame->private_ref)  //私有参考要处理,并最后释放掉内存.
		{
            FrameDecodeData *fdd = (FrameDecodeData*)frame->private_ref->data;

            if (fdd->post_process) {
                ret = fdd->post_process(avctx, frame);
                if (ret < 0) {
                    av_frame_unref(frame);
                    return ret;
                }
            }
        }
    }

    /* free the per-frame decode data */
    av_buffer_unref(&frame->private_ref);

    return ret;
}

如此,代码比较粗造的完全标注了一遍. 如果你要想研究具体算法,那就跟进实现类的代码.

写完此篇博客,发现自己1年前也曾写过类似的内容,是基于ffmpeg4.4,以解码h264数据来分析的.链接如下:

ffmpeg 解包流程

而本播是基于ffmpeg6.1, 以mpeg2 audio 数据来分析的解码过程.

相关推荐
别动哪条鱼2 小时前
FFmpeg API 数据结构及其详细说明:
数据结构·ffmpeg·音视频·aac
Industio_触觉智能2 小时前
瑞芯微RK3568平台FFmpeg硬件编解码移植及性能测试实战攻略
ffmpeg·rk3588·rk3568·瑞芯微·rk3562·rk3576
别动哪条鱼8 小时前
AAC ADTS 帧结构信息
网络·数据结构·ffmpeg·音视频·aac
tokepson17 小时前
关于音频处理工具FFmpeg | 笔记备注
计算机·ffmpeg·技术·记录
Hello.Reader1 天前
用纯 Go 实现一个 AES-128 加密 m3u8 视频下载器(不依赖 ffmpeg)
golang·ffmpeg·音视频·m3u8
你好音视频1 天前
RTSP推流:RTP包组装逻辑详解
ffmpeg·音视频
pu_taoc1 天前
ffmpeg实战2-从MP4文件提取 音频和视频
c语言·c++·ffmpeg·音视频
香蕉ai大玩家1 天前
ffmpeg.dll丢失怎么办?ffmpeg.dll找不到是什么情况
ffmpeg
爱宇阳1 天前
使用 PowerShell + ffmpeg 自动压缩视频(支持 CRF、无损、目标大小模式)
ffmpeg·音视频