🎬 第一章 流媒体与直播基础理论
📄 一句话概括

直播的成本结构是「一进多出」:主播上传一份,服务端要为每个观众单独下发一份。所以真正贵的是分发,不是推流。

📝 本节和前面几节的关系

1.3.1 走完了一条链路的八个环节,这一节把镜头拉远,看整体是什么形状。第 8~13 章讲的 Live555、Nginx、SRS、ZLMediaKit,全都在实现这个形状。

一、基本模型:一进多出

不管技术栈怎么变,直播的系统形状永远是这三层:

① 源(主播):一路上行,带宽由主播家的宽带上限决定。 ② 中心(服务器/集群):接收这一路,转封装,再分发给观众。 ③ 边缘(观众):N 路下行,每路都要一份完整的数据。

关键的不对称在于:上行是 1,下行是 N。 这个不对称是整个直播行业的成本来源。

有人会问:能不能让主播直接连观众(P2P)? 技术上可以(WebRTC 就支持),但主播的上行扛不住——1 Mbps 的上行,连两个观众就满了。 所以直播几乎从来不是 P2P,只有一对一通话才是。

二、带宽是乘出来的

服务端的出口带宽 = 观众数 × 单路码率。 这个乘法让很多人第一次算的时候吓一跳。

4 Mbps 的 1080p,1000 人看就是 4 Gbps。 一台普通服务器的网卡是 10 Gbps,也就是说一千人出头就把一块网卡占满了。 一万人看要 40 Gbps,十万要 400 Gbps——到这个时候,单机彻底不可能,必须靠集群和 CDN。

而 CDN 的本质就是「把这个乘法拆成很多个小乘法」: 源站只往上发给几十个边缘节点,每个边缘节点再发给几千人。 源站的出口从「人数 × 码率」变成「节点数 × 码率」。

这就是为什么 HLS 这种「切片 + 普通 HTTP」的协议能赢: 它让 CDN 用现成的静态缓存就能干活,不需要为流媒体专门改造。

三、三种分发拓扑

① 单机转发:一台 Nginx-rtmp 或 SRS,几百到几千路就顶住了。 优点是简单、延迟低;缺点是没有冗余,挂了全挂。 ② 集群(源站 + 边缘):一台源站收流,多台边缘回源再分发。 SRS 的 cluster、ZLMediaKit 的边缘模式都是这个思路。 优点是能扩容;缺点是要处理「回源」带来的额外延迟。 ③ CDN:把边缘铺到各个城市、各个运营商。观众就近取流,延迟低、带宽成本被摊薄。 缺点是贵,而且你对链路几乎没有控制权。

实际系统经常是三者叠加: 自建源站 → 自建一层边缘 → 再往外接 CDN。 每一层都在把「一个观众一份」摊薄一点。

四、两个特殊形态:连麦与合流

基本模型是「一进多出」,但有两类场景会打破它:

① 连麦:多个主播同时推流,观众要同时看到多路。这时候服务端要做「合流」——把几路画面拼成一路再分发。 否则观众就要同时拉 N 路,带宽和同步都受不了。 ② 互动直播(PK):两个主播的画面并排,中间还要实时同步比分、弹幕。 这已经超出「流媒体」的范畴,属于「实时音视频 + 信令」。

这两类场景的延迟要求是「连麦级」的(400 毫秒内),所以它们几乎一定用 WebRTC 而不是 RTMP。第 13 章的 WebRTC 就是为这个准备的。

五、动手 Demo:真拉:1 路、4 路、16 路同时拉同一份数据

本地起一个 HTTP 服务,用 1/4/16 个线程同时拉同一份 3 MB 数据, 量「服务端总共吐了多少字节」——这是分发的真实成本。

import http.server, socketserver, threading, time, urllib.request

SIZE = 3 << 20
DATA = bytes(SIZE)

class H(http.server.BaseHTTPRequestHandler):
    def log_message(self, *a):
        pass
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Length", str(len(DATA)))
        self.end_headers()
        try:
            self.wfile.write(DATA)
        except Exception:
            pass

class S(socketserver.ThreadingTCPServer):
    daemon_threads = True
    allow_reuse_address = True

srv = S(("127.0.0.1", 0), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
url = "http://127.0.0.1:%d/f" % srv.server_address[1]

def one():
    with urllib.request.urlopen(url) as r:
        while r.read(65536):
            pass

for n in (1, 4, 16):
    ts = [threading.Thread(target=one) for _ in range(n)]
    t0 = time.perf_counter()
    for t in ts:
        t.start()
    for t in ts:
        t.join()
    el = time.perf_counter() - t0
    print("%2d 路:%.3f 秒,服务端共吐出 %d MB" % (n, el, n * SIZE // (1 << 20)))
srv.shutdown()

实跑输出:

--------------------------------------------------------------------
一路推流 N 路分发——带宽为什么是【乘】出来的
--------------------------------------------------------------------
① 真拉:同一份 3 MB 数据,1/4/16 个观众同时拉

  观众数     总耗时       服务端吐出的字节     单观众平均
  ------------------------------------------------------------
  1 路       0.004 秒     3 MB                 682.4 MB/秒
  4 路       0.018 秒     12 MB                653.7 MB/秒
  16 路      0.539 秒     48 MB                89.1 MB/秒

  **注意「服务端吐出的字节」是随观众数线性涨的**:1 路 3 MB,16 路就是 48 MB。
  **这就是直播的成本结构。**
  **主播只上传一份,服务端要为每个观众单独下发一份。**

② 换算成真实带宽

  **一路 1080p 直播按 4 Mbps 算,服务端出口带宽 = 观众数 × 4 Mbps。**
  观众数      出口带宽     意味着什么
  ------------------------------------------
  100         400 Mbps     单机可扛
  10000       40 Gbps      必须上 CDN
  1000000     4 Tbps       多家 CDN 分层

  **1 Mbps 上行的小主播,能给多少人看?只有自己那台服务器。**
  **再多的人都靠服务端下发,跟他家的上行带宽没关系。**

③ 所以直播系统里真正贵的是「分发」

  **① 单机转发**(Nginx-rtmp、SRS):简单,几百到几千路就顶住了。
  **② 集群**(SRS 集群、ZLMediaKit 边缘):一层一层往下发。
  **③ CDN**:把「一个观众一份」摊到很多台机器、很多个机房。
  **这三种做法回答的是同一个问题:怎么把「乘出来的带宽」摊平。**

「服务端吐出的字节」是随观众数【线性】涨的:1 路 3 MB,16 路就是 48 MB。 注意单观众的速率在 16 路时明显掉下来——本地环回也有上限,16 个线程抢一个进程的 CPU。 真实网络里这个瓶颈出现在网卡、在交换机、在运营商链路上,量级更大但道理一样。

30 秒实验

  1. 把 SIZE 改成 30 MB 再跑 16 路,看总吞吐还能不能撑住——这里是本地环回,真实场景要先除以网络带宽。
  2. 算一笔账:4 Mbps 的流,1000 人看要多少出口带宽?4 Gbps,一块万兆网卡的一半。
  3. 把「16 路」改成「64 路」,看耗时是不是还是线性的——如果不是,说明瓶颈已经从网络换成了 CPU 调度。

常见误区

  1. 「主播上行够快就能直接连观众」——1 Mbps 上行连两个观众就满了。直播几乎不用 P2P,只有一对一通话才用。
  2. 「上 CDN 就不用管带宽了」——CDN 只是把乘法拆成小乘法,总流量没变,账单也没少。
  3. 「单机转发扛不住就换大机器」——出口带宽是网卡物理限制,换 CPU 没用,只能加机器或上 CDN。
自测三问(点开对答案) 1. 10 万人看 4 Mbps 的直播,源站出口要多大? —— **不做 CDN 是 400 Gbps,物理上不可能。**接了 CDN 后,源站只需要「节点数 × 4 Mbps」。 2. 为什么连麦要先合流再分发? —— **否则每个观众要同时拉 N 路,带宽和音画同步都受不了。** 3. HLS 为什么特别适合 CDN? —— **它就是一堆静态文件加一个文本播放列表,CDN 不用做任何流媒体改造就能缓存。**

相关笔记