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数据来分析的.链接如下:
而本播是基于ffmpeg6.1, 以mpeg2 audio 数据来分析的解码过程.