🎬 第四章 FFmpeg命令行实现音视频转封装
📄 一句话概括

三个盒子三种设计:AVI 索引在尾、FLV 边写边记、TS 根本没有索引。 「没有索引」正是 TS 能用于广播的根本原因。

📝 本节和前面几节的关系

4.3.1~4.3.3 分别转过这三个盒子,这一节把它们的设计摆在一起看。 看懂设计,就知道什么场合该选谁。

一、三个盒子,三种设计取向

差别不在「支持什么编码」,在「怎么记账」:

盒子 索引在哪 时间戳粒度 典型用途


AVI 文件尾 粗(按块) 老设备、离线播放 FLV 边写边记 毫秒 直播、低延迟 TS 根本没有 90 kHz 计数 广播、HLS

第一行的「索引在尾」是它最大的弱点:录制中断,索引没写上去,文件就打不开。 第二行解决了这个问题,代价是索引一直在文件里穿插着写,稍微占点地方。

第三行最极端:不要索引。代价是「没法快进到任意位置」,好处是「从哪儿切进去都能解」。 广播要的正是后者——观众随时打开电视,不能等他先下一份目录。

二、TS:188 字节一包,靠 0x47 对齐

TS 的结构简单到可以手算:

项 值


文件字节 63920 按 188 切 340 包 首字节是 0x47 340/340 尾部余数 0

340 个包全部以 0x47 开头,一个不差——这就是「固定包长加同步字节」的威力。 播放器从文件中间随便切一个位置,往后找最近的 0x47 就能开始解。

对比 MP4:没有固定包长,想从中间进必须读 moov 索引。 所以「能不能从任意位置开始」这件事,是盒子结构决定的,不是解码器决定的。

三、体积差异的来源

同样内容,三个盒子差出 75%:

文件 字节 相对 mp4


原 MP4 37982 100% flv 37807 100% mkv 36531 96% ts 63920 168%

ts 那 68% 的增量全是包头:每 188 字节带 4 个字节的头,比例正好约 5%, 再加上每个包填充不满就浪费——所以短素材上这个比例会被放大。

所以「转成什么格式」这个决定里,体积应该排在兼容性与用途后面。 为了省几个百分点去换一个不合用的盒子,是典型的算小账。

四、动手 Demo:三个盒子的实测:包长、同步字节、体积

TS 的包长是不是真的 188 字节?用「每 188 个字节取一个字节看是不是 0x47」来验。 再把三个盒子的体积并排放,看差额从哪来。

import av, os

def ts_check(p):
    with open(p, u"rb") as f:
        data = f.read()
    n = len(data) // 188
    good = sum(1 for i in range(n) if data[i * 188] == 0x47)
    return n, good, len(data) % 188

n, good, rem = ts_check(u"o.ts")
print(u"ts %d 字节,%d 包,首字节 0x47 的 %d 个,余 %d 字节"
      % (os.path.getsize(u"o.ts"), n, good, rem))

for name in (u"m4.mp4", u"o.flv", u"o.mkv", u"o.ts"):
    print(u"%-8s %d" % (name, os.path.getsize(name)))

实跑输出:

--------------------------------------------------------------------
4.3.4 AVI/FLV/TS 三个盒子的脾气
--------------------------------------------------------------------

**① 三个盒子,三种完全不同的设计**

  盒子     索引在哪         时间戳粒度      典型用途
  --------------------------------------------------------------
  AVI      文件尾           粗(按块)      老设备、离线播放
  FLV      边写边记         毫秒            直播、低延迟
  TS       根本没有索引     90 kHz 计数     广播、HLS

**第三行那个「根本没有索引」是 TS 能用于广播的根本原因:**
**从流的任意位置切进去都能立刻解码,不需要先读一张目录。**

**② TS 的包长固定 188 字节,靠一个同步字节对齐**

  项                      值
  ------------------------------------
  TS 字节                 63920
  按 188 字节切的包数     340
  首字节是 0x47 的包      340/340
  尾部剩几个字节          0

**0x47 这个字节就是同步字节,每个包的第一个字节都是它。**
**所以「从任意位置切进去」这件事在 TS 里是这样实现的:往后找最近的 0x47 就开始解。**

**③ 三个盒子体积对比**

  文件       字节
  --------------------
  原 MP4     37982
  flv        37807
  mkv        36531
  ts         63920

**体积差的是「盒子自己的账」:TS 每个包都带包头,AVI 有块索引,FLV 有 tag 头。**
**所以同样内容换个盒子,大小能差出百分之几到百分之十几,这是正常的。**

sum(1 for i in ... if ...) 这一行是本节的核心:把「包长 188」当成一个假设去验。 如果包长猜错,good 会立刻掉到接近 0——这是一个很好的自检手段。

余数那一项也别忽略:它说明文件长度正好是 188 的整数倍,没有尾包残渣。 真实直播流里偶尔会有尾部不完整的包,那时候余数就不为 0。

体积对比里,ts 比 mp4 大 68%,这个数在短片子上偏高。 片子越长,包头占比越稳定在 4/188 左右,增量会收敛到约 2%。

30 秒实验

  1. 造三个盒子:分别 -c copy 转 flv、mkv、ts,比体积。
  2. 验同步字节:用 xxd -s 188 -l 4 出.ts 看第 189 个字节是不是 0x47。
  3. 看时长差异:ffprobe -show_format 三个文件,时长应完全一致。

常见误区

  1. 以为 TS 的包长可以变。它就是固定 188 字节,这是它能被硬件解析的前提。
  2. 以为 TS 没索引是缺陷。那是为广播做的取舍:牺牲随机访问,换来随时切入。
  3. 为了省体积选 TS。它恰恰是最费体积的那个,选它是因为用途不是体积。
自测三问(点开对答案) 1. TS 每个包的第一个字节是什么?为什么重要? —— 是 0x47 同步字节。靠它可以在流的任意位置对齐到包边界,从而实现随时切入。 2. 为什么 AVI 录到一半断电文件就废了? —— 因为它的索引写在文件尾,中断时索引还没写,播放器无法定位数据。 3. 同样内容,TS 比 MP4 大 68%,增量来自哪里? —— 来自每 188 字节一个的包头,以及包内填充造成的浪费。

相关笔记