4、查看媒体信息:使用 ffprobe 分析文件,理解流、包、帧的概念
说实话,我刚开始接触多媒体开发时,也被一堆术语搞晕过。
什么流啊、包啊、帧啊,听起来都差不多。
直到我真正用 ffprobe 去扒开一个视频文件,才恍然大悟——原来它们各自扮演着完全不同的角色。
这一节,我们就用 ffprobe 这个利器,把媒体文件拆开来看个清楚。
4.1 流(Stream)—— 文件里的独立轨道
你可以把流想象成一条独立的「数据轨道」。
一个 MP4 文件里,通常至少有一条视频流和一条音频流。有的还有字幕流、数据流。
它们彼此独立,但又通过时间戳同步在一起。
核心理解:流是封装容器里的逻辑轨道。每条流都有自己的编码格式、参数、时长。
用 ffprobe 看一眼就明白了。打开终端,执行:
ffprobe -v quiet -show_streams input.mp4
输出会很长,我挑关键字段解释一下:
| 字段 | 含义 | 常见值举例 |
|---|---|---|
index |
流在文件中的序号 | 0, 1, 2 |
codec_name |
编码器名称 | h264, aac, hevc |
codec_type |
流类型 | video, audio, subtitle |
width/height |
视频分辨率 | 1920, 1080 |
sample_rate |
音频采样率 | 44100, 48000 |
bit_rate |
码率(bps) | 2000000 |
我个人习惯先看 codec_type,快速确认文件里到底有几路视频、几路音频。有一次我拿到一个监控录像,ffprobe 一查发现居然有 4 条视频流——原来摄像头同时录了主码流和子码流。
4.2 包(Packet)—— 编码后的数据块
流是宏观的轨道,而包是这条轨道上最小的「传输单元」。
编码器把原始帧压缩后,打包成一个一个的包。包里面装的是已经编码好的数据,比如 H.264 的 NAL 单元。
我的经验:包是 FFmpeg 内部传递数据的基本单位。解封装时,容器格式(如 MP4)被拆成包;封装时,包又被组装成容器。你写代码时打交道最多的就是 AVPacket。
用 ffprobe 看包的细节:
ffprobe -v quiet -show_packets input.mp4 | head -30
输出示例:
[PACKET]
codec_type=video
stream_index=0
pts=0
pts_time=0.000000
dts=0
dts_time=0.000000
duration=2002
duration_time=0.033367
size=45678
pos=48
flags=K_
[/PACKET]
这里有几个关键字段:
- pts/dts:显示时间戳和解码时间戳。简单说,pts 决定什么时候显示,dts 决定什么时候解码。
- duration:这个包在时间轴上的长度。
- size:包的大小(字节)。
- flags:标记位。
K_表示关键帧,_表示非关键帧。
嗯,这里要注意:包不等于帧。一个包可能包含一整个帧,也可能只包含帧的一部分(比如 H.264 的切片)。反过来,一个帧也可能被拆成多个包。
4.3 帧(Frame)—— 你真正看到听到的东西
帧是解码后的原始数据。
视频帧就是一张一张的图片,音频帧就是一段一段的 PCM 采样。
包经过解码器处理后,才变成帧。
避坑指南:我曾经在做一个实时推流项目时,发现画面卡顿。排查了半天,最后用 ffprobe 看帧信息才发现——视频帧的 pts 间隔不均匀,有的帧间隔 40ms,有的突然跳到 80ms。这就是编码器参数没调好导致的。
查看帧信息:
ffprobe -v quiet -show_frames input.mp4 | head -40
输出示例:
[FRAME]
media_type=video
stream_index=0
key_frame=1
pkt_pts=0
pkt_pts_time=0.000000
pkt_dts=0
pkt_dts_time=0.000000
best_effort_timestamp=0
best_effort_timestamp_time=0.000000
pkt_duration=2002
pkt_duration_time=0.033367
pkt_pos=48
pkt_size=45678
width=1920
height=1080
pix_fmt=yuv420p
pict_type=I
[/FRAME]
帧信息比包信息多了很多解码后的参数:
- pict_type:帧类型。I 帧(关键帧)、P 帧(前向预测帧)、B 帧(双向预测帧)。
- key_frame:是否为关键帧。1 表示是,0 表示否。
- width/height/pix_fmt:解码后的实际分辨率、像素格式。
4.4 三者的关系 —— 一张图说清楚
你想想看,从文件到画面,经历了什么?
我画了一张图,帮你理清流、包、帧的关系:
说白了,整个流程就是:
- 容器(MP4 文件)包含多条流。
- 每条流被分割成一个个包(编码后的数据块)。
- 包经过解码器解码,变成帧(原始图像/音频数据)。
- 帧按照 pts 时间戳顺序渲染,最终呈现给你。
4.5 实战:用一条命令看全貌
我平时最常用的调试命令是这条:
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
输出 JSON 格式,一目了然。你可以看到:
- format 部分:文件总时长、总码率、封装格式。
- streams 数组:每条流的详细参数。
小技巧:如果你只想看关键信息,加个 -select_streams v:0 只选第一条视频流。我经常用这个来快速确认视频编码参数,省得被音频流干扰。
4.6 避坑总结
最后,分享几个我踩过的坑:
- 包和帧不是一一对应的。别以为一个包就是一个帧。H.264 的 B 帧可能跨包,音频包可能包含多个采样帧。
- pts 和 dts 不一样。B 帧的存在导致解码顺序和显示顺序不同。你用
-show_packets看到的 dts 是递增的,但 pts 可能跳来跳去。 - 关键帧很重要。我曾经在切片时没注意关键帧间隔,导致用户拖动进度条后黑屏了好几秒。后来强制设了
-g 30才解决。