简介: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区分EAGAIN与EOF
- 🌻4.实战案例
- 4.1准备编码器输出循环
- 4.2读取包并处理背压状态
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
avcodec_receive_packet()如何从编码器读取压缩数据,分析内部包缓存、编码器输出和调用者持有的AVPacket之间的关系,并通过收包循环验证编码输出的读取方式。
编码器接收原始AVFrame后,不一定马上输出压缩包。编码器可能积累输入,执行帧重排序,或等待更多数据。avcodec_receive_packet()负责读取当前已经可用的编码结果。
成功返回时,输出包是引用计数的AVPacket。下一次调用该接口前,FFmpeg会先对传入的包执行av_packet_unref(),因此调用者应在再次收包前处理或转移上一次包的数据。
🌻2.应用场景和用法
2.1循环读取编码输出
avcodec_receive_packet()通常在每次提交音视频帧后调用,并循环读取,直到编码器返回AVERROR(EAGAIN)。这个循环可以处理一帧输入产生零个、一个或多个编码包的情况。
| 使用场景 | 读取方式 | 判断依据 |
|---|---|---|
| 普通编码过程 | 提交帧后循环收包 | AVERROR(EAGAIN)表示当前需要更多输入 |
| 编码器有延迟 | 多次提交帧后继续收包 | 每个输入帧不保证立即对应一个输出包 |
| 结束排空 | 先发送NULL帧,再持续收包 | AVERROR_EOF表示编码器输出完成 |
| 写入封装器或文件 | 收到包后写入,再释放包引用 | 包数据有效期由引用计数对象管理 |
2.2函数原型与返回语义
函数原型如下:
intavcodec_receive_packet(AVCodecContext*avctx,AVPacket*avpkt);| 参数或返回值 | 含义 |
|---|---|
avctx | 已打开的编码器上下文 |
avpkt | 输出包对象;函数开始时会先释放其已有引用 |
0 | 成功得到一个编码包 |
AVERROR(EAGAIN) | 当前没有可读输出,应继续提交输入帧 |
AVERROR_EOF | 编码器已经排空,不会再产生输出包 |
| 其他负值 | 编码器未打开、上下文类型不符或实际编码错误 |
接口只读取编码输出,不负责写文件、封装容器或永久保存包数据。调用者必须在下一次复用同一个AVPacket前处理当前输出,并通过av_packet_unref()释放引用;若要延长数据生命周期,应使用引用或移动引用接口管理包。
🌻3.调用流程剖析
3.1优先取出内部缓存包
avcodec_receive_packet()首先调用av_packet_unref(avpkt)清理调用者传入对象的旧内容,然后检查编码器上下文是否打开,以及它是否确实是编码器。
如果FFmpeg内部的buffer_pkt中已有数据,函数通过av_packet_move_ref()把内部包引用移动到调用者的avpkt。移动后,内部包不再持有该引用,调用者负责管理输出包的生命周期。
如果内部缓存为空,函数进入编码器实际输出路径。根据编码器接口类型,FFmpeg调用新式的receive_packet回调,或使用兼容路径执行编码并取得包。
成功输出的数据包必须是引用计数的。调用者若持续收包但不释放或转移包引用,会造成内存持续占用。
3.2区分EAGAIN与EOF
普通编码阶段收到AVERROR(EAGAIN),表示当前没有新包可取,编码器需要更多输入。调用者应提交下一帧。
结束时,调用者先通过avcodec_send_frame(ctx, NULL)通知编码器停止接收普通输入。随后继续调用avcodec_receive_packet(),直到返回AVERROR_EOF。这个结束信号和普通阶段的AVERROR(EAGAIN)含义不同,不能将两者都当作“编码结束”。
🌻4.实战案例
4.1准备编码器输出循环
下面的函数假设AVCodecContext已经配置并打开,输入帧也已经准备好。AVPacket由调用者创建,函数负责读取所有当前可用的包。
4.2读取包并处理背压状态
收包循环中,成功时处理包内容并释放引用。AVERROR(EAGAIN)表示当前批次读取完成;其他错误码交给上层处理。结束排空阶段使用同一循环,但需要持续读取到AVERROR_EOF。
4.3完整代码
#include<stdio.h>#include<libavcodec/avcodec.h>#include<libavutil/error.h>/* 读取当前可用的全部编码包 */staticintreceive_packets(AVCodecContext*ctx,AVPacket*pkt,FILE*output){intret;while(1){/* receive_packet会先释放pkt此前持有的引用 */ret=avcodec_receive_packet(ctx,pkt);if(ret==AVERROR(EAGAIN))return0;if(ret==AVERROR_EOF)returnAVERROR_EOF;if(ret<0)returnret;/* 在下一次收包前处理当前有效数据 */if(fwrite(pkt->data,1,pkt->size,output)!=(size_t)pkt->size){av_packet_unref(pkt);returnAVERROR(EIO);}/* 释放本次拿到的包缓冲区引用 */av_packet_unref(pkt);}}/* 普通输入阶段:发送帧后立即读取可用输出 */staticintencode_one_frame(AVCodecContext*ctx,constAVFrame*frame,AVPacket*pkt,FILE*output){intret;ret=avcodec_send_frame(ctx,frame);if(ret<0)returnret;ret=receive_packets(ctx,pkt,output);returnret==AVERROR_EOF?0:ret;}/* 输入结束时先发送NULL帧,再读取到AVERROR_EOF */staticintfinish_encoder(AVCodecContext*ctx,AVPacket*pkt,FILE*output){intret;ret=avcodec_send_frame(ctx,NULL);if(ret<0)returnret;while(1){ret=receive_packets(ctx,pkt,output);if(ret==AVERROR_EOF)return0;if(ret<0)returnret;}}如果avcodec_send_frame()返回AVERROR(EAGAIN),上层应先调用receive_packets()读取输出,再重试同一个输入帧。普通阶段不能把AVERROR(EAGAIN)误认为编码器已结束。
🌻5.总结
avcodec_receive_packet()从编码器内部缓存或实际输出路径取出引用计数的压缩包,并用AVERROR(EAGAIN)与AVERROR_EOF区分等待输入和排空完成。