☰
Linux FFmpeg深度解析之av_parser_parse2调用流程与实战(二十二)
2026/11/3 10:36:07 网站建设 项目流程

简介: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准备裸H.264输入和解析器
        • 4.2循环解析并冲刷文件尾部
        • 4.3完整代码
      • 🌻5.总结

🌻1.前言

本篇目的:理解av_parser_parse2()如何把任意输入字节块交给对应解析器,分析输入消耗量、输出帧数据和时间戳状态之间的关系。

裸码流的读取块不一定与压缩帧边界一致。av_parser_parse2()接收一段输入数据,调用解析器识别已完成的帧,并报告本次消耗的输入字节数。它负责解析码流边界,不负责解码压缩帧。

输入压缩字节块

av_parser_parse2

编码格式解析器

输出帧指针和大小

返回已消耗字节数

🌻2.应用场景和用法

2.1拆分连续压缩码流

当应用从文件或网络读到的字节块包含半帧、多帧或跨帧数据时,需要逐次调用av_parser_parse2()。每次调用都要根据返回的消耗字节数推进输入指针,并在输出大小大于0时处理一帧。

使用场景输入特点解析器作用
裸H.264或MPEG视频读取块可能包含多个访问单元按码流边界输出完整帧
连续压缩音频一次读取可能只包含部分音频帧在多次调用间保留解析状态
携带时间戳的码流输入块具有PTS、DTS和文件位置记录输入区间并关联帧时间戳
文件结束解析器内部可能还有未输出数据以零长度输入冲刷解析器

如果输入本身已经是完整压缩包,通常直接把包交给解码器即可。不要把解析器输出大小与本次输入消耗量混为一谈:前者通过poutbuf_size返回,后者由函数返回值表示。

2.2函数原型与返回语义

函数原型如下:

intav_parser_parse2(AVCodecParserContext*s,AVCodecContext*avctx,uint8_t**poutbuf,int*poutbuf_size,constuint8_t*buf,intbuf_size,int64_tpts,int64_tdts,int64_tpos);
参数含义
s由av_parser_init()创建的解析器上下文
avctxcodec_id必须与解析器支持的编码格式匹配
poutbuf、poutbuf_size接收解析出的帧数据指针和大小
buf、buf_size当前输入字节块;输入末尾需提供AV_INPUT_BUFFER_PADDING_SIZE个填充字节
pts、dts、pos当前输入块对应的时间戳和字节位置

函数返回本次消耗的输入字节数,不是错误码,也不是输出帧大小。若*poutbuf_size为0,当前调用没有形成完整帧,*poutbuf会被设为NULL。文件结束时传入buf_size=0,解析器可以输出仍保留在内部的最后一帧。

最小调用方式如下:

uint8_t*parsed_data=NULL;intparsed_size=0;intconsumed=av_parser_parse2(parser,avctx,&parsed_data,&parsed_size,input_data,input_size,pts,dts,stream_pos);/* consumed用于推进输入;parsed_size大于0时处理一帧 */

解析器返回的数据指针不应直接释放。它可能指向输入缓冲区,也可能指向解析器内部缓冲区;需要跨越下一次解析调用保留时,应复制到调用者管理的AVPacket存储中。

🌻3.调用流程剖析

3.1记录输入位置并调用解析器实现

函数先断言AVCodecContext.codec_id与解析器支持的编码ID匹配。首次调用时,它使用pos初始化当前和下一帧偏移;每次输入还会把字节区间及对应的PTS、DTS、文件位置记录到解析器的时间戳队列。

随后,函数调用s->parser->parser_parse()处理输入。若buf_size为0,函数会传入内部的填充数据,以通知解析器冲刷剩余内容。

输入字节及时间信息

检查codec_id匹配

记录输入区间和时间戳

调用parser_parse

得到解析帧和消耗量

3.2更新帧偏移并返回消耗量

解析器识别出完整帧后,av_parser_parse2()更新当前帧偏移和下一帧偏移,并设置fetch_timestamp,让下一次调用继续获取对应时间戳。对于视频,它还会在AVCodecContext未设置相关字段时补充解析得到的场序、宽高等信息。

如果本次没有输出帧,函数把输出指针设为NULL。无论是否输出帧,输入推进都应依据返回的消耗字节数完成;当文件读完后,还要用零长度输入执行冲刷。

🌻4.实战案例

4.1准备裸H.264输入和解析器

案例读取一个裸H.264文件,将整个文件加载到带零填充的缓冲区。AVCodecContext只用于提供匹配的编码ID和视频媒体类型,本例不打开解码器,也不解码帧。

4.2循环解析并冲刷文件尾部

循环调用av_parser_parse2(),按返回值推进输入指针和文件位置。每当parsed_size大于0,帧计数加一。若一次调用既没有消耗输入也没有输出帧,程序停止,避免陷入无进展循环。所有输入处理完后,以零长度输入冲刷解析器。

4.3完整代码
#include<limits.h>#include<stdio.h>#include<libavcodec/avcodec.h>#include<libavutil/mem.h>#defineINPUT_PADDINGAV_INPUT_BUFFER_PADDING_SIZEintmain(intargc,char**argv){constAVCodec*codec=NULL;AVCodecContext*avctx=NULL;AVCodecParserContext*parser=NULL;FILE*file=NULL;uint8_t*input_buffer=NULL;uint8_t*data;uint8_t*parsed_data=NULL;longfile_size;intremaining;intparsed_size=0;intparsed_frames=0;intret=1;int64_tpos=0;if(argc!=2)gotodone;/* 查找H.264解码器信息,用其编码ID创建匹配的上下文 */codec=avcodec_find_decoder(AV_CODEC_ID_H264);if(codec==NULL)gotodone;avctx=avcodec_alloc_context3(codec);if(avctx==NULL)gotodone;/* 创建H.264解析器;当前构建缺少解析器时返回失败 */parser=av_parser_init(codec->id);if(parser==NULL)gotodone;file=fopen(argv[1],"rb");if(file==NULL)gotodone;if(fseek(file,0,SEEK_END)!=0)gotodone;file_size=ftell(file);if(file_size<0||file_size>INT_MAX-INPUT_PADDING)gotodone;if(fseek(file,0,SEEK_SET)!=0)gotodone;/* 解析器要求有效输入末尾保留指定长度的零填充区 */input_buffer=av_mallocz((size_t)file_size+INPUT_PADDING);if(input_buffer==NULL)gotodone;if(fread(input_buffer,1,file_size,file)!=(size_t)file_size)gotodone;data=input_buffer;remaining=(int)file_size;/* 返回值是已消耗输入字节数,输出帧大小由parsed_size给出 */while(remaining>0){intconsumed;parsed_data=NULL;parsed_size=0;consumed=av_parser_parse2(parser,avctx,&parsed_data,&parsed_size,data,remaining,AV_NOPTS_VALUE,AV_NOPTS_VALUE,pos);if(parsed_size>0)parsed_frames++;if(consumed==0&&parsed_size==0)gotodone;data+=consumed;remaining-=consumed;pos+=consumed;}/* 以零长度输入通知解析器处理文件尾部缓存的数据 */for(;;){intconsumed;parsed_data=NULL;parsed_size=0;consumed=av_parser_parse2(parser,avctx,&parsed_data,&parsed_size,NULL,0,AV_NOPTS_VALUE,AV_NOPTS_VALUE,pos);if(parsed_size==0)break;parsed_frames++;}/* 至少得到一帧解析结果,才认为本案例完成 */if(parsed_frames>0)ret=0;done:av_free(input_buffer);if(file!=NULL)fclose(file);av_parser_close(parser);avcodec_free_context(&avctx);returnret;}

🌻5.总结

av_parser_parse2()把输入字节交给匹配的解析器,返回本次消耗量,并通过输出参数提供已识别的压缩帧。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询