4、视频流提取:从MP4中提取H.264/H.265裸流、提取时保持原始编码参数
好,咱们进入第四讲。这一讲的主题很明确——从MP4里把视频裸流提出来。说白了,就是把那个封装壳子剥掉,只留下纯纯的视频编码数据。
为什么要干这事儿?我遇到过不少场景:比如你要做码流分析、要喂给硬件解码器、或者要做二次转封装。这时候,带着MP4的moov盒子反而碍事。裸流,才是最干净的数据。
4.1 什么是裸流?为什么需要它?
先搞清楚概念。H.264裸流,通常以 0x00 0x00 0x00 0x01 开头,后面跟着NAL单元。H.265也一样,只是NAL单元类型定义不同。
MP4里的视频轨道,其实也是这些NAL单元。只不过它们被切碎、打包、塞进了mdat盒子。提取裸流,就是把这些碎片重新拼起来,还原成完整的字节流。
你想想看,如果直接拿FFmpeg的 -c copy 输出,默认会带上MP4的封装。但我们要的是 .h264 或 .hevc 文件,里面只有起始码+NAL数据。
核心要点:提取裸流时,必须保留原始编码参数(SPS/PPS/VPS)。否则解码器不认识你的码流,直接罢工。
4.2 基础命令:一行搞定
先来个最直接的。假设你有一个 input.mp4,里面是H.264视频。
ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb output.h264
这条命令干了三件事:
-c:v copy:视频流直接拷贝,不解码不重编码。速度最快,质量无损。-bsf:v h264_mp4toannexb:比特流过滤器。把MP4格式的NAL单元(带长度前缀)转换成Annex B格式(带起始码)。output.h264:输出裸流文件。扩展名不重要,但约定俗成用.h264或.264。
如果是H.265,把过滤器换成 hevc_mp4toannexb:
ffmpeg -i input.mp4 -c:v copy -bsf:v hevc_mp4toannexb output.h265
嗯,这里要注意:有些MP4文件里的H.265,其实叫HEVC。但FFmpeg里统一用 hevc 这个编解码器名称。别写成 h265,会报错。
4.3 深入理解:比特流过滤器在干什么?
为什么需要这个过滤器?我刚开始做的时候也纳闷——直接copy不行吗?
不行。MP4里存储NAL单元的方式,和裸流不一样:
| 封装格式 | NAL单元分隔方式 | 示例 |
|---|---|---|
| MP4 | 4字节长度前缀(大端) | 00 00 03 21 [NAL数据] |
| Annex B(裸流) | 起始码 00 00 00 01 |
00 00 00 01 [NAL数据] |
MP4里每个NAL单元前面,写的是这个单元的长度。解码器读到长度,就知道要读多少字节。而裸流里,用固定的起始码来标记边界。
所以,h264_mp4toannexb 这个过滤器,就是把长度前缀替换成起始码。同时,它还会做一件重要的事——把SPS和PPS提取出来,插入到关键帧前面。
个人经验:我曾经遇到一个MP4文件,里面的SPS/PPS只出现了一次,在文件开头。如果不用这个过滤器,直接copy出来的裸流,后面的关键帧就没有SPS/PPS。解码器读到第二个关键帧就崩了。这个过滤器会自动在每个IDR帧前插入SPS/PPS,保证码流完整。
4.4 保持原始编码参数:不只是SPS/PPS
「保持原始编码参数」这句话,听起来简单,做起来有坑。
原始编码参数包括:
- SPS(序列参数集):分辨率、帧率、档次、级别等
- PPS(图像参数集):熵编码模式、分片数量等
- VPS(视频参数集,仅H.265):分层编码信息
- SEI(补充增强信息):可能包含色彩空间、HDR元数据等
用 -bsf:v h264_mp4toannexb 时,默认会保留这些参数。但有一个例外——如果原始MP4里,SPS/PPS被放在了moov盒子的 avcC 或 hvcC 中,而不是在视频数据流里,过滤器会从这些box中读取并插入。
我建议你验证一下:
ffprobe -v error -show_entries stream=codec_name,codec_tag_string,profile,level,coded_width,coded_height input.mp4
提取后,再用同样的命令检查裸流文件:
ffprobe -v error -show_entries stream=codec_name,profile,level,coded_width,coded_height output.h264
如果参数一致,说明提取成功。如果不一致,八成是过滤器没生效,或者原始文件有问题。
避坑指南:我曾经遇到一个客户提供的MP4,里面视频轨道的 avcC box里SPS写的是1080p,但实际视频数据是720p。提取裸流后,解码器按照SPS的1080p去分配缓冲区,结果解码失败。这种情况,你得用 -bsf:v filter_units=remove_types=... 手动处理,或者干脆重新编码。
4.5 进阶:提取特定NAL单元类型
有时候,你只想提取I帧,或者只想提取SPS/PPS。FFmpeg的比特流过滤器可以做到。
提取所有IDR帧(H.264):
ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb -f h264 - | ffmpeg -i - -c:v copy -bsf:v 'filter_units=pass_types=5' output_idr.h264
这里 pass_types=5 表示只保留NAL类型为5的单元(IDR帧)。H.265的IDR类型是19和20。
提取SPS/PPS:
ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb -f h264 - | ffmpeg -i - -c:v copy -bsf:v 'filter_units=pass_types=7,8' output_sps_pps.h264
7是SPS,8是PPS。H.265的话,VPS是32,SPS是33,PPS是34。
嗯,这里要提醒一下:管道操作虽然方便,但会丢失时间戳信息。如果你需要保持帧的时序,建议用 -vsync 0 或者写脚本分段处理。
4.6 实战:批量提取与校验
假设你有一个目录,里面全是MP4文件。你想批量提取H.264裸流,并校验提取结果。
写个简单的shell脚本:
#!/bin/bash
for f in *.mp4; do
# 提取裸流
ffmpeg -i "$f" -c:v copy -bsf:v h264_mp4toannexb "${f%.mp4}.h264" -y
# 校验:检查是否有起始码
if hexdump -C "${f%.mp4}.h264" | head -n 1 | grep -q "00000001"; then
echo "✅ $f -> 提取成功"
else
echo "❌ $f -> 提取失败,文件可能为空或格式错误"
fi
done
这个脚本会检查每个输出文件的前几个字节,看是不是以 00 00 00 01 开头。如果是,基本就对了。
我个人习惯再加一步——用 ffprobe 检查裸流的参数是否和源文件一致:
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate -of default=noprint_wrappers=1 input.mp4
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate -of default=noprint_wrappers=1 output.h264
对比两行输出,宽高、帧率、编码器名称应该完全一致。
4.7 知识体系:视频流提取的核心逻辑
下面这张图,帮你理清整个流程:
这张图把整个流程串起来了。从MP4输入,到流拷贝(不解码),再到比特流过滤(转换格式),最后输出裸流。同时,原始编码参数必须完整保留,这是解码器能正常工作的前提。
4.8 常见问题与排查
最后,分享几个我实际踩过的坑:
- 输出文件大小为0:检查输入文件是否有视频轨道。用
ffprobe -v error -show_entries stream=codec_type -of default=noprint_wrappers=1 input.mp4看看有没有codec_type=video。 - 提取后文件变大:正常。因为MP4里NAL单元用4字节长度前缀,而裸流用4字节起始码。长度前缀不占用额外空间(它本身就是数据的一部分),但起始码是额外插入的。如果原始MP4里有很多小NAL单元,起始码的开销会明显。
- 解码器报错「no frame!」:大概率是SPS/PPS丢失了。检查是否用了
-bsf:v过滤器。如果用了还不行,试试手动指定-bsf:v 'h264_mp4toannexb|filter_units=pass_types=7,8,5'强制保留关键参数。 - 提取的裸流无法播放:有些播放器要求裸流文件有正确的扩展名。H.264用
.h264或.264,H.265用.h265或.hevc。另外,检查一下播放器是否支持裸流——VLC和MPV通常可以,但Windows Media Player不行。
好了,这一讲就到这里。记住核心命令和那个过滤器,大部分场景都能搞定。下一讲我们会聊音频流提取,到时候再细说。
公众号:蓝海资料掘金营,微信deep3321