JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

串流影片:DASH 與自適應位元率

一部兩小時的電影為何能在你按下播放的瞬間就開始播、Wi-Fi 一弱也不會卡住、還能在筆電上清晰、在手機上恰好——而這一切都跑在普通的 HTTP 上,由播放器、而非伺服器來做主。

問題所在:一支影片、千百觀眾、每個網路都不同

整級下來,你一直在讓網頁感覺起來「離你很近」:用快取,這樣已經有的東西不必重抓;用一張由邊緣伺服器組成的內容傳遞網路,讓位元從離你不遠的倉庫出發;用 anycast 與負載平衡,讓你的請求落在附近一台健康的機器上。現在,我們把這一切瞄準最難妥善遞送的那一樣東西:影片。一部電影不是 50 KB 的網頁,而是好幾 GB,它必須照順序抵達,還得在一條你無法掌控、且可能分分鐘變速的連線上,順暢播放好幾個小時。

而這裡有殘酷的一面:你的網路並非單一固定速度。你在火車上看,訊號穿過隧道時衰弱;家裡另一個人開始一筆巨大的下載;你從強 Wi-Fi 走進一個訊號弱的角落。如果影片只用單一固定畫質送出,你就會被困在兩個糟糕選項之間:要嘛選一個低到連你的鏈路明明很好時也難看的畫質,要嘛選一個高畫質、卻在你鏈路一弱就卡住轉圈圈。現代串流的全部功夫,就是逃出那個陷阱——而那個訣竅,出乎意料地,是「乾脆別把影片當成一筆長長的下載來看待」。

核心想法:把電影切成小塊

現代 HTTP 串流的兩大標準是 DASH(基於 HTTP 的動態自適應串流)與蘋果的 HLS(HTTP 即時串流),它們的第一步,是把影片切成許多短短的片段——通常每段二到十秒。關鍵在於:伺服器會把整部電影編碼好幾遍,每一種畫質各編一遍:也許一個小小的 240p 版本、一個 480p、一個 720p、一個清晰的 1080p,還有一個 4K。每一種畫質都在「相同的時間邊界」上切開,所以 240p 那份的第 37 段,涵蓋的正是電影裡與 1080p 那份第 37 段「同一個瞬間」。它們是同一刻可互換的切片,只是有重有輕。

伺服器還會寫出一個小小的文字檔——DASH 稱它為 MPD,也就是「清單檔」(manifest)——本質上就是一份菜單。它列出每一種可用畫質、各自的解析度與位元率,以及每一段該去哪裡抓。當你按下播放,播放器會先下載清單檔。它下載的不是影片,而是這份目錄與畫質清單;從那一刻起,做每一個決定的都是播放器。伺服器的任務於是縮小成它本來就極擅長的事:在被要求時,用 HTTP 發出一個個小檔案。沒有特殊的影片協定,伺服器上也沒有一個被撐開的串流連線。每一段都只是又一個 HTTP GET,就跟抓一張圖片一樣。

自適應位元率:播放器一段一段地選

現在來到核心。因為每一段在每一種畫質都存在,播放器便能在進行的過程中,為每一段「分別」選一個畫質。這就是 自適應位元率串流,常簡稱 ABR。每下載完一段,播放器就量測這次抓取的狀況——主要是它「實際」拿到了多少吞吐量。如果這段又快又有餘裕地抵達,下一次播放器就伸手去拿更高的畫質;如果它抓得很慢、只勉強趕上,播放器就退到較輕的畫質,好讓下一塊在被需要之前必然送達。影片畫質於是隨著你真實、變動中的連線一階一階上下移動,而不是把一切都押在開頭那一次猜測上。

但只看吞吐量會跳動、容易誤判,所以好的 ABR 播放器會倚靠第二個更穩的訊號:播放緩衝區。片段抵達時,播放器並不立刻顯示,而是在你正在看的進度之前,囤積幾秒鐘已解碼的影片。那個緩衝區,就是你的避震器。如果你的鏈路在隧道裡短暫消失,播放器會一邊等待、一邊從緩衝區繼續播放,而你完全不會察覺。於是聰明的播放器會像看油表那樣盯著它的緩衝水位:當緩衝舒舒服服地滿著,它就有本錢賭一把更高的畫質;當緩衝正在見底,它就抓下它必須抓的最低畫質來回填——因為畫面稍微糊一點,永遠好過凍結轉圈圈。

  1. 按下播放。播放器抓取清單檔,得知可用畫質清單,以及每一段住在哪裡。
  2. 為了快速開播,它先以保守的低畫質請求第一段——幾秒內開始,好過為了一塊沉重的 4K 苦等。
  3. 它替那次下載計時、記下實際拿到的吞吐量,同時在播放頭之前把播放緩衝區填起來。
  4. 下一段,它挑一個「量到的吞吐量能輕鬆支撐」的畫質——緩衝健康就升一階,緩衝見底就降一階。
  5. 整部電影裡每一段都重複這個動作,於是畫質不斷追隨你變動的鏈路,而你從沒碰過任何設定。

為什麼用 HTTP、為什麼切段,以及 CDN 如何讓它變便宜

退一步,欣賞這些零件如何彼此咬合。每一段都是尋常的 HTTP GET,這意味著「爆紅」這個龐大的問題,靠快取就自己解決了。當一部熱門劇上架,數百萬人在幾分鐘內請求 1080p 版本的第 1 段——但「每個邊緣」的第一個請求會去源站抓,之後的每個請求都是快取命中,直接由各觀眾附近的邊緣伺服器供應。少少幾個不同的檔案(幾種畫質乘以片段數)就涵蓋了整個觀眾群。源站只看見細細一注水流;洪水則由 CDN 吸收。這正是前幾篇講的「在地倉庫」想法,如今在做它最吃重的活。

把決定推到播放器那一端,是這個設計的下半段,而它遵循的,正是你整條階梯一路見到的同一個端到端精神:讓網路的中間保持笨拙而簡單,把聰明放進邊緣的主機裡。伺服器不追蹤你的鏈路速度、也不照看你的連線階段——它甚至看不見壅塞在哪裡。但你的播放器一段一段地、直接感受到每一次卡頓與每一次衝高,所以它正是做出畫質決定最對的地方。網路維持成一台簡單的檔案伺服器,而智慧,就住在「體驗發生的地方」。

  manifest (MPD) lists the same movie at several bitrates:

  time:     seg 1     seg 2     seg 3     seg 4     ...
  1080p  [  6 Mbps |  6 Mbps |  6 Mbps |  6 Mbps ]
   720p  [  3 Mbps |  3 Mbps |  3 Mbps |  3 Mbps ]
   480p  [  1 Mbps |  1 Mbps |  1 Mbps |  1 Mbps ]
   240p  [  0.3M   |  0.3M   |  0.3M   |  0.3M   ]

  player path (measured link wobbles):  720p -> 1080p -> 480p -> 720p
              (good)   (great)   (dip!)   (recovered)
每一種畫質都在相同的時間邊界上切開,所以播放器能在段與段之間切換層級而不留可見的斷點——鏈路慷慨時往上爬,鏈路一收緊就立刻蹲低。

誠實的限制:延遲、公平性,以及直播真正想要的

ABR 很出色,但要誠實面對它買得到與買不到什麼。首先有一個值得殺掉的迷思:自適應串流並不會讓你的連線變快。它打不過物理,也變不出你沒有的頻寬。在一條真的很弱的鏈路上,你就只是得到較低解析度的畫面——而且是優雅地、不卡頓地得到,這才是真正的勝利。ABR 是拿解析度去換流暢;它不創造容量。而「先囤緩衝」意味著「正在發生的事」與「你看到的畫面」之間永遠存在一段延遲,這對電影沒問題,但對任何「直播」就是個真正的問題。

緩衝與延遲之間的那股張力,正是核心取捨。一個較大的播放緩衝區能熬過更兇的下陷,卻把你推得離即時更遠;一個小緩衝讓你貼近直播,卻在鏈路一打嗝就卡住。看一場運動轉播,你會想要低延遲,免得你還沒看到進球、就先聽見鄰居歡呼,所以直播會用短片段與小緩衝——代價是更頻繁的畫質切換。看一部電影,沒人介意落後「真實」時間二十秒,所以播放器大可囤一個肥緩衝,幾乎什麼狀況都能扛過去。目標不同,同一套機械、調得不一樣罷了。

最後有兩點誠實話。因為每一段都是透過 TCP 的獨立抓取,每塊開頭的慢啟動,與一個錯估吞吐量的播放器,可能彼此打架——而數個 ABR 播放器共用一條家用鏈路時,行為可能不公平,各自搶或讓頻寬的方式,會讓彼此的估計全都晃動。這也是串流愈來愈跑在 HTTP/3 與 QUIC 上的原因之一:QUIC 跑在 UDP 上、去除了 TCP 的隊頭阻塞,而它更快的連線建立,恰好適合短片段請求那種穩定的滴答節奏。至於對話式的即時媒體——兩個人現場交談的視訊通話——則完全是另一頭野獸:它無法容忍長達數秒的緩衝,所以它寧取低延遲而捨可靠性,建立在不同的基礎上,而非那套讓「已儲存影片」如此順暢的「先囤再適應」模型。