🎬 第八章 FFmpeg命令行实现流媒体功能及直播应用
📄 一句话概括

RTP 承载 TS 时,一包装几个 TS 包由 pkt_size 算出来,不是一个固定值。 写 1316 只装 6 个,写 1400 才装 7 个。

📝 本节和前面几节的关系

8.2.1 讲了包头,这一节讲「怎么把 TS 塞进 RTP」。 这一步做错,接收端会收到一堆「格式对但内容接不上」的包。

一、先看素材封成 TS 有多大

RTP 里最常见的载荷是 MPEG-TS,因为 TS 天生就是「一个个定长小包」。 把 25 帧素材封成 TS,实测 18800 字节。

项 值


总字节 18800 TS 包数(188 一包) 100 除不尽的零头 0 字节

188 是 TS 的固定包长,写死在标准里。 所以 TS 的字节数永远是 188 的整数倍——100 个包正好 18800。

这个整除关系很重要:RTP 一包装几个 TS 包,必须是整数。 不能把第 7 个 TS 包切成一半塞进去,那样接收端根本拼不回来。

二、pkt_size 决定装几个

同一个素材,两个 pkt_size 值,实测结果完全不同:

pkt_size RTP 包数 每包最大 含 TS 包


1316 16 1140 6 1400 14 1328 7

注意每包字节不是 pkt_size 本身,而是比它小一截。 因为 pkt_size 是「整个 UDP 包的上限」,里面还要扣 RTP 头 12、IP/UDP 头 28。

算一遍就明白了:

算式 结果


1400 减 12 减 28 1360 字节可用 1360 里容几个 188 7 个,用掉 1316 1316 加回 12 1328(实测每包) 1316 减 12 减 28 1276 字节可用 1276 里容几个 188 6 个,用掉 1128 1128 加回 12 1140(实测每包)

所以「RTP 一包装 7 个 TS 包」这个说法,只在 pkt_size 给到 1400 时才成立。 按 pkt_size=1316 写,实际只有 6 个——差一整包。

三、两个数各自的取舍

装 6 个还是 7 个,影响的是「头部开销占比」:

方案 每包开销 开销占比


6 个 TS(1128 负载) 40 字节 约 3.4% 7 个 TS(1316 负载) 40 字节 约 2.9%

40 字节 = RTP 12 + UDP 8 + IP 20。 这一笔是固定成本,装得越多摊得越薄。

但装太满有风险:网络路径上的 MTU 不一定是 1500。 走 VPN、走隧道、走 PPPoE,有效 MTU 可能只有 1400 甚至更小。 所以在公网上很多人宁可牺牲一点效率,也要把包控在 1200 上下。

结论:局域网里用 1400,跨公网用 1200 上下,是常见的选择。 这些数都不是标准,是「踩过坑之后大家默认这么干」。

四、VLC 当 RTP 服务器

VLC 的写法是这样:

vlc s7.mp4 --sout="#rtp{dst=127.0.0.1,port=5004,sdp=rtsp://:8554/live}"

参数 含义


dst 往哪个地址送 port 往哪个端口送(5004 是 RTP 的惯例端口) sdp 顺手开一个 RTSP,让人来要 SDP

不开 sdp 的话,收流端必须自己有一份 SDP 文件,还要知道端口。 开了就方便:客户端只要连 RTSP 拿 SDP,剩下按 SDP 里的端口收。

5004 与 5005 是 RTP/RTCP 的惯例端口对。 它不是强制的,但工具默认都这么写,时间久了就变成了约定。

五、动手 Demo:两种 pkt_size 的差别

下面这段代码把 25 帧封成 TS,量总字节与包数; 再用两个不同的 pkt_size 各发一遍,抓每个 UDP 包的实际长度。

from fractions import Fraction
import av, io

buf = io.BytesIO()
o = av.open(buf, "w", format="mpegts")
st = o.add_stream("libx264", rate=Fraction(25, 1))
st.width, st.height, st.pix_fmt = 160, 90, "yuv420p"
st.bit_rate = 400000

c = av.open("s7.mp4")
n = 0
for f in c.decode(video=0):
    for p in st.encode(f):
        o.mux(p)
    n += 1
    if n >= 25:
        break
for p in st.encode(None):
    o.mux(p)
o.close()
c.close()
ts = buf.getvalue()
print(u"TS %d 字节 = %d 个 188" % (len(ts), len(ts) // 188))

for pkt in (1316, 1400):
    room = pkt - 12 - 28
    print(u"pkt_size=%d 可用 %d 字节,装 %d 个 TS 包"
          % (pkt, room, room // 188))

实跑输出:

--------------------------------------------------------------------
8.2.2 VLC 作为 RTP 流媒体服务器
--------------------------------------------------------------------

**① 先看素材封成 mpegts 有多大**

  项                      值
  ----------------------------------
  总字节                  18800
  TS 包数(188 一包)     100
  除不尽的零头            0 字节

**② 交给 PyAV 用 RTP 发,抓包看它怎么切**

  pkt_size     RTP 包数     每包最大     负载     含 TS 包
  ------------------------------------------------------------
  1316         16           1140         1128     6
  1400         14           1328         1316     7

**注意每包字节不是 `pkt_size`,而是比它小一截:**
**`pkt_size` 是「整个 UDP 包的上限」,里面还要扣掉 12 字节 RTP 头与 28 字节 IP/UDP 头。**
**剩下的空间再往下取到 188 的整数倍——封装器不会为了凑满去切开一个 TS 包。**

**所以大家嘴里说的「1316 装 7 个 TS 包」是对的,但那只在 `pkt_size=1400` 时成立。**
**真按 `pkt_size=1316` 写,一包只有 6 个 TS 包(1128 字节负载)——**
**这是本节最值得记住的一个数:**
**同一个词在「书上的说法」与「参数的实际含义」里不是一件事。**

**③ 这个除法是每一包都要做的**

  算式                         结果
  ---------------------------------------------------
  1400 减 12 减 28             1360 字节可用
  1360 里能容几个 188          7 个(用掉 1316)
  1316 加回 12 字节 RTP 头     1328(实测每包长)
  1316 减 12 减 28             1276 字节可用
  1276 里能容几个 188          6 个(用掉 1128)
  1128 加回 12 字节 RTP 头     1140(实测每包长)

**两行算式把上表两个数字都解释完了:1328 与 1140。**
**所以「一包装几个 TS 包」完全由 `pkt_size` 决定,**
**不是一个固定值——这一点比记住 1316 更有用。**

**顺带说一句:TS 包数 × 188 就是总字节,除不尽的那个零头会单独成一个短包。**
**本节素材 18800 字节,正好 100 个 TS 包,除得尽。**

**④ VLC 当服务器的那条命令**

`vlc s7.mp4 --sout="#rtp{dst=127.0.0.1,port=5004,sdp=rtsp://:8554/live}"`

**`dst` 与 `port` 是往哪送,`sdp` 是顺手开一个 RTSP 让人来要地址。**
**不开 `sdp` 的话,客户端必须自己知道端口,还要有一份 SDP 文件。**

第一行是 18800 字节、100 个 TS 包,跟节里的表一致。

后面两行才是重点:pkt_size=1316 只留出 1276 字节放数据, 1276 除以 188 得 6.8,取整就是 6 个——所以每包最大只有 1140 字节。

pkt_size=1400 留出 1360,除下来 7.2,能放 7 个,每包 1328 字节。 两个实测数都跟这个算式对得上,一个字节不差。

30 秒实验

  1. 试 1200:可用 1160,只能装 6 个(用掉 1128),包长 1140。
  2. 试 900:可用 860,装 4 个(752),包长 764。
  3. 试 188:可用 148,一个都装不下,看封装器报什么错。

常见误区

  1. 把 pkt_size 当负载长度。它是整包上限,还要扣 40 字节头。
  2. 以为一包固定 7 个 TS 包。只有 pkt_size 够大才是 7 个。
  3. 公网也往 1400 顶。隧道与 PPPoE 会把有效 MTU 压到 1400 以下。
自测三问(点开对答案) 1. `pkt_size=1316` 每包装几个 TS 包? —— 6 个。1316 减 40 得 1276,除 188 取整是 6。 2. 为什么要减 40? —— RTP 头 12、UDP 头 8、IP 头 20,一共 40 字节。 3. 公网上为什么不敢用 1400? —— 隧道、VPN、PPPoE 会把有效 MTU 压低,超了就会分片或丢包。

相关笔记