从H.264码流中提取SPS和PPS需先按起始码(0x00000001或0x000001)切分NALU,再取每个NALU首字节低5位:值为7即SPS,8即PPS;Annex B格式需剔除起始码但保留原始字节,AVCC格式则需解析avcC box中length字段后的裸数据。

怎么从H.264码流里提取出SPS和PPS的NALU
SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)都封装在类型为 0x07 和 0x08 的NALU中,但原始码流里不会直接标出“这是SPS”,得靠解析NALU header里的 nal_unit_type 字段来识别。关键前提是:你拿到的是未解复用的、带起始码(0x00000001 或 0x000001)的Annex B格式码流——这是最常见的场景。
实操时先按起始码切分NALU,再读第一个字节(去掉起始码后的首字节),用位运算取低5位:(byte & 0x1F),结果为 7 就是SPS,8 就是PPS。
- 起始码长度不固定:优先匹配
0x00000001(4字节),没匹配上再试0x000001(3字节);否则会把SPS开头的0x000000错切成三段 - 切出来的NALU数据要剔除起始码,但保留原始字节(包括可能存在的
0x00emulation prevention bytes)——H.264标准要求解码前做反规避处理,即删掉紧跟在0x0000后面的0x03 - 一个码流里SPS/PPS可能出现多次(如流切换或重发),通常以第一次出现为准;但某些实时流(如RTP打包的AVCC格式)不带起始码,得先解析
avcCbox 提取 lengthSize 和 SPS/PPS raw data
怎么解析SPS里的关键参数(如分辨率、profile)
SPS结构由H.264标准 Annex A 定义,不是简单memcpy能读的——它用的是基于位的变长编码(Exp-Golomb + 位字段),必须逐bit解析。比如 profile_idc 占8位,但紧接着的 constraint_set_flags 是6个1-bit标志位,再后面 level_idc 又是8位。直接按字节读会错位。
推荐用轻量级解析逻辑(不依赖FFmpeg):维护一个 bit_offset,每次读n bit就移位+掩码,例如读ue(v)(无符号指数哥伦布码)需先数前导零,再读对应位数的值。FFmpeg里 ff_h264_decode_seq_parameter_set 是参考实现,但剥离出来太重;更实用的是抄 libde265 中的 sps_parser.h 逻辑,或用 h264bitstream 库里的 read_nal_unit() + 手动遍历 sps->pic_width_in_mbs_minus1 等字段。
-
pic_width_in_mbs_minus1和pic_height_in_map_units_minus1要结合frame_mbs_only_flag和mb_adaptive_frame_field_flag才能算出真实宽高 -
log2_max_frame_num_minus4决定 frame_num 字段长度,影响解码器窗口大小,硬编码成16会丢帧 - 忽略
vui_parameters_present_flag导致无法获取timing_info(如num_units_in_tick),进而算不出帧率
为什么用FFmpeg avcodec_send_packet解析后拿不到SPS/PPS
因为 avcodec_send_packet 是送入解码器队列,触发的是解码行为,不是解析行为。SPS/PPS被内部消耗用于初始化解码上下文,除非你主动从 AVCodecContext 里捞——但FFmpeg 4.0+ 已废弃 codec->extradata 的自动更新,且 extradata 只在首次打开解码器时有效,后续SPS重发不会刷新它。
真正可靠的方式是:在调用 avcodec_send_packet 前,自己预扫描输入packet的 data,用前面说的起始码+NALU type方式提前截出SPS/PPS,存到独立缓冲区;或者启用 AV_CODEC_FLAG_DROPCHANGED 并监听 AV_PKT_DATA_NEW_EXTRADATA 类型的side data(需设置 pkt->side_data 有效)。
- 别依赖
avcodec_open2返回后立刻读codec_ctx->extradata——如果输入码流第一帧不是IDR,这里可能是空的 - RTP over RTCP场景下,SPS/PPS常走SEI或单独的STAP-A包,得在RTP payload解析层就分离,不能等进解码器
-
av_dump_format输出的 “Extradata” 是AVCC格式(含length字段),和Annex B的SPS raw data二进制不同,直接 memcpy 会失败
AVCC格式下怎么定位SPS/PPS(如MP4文件)
MP4里的H.264视频轨,SPS/PPS存在 avcC box 中,不是NALU流。box开头是版本、profile、level等字段,之后是 numOfSequenceParameterSets(1字节),接着是每个SPS的2字节length,然后才是SPS data。PPS同理,但放在SPS之后,且 numOfPictureParameterSets 也是1字节。
解析时先定位 avcC box(通常在 moov.avc1 下),跳过前7字节header,读第7字节得 lengthSizeMinusOne(常用值为3 → length占4字节),再按该长度读SPS/PPS的length字段,最后按length读原始数据。注意:这里的SPS data就是裸字节,**没有起始码**,也不能直接喂给Annex B解析器。
- MP4中
avcC的SPS可能含多个(numOfSequenceParameterSets > 1),但实际只取第一个;FFmpeg的ff_isom_write_avcc默认只写一个 - Android MediaCodec 要求传入的
CSD-0必须是AVCC格式的SPS(即带length头),传Annex B格式会报error 0xffffffea - 用
mp4dump工具可快速验证:运行mp4dump xxx.mp4 | grep -A 20 avcC,看到sequenceParameterSets下的十六进制数据就是你要的SPS
最易被忽略的一点:SPS里的 chroma_format_idc 和 bit_depth_luma_minus8 共同决定YUV格式(如yuv420p还是yuv444p10le),但很多解析代码只读宽高,导致后续图像显示错位或绿屏——得连同色度参数一起校验。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











