尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

Linux FFmpeg深度解析之avcodec_receive_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区分EAGAIN与EOF4.实战案例4.1准备编码器输出循环4.2读取包并处理背压状态4.3完整代码5.总结1.前言本篇目的理解avcodec_receive_packet()如何从编码器读取压缩数据分析内部包缓存、编码器输出和调用者持有的AVPacket之间的关系并通过收包循环验证编码输出的读取方式。编码器接收原始AVFrame后不一定马上输出压缩包。编码器可能积累输入执行帧重排序或等待更多数据。avcodec_receive_packet()负责读取当前已经可用的编码结果。成功返回时输出包是引用计数的AVPacket。下一次调用该接口前FFmpeg会先对传入的包执行av_packet_unref()因此调用者应在再次收包前处理或转移上一次包的数据。已提交的AVFrame编码器内部处理内部包缓存或编码器输出avcodec_receive_packet调用者得到引用计数AVPacket处理后释放或转移包引用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回调或使用兼容路径执行编码并取得包。否是是否调用avcodec_receive_packet释放avpkt旧引用编码器状态有效返回错误码内部缓存包可用移动内部包引用调用编码器输出路径返回输出包成功输出的数据包必须是引用计数的。调用者若持续收包但不释放或转移包引用会造成内存持续占用。3.2区分EAGAIN与EOF普通编码阶段收到AVERROR(EAGAIN)表示当前没有新包可取编码器需要更多输入。调用者应提交下一帧。结束时调用者先通过avcodec_send_frame(ctx, NULL)通知编码器停止接收普通输入。随后继续调用avcodec_receive_packet()直到返回AVERROR_EOF。这个结束信号和普通阶段的AVERROR(EAGAIN)含义不同不能将两者都当作“编码结束”。成功EAGAIN输入结束时发送NULL否是已提交输入帧循环读取编码包接收结果处理并释放AVPacket继续提交输入编码器进入排空状态继续读取延迟输出收到EOF编码输出结束4.实战案例4.1准备编码器输出循环下面的函数假设AVCodecContext已经配置并打开输入帧也已经准备好。AVPacket由调用者创建函数负责读取所有当前可用的包。4.2读取包并处理背压状态收包循环中成功时处理包内容并释放引用。AVERROR(EAGAIN)表示当前批次读取完成其他错误码交给上层处理。结束排空阶段使用同一循环但需要持续读取到AVERROR_EOF。4.3完整代码#includestdio.h#includelibavcodec/avcodec.h#includelibavutil/error.h/* 读取当前可用的全部编码包 */staticintreceive_packets(AVCodecContext*ctx,AVPacket*pkt,FILE*output){intret;while(1){/* receive_packet会先释放pkt此前持有的引用 */retavcodec_receive_packet(ctx,pkt);if(retAVERROR(EAGAIN))return0;if(retAVERROR_EOF)returnAVERROR_EOF;if(ret0)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;retavcodec_send_frame(ctx,frame);if(ret0)returnret;retreceive_packets(ctx,pkt,output);returnretAVERROR_EOF?0:ret;}/* 输入结束时先发送NULL帧再读取到AVERROR_EOF */staticintfinish_encoder(AVCodecContext*ctx,AVPacket*pkt,FILE*output){intret;retavcodec_send_frame(ctx,NULL);if(ret0)returnret;while(1){retreceive_packets(ctx,pkt,output);if(retAVERROR_EOF)return0;if(ret0)returnret;}}如果avcodec_send_frame()返回AVERROR(EAGAIN)上层应先调用receive_packets()读取输出再重试同一个输入帧。普通阶段不能把AVERROR(EAGAIN)误认为编码器已结束。5.总结avcodec_receive_packet()从编码器内部缓存或实际输出路径取出引用计数的压缩包并用AVERROR(EAGAIN)与AVERROR_EOF区分等待输入和排空完成。
返回列表