🎬 第三章 FFmpeg三大常用工具及应用选项详解
📄 一句话概括

一条流一份档案,八项里最该看的是 time_base 和 pix_fmt。 nb_frames 常常没有或者不准,别拿它当准数。

📝 本节和前面几节的关系

3.3.3 钻到最细的帧,这一节回到中间层——每条流的属性。 3.3.5 讲输出格式,3.3.6 讲怎么在多流里挑一路。

一、八项里最该看什么

一条流的属性很多,但真正影响后续操作的只有八项:

字段 含义 为什么重要


index 流的编号 -map 要靠它选 codec_name 编码名 决定能不能解 width / height 分辨率 决定解码压力 pix_fmt 像素格式 决定能不能挂滤镜 time_base 时间戳的单位 算时长必须用它 avg_frame_rate 平均帧率 分母为 0 说明有问题 nb_frames 容器里记的帧数 常常没有或不准 bit_rate 这条流的码率 估带宽

第五行最容易踩坑:time_base 是「一个时间戳单位等于多少秒」,不是帧率。 1/12800 的意思是「一个单位 = 1/12800 秒」,所以 pts 涨 512 就是 40 毫秒。

算时长必须用它:pts × time_base 才是秒数。 直接拿 pts 当毫秒用,是最常见的「算出来时长差了十几倍」的原因。

二、nb_frames 为什么不可靠

这个字段的正确用法是「参考」,不是「相信」:

容器 nb_frames 有值吗 原因


mp4 常有,但不一定对 记在索引里,录制中断就偏 mkv 常常是 N/A 不预先记录总帧数 裸流 h264 一定没有 根本没有容器这一层

想要准确帧数只有一个办法:解一遍数。 这就是 -count_frames 存在的理由——它会让 ffprobe 真的去数,代价是慢。

对 mkv 尤其要注意:它边写边记,结尾才能知道总数, 所以文件正在写的时候读它,nb_frames 一定是 N/A。

三、音频流没有宽高

同一个文件里,视频流有 width/height,音频流没有——这不是读错了:

流类型 有宽高吗 有哪些独有字段


视频 有 pix_fmt、avg_frame_rate 音频 没有 sample_rate、channels、sample_fmt 字幕 没有 几乎没有独有字段

所以写脚本解析时,必须先看类型,再决定取哪些字段。 一律去取 width 的结果是——在音频流上直接抛异常,整个脚本中断。

更稳的写法是「先按类型分组,再分别处理」。 这也是 3.3.6 那个 -select_streams 的价值:它让你一次只面对一种类型。

四、动手 Demo:一条流一份档案

用一个「视频+音频」的双流文件,把两条流的档案都打出来。 重点是看:音频那两列确实是空的,以及时间基两条流不一样。

import av

c = av.open(u"av3.mp4")
for s in c.streams:
    cc = s.codec_context
    w = getattr(cc, u"width", 0)
    h = getattr(cc, u"height", 0)
    print(u"#0:%d  %-6s %-8s  %sx%s  时间基 1/%d"
          % (s.index, s.type, cc.name, w or u"-", h or u"-",
             s.time_base.denominator))

# 音频的独有字段
for s in c.streams.audio:
    cc = s.codec_context
    print(u"音频:采样率 %d  声道 %d" % (cc.sample_rate, cc.channels))
c.close()

实跑输出:

--------------------------------------------------------------------
ffprobe -show_streams——一条流一份档案
--------------------------------------------------------------------

**① 流段里最该看的八项**

  字段               含义               为什么重要
  ---------------------------------------------------------------
  index              流的编号           -map 要靠它选
  codec_name         编码名             决定能不能解
  width / height     分辨率             决定解码压力
  pix_fmt            像素格式           决定能不能挂滤镜
  time_base          时间戳的单位       算时长必须用它
  avg_frame_rate     平均帧率           分母为 0 就说明有问题
  nb_frames          容器里记的帧数     常常没有或不准
  bit_rate           这条流的码率       估带宽

**第五行最容易踩坑:time_base 是「一个时间戳单位等于多少秒」,不是帧率。**
**1/12800 的意思是「一个单位 = 1/12800 秒」,所以 pts 涨 512 就是 40 毫秒。**

**② 本机这个双流文件的流段**

  编号     类型      编码     宽      高     时间基
  ------------------------------------------------------
  0        video     h264     160     90     1/12800
  1        audio     aac      —       —      1/44100

**注意音频那两列是空的——音频没有宽高,这是正常的,不是读错了。**
**所以脚本里解析时要先看类型,别一律去取 width。**

**③ nb_frames 这个字段为什么不可靠**

  容器                nb_frames 有值吗     原因
  ---------------------------------------------------------------------
  mp4                 常有,但不一定对     记在索引里,录制中断就偏
  mkv                 常常是 N/A           不预先记录总帧数
  裸流(h264/es)     一定没有             根本没有容器这一层

**想要准确帧数只有一个办法:解一遍数。**
**这就是 `-count_frames` 这个参数存在的理由——它会让 ffprobe 真的去数。**

**④ streams 和 packets 的编号是对得上的**

**流段里 index=1 是音频,包段里 `-select_streams a` 拿到的就是它。**
**两处的编号是同一套,所以 `-map 0:1` 和 `-select_streams a` 指向同一个东西。**

getattr(cc, "width", 0) 那个兜底不是多余的: 音频的 codec context 上确实没有 width 属性,直接取会抛 AttributeError。

注意两条流的时间基可能不一样。 视频常见 1/12800,音频常见 1/44100 或 1/1000000——各按各的采样节奏记。 所以混算时长时必须分别换算,不能用同一个时间基。

s.time_base.denominator 拿到的是分母。 完整写出来是「分子/分母」,常见的时间基分子都是 1,但不敢假设—— 有的封装会用 1001/30000 这种形式,倒过来读就错了。

30 秒实验

  1. 看时间基:ffprobe -show_streams 片子.mp4 | grep time_base,两条流各是多少。
  2. 查帧率:ffprobe -show_streams 片子.mp4 | grep frame_rate,两个值可能不同。
  3. 数准帧数:ffprobe -count_frames -show_streams 片子.mp4 | grep nb_read_frames。

常见误区

  1. 拿 pts 当毫秒用。pts 是时间基的倍数,不是毫秒。要乘 time_base 才是秒。
  2. 相信 nb_frames。它只是声明值,mkv 里常常是 N/A。要准就 -count_frames。
  3. 对音频流取 width。音频没有宽高,直接取会抛异常。先判断类型再取字段。
自测三问(点开对答案) 1. time_base 是 1/12800,pts 是 512,这一帧在第几秒? —— 512 × (1/12800) = 0.04 秒,也就是第 40 毫秒。 2. 为什么音频流没有 width 和 height? —— 因为它不是画面。音频的对应属性是采样率、声道数、采样格式,完全另一套。 3. r_frame_rate 和 avg_frame_rate 有什么区别? —— 前者是「标称帧率」(按时间基算出来的基准),后者是「实际平均帧率」。可变帧率素材上两者会不同。

相关笔记