当前位置: 首页 > news >正文

h264 extradata解析,提取参数集SPS/PPS

1 背景

在音视频处理中,AVCC 格式转换为 Annex-B 格式的典型场景包括以下六种情况:

一、Android/Harmony等平台硬件解码场景
Android/Harmony 系统硬解码器通常仅支持 Annex-B 格式的码流输入。当从 MP4/MKV/MOV等容器(默认使用 AVCC 格式)中提取 H.264码流进行解码时,需通过 SPS/PPS 提取、起始码替换等操作完成格式转换。例如 MP4 文件头部的 AVCDecoderConfigurationRecord 数据结构需被解析后重组为 Annex-B 的头部结构。

二、实时流传输协议封装
在 RTP/RTSP 协议传输或 TS 流封装场景中,Annex-B 格式作为标准要求存在。由于 Annex-B 使用 0x000001 或 0x00000001 起始码标识 NALU 边界,便于流媒体服务端进行码流分割和实时传输。

三、容器格式转换场景
将 MP4 文件重新封装为 FLV、TS 等格式时,需遵循目标容器的编码规范。例如 FLV 容器要求视频编码信息通过 AVCDecoderConfigurationRecord 传递,而 TS 流通常直接使用 Annex-B 格式的起始码分割 NALU。

四、硬件解码兼容性修复
某些硬件解码器(如部分 GPU 加速方案)对 Annex-B 格式有强依赖性。若使用 FFmpeg 的 h264_mp4toannexb 等 Bitstream Filter 处理码流时未正确处理 SPS/PPS 插入逻辑,可能引发硬解异常,此时需规范转换流程。

五、防竞争字节处理需求
AVCC 格式使用长度前缀标识 NALU 大小,而 Annex-B 需在码流中插入防竞争字节(Emulation Prevention Bytes)避免起始码冲突。当需要兼容老式解码设备或特定流媒体服务器时,必须进行格式转换以实现防竞争处理。

六、低延迟直播场景优化
Annex-B 格式允许在关键帧前周期性插入 SPS/PPS,使解码器可从视频流任意位置开始解码。这种特性使其更适合低延迟直播场景,而 AVCC 格式因依赖容器头部的集中式参数存储,难以满足实时流随机接入需求。

AVCC转 Annex-B 场景中,当视频帧是IDR帧时,需要将参数集SPS/PPS在IDR前拷贝到解码器输入缓存中才能正常解码。本文讲解H.264 extradata语法以及如何从mp4/mkv/mov等容器中提取参数集SPS/PPS。

2 AVCC和Annex-B的定义和区别

  • AVCC:AVCC是一种用于封装H.264码流的格式,它将SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)封装为ExtraData,通常用于MP4/MKV/MOV等封装格式的文件头中,播放器通过这些信息初始化解码器‌。
  • Annex-B‌:这是一种对NALU层进行包装的格式,每个NALU前面加上起始码(如0x000001或0x00000001),用于分隔不同的NALU。为了防止原始码流中出现相同的起始码,Annex-B引入了防竞争字节(Emulation Prevention Bytes)的概念,即在出现0x0001之前插入一个字节,内容为0x3‌。 AVCC以长度信息替代起始码,其NALU内部的原始数据无需额外处理即可避免数据与起始码冲突的问题,因此没有防竞争码。

AVCC 结构:

AVCC封装的H.264码流中,最前面的是AVCDecoderConfigurationRecord(或称为extradata)。AVCDecoderConfigurationRecord语法如下所示(H.264-AVC-ISO_IEC_14496-15):

字段解释: configurationVersion :占1字节,版本号,固定为1。 AVCProfileIndication :占1字节,SPS的profile_idc,即(H.264标准规定的)SPS的第一个字节。 profile_compatibility:占1字节,即(H.264标准规定的)SPS的第二个字节。 AVCLevelIndication :占1字节,SPS的level_idc,即(H.264标准规定的)SPS的第三个字节。 lengthSizeMinusOne:取该字节低2位,lengthSizeMinusOne为存贮NALUnitLength所需的空间减1, 单位为字节。比如,当lengthSizeMinusOne为2,那么存贮该路H.264码流的每个NALUnitLength所需 的空间为(2+1)个字节。我们在读取每个NALU之前,先读(2+1)个字节,把读取到的数据转成整数, 这就是该NALU的总长度了。 numOfSequenceParameterSets:取该字节低5位,该extradata中SPS个数。 sequenceParameterSetLength:占2字节,SPS长度,以字节为单位。 sequenceParameterSetNALUnit:SPS二进制数据,长度为sequenceParameterSetLength字节。 numOfPictureParameterSets:占1字节,该extradata中PPS个数。 pictureParameterSetLength:占2字节,PPS长度,以字节为单位。 pictureParameterSetNALUnit:PPS二进制数据,长度为pictureParameterSetLength字节。 chroma_format:取低2位,包含由chroma_format_idc参数定义的chroma_format指示符。 bit_depth_luma_minus8:取低3位,表示Luma数组中样本的位深度。例如,位深度为8用0 表示 (位深度=8+bit_depth_luma_minus8)。此字段的值应在0到4的范围内,包括0和4。 bit_depth_chroma_minus8:取低3位,表示Chroma阵列中样本的位深度。例如,位深度为8用0 表示 (位深度=8+bit_depth_luma_minus8)。此字段的值应在0到4的范围内,包括0和4。 numOfSequenceParameterSetExt:占1字节,表示用于解码AVC基本流的序列参数集扩展的数量。 sequenceParameterSetExtLength:占2字节,表示SPS 扩展的长度。 sequenceParameterSetExtNALUnit:表示SPS 扩展二进制数据,长度为sequenceParameterSetExtLength字节。

AVCProfileIndication,profile_compatibility,AVCLevelIndication三个字段对应H.264标准(44 Rec. ITU-T H.264 (08/2021))定义的前三个字节,如下图所示。

Annex-B‌ 结构(Recommendation ITU-T H.264 08/2021):

3 实例分析

01 :configurationVersion,固定为1
64:AVCProfileIndication, 1字节
00:profile_compatibility,1字节
28:AVCLevelIndication,1字节
ff:低2位是 “11”,即3, 说明存储 “NALUnitLength”所需空间是3+1 ,即4.
e1:numOfSequenceParameterSets取低5位,“00001” ,即1
00 1e:这两个字节是 numOfSequenceParameterSets的长度,二进制“11110”, 十进制30,SPS nal 长30个字节,跳过sps nal 30个字节。
01:pps个数 1
00 06:pps二进制数据长度,6个字节,跳过6个字节
fd:chroma_format,取低2位为0
f8:bit_depth_luma_minus8,取低三位,0
f8:bit_depth_chroma_minus8,取低三位,0

4 获取avc extradata

h264, extra data存在于trak->mdia->minf->stbl->stsd->avc1->avcc ,通常使用ffmpeg解封装后得到。

// 解析文件intret=avformat_open_input(&fmtCtx_,filepath_.toStdString().c_str(),nullptr,nullptr);if(ret<0){qDebug()<<"avformat_open_input failed";return;}ret=avformat_find_stream_info(fmtCtx_,nullptr);if(ret<0){qDebug()<<"avformat_find_stream_info failed";return;}for(inti=0;i<fmtCtx_->nb_streams;i++){AVStream*stream=fmtCtx_->streams[i];if(stream->codecpar->codec_id!=AV_CODEC_ID_H264){qDebug()<<"please input h264 file";return;}constAVCodec*dec=avcodec_find_decoder(stream->codecpar->codec_id);AVCodecContext*codec_ctx;if(!dec){qDebug()<<"Failed to find decoder for stream:"<<i;return;}codec_ctx=avcodec_alloc_context3(dec);if(!codec_ctx){qDebug()<<"Failed to allocate the decoder context for stream:"<<i;return;}ret=avcodec_parameters_to_context(codec_ctx,stream->codecpar);if(ret<0){qDebug()<<"Failed to copy decoder parameters to input decoder context for stream:"<<i;return;}if(codec_ctx->codec_type==AVMEDIA_TYPE_VIDEO||codec_ctx->codec_type==AVMEDIA_TYPE_AUDIO){if(codec_ctx->codec_type==AVMEDIA_TYPE_VIDEO){codec_ctx->framerate=av_guess_frame_rate(fmtCtx_,stream,NULL);videoCodecCtx_=codec_ctx;videoDec_=(AVCodec*)dec;videoStreamIndex_=i;if(videoCodecCtx_->extradata_size>0){QString exepath=QCoreApplication::applicationDirPath()+"/extradata.bin";FILE*fp=fopen(exepath.toStdString().c_str(),"w");if(fp){fwrite(videoCodecCtx_->extradata,videoCodecCtx_->extradata_size,1,fp);fclose(fp);}}}elseif(codec_ctx->codec_type==AVMEDIA_TYPE_AUDIO){audioCodecCtx_=codec_ctx;audioDec_=(AVCodec*)dec;audioStreamIndex_=i;}ret=avcodec_open2(codec_ctx,dec,NULL);if(ret<0){qDebug()<<"Failed to open decoder for stream:"<<i;return;}}}

5 解析avc extradata

voidMainWindow::parseAvcExtradata(constuint8_t*extra_data){intpos=1;// AVCProfileIndication,1字节intavcprofileidc=extra_data[pos++];// profile_compatibility,1字节intavcprofilecompat=extra_data[pos++];// AVCLevelIndication,1字节intavclevelidc=extra_data[pos++];// lengthSizeMinusOne,低2位intlensize=extra_data[pos++]&0x03;// numOfSequenceParameterSets,低5位intnumSPS=extra_data[pos++]&0x1F;// aways 1std::vector<uint8_t>sps;for(inti=0;i<numSPS;++i){// sequenceParameterSetLength,2字节intspsLen=(extra_data[pos]<<8)|extra_data[pos+1];pos+=2;sps.assign(extra_data+pos,extra_data+pos+spsLen);pos+=spsLen;}std::vector<uint8_t>pps;// numOfPictureParameterSets,低5位intnumPPS=extra_data[pos++];for(inti=0;i<numPPS;++i){// pictureParameterSetLength,2字节intppsLen=(extra_data[pos]<<8)|extra_data[pos+1];pos+=2;pps.assign(extra_data+pos,extra_data+pos+ppsLen);pos+=ppsLen;}// chroma_format, 取低2位intchroma_format=extra_data[pos++]&0x03;// bit_depth_luma_minus8, 取低3位intbit_depth_luma_minus8=(extra_data[pos++]&0x07);// bit_depth_chroma_minus8, 取低3位intbit_depth_chroma_minus8=(extra_data[pos++]&0x07);// numOfSequenceParameterSetExt,占1字节intspsextnum=extra_data[pos++];std::vector<uint8_t>spsext;for(inti=0;i<spsextnum;++i){// sequenceParameterSetExtLength,2字节intspsextLen=(extra_data[pos]<<8)|extra_data[pos+1];pos+=2;spsext.assign(extra_data+pos,extra_data+pos+spsextLen);pos+=spsextLen;}}

6 代码连接

avc extradata提取和解析
代码运行界面如下:

C++ 音视频开发问题(音视频同步,花屏,卡顿,crash,CPU占用过高,延时问题)解决,代码跨平台移植/国产平台适配 / 资源优化等一系列音视频痛点问题

http://www.jsqmd.com/news/1328788/

相关文章:

  • 如何快速掌握AKShare:面向金融数据获取的完整指南
  • 3分钟快速上手:Wand-Enhancer终极指南,免费解锁WeMod全部高级功能
  • TQVaultAE:泰坦之旅玩家的终极装备管理解决方案,告别仓库焦虑时代
  • AI模型说用户会流失,但实际没走?揭秘留存预测中F1-score失灵背后的2大分布漂移真相
  • Unity Scene文件深度解析:数字世界的“建筑蓝图“
  • 3分钟学会FanControl:Windows电脑风扇控制终极解决方案
  • 2026年英国留学中介哪家性价比高:五家优选深度解析 - 科技焦点
  • 办理香港投资移民推荐机构怎么选?这3个风险提示比宣传更重要 - 北极星移民
  • AI Coding 时代,Java 程序员的 3 条活路
  • 2026亚马逊多店铺运营的环境隔离架构与合规实践
  • AtlasOS:Windows系统优化的革命性模块化解决方案
  • 网盘下载速度慢?LinkSwift直链解析工具让你体验满速下载
  • AI写的高抛低吸策略,胜率还不错
  • OpenHarmony 标准/小型/轻量 系统编译
  • DDrawCompat完整指南:让经典DirectX游戏在现代Windows上重生
  • Unity为啥不使用继承?——一场“血统枷锁“的技术反思
  • 为什么你用AI做了10个小程序却没赚到1分钱?资深变现顾问连夜整理的7条反直觉真相
  • WPF基础知识汇总一(命令,样式)
  • CPPS培训费和考试费要分开交吗? - 众智商学院官方
  • U盘文件被病毒隐藏?用CMD的attrib命令一键恢复全攻略
  • 2026年全国成人学历提升机构口碑综合评测 - 互联网科技品牌测评
  • Java开发者收藏:轻松掌握AI,1个月落地项目,直接涨薪!
  • 画埋件图是着重考虑哪些方面
  • AI字体匹配失效真相(2024字体神经网络训练数据泄露报告)
  • UE4SS终极指南:3分钟掌握Unreal Engine游戏修改神器
  • 《荷塘月色》AI率超60%?“用AI查AI”的荒诞游戏,还要困住多少毕业生?
  • 36种Cherry MX键帽3D模型免费下载:5分钟开启个性化键盘定制之旅 [特殊字符]
  • 5大核心功能深度解析:鸣潮工具箱如何重塑你的游戏体验
  • 智能体安全:2026年AI安全产业新核心与实战指南
  • 如何在5分钟内启动Inflect-Nano-v2:超轻量级本地TTS的快速上手指南