流媒体不是「更快的下载」,而是换了一种契约:不用等整份文件到齐,凑够一个缓冲就开始播。代价是它不保证完整、也不保证不卡。
这是全书第一节,从零开始。但如果你是接着 26 课《FFmpeg 音视频原理及应用》来的,那一课讲的是「怎么把画面压成码流」,这一课讲的是「压好的码流怎么送到观众眼前」。
一、从「下载」到「流」:省掉的是哪一段等待
下载的做法很直白:把文件完整搬到本地,再打开。 所以「能开始看」的时刻,等于「最后一个字节到齐」的时刻。
流媒体的做法是把这两件事拆开: 数据一边到,播放器一边放,只要本地攒下的一小段够播,就开。 于是「开始看」的时刻提前到了「第一个缓冲填满」的时刻。
这两个时刻差多少,取决于文件多大、带宽多快。 文件越大、带宽越慢,差得越多。 一个 2 小时的电影,下载完再播可能要等十几分钟;流媒体等两秒。
但这不是免费的。 下载完之后播放器对网络毫无要求,流媒体则要网络一直稳——它把「一次等待」换成了「一直在线」。
二、流媒体的三个前提
要让「边收边放」成立,技术上得同时满足三条:
① 码率要远低于原始数据。 未经压缩的一帧 1080p 有 3 MB,25 帧就是 75 MB 每秒,没有任何网络扛得住。H.264 压完只要几百 KB 每秒——这是流媒体能存在的地基。
② 数据要能切成小块,且每块能独立定位。 TS 切 188 字节的包、FLV 切 Tag、HLS 切几秒一片,都是为了让「先到的那部分」能马上用起来。
③ 解出来的第一帧不能依赖后面还没到的数据。 所以每个片段开头都得有一个关键帧(I 帧),播放器从它开始解,才不会糊成一团。
三条缺一条,流媒体就退化成下载。 早期网上看视频要「缓冲 100%」,就是第三条没做好——整个文件只有一个关键帧。
三、三个指标:首屏、卡顿、延迟
评价一个流媒体系统,业内基本只看三个数:
① 首屏时间:从点开,到第一帧画面出现。点播要求 1 秒内,直播容忍 2~3 秒。 ② 卡顿率:播放过程中停住等待的次数或时长占比。这个指标比首屏更重要——观众能忍开始慢,不能忍中间卡。 ③ 延迟:画面发生,到观众看到,中间隔了多久。点播不在乎,直播极在乎(连麦要求 400 毫秒内)。
三个指标互相拉扯。 想首屏快,就少缓冲;少缓冲就更容易卡;想不卡就多缓冲;多缓冲就延迟大。后面每一章讲的技术,本质上都是在解这个三角。
四、为什么现在才普及
流媒体的原理几十年前就有了,但真正普及是最近十几年。 三个条件同时到位:
① 带宽:4G/光纤让家庭和移动端都能稳定跑几 Mbps。2G 时代再好的编码也没用——管子太细。 ② 编码效率:H.264 比 MPEG-2 省一半以上码率,H.265 再省 30~50%,AV1 继续往下压。 ③ 分发成本:CDN 把「一个观众一份」摊到上千台机器上,让「一万人同时看」从不可能变成几毛钱。
这三条对应的正是本书的三个部分: 协议(第 2~6 章)解决「怎么送」,服务器(第 7~13 章)解决「怎么分发」,案例(第 14 章)把它们拼起来。
五、动手 Demo:流媒体与下载,到底差在哪一秒
下面真起一个限速的本地 HTTP 服务,按 8 Mbps 发 4 MB 数据, 分别掐表量「拿到第 1 MB」和「全部拿完」的时刻。
import http.server, socketserver, threading, time, urllib.request
SIZE, RATE = 4 << 20, 1 << 20 # 4 MB,按 1 MB/秒发
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(SIZE))
self.end_headers()
for i in range(0, SIZE, 32768):
t0 = time.perf_counter()
self.wfile.write(b"x" * min(32768, SIZE - i))
time.sleep(max(0, 32768 / RATE - (time.perf_counter() - t0)))
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]
t0, got, first = time.perf_counter(), 0, None
with urllib.request.urlopen(url) as r:
while True:
b = r.read(16384)
if not b:
break
got += len(b)
if first is None and got >= 1 << 20:
first = time.perf_counter() - t0
print("够播了 %.2f 秒 / 全部到齐 %.2f 秒" % (first, time.perf_counter() - t0))
srv.shutdown()
实跑输出:
--------------------------------------------------------------------
流媒体与下载——【边下边播】省掉的是哪一段等待
--------------------------------------------------------------------
① 场景设定
**一个 4 MB 的视频,带宽 8 Mbps**(理论 1 MB/秒),**播完要 4 秒。**
**本地真起一个 HTTP 服务,按 8 Mbps 限速发数据,真去拉——数字是掐表出来的。**
② 两种做法的时刻表
做法 要等多久才能开始 播完时刻 同时占用
----------------------------------------------------------------------
下载完再播 4.21 秒 8.40 秒 4 MB
流媒体(边下边播) 1.03 秒 4.19 秒 1 MB 缓冲
**下载完再播的「开始」等于「全部到齐」:4.21 秒拿到 4 MB。**
**流媒体只要凑够一个缓冲:1.03 秒拿到第 1 MB,够播就开。**
**两者的差距就是 3.18 秒——这就是「打开就能看」的全部秘密。**
③ 但流媒体不是白拿的
**① 它必须能【边收边解】**:容器格式得能自描述,不能等文件尾才有索引。
**② 它要【缓冲】**:网速抖一下就得停,所以播放器总要攒几秒数据再开。
**③ 它【不保证完整】**:中途断网,看到一半就是一半。
**下载完再播在这三点上全部相反,所以两者不是「快慢」,是两种契约。**
够播只用一秒出头,全部到齐要四秒多——中间这一段时间,就是流媒体替你省掉的。 注意这个倍率不是固定的:文件越大、带宽越小,省得越多。 所以流媒体的价值不在「快」,在「不必等完」。
30 秒实验
- 把 SIZE 改成 40 MB 再跑一次,看「够播」时刻变不变——它只跟缓冲多少有关,跟文件总大小无关。
- 把 RATE 改成 2 MB/秒,观察两条曲线的间距怎么变。
- 打开浏览器开发者工具的网络面板,看一个在线视频的首个请求——你会发现它一直在读,永远读不完。
常见误区
- 「流媒体就是把视频压小」——压缩是前提,不是定义。下载一个压得很小的 mp4 仍然是下载。
- 「流媒体一定比下载快」——传完全部字节的时间是一样的,快的只是「开始看」的时刻。
- 「缓冲越多越好」——缓冲越多越不容易卡,但延迟越大。直播里缓冲是敌人。
自测三问(点开对答案)
1. 一个 100 MB 的视频、带宽 2 MB/秒,下载完再播要等多久? —— **50 秒。**这和视频多长无关,只和大小、带宽有关。 2. 为什么流媒体的每个片段开头都要有关键帧? —— **因为解码要从关键帧开始。**中间帧只存「和前一帧的差」,拿不到前一帧就解不出来。 3. 首屏、卡顿、延迟,哪个指标最影响观感? —— **卡顿。**观众能接受开始慢两秒,但中间卡一次就会退出。- ☐ (本节完,下一节 1.2.1 TCP的三次握手与四次挥手)