🎬 第三章 夯实FFmpeg基础:重要数据结构及API
📄 一句话概括

解封装只做一件事:把容器拆开,把字节分给各条流。 六个数读出来,文件的家底就清楚了。

📝 本节和前面几节的关系

3.1.2 的第 1、2 步,这一篇展开讲。 下一篇 3.2.1 开始逐个结构体看,这一篇是它们的第一次露面。

一、解封装到底做了什么

容器就像信封,码流是信纸。 解封装是拆信封,不是读信。 MP4 里有 moov、mdat 这些盒子,MKV 里有 EBML 元素, FLV 里有 tag——格式各不相同,但拆完得到的都是同一种东西:带时间戳的包。 层 输入端 输出端


解封装 文件、网络流 带时间戳的压缩包 解码 压缩包 原始帧 转换 原始帧 能显示的格式

这个分层解释了为什么换个容器不用改解码代码。 MP4 换成 MKV,变的只是拆信封那一段, 后面的解码一模一样。

二、容器这一层能读出的六个字段

这六个数是看一个陌生文件的第一眼。 字段 含义 单位


format 容器格式的短名 — duration 总时长 微秒 bit_rate 整文件平均码率 bit/秒 size 文件字节数 字节 start_time 起点时间戳 时间基 metadata 写文件的人留下的字 —

duration 的单位是微秒,这一条最容易错。 2023220 不是两千秒,是 2.02 秒。 要换成秒得除 1000000。 metadata 里的 encoder 是个线索。 它写着 Lavf62.12.102,说明这文件是本机 libavformat 写的。 拿别人给的文件时,这一行能看出工具链。

三、每一路流各自的账

容器是一本总账,每一路流是一本分账。 总码率是各路之和,总时长取最长的那一路。 字段 视频流 音频流


index 0 1 codec h264 aac time_base 1/12800 1/44100 duration 25600 89088 bit_rate 44380 138049 frames 50 0

音频的 frames 是 0,这不算错。 音频轨的头里一般不放总帧数,视频轨能放。 别把 0 读成「没有音频」。 视频的 duration 25600 乘 1/12800 是 2 秒,音频的 89088 乘 1/44100 是 2.02 秒。 同一段内容, 两条轨道的时长差一点点,是正常的。

四、时间基不一样,pts 不能直接比

这是本课后面反复要用的一个换算。 视频的时间基是 1/12800,音频是 1/44100。 同一个 pts 值 12800,在视频里是 1 秒,在音频里是 0.29 秒。 要做的事 正确的式子


pts 换秒 pts × time_base 秒换 pts 秒 ÷ time_base 比较两路 各自换秒之后再比 换到同一个时间基 pts × 原基 ÷ 新基

时间基是整数分子除以整数分母,不是浮点。 换算是整数运算, 要小心除不尽时的舍入——差一格就是一帧的几十毫秒。 下一节 3.2.1 会看到,时间基这个字段挂在三层上:容器、流、包,每一层的值可能都不一样。 以流上的为准最稳。

五、动手 Demo:把流信息一次读干净

真读一遍,看这个文件的家底。

import av

c = av.open("_media32/tiny.mp4")
print(c.format.name, c.duration, c.bit_rate, c.size)
for s in c.streams:
    print(s.index, s.type, s.codec_context.name,
          s.time_base, s.duration)
print(dict(c.metadata))
c.close()

实跑输出:

--------------------------------------------------------------------
把流信息一次读干净
--------------------------------------------------------------------

**① 容器这一层能读出什么**

  字段           值                          含义
  ---------------------------------------------------------------------
  format         mov,mp4,m4a,3gp,3g2,mj2     容器格式的短名
  duration       2023220(÷1000000)         微秒,=2023 毫秒
  bit_rate       182429                      整文件平均码率,bit/秒
  size           46137                       文件字节数
  start_time     0                           起点时间戳
  flags          0x200001                    打开方式的位标志

**duration 的单位是微秒,不是秒**,也不是 ffmpeg 命令行打出来的那种 00:00:02.02。
要换算成秒得除 1000000。

**② metadata 里的四条是写文件的人留下的**

compatible_brands = isomiso2avc1mp41
encoder = Lavf62.12.102
major_brand = isom
minor_version = 512

**encoder 那一行写着 Lavf62.12.102**,正是本机 libavformat 的版本。
拿别人的文件时,这一行能告诉你它是被哪一版写出来的。

**③ 每一路流各自的账**

  字段             视频流      音频流
  ----------------------------------------
  index            0           1
  codec            h264        aac
  time_base        1/12800     1/44100
  duration         25600       89224
  bit_rate         44380       127993
  frames           50          88
  average_rate     25          —
  sample_rate      —           44100

**两路流的时间基不一样**:视频 1/12800,音频 1/44100。
所以 pts 不能直接比大小,必须各自乘自己的时间基换成秒。

**④ 音频的 frames 是 0,视频是 50**

这是「头上写没写总帧数」的区别。视频轨道的头部能放帧数,音频轨道一般不放。
别把 0 当成「没有音频」。

**⑤ 一条命令代替这一整节**

ffprobe 打出来的就是这张表。本机没装 ffprobe(后面 3.7 会验证),所以只能用代码读。
**能读代码的地方,读代码比调命令行稳。**

c.duration 是 2023220,除一百万才得 2.02 秒。 metadata 里 encoder 那一行是写文件的工具留下的,不是内容信息。

30 秒实验

  1. 把 c.duration / 1000000 打出来,跟 ffprobe 习惯的显示对上。
  2. 读一个 mp3 文件,看它的流数与时间基跟 mp4 差多少。
  3. 故意用一个不存在的文件名,看 av.open 抛的是哪种异常。

常见误区

  1. 把 duration 当秒用。 它的单位是微秒,直接用会差一百万倍。
  2. 拿视频的 pts 去跟音频 pts 比。 两路时间基不同,必须先换秒。
  3. 以为 frames 为 0 就是没有这一路。 音频轨一般不在头里写总帧数。
自测三问(点开对答案) 1. `c.duration` 是 2023220,这是几秒? —— 2.02322 秒。单位是微秒,除 1000000。 2. 为什么视频与音频的时长不完全相同? —— 两路独立编码,各自的最后一帧结束时刻不同,差几十毫秒很正常。 3. 解封装之后得到的是帧还是包? —— 包。带时间戳的压缩包,还没解码。

相关笔记