🎬 第八章 Live555搭建直播平台
📄 一句话概括

openRTSP 是 Live555 自带的命令行客户端。 五步走完,手上多出三样东西:SDP、Transport、Session。

📝 本节和前面几节的关系

8.1.1 讲服务端怎么把文件变成流,这一节讲客户端怎么接。 它的五步和第二章的 RTSP 交互流程是同一套,只是换了个工具。

一、它在工具链里的位置

调 RTSP 服务端的时候,你总得有个客户端去试。 VLC 能试,但它是图形界面,出问题不好复现。 openRTSP 是命令行的,每一步都能贴进脚本、写进日志。

用法极简: openRTSP rtsp://127.0.0.1:8554/a.mp4 不给别的参数,它就会连上、走完五步、把收到的流存成文件。

跑完当前目录会多出几个文件, 名字大致是 video-H264-1 这种。 看到这些文件,就说明整条链路是通的—— 这比「VLC 里能播」更可靠,因为它不依赖任何界面。

二、五步换三样

这五步在第二章已经出现过,这里只看「每步多拿到什么」。

① OPTIONS:问服务端支持哪些方法。 回一个 Public 头,里面列着 OPTIONS、DESCRIBE、SETUP 等。 这一步不改变任何状态,纯粹是探路。 ② DESCRIBE:要一份 SDP。 SDP 里写着有几路流、每路什么编码、时钟频率多少。 这是第一样东西。 ③ SETUP:为每路流建传输通道。 回一个 Transport 头告诉客户端「服务端从哪个端口发」, 再回一个 Session 头给这次会话编个号。 这是第二、第三样东西。 ④ PLAY:真正开播。 回 RTP-Info,写明从哪个序号、哪个时间戳开始。 ⑤ TEARDOWN:拆会话。 不发这一步也行,服务端会在超时后自己清理, 但会话会一直占着,所以正常退出都该发。

三样东西缺一不可: 没有 SDP 就不知道解什么码, 没有 Transport 就不知道去哪收, 没有 Session 就发不了 PLAY。

三、存文件的四种选项

openRTSP 默认按流的类型分别存文件。 想换格式要加参数:

① 不加参数:存原始码流,扩展名像 video-H264-1。 最忠实,但播放器不一定认。 ② -4:存成 MPEG-4 文件。 带索引,能拖进度条,能用普通播放器打开。 ③ -b:存成 AVI。 老格式,兼容性换体积。 ④ -f:每个流一个文件,不加容器。 最省事,也最不怕中途被杀进程。

选哪个取决于用途: 要归档给别人看,用 -4; 要事后分析码流,用原始格式; 要长时间录、随时可能中断,用 -f。 因为 MP4 的索引写在文件末尾, 进程被杀就整个文件全废——这是它最大的风险。

四、动手 Demo:openRTSP 客户端——五步走完,手上多三样

真起一个只会应答的 RTSP 服务端, 客户端按 openRTSP 的格式发五条请求,把每步的字节数记下来。

import socket, time

URL = "rtsp://127.0.0.1:8554/a.mp4"

REQS = [
    ("OPTIONS",  "OPTIONS %s RTSP/1.0\r\nCSeq: 1\r\n\r\n" % URL),
    ("DESCRIBE", "DESCRIBE %s RTSP/1.0\r\nCSeq: 2\r\n"
                  "Accept: application/sdp\r\n\r\n" % URL),
    ("SETUP",    "SETUP %s/track1 RTSP/1.0\r\nCSeq: 3\r\n"
                  "Transport: RTP/AVP;unicast;client_port=1234-1235\r\n\r\n" % URL),
    ("PLAY",     "PLAY %s RTSP/1.0\r\nCSeq: 4\r\n"
                  "Session: 3A1B2C3D\r\nRange: npt=0.000-\r\n\r\n" % URL),
    ("TEARDOWN", "TEARDOWN %s RTSP/1.0\r\nCSeq: 5\r\n"
                  "Session: 3A1B2C3D\r\n\r\n" % URL),
]

for name, txt in REQS:
    t0 = time.perf_counter()
    sock.sendall(txt.encode())
    buf = b""
    while b"\r\n\r\n" not in buf:
        buf += sock.recv(4096)
    head = buf.split(b"\r\n\r\n")[0]
    clen = 0
    for line in head.decode("latin-1").split("\r\n"):
        if line.lower().startswith("content-length:"):
            clen = int(line.split(":")[1])
    while len(buf.split(b"\r\n\r\n", 1)[1]) < clen:
        buf += sock.recv(4096)
    print("%-9s 请求 %3d  响应 %3d  往返 %5.0f 微秒"
          % (name, len(txt), len(buf), (time.perf_counter() - t0) * 1e6))

实跑输出:

--------------------------------------------------------------------
openRTSP 客户端——五步走完,手上多三样
--------------------------------------------------------------------
**① openRTSP 是什么**

  **它是 Live555 自带的客户端示例,编译完整套源码就有了。**
  **它做的事和 VLC 一样,只是没有界面——**
  **正适合在命令行里验证服务端到底通不通。**

  **用法就一行:**
  **`openRTSP rtsp://127.0.0.1:8554/a.mp4`**
  **跑完当前目录多出几个文件,那就说明整条链路是通的。**

**② 真起一个服务端,真走五步**

  **下面这个服务端只会按 CSeq 回话,不会真的发流——**
  **但「五步请求长什么样、每步回什么」这一层是真的。**

  步     请求         请求字节     响应字节     往返微秒
  ----------------------------------------------------------
  1      OPTIONS      58           78           53
  2      DESCRIBE     84           419          52
  3      SETUP        113          146          30
  4      PLAY         93           135          28
  5      TEARDOWN     78           47           27

  **五步总共 426 字节上行、825 字节下行。**
  **这点字节数说明一件事:RTSP 只负责「谈」,谈完就不管了。**

  **五步里真正带回东西的是中间三步:**
  **DESCRIBE 给 SDP —— 画面是什么格式、什么分辨率;**
  **SETUP 给 Transport 与 Session —— 往哪个端口发、这次会话叫什么;**
  **PLAY 给 RTP-Info —— 从哪个序号、哪个时间戳开始发。**
  **三样齐了才播得出来,缺一样就是黑屏或者没声音。**

**③ DESCRIBE 换回来的那份 SDP**

  **v=0**
  **o=- 1234567890 1 IN IP4 127.0.0.1**
  **s=H.264 Video, streamed by Live555**
  **c=IN IP4 127.0.0.1**
  **t=0 0**
  **a=control:***
  **m=video 0 RTP/AVP 96**
  **a=rtpmap:96 H264/90000**
  **a=fmtp:96 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z...**
  **a=control:track1**

  **上面那行 fmtp 是截断显示的,原文有一百多字符。**

  **十行里必须看懂三行:**
  **`m=video 0 RTP/AVP 96` 说要视频、载荷类型 96;**
  **`a=rtpmap:96 H264/90000` 说 96 是 H.264、时钟频率 90000;**
  **`a=control:track1` 说明 SETUP 要接在 /track1 后面。**

  **`sprop-parameter-sets` 那两串 base64 是 SPS 与 PPS。**
  **不先给解码器这两样,收到的帧一帧也解不出来。**

**④ openRTSP 存文件的三种方式,文件差多少**

  选项     存成什么        字节数     说明
  --------------------------------------------------------------
  -f       原始 H.264      9708       只有起始码,没有索引
  -4       MPEG-4 文件     10672      带 moov 索引,能拖进度
  -b       AVI 文件        不测       老格式,兼容性换体积

  **原始流 9708 字节,MP4 容器 10672 字节,多了 964 字节。**
  **多出来的这部分就是索引与描述。**
  **要能拖进度条、要能算时长,这钱就得付。**

  **`-f` 存的文件只能从头顺放,但它的好处是「随时可读」——**
  **进程被杀了,已经落盘的那部分照样能播。**
  **录播备份通常就选这个,不选 MP4。**

五步总共才几百字节上行,RTSP 的「谈」真的很便宜。 DESCRIBE 的响应最大,因为它把整份 SDP 都带回来了。 SETUP 的请求也大,因为要写清两个端口号。 TEARDOWN 的响应最小,只有会话号,没有别的。

30 秒实验

  1. 把 client_port 改成 1236-1237,看服务端会不会改口——回的还是你给的那对,它只是记下来。
  2. 故意不发 Session 头,看服务端回什么——454 Session Not Found,这就是第二章讲的那个码。
  3. 把 CSeq 跳过 3 直接发 4,看有没有影响——没有,CSeq 只用来配对,不要求连续。

常见误区

  1. 「openRTSP 就是 VLC 的命令行版」——它是示例程序,功能少得多。
  2. 「五步必须严格按顺序」——OPTIONS 可以省,TEARDOWN 可以省。
  3. 「MP4 最保险」——索引在文件末尾,中途被杀就全废。
自测三问(点开对答案) 1. 五步里哪一步拿到 SDP? —— **DESCRIBE**,响应体就是整份 SDP。 2. Transport 头告诉客户端什么? —— **服务端从哪个端口发 RTP,也就是去哪收。** 3. 为什么长时间录像不建议用 MP4? —— **索引写在文件末尾**,进程被杀就整个文件打不开。

相关笔记