抓包不是「看热闹」,是「验证你说的和它做的是不是一回事」。 pcap 文件就三个数字:24 字节全局头、16 字节每包头、54 字节协议壳——懂了这三个, 抓包文件你自己也能造。
上一节验证了「发」,这一节验证「谈」——把整段 RTSP 交互录下来。 自己写一遍 pcap 生成器,比看十遍 Wireshark 界面有用: 你会知道「界面里那一行」是怎么从字节里长出来的。 报文逐字格式在第一章 1.3/1.5,这一节管「怎么录、怎么看」。
一、pcap 文件长什么样
一个 pcap 就是「全局头 + 一堆包头 + 一堆原始字节」,没有索引、没有压缩。
| 部分 | 大小 | 写了什么 |
|---|---|---|
| 全局头 | 24 字节 | 魔数、版本、快照长度、链路类型 |
| 每包头 | 16 字节 | 秒、微秒、实长、原长 |
| 包数据 | 变长 | 从以太网头开始的整帧 |
魔数 0xA1B2C3D4 是判断字节序的开关:读出来是这个值就是小端,反过来就是大端。
pcap 文件坏了,第一件事就是看这四个字节。
链路类型那个字段决定了「包数据开头是什么」:1 是以太网,101 是原始 IP,105 是 802.11。 类型对不上,Wireshark 就会把整包标红。
二、54 这个数从哪来
以太网头 14 + IP 头 20 + TCP 头 20 = 54。
| 层 | 字节 | 里面有什么 |
|---|---|---|
| 以太网 | 14 | 目的 MAC 6 + 源 MAC 6 + 类型 2 |
| IP | 20 | 版本、长度、协议、源目的 IP |
| TCP | 20 | 源目的端口、序列号、标志位 |
这个 54 是所有「抓包长度对不上」问题的答案:你在代码里数出 105 个字符,
Wireshark 列表里显示 159,差的就是它。
而如果你只看 data[54:],拿到的正好是 RTSP 报文的第一个字节。
注意 TCP 头可以更长:带选项时(比如时间戳选项)会是 32 或 40 字节,那时偏移就不是 54 了。 所以正规做法是读 IP 头的长度字段算出来,不是写死 54。
三、Wireshark 里该按什么过滤
过滤表达式不多,记住六条够用。
| 想看什么 | 过滤式 | 说明 |
|---|---|---|---|
| RTSP 信令 | rtsp | 只有那五步,看得清 |
| RTP 媒体 | rtp | 一秒几十个包,很多 |
| 某一路流 | rtp.ssrc == 0x0A0B0C0D | 多路时必用 |
| 某个包序 | rtp.seq == 100 | 追单个包 |
| 只看请求 | rtsp.method | 过滤掉响应 |
| 重建视频 | rtp → 菜单「解码为 RTP」 | 能直接放出来 |
第一行和最后一行是排查的两端:先用 rtsp 看握手对不对,再用「解码为 RTP」看画面能不能重建。
中间那四条是「多路场景」的必备:不按 SSRC 过滤,几十路混在一起根本看不出哪是哪。
四、抓包能证明什么,不能证明什么
这条界线要划清楚,否则会得出错误结论。
| 能证明 | 不能证明 |
|---|---|
| 握手顺序对不对 | 画面对不对 |
| 有没有丢包、丢在第几包 | 解码器解不解得开 |
| 延迟大概多少 | 客户端渲染快不快 |
| 编码参数是多少 | 画质好不好 |
右边这一列要靠「解码」才能回答,而抓包只到 RTP 载荷为止。 D23 里那句「Wireshark 只能标成 RTP 包,序列号 XXX」说的就是这个:H.264 载荷是二进制, 抓包软件没有解码器,认不出里面是画面。 想看图,得把载荷抽出来拆 NAL 再喂解码器——这就是第七章的活。
五、动手 Demo:写一个 pcap,再读回来
手工造 8 个包(OPTIONS 到 PLAY 一来一回),按 pcap 格式写盘,再按字节读回来, 把 TCP 载荷首行打出来。 这一步走通,Wireshark 的「神秘感」就没了。
def _pcap_write(path, pkts):
with open(path, "wb") as f:
# 全局头:魔数 版本 时区 精度 快照长 链路类型
f.write(struct.pack("<IHHiIII", 0xA1B2C3D4, 2, 4,
0, 0, 65535, 1))
for ts, data in pkts:
# 每包头:秒 微秒 实长 原长
f.write(struct.pack("<IIII", int(ts),
int((ts % 1) * 1e6),
len(data), len(data)))
f.write(data)
def _eth_ip_tcp(payload, sport, dport, seq, ack):
tcp = struct.pack("!HHIIBBHHH", sport, dport, seq, ack,
(5 << 4), 0x18, 8192, 0, 0)
ip = struct.pack("!BBHHHBBH4s4s", 0x45, 0, 20 + len(tcp)
+ len(payload), 0, 0, 64, 6, 0,
bytes([192,168,1,100]), bytes([192,168,1,64]))
eth = (b"\x00\x0c\x29\x11\x22\x33"
b"\x00\x0c\x29\x44\x55\x66" + b"\x08\x00")
return eth + ip + tcp + payload
实跑输出:
--------------------------------------------------------------------
把 RTSP 会话写成 pcap,再自己读回来
--------------------------------------------------------------------
**Wireshark 看到的「一条条」,本质就是个 pcap 文件。** 这一节先手写一个 pcap 生成器,
把完整 RTSP 会话灌进去,再解析回来。
写入 **8 个包**(OPTIONS 到 PLAY,八条报文一来一回)
读回来:魔数 0xA1B2C3D4,版本 2.4,快照长 65535,链路类型 1(1 = 以太网)
包数 8,文件 1390 字节。
# 时刻 TCP 载荷首行 长度
----------------------------------------------------------------------------
1 0.000 秒 OPTIONS rtsp://192.168.1.64:554/Streaming/C… 105
2 0.002 秒 RTSP/1.0 200 OK 78
3 0.004 秒 DESCRIBE rtsp://192.168.1.64:554/Streaming/… 102
4 0.006 秒 RTSP/1.0 200 OK 80
5 0.008 秒 SETUP rtsp://192.168.1.64:554/Streaming/Cha… 134
6 0.010 秒 RTSP/1.0 200 OK 130
7 0.012 秒 PLAY rtsp://192.168.1.64:554/Streaming/Chan… 111
8 0.014 秒 RTSP/1.0 200 OK 66
**pcap 三个数字要记住:24 是全局头,16 是每包头,54 是以太网+IP+TCP「壳」的长度。**
所以 Wireshark 顶部那个 Frame 长度,永远等于「TCP 载荷 + 54」。
你在代码里数出 105 个字符,它在列表里显示 159,差的就是壳。
**抓包能看到协议,但看不到内容**:RTP 里的 H.264 载荷是二进制,Wireshark 只能标成「RTP 包,
序列号 XXX」。
想知道画面里是什么,得把载荷按 H.264 拆 NAL 再解码——那是第七章的事。
读回来报的是「魔数 0xA1B2C3D4、版本 2.4、快照长 65535、链路类型 1」,四个字段全部对得上, 说明写对了。 8 个包、1390 字节——算一下:24 + 8×16 = 152,剩下的 1238 全是被抓的数据。 pcap 的开销就只有这 152 字节。
时刻那一列每次跑都一样(0.000/0.002/…),因为时间戳是我自己写死的。 真抓包里这一列是系统时间,能看出往返时间——这也是为什么抓包能用来算延迟。
TCP 序列号这一列我没打印,但它必须递增:真实抓包里如果序列号倒退或重复,就是重传, 重传一多,播放就卡。这是抓包查卡顿的第一招。
30 秒实验
- 把全局头的魔数改成
0xD4C3B2A1(大小端反过来),用 Wireshark 打开,看它能不能读、报什么错。 - 把链路类型从 1 改成 101,再用 Wireshark 打开,看它把包解析成什么——这就是「类型对不上」的表现。
- 多加 20 个包,把时间戳改成真实递增(每次 +5 毫秒),算一下平均往返延迟。
常见误区
- 以为抓包能看出画面对不对。 抓包只到 RTP 载荷,H.264 是二进制,不解码看不出画面。 要看画面得走「解码为 RTP」。
- 以为 54 是固定偏移。 那是没选项的 TCP 头。 带选项时是 62 或 70,该读 IP 头长度字段算,不该写死。
- 以为抓到包就说明链路正常。 抓到包只说明「到网卡了」,丢包、乱序、 重传都得看 RTP 序列号和 TCP 序列号才知道。
自测三问(点开对答案)
1. pcap 的魔数有什么用? —— 判断字节序。读出来是 0xA1B2C3D4 说明是小端存的,反过来就是大端。文件打不开先看这四个字节。 2. Wireshark 显示包长 159,你的代码数出 105,为什么? —— 差的 54 是以太网 14 + IP 20 + TCP 20 的协议壳。 Wireshark 显示的是整帧长度,你数的是 TCP 载荷。 3. 为什么多路场景必须按 SSRC 过滤? —— 每路流有独立的序列号和时间戳,混在一起看会以为大量丢包和乱序。 按 SSRC 分开,每一路才是自洽的。下一节换个角度:不再看工具,而是问「播放器到底由哪些零件组成」,把这一章的东西串成一张图。
- ☐ (本节完,下一节 02.02.01 视频播放器简介)