Linux FFmpeg深度解析之avcodec_send_packet调用流程与实战(十七)
简介:CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀
人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
点击获取二维码
🍉🍉🍉文章目录🍉🍉🍉
- 🌻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。如果解码器内部暂时无法接收新包,调用者需要先取出已有输出,再重试同一个包。
🌻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()保存输入包引用。调用成功仅表示包已进入解码器输入状态,不等于已经得到输出帧。
3.2按发送接收状态机推进解码
avcodec_send_packet()和avcodec_receive_frame()构成一组相互配合的接口。当发送返回EAGAIN,应用要读取输出;输出读取到EAGAIN后,再重试尚未被接受的输入包。FFmpeg保证发送端和接收端不会同时处于EAGAIN,因此调用者可以通过另一端继续推进。
对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时要求调用者先接收输出并重试尚未被接受的原包。