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

在盡力而為的網路上承載即時媒體

網際網路對你的封包何時抵達什麼都不保證——可我們每天卻在它上面打電話、開直播、開視訊會議。本篇指南為整個難題畫出地圖:連續媒體的各種類別、三個敵人(延遲、遺失,以及最陰險的那個——抖動),還有這一級階梯接下來要逐一拆解的整套工具。

一種不一樣的貨物

到目前為止你在網路上搬運的一切——網頁、電子郵件、下載的檔案——都共有一個寬容的特性:它們不在乎每個位元組「究竟」何時抵達,只在乎每個位元組「終究」會完好且有序地抵達。這正是盡力而為的 IP 加上 TCP 所要兌現的承諾。而這一級階梯講的,是一種根本不同的貨物:連續媒體——一通電話、一場直播、一場視訊會議裡的聲音與畫面。在這裡,時間本身就是內容。對一通即時通話來說,一個正確卻遲到三秒的畫格,根本就是垃圾。

多媒體網路研究的,正是我們如何哄著這種對時間敏感的貨物,穿過一張對時間毫無承諾的網路。這份張力就是整個故事。網際網路的核心當初被刻意保持笨拙而簡單——正如你在很早的基礎那一級看到的,這正是它得以擴展的原因——所以它沒有任何內建的「這個封包很急」的概念。即時媒體只能蓋在這份漠不關心之上。本篇指南接下來會替三件可能出錯的事命名、介紹大多數時候能救我們的那一招,以及當那一招不夠用時、更重的那套機器。

三種類別,三種不同的胃口

並非所有媒體都同樣挑剔,而這些差異決定了哪些招數有用。第一類是串流的儲存媒體:一段你按下播放鍵的錄好的影片或 podcast。內容早已完整地存在伺服器上,所以播放器可以悄悄地先下載未來幾秒、在顯示任何東西之前就先囤起來。那份囤貨——一個播放緩衝區——讓串流的儲存媒體能在網路顛簸時撐過去,而你毫無察覺。唯一真正的代價,是一小段開頭的啟動延遲,而我們很樂意拿它去換取流暢。

第二類是即時串流:一場運動比賽或演唱會在發生的同時被送出去。它像儲存媒體之處,在於資料流是單向的、播放器仍能稍微緩衝一點;但它沒辦法緩衝得太超前——「現在」之後根本沒有任何已錄好的東西。即時串流能容忍幾秒的延遲(你們都見過某個轉播比隔壁房間慢半拍),它就用這份延遲預算去把事情抹平,只是這份預算很緊。

第三類最難、也最有趣:對話式即時媒體——一通 VoIP 電話或一場視訊會議,雙方你來我往地交談。在這裡你無法預先緩衝,因為對方還沒開口、下一句話就還不存在;你也無法躲在長延遲後面,因為一旦來回的時延超過大約幾百毫秒,對話就會崩潰——人們開始互相搶話。對話式即時媒體擁有所有類別中最緊的時間預算,這正是它坐在整級階梯正中央的原因。

三個敵人:延遲、遺失,以及那個陰險的

有三件事能傷害即時媒體,而把它們嚴格分開來看很值得。第一個是延遲(latency):一個封包從一端走到另一端要花多久。回想基礎那一級:延遲大多是固定的物理——隨距離而來的傳播加上一點點處理——而關鍵在於,更大的頻寬並不會把它縮小。更粗的管子讓每秒能流過更多封包,卻不會讓任何「單一」封包跑得比光速還快。對於對話,來回的總延遲必須保持小,否則交談就會變得彆扭。

第二個敵人是封包遺失。在盡力而為的網路上,一台壅塞、佇列已滿的路由器就直接把封包丟掉,而遺失的音訊或視訊取樣已來不及重送來幫上忙——等替補抵達時,那一瞬間的聲音早已過去。出乎意料的是,媒體對少量遺失的容忍度,遠比你猜的要好。少了幾毫秒的音訊,可以靠猜測「原本大概是什麼」來遮掩過去,這項技巧叫遺失隱藏,第二篇指南會看。所以遺失是壞事,但少量是撐得住的——這正是我們常常拒絕付 TCP 全額去消滅它的原因。

第三個敵人是那個陰險的,也是即時媒體真正的反派:抖動(jitter)。抖動是延遲在封包與封包之間的變動。想像音訊被切成一個個小封包、每 20 毫秒送一個,像穩定的鼓點。每個封包在沿途路由器的佇列裡等的時間都不一樣,於是它們抵達得參差不齊——這個早、下個晚、兩個擠在一起。就算平均延遲沒問題,這份不均勻對於必須以完全穩定的速率重播的媒體而言,就是毒藥。抖動正是為什麼一通通話明明什麼都沒遺失、聽起來卻一卡一卡的原因。

播放緩衝區:拿一點延遲換來大把的流暢

這裡是整級階梯最重要的一個觀念,好消息是它便宜、而且不需要網路任何幫忙就能運作。接收端不會在每個封包抵達的那一瞬間就把它播出去。它反而把進來的封包在一個播放緩衝區——一座小水庫——裡短暫地存放著,再用一個平穩、固定的時鐘把它們播出來。早到的封包在水庫裡等著;遲到的則靠這份預留的緩衝搶得先機。只要某個封包多出來的延遲,還落在你預留的這道墊子之內,聽的人就會聽到一條完全平穩的串流。你是靠刻意加上一段小而固定的延遲,把抖動吸收掉了。

Packets leave the sender on a perfect 20 ms beat:
   sent:    |--20--|--20--|--20--|--20--|

Network jitter scatters their arrival times:
   arrive:  | .  ..|.    .|  . . | ..  .|     (uneven!)

Receiver holds them in a buffer, then plays on a steady clock:
   played:  |--20--|--20--|--20--|--20--|     (smooth again)
           ^ small fixed startup delay = the jitter cushion
播放緩衝區吸收抖動:封包抵達得參差不齊,在緩衝區裡短暫等候,再以穩定的節拍重播。代價是一段小而固定的額外延遲——也就是這道墊子的大小。

但這個緩衝區不可能免費,因為它本身就是用延遲做成的,而我們剛說過延遲正是敵人之一。墊子做得太小,任何遲到的封包都會錯過它的時段、形同遺失;做得太大,一場對話就會漂進那個彆扭的、互相搶話的地帶。所以播放緩衝區是個調校問題:剛好足夠吞下抖動的延遲,一點不多。看一部電影你可以慷慨;打一通即時電話你就得小氣。第二篇指南整篇都在講怎麼把這個大小選好,包括聰明的接收端如何隨著網路狀況變化、即時重新估算它。

相關協定,以及向網路討人情

媒體仍然需要打包與協調,於是一小族通訊協定長了出來,騎在 UDP 之上(你會記得,選 UDP 正是因為 TCP 的重送來得太晚、幫不上忙)。第一個是 RTP,即時傳輸協定:它替每一塊音訊或視訊包上一個序號(用來偵測遺失與亂序)和一個時間戳(好讓接收端驅動它的播放時鐘)。與它並肩同行的 RTCP,則把週期性的品質報告——遺失多少、抖動多少——回送給發送端,好讓發送端能因應調整。另外,SIP 是負責找到對方、建立通話的訊號協定,就像你在交談前先撥號那樣。第三篇指南會帶一整通 VoIP 電話走過這些。

通訊協定和播放緩衝區都是「端主機」的招數:聰明,但若中間那張網路真的超載了,它們可變不出延遲來。所以這級階梯更深的問題是:我們能不能向網路本身討特殊待遇——讓路由器替對時間敏感的流量厚此薄彼?這整個想法有個名字,叫服務品質(QoS):一套機制,讓網路給某些資料流比其他資料流更好的延遲、遺失或頻寬,而不是對每個封包一視同仁。

QoS 建立在兩種機器之上,兩者都在此先預告、由第四篇指南詳述。在路由器「內部」,排程紀律決定佇列裡下一個離開的是誰的封包:單純的先進先出(FIFO)對所有人一視同仁;優先權方案讓緊急的語音插隊;加權公平佇列則發給每條資料流一份有保證的鏈路切片。在網路「邊緣」,監管與塑形控制一條資料流能以多快的速度注入流量——監管把超過約定速率的封包丟棄或標記,而塑形則把它們緩衝起來、把突發抹平——兩者通常都是用兩個簡單的模型搭起來的:漏桶權杖桶

為什麼網際網路無論如何還是大多維持盡力而為

有了這一整套機器,你或許會以為網際網路上到處都是有保證品質的快速道。其實不是,而原因正是這級階梯安靜的主旋律。曾有兩套宏大的架構,被設計來在整個網際網路上提供 QoS。整合服務(IntServ)要求路徑上的每一台路由器,在資料流開始之前、先替每一條個別的資料流預留資源——一種逐通話的保證,像預訂一條私人車道。它行得通,卻無法擴展:一台承載數百萬條資料流的骨幹路由器,沒辦法替每一條都追蹤狀態。區分服務(DiffServ)是較輕的答案:不逐流預留,而是在邊緣替每個封包標上一個小小的類別標籤,再讓路由器對整個類別給予粗略的優待。第五篇指南會完整地比較它們。

然而即使是 DiffServ,實務上也斑駁不齊,而在公開的網際網路上,大多數流量仍以盡力而為的方式行進——每個封包待遇相同,毫無承諾。為什麼比較簡單、比較笨的那個選項贏了?一部分是因為,要在一個封包所穿越的許多獨立網路(每一個都有自己的擁有者與利益)之間執行保證,實在很難。但主要是因為端主機變得太會應付了:慷慨的播放緩衝區、能自適應的編解碼器、在網路吃緊時降低畫質的自適應位元率串流,以及越來越粗、把平均壅塞壓得很低的鏈路。當邊緣夠聰明、而容量又便宜時,重新改造核心的誘惑就消退了。