🎬 第十一章 SRS搭建直播平台
📄 一句话概括

推流是四步:握手、connect、createStream、publish。 四步走完才能发 metadata 与音视频,顺序错了服务端直接踢。

📝 本节和前面几节的关系

11.5.1 把带 RTC 的版本编出来了,这一节先用普通的 RTMP 推。 11.5.3 换 WebRTC 播同一路流。

一、推流是四步

① 握手:发 C0+C1 共 1537 字节,收 S0+S1+S2 共 3073 字节。 ② connect:带 app 名与 tcUrl,等一个 _result。 ③ createStream:要一条流,回的 _result 里带流号。 ④ publish:带流名,等 onStatus 里的 Publish.Start。

四步的顺序不能变,因为每一步都在给下一步准备参数: connect 决定 app,createStream 决定流号,publish 决定流名。 顺序错了服务端会直接断开,而且不会告诉你为什么。

握手这 3073 字节是双向对称的,这个数很好记: 1537 是 C0 一个字节加 C1 的 1536 字节,另一侧完全一样。

二、真跑一遍:每一步多少字节

这一节不是画流程图,是真起一个只负责接收的 RTMP 服务端, 把一段真编出来的 FLV 推上去,逐步记字节数。

实测下来:握手上下各 3073 字节,后面三条命令都是几十字节, 而视频消息一口气发了一万多字节。 整段上行 13749 字节,服务端只回了 232 字节。

这个比例说明推流的本质: 客户端说一百句,服务端回一句。 所以推流的瓶颈永远在推流端的上行带宽,不在服务端的响应。

三、这份数据说明了什么

两条结论特别值得记:

① 视频消息最大与最小差了 67 倍。 最大那条是关键帧(I 帧),小的是普通帧(P 帧)。 这意味着码率只是平均值,瞬时带宽要按关键帧算, 否则一到关键帧就卡。 ② 消息被切成 128 字节一块,块头开销占 4.1%。 握手之后一共 10676 字节,其中负载 10240 字节, 剩下 436 字节全是块头。

第二条有个反向的推论: 消息越大,块头摊得越薄;消息越小,块头占比越高。 所以 RTMP 在低码率、小帧的场景下效率最差——这也解释了为什么同一路流,音频单独推的时候开销比例会更高。

四、动手 Demo:推 RTMP 流——四步,一次真的推上去

起一个只接收的 RTMP 服务端,手工完成握手与三条命令, 再把整段 FLV 的 tag 按消息发过去。

# 1. 握手:发 C0+C1,收 S0+S1+S2
c.sendall(b"\x03" + c1)            # C0 + C1(1536)
s1 = rd(c, 1537)                     # S0 + S1
rd(c, 1536)                          # S2
c.sendall(s1[1:])                    # C2 = S1 回抄

# 2. connect
body = amf_str("connect") + amf_num(1) + amf_obj({
    "app": "live", "tcUrl": "rtmp://127.0.0.1/live"})
c.sendall(chunk(3, 20, 0, 0, body))

# 3. createStream
c.sendall(chunk(5, 20, 0, 0,
                amf_str("createStream") + amf_num(2) + amf_null()))

# 4. publish
c.sendall(chunk(5, 20, 1, 0,
                amf_str("publish") + amf_num(3) + amf_null()
                + amf_str("livestream")))

# 5. metadata 与视频帧,逐个 tag 发
for t, ts, body in flv_tags(raw):
    if t == 18:
        c.sendall(chunk(4, 18, 1, ts, body))    # metadata
    elif t == 9:
        c.sendall(chunk(6, 9, 1, ts, body))     # 视频

实跑输出:

--------------------------------------------------------------------
推 RTMP 流——四步,一次真的推上去
--------------------------------------------------------------------
**① 推流是四步,一步都不能少**

  步骤             发什么                 等什么回应
  -----------------------------------------------------------------
  握手             C0+C1 共 1537 字节     S0+S1+S2 共 3073 字节
  connect          app 名与 tcUrl         `_result`
  createStream     要一条流               `_result` 带流号
  publish          流名                   `onStatus` 的 Start

  **四步走完才能发 metadata 与音视频。**
  **顺序错了服务端会直接踢掉,而且不会告诉你为什么。**

**② 真跑一遍,全程实测**

  **先编一段 FLV,再起一个只负责接收的 RTMP 服务端,把它推上去。**

  **每一步各花了多少字节:**

  阶段             上行字节     下行字节
  ------------------------------------------
  握手             3073         3073
  connect          124          83
  createStream     37           41
  publish          45           108
  metadata         197          0
  视频             10273        0
  合计             13749        232

  消息类型        条数     说明
  ---------------------------------------------------------------
  20 命令         3        connect、createStream、publish
  18 metadata     1        onMetaData,尺寸与码率
  9 视频          27       序列头 1 条 + 每帧 1 条 + 结束标记

  **publish 的回应是 `NetStream.Publish.Start`,服务端认下了这条流。**
  **整段上行 13749 字节,服务端只回了 232 字节。**
  **推流是极度不对称的:客户端说一百句,服务端回一句。**

**③ 这份数据说明了什么**

  **26 条视频消息里,最大 3198 字节、最小 48 字节,差了 67 倍。**
  **最大那条是关键帧(I 帧),小的是普通帧(P 帧)。**
  **这一点对带宽规划很关键:码率是平均值,**
  **瞬时带宽要按关键帧算,否则一到关键帧就卡。**

  **消息被切成了 128 字节一块:31 条消息里,25 条要切好几块。**
  **握手之后一共发了 10676 字节,其中消息负载 10240 字节,**
  **剩下的 436 字节全是块头的开销,占 4.1%。**
  **这就是 RTMP 在高码率下效率不如裸 TCP 的原因:**
  **消息越大,块头摊得越薄;消息越小,块头占比越高。**

握手上下各 3073 字节,三条命令加起来不到 250 字节。 视频一口气 10273 字节,上行的绝大部分都在这儿。 服务端全程只回了 232 字节,比例悬殊。 publish 的回应是 NetStream.Publish.Start,收到它才算推上去。

30 秒实验

  1. 把 publish 放到 createStream 前面,猜会怎样——服务端直接断开,因为它还不认识那个流号。
  2. 不发 metadata 只发视频,看还能不能播——多数播放器能播,但不知道尺寸,可能显示错位。
  3. 把 chunk size 从 128 调大,猜块头占比怎么变——降下来,这正是 RTMP 支持协商块长的原因。

常见误区

  1. 「握手是单向的」——双向各 3073 字节,完全对称。
  2. 「推流带宽看平均值」——要看关键帧那一瞬间的峰值。
  3. 「服务端要回很多数据」——推流阶段只回几条应答。
自测三问(点开对答案) 1. 推流四步的顺序是什么? —— **握手、connect、createStream、publish。** 2. 握手一共多少字节? —— **单向 3073 字节**,双向各一份。 3. 为什么关键帧那一瞬间带宽最高? —— **I 帧不参考别的帧,数据量最大**,实测差 67 倍。

相关笔记