ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux FFmpeg深度解析之avcodec_send_packet调用流程与实战(十七)

Linux FFmpeg深度解析之avcodec_send_packet调用流程与实战(十七)

简介:CSDN博客专家、《Android系统多媒体进阶实战》作者

博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀

人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

点击获取二维码


🍉🍉🍉文章目录🍉🍉🍉

      • 🌻1.前言
      • 🌻2.应用场景和用法
        • 2.1向解码器提交压缩数据包
        • 2.2函数原型与返回语义
      • 🌻3.调用流程剖析
        • 3.1检查上下文并保存输入包
        • 3.2按发送接收状态机推进解码
      • 🌻4.实战案例
        • 4.1准备解码对象
        • 4.2提交包并处理EAGAIN
        • 4.3发送与接收代码
      • 🌻5.总结

🌻1.前言

本篇目的:理解avcodec_send_packet()如何把压缩数据包提交给解码器,分析输入何时被接受、何时需要重试,并通过发送循环验证EAGAIN的处理方式。

Demuxer从媒体文件中取出压缩数据后,应用需要把AVPacket交给解码器。avcodec_send_packet()负责提交输入,并不保证一次调用就能立即产出AVFrame。如果解码器内部暂时无法接收新包,调用者需要先取出已有输出,再重试同一个包。

可以

暂时不能

Demuxer得到AVPacket

提交压缩数据

解码器能否接收

输入进入解码器状态

先读取已有AVFrame

后续调用读取解码结果

🌻2.应用场景和用法

2.1向解码器提交压缩数据包

解码流程中,demuxer读取容器并将压缩数据封装为AVPacket,解码器通过avcodec_send_packet()接收它。一个包可能产生零帧、一帧或多帧,因此发送和接收不能假设严格的一包一帧。

常见使用场景:

场景调用方式需要关注的结果
常规解码发送demuxer取出的目标流AVPacket成功表示包被接受,后续继续取帧
解码器暂时忙收到AVERROR(EAGAIN)先调用接收接口取出输出,再重试原包
输入文件结束发送NULL包通知解码器进入draining状态,排出延迟帧
seek或切换码流后恢复先清空旧解码状态使用avcodec_flush_buffers()后再提交新包
2.2函数原型与返回语义
intavcodec_send_packet(AVCodecContext*avctx,constAVPacket*avpkt);
参数或返回值含义
avctx已打开的解码器上下文
avpkt待解码的压缩包;传入NULL表示输入结束并启动draining
0输入包已被接受,或draining请求已接受
AVERROR(EAGAIN)当前无法接收新输入,必须先读取输出,之后重试当前包
AVERROR_EOF已进入draining状态,不能再发送普通包
AVERROR(EINVAL)上下文未打开、上下文对应编码器,或状态不允许提交

提交接口会为内部输入包建立引用;调用者仍需按自己的生命周期释放AVPacket。如果包不是引用计数数据,FFmpeg可能需要复制数据。

相邻接口的分工是:av_read_frame()从容器读取压缩包,avcodec_send_packet()提交输入,avcodec_receive_frame()取出解码帧,avcodec_flush_buffers()在seek等场景重置解码器状态。

🌻3.调用流程剖析

3.1检查上下文并保存输入包

源码先确认AVCodecContext已经打开且对应解码器。如果上下文已经开始draining,普通输入会返回AVERROR_EOF。

对于带数据或side data的包,函数先检查内部输入包槽位是否为空。如果槽位仍有未处理数据,返回AVERROR(EAGAIN);否则调用av_packet_ref()保存输入包引用。调用成功仅表示包已进入解码器输入状态,不等于已经得到输出帧。

否

是

AVCodecContext与AVPacket

检查解码器和draining状态

内部输入槽是否可用

返回EAGAIN

引用输入包并保存

返回提交结果

3.2按发送接收状态机推进解码

avcodec_send_packet()和avcodec_receive_frame()构成一组相互配合的接口。当发送返回EAGAIN,应用要读取输出;输出读取到EAGAIN后,再重试尚未被接受的输入包。FFmpeg保证发送端和接收端不会同时处于EAGAIN,因此调用者可以通过另一端继续推进。

成功

EAGAIN

得到帧

EAGAIN

发送当前AVPacket

发送结果

循环读取AVFrame

接收结果

重试当前AVPacket

对EAGAIN收到的包不能直接丢弃,也不能读取完输出后改发下一个包;这个包尚未被接受,必须重试它。输入文件结束时发送NULL包是另一种状态转换,之后应持续接收,直到AVERROR_EOF。

🌻4.实战案例

4.1准备解码对象

先通过avcodec_find_decoder()查找解码器,创建并打开AVCodecContext,再从demuxer取得匹配流的AVPacket。发送前应确认包属于目标音视频流,并为接收过程准备可复用的AVFrame。

4.2提交包并处理EAGAIN

发送循环需要保留当前包,直到提交成功。若发送返回EAGAIN,先排空当前可用帧,再重试相同的AVPacket。

4.3发送与接收代码

下面的函数接收一个已经准备好的解码器上下文、压缩包和帧对象。process_frame()代表应用自己的帧处理逻辑。

#include<libavcodec/avcodec.h>#include<libavutil/error.h>/* 读取当前可用帧,直到解码器需要更多输入 */staticintreceive_available_frames(AVCodecContext*decoder,AVFrame*frame){intret;while(1){ret=avcodec_receive_frame(decoder,frame);if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF)return0;if(ret<0)returnret;/* 在这里处理当前解码帧 */process_frame(frame);/* 及时释放当前帧持有的引用 */av_frame_unref(frame);}}/* 提交一个包;若输入槽暂不可用,先接收帧再重试 */staticintsubmit_packet(AVCodecContext*decoder,constAVPacket*packet,AVFrame*frame){intret;while(1){ret=avcodec_send_packet(decoder,packet);if(ret!=AVERROR(EAGAIN))break;/* EAGAIN表示当前包未被接受,先取出已有输出 */ret=receive_available_frames(decoder,frame);if(ret<0)returnret;}if(ret<0)returnret;/* 包已接受,继续取出当前已经产生的帧 */returnreceive_available_frames(decoder,frame);}

实战中,submit_packet()成功后,demuxer才继续读取下一包。遇到文件末尾时,应单独发送NULL包并继续接收,不能把普通包的EAGAIN处理误用为输入结束流程。

🌻5.总结

avcodec_send_packet()把压缩包提交给解码器,遇到EAGAIN时要求调用者先接收输出并重试尚未被接受的原包。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进