某些封装格式(例如MP4/FLV/MKV等)的H.264码流的SPS和PPS信息存储在AVCodeccontext结构体的extradata中。分离某些封装格式(例如MP4/FLV/MKV等)中的H.264的时候,需要首先写入SPS和PPS,否则会导致分离出来的数据没有SPS、PPS而无法播。需要使用ffmpeg中名称为“h264 mp4toannexb”的bitstream filter处理
关键概念解析与操作指南
一、SPS/PPS 的作用与存储位置
- SPS (Sequence Parameter Set)
定义视频序列的全局参数,包括分辨率、帧率、编码级别等。 - PPS (Picture Parameter Set)
定义图像解码参数,如熵编码模式、分片参数等。 - 存储位置
- 在 MP4/FLV/MKV 等封装格式中,SPS/PPS 通常存储在
AVCodecContext->extradata
中,而非嵌入到每个关键帧。 - 裸 H.264 流(如 Annex B 格式)要求在每个关键帧(IDR帧)前携带 SPS/PPS。
- 在 MP4/FLV/MKV 等封装格式中,SPS/PPS 通常存储在
二、问题根源分析
当直接从封装格式(如 MP4)中提取 H.264 裸流时:
- 直接提取的缺陷:
输出的 H.264 数据缺少 SPS/PPS 头部信息,导致播放器无法初始化解码器。 - 典型报错:
[h264 @ 0x7f8a5c006800] no frame! Invalid NAL unit size
三、解决方案:使用 Bitstream Filter
通过 FFmpeg 的 h264_mp4toannexb
比特流过滤器,实现以下功能:
- 从
extradata
提取 SPS/PPS:
自动读取AVCodecContext->extradata
中的参数集。 - 转换为 Annex B 格式:
- 在码流起始位置插入 SPS/PPS。
- 在每个关键帧前插入起始码
0x00000001
或0x000001
。
四、操作命令示例
提取 MP4 中的 H.264 裸流并修复 SPS/PPS:
ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb output.h264
- 参数说明:
-c:v copy
:直接复制视频流,不重新编码。-bsf:v h264_mp4toannexb
:对视频流应用比特流过滤器。
五、不同封装格式的适配
封装格式 | 比特流过滤器 | 作用 |
---|---|---|
MP4 | h264_mp4toannexb | 插入 SPS/PPS,添加起始码 |
FLV | h264_mp4toannexb | 同上(FLV 的 H.264 存储方式类似 MP4) |
MKV | h264_mp4toannexb | 同上 |
TS | 无需过滤器 | TS 流已符合 Annex B 格式 |
六、验证输出文件
- 使用
ffprobe
检查:
确认输出中包含ffprobe -v error -show_streams output.h264
codec_tag_string=avc1
和extradata
。 - 播放测试:
ffplay output.h264
七、底层原理详解
-
extradata
结构- MP4 中
extradata
的格式为 AVCDecoderConfigurationRecord,包含 SPS/PPS 的二进制数据。 h264_mp4toannexb
过滤器解析此结构,将其转换为 Annex B 的头部。
- MP4 中
- Annex B 格式要求
- 每个 NAL 单元以起始码
0x00000001
或0x000001
开头。 - SPS/PPS 必须出现在码流开头或关键帧前。
- 每个 NAL 单元以起始码
八、扩展场景:处理多路流
若需合并多个 MP4 文件的 H.264 流,需对每个输入单独应用过滤器:
ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex \ "[0:v]bsf=h264_mp4toannexb[0v];[1:v]bsf=h264_mp4toannexb[1v]; \ [0v][1v]concat=n=2:v=1" -c:v libx264 output_combined.mp4
通过上述方法,可确保提取的 H.264 裸流符合播放器要求,避免因缺失 SPS/PPS 导致的解码失败问题。
ffmpeg -i 1.mp4 -c:v copy output_without_sps_pps.h264转换完成后通过ffplay播放的h264为什么播放速度很快
问题原因解析
使用 ffmpeg -i 1.mp4 -c:v copy output.h264
转换的 H.264 裸流播放速度异常加快,主要与 时间戳信息丢失 和 封装格式差异 相关,具体原因如下:
1. 时间基(Timebase)未正确传递
- MP4 容器特性:MP4 文件的时间戳基值(
time_base
)通常为1/1000
或1/90000
,而 H.264 裸流无容器层,默认时间基可能被错误解析为1/25
或1/30
[2][7]。 - 播放器误判:
ffplay
根据 H.264 裸流的 NAL 单元间隔推算帧率,若时间戳缺失或错误,会强制按默认高帧率(如 25fps)播放[1][7]。
2. 关键帧间隔(GOP)与 B/P 帧依赖断裂
-
-c:v copy
的局限性:直接复制流时,B/P 帧的解码依赖关系可能因裸流缺少容器层的元数据而断裂,导致解码器跳过参考帧计算,加速播放[2][7]。
3. SPS/PPS 信息未嵌入
- 缺失参数集:MP4 中的 SPS/PPS 存储在
extradata
中,直接复制时未通过h264_mp4toannexb
过滤器插入码流,播放器可能无法正确初始化解码器时间轴[1][2]。
解决方案
1. 强制指定输出帧率
通过 -r
参数显式定义输出帧率,覆盖默认值:
bashCopy Code
ffmpeg -i 1.mp4 -c:v copy -r 30 output.h264
- 作用:强制 H.264 裸流按 30fps 播放,避免时间基误判[2][7]。
2. 添加比特流过滤器处理时间戳
使用 setpts
滤镜修正时间戳:
bashCopy Code
ffmpeg -i 1.mp4 -c:v copy -bsf:v h264_mp4toannexb -vf "setpts=PTS-STARTPTS" output.h264
- 关键参数:
-bsf:v h264_mp4toannexb
:插入 SPS/PPS 并修正 NAL 单元格式[1][2]。setpts=PTS-STARTPTS
:重置时间戳为从零开始,消除容器时间基差异[7]。
3. 验证与调试
- 检查时间戳信息:
bashCopy Code
确认输出帧的ffprobe -show_frames output.h264 | grep "pkt_pts_time"
pkt_pts_time
均匀递增(如 0.033s 间隔对应 30fps)[7]。 - 对比播放效果:
bashCopy Code
ffplay -vf "setpts=PTS-STARTPTS" output.h264 # 强制按原始速度播放
扩展场景:直播推流中的类似问题
若需将 USB 相机的 RGBA 数据编码为 H.264 并推流至 RTMP,需在编码器初始化时手动插入 SPS/PPS,并通过 avcodec_parameters_from_context
传递至输出流上下文,否则 RTMP 播放会出现加速或花屏[1][2]。
总结
H.264 裸流播放速度异常的本质是 时间戳与帧率元数据丢失,需通过过滤器修正时间基或显式指定帧率参数[1][2][7]。