比:從延遲原理到混合架構(gòu)選型指南)
做了這么多年流媒體和直播相關(guān)的項(xiàng)目我?guī)缀趺刻於紩?huì)被問(wèn)到同一個(gè)問(wèn)題HLS和WebRTC到底哪個(gè)才是實(shí)時(shí)視頻流的終極答案這個(gè)標(biāo)題看著像是在問(wèn)技術(shù)選型其實(shí)背后藏著的是對(duì)實(shí)時(shí)這兩個(gè)字的理解差異。在正式開(kāi)始之前我先給個(gè)結(jié)論如果是做演唱會(huì)直播、視頻網(wǎng)站點(diǎn)播、或者延時(shí)容忍度在5秒以上的場(chǎng)景HLS依然是那個(gè)最成熟、最穩(wěn)妥的老將但如果你要做的是視頻連麥、在線課堂互動(dòng)、遠(yuǎn)程操作指導(dǎo)或者說(shuō)任何需要兩個(gè)人像面對(duì)面說(shuō)話(huà)一樣交流的場(chǎng)景WebRTC幾乎是唯一的選擇。圍繞這個(gè)標(biāo)題我從底層原理、延遲來(lái)源、弱網(wǎng)表現(xiàn)、部署成本、以及實(shí)戰(zhàn)踩坑這幾個(gè)維度把兩種技術(shù)掰開(kāi)揉碎講清楚希望能幫你少走彎路。1. 內(nèi)容整體設(shè)計(jì)與思路拆解1.1 為什么這兩種技術(shù)總被放在一起比較先說(shuō)個(gè)挺有意思的現(xiàn)象。HLS和WebRTC雖然在名字上都有流的概念但它們倆根本不是同一個(gè)物種。HLS全稱(chēng)是HTTP Live Streaming是蘋(píng)果在2009年推出的基于HTTP協(xié)議的流媒體傳輸方案核心思想是把一個(gè)連續(xù)的視頻流切成一個(gè)個(gè)小文件TS切片或者現(xiàn)在的fMP4切片然后通過(guò)HTTP協(xié)議逐個(gè)下載播放。WebRTC則是Web Real-Time Communication的縮寫(xiě)是一套由Google主導(dǎo)、W3C標(biāo)準(zhǔn)化、支持瀏覽器之間實(shí)時(shí)音視頻通信的技術(shù)框架走的是UDP為主的SRTP加密通道。我把它們放在一起對(duì)比不是為了爭(zhēng)誰(shuí)優(yōu)誰(shuí)劣而是因?yàn)楹芏鄻I(yè)務(wù)場(chǎng)景正在模糊這兩者的邊界。比如直播平臺(tái)想在互動(dòng)直播間里引入低延遲連麥視頻會(huì)議廠商想要提升大規(guī)模旁路直播的畫(huà)質(zhì)和穩(wěn)定性這些需求逼迫著我們必須在兩者之間做取舍甚至要兩套方案并行。理解它們的設(shè)計(jì)目標(biāo)和協(xié)議棧差異是做出正確選擇的前提。1.2 從協(xié)議??幢举|(zhì)一個(gè)站在HTTP肩膀上一個(gè)自建實(shí)時(shí)通道HLS最聰明的地方在于它完全依賴(lài)標(biāo)準(zhǔn)HTTP協(xié)議因此天生就擁有HTTP的所有附屬能力可以跑在任意CDN上、可以被瀏覽器和移動(dòng)端原生播放器原生支持、可以自由穿越絕大多數(shù)防火墻和網(wǎng)關(guān)。它的數(shù)據(jù)流是一段一段的獨(dú)立文件每個(gè)切片都是完整的可播單元播放器下載一個(gè)播一個(gè)簡(jiǎn)單粗暴。但這種切片順序下載模式天然引入了較大的延遲因?yàn)榉?wù)端至少要積累一段時(shí)間的數(shù)據(jù)才能切成一個(gè)切片播放器為了流暢播放還要額外緩沖幾個(gè)切片才算安全。WebRTC的協(xié)議棧則是完全不同的思路。它底層走的是RTP/RTCP實(shí)時(shí)傳輸協(xié)議/實(shí)時(shí)傳輸控制協(xié)議默認(rèn)跑在UDP上。UDP不保證可靠交付不保證順序但它足夠快沒(méi)有TCP那套三次握手、擁塞控制、重傳機(jī)制的拖累。為了讓UDP上也能做到高質(zhì)量音視頻傳輸WebRTC在應(yīng)用層實(shí)現(xiàn)了前向糾錯(cuò)FEC、丟包重傳NACK、抖動(dòng)緩沖Jitter Buffer、自適應(yīng)碼率REMB/GCC等一系列機(jī)制。簡(jiǎn)單類(lèi)比就是HLS是開(kāi)著卡車(chē)在標(biāo)準(zhǔn)高速公路上跑路好走但紅綠燈多WebRTC是直接開(kāi)著越野車(chē)在沒(méi)有路的地方跑不走紅綠燈但需要自己避開(kāi)坑洼。我見(jiàn)過(guò)很多開(kāi)發(fā)者一開(kāi)始以為HLS延遲高是因?yàn)閰f(xié)議老舊其實(shí)不是。HLS高延遲的根源在于它的傳輸模型是下載—播放模式服務(wù)端必須等切片生成完畢播放器必須等緩沖足夠才開(kāi)始播放。這跟火車(chē)調(diào)度一樣你要求每一站都停那平均車(chē)速肯定上不去。理解這一點(diǎn)對(duì)于后面我們判斷哪些場(chǎng)景能用HLS、哪些場(chǎng)景必須上WebRTC至關(guān)重要。1.3 我為什么認(rèn)為這場(chǎng)對(duì)比是一個(gè)偽命題說(shuō)句實(shí)話(huà)把HLS和WebRTC放在天平上稱(chēng)誰(shuí)更優(yōu)秀有點(diǎn)像一個(gè)問(wèn)卡車(chē)和越野車(chē)哪個(gè)更好——這取決于你的路況和貨物。HLS追求的是兼容性和分發(fā)效率它的最佳場(chǎng)景是一對(duì)多的大規(guī)模廣播幾百萬(wàn)人同時(shí)看一場(chǎng)直播視頻號(hào)、B站、抖音這類(lèi)場(chǎng)景到現(xiàn)在的主流方案依然是HLS或類(lèi)似的分片傳輸。WebRTC追求的是實(shí)時(shí)雙向互動(dòng)它的最佳場(chǎng)景是少數(shù)人之間的實(shí)時(shí)交流哪怕中間經(jīng)歷過(guò)SFU轉(zhuǎn)發(fā)參與者的數(shù)量也遠(yuǎn)不及直播廣播的量級(jí)。不過(guò)這個(gè)偽命題值得深挖的恰恰是兩種技術(shù)在邊界地帶的碰撞。比如低延遲直播領(lǐng)域目前業(yè)界做了LL-HLSLow-Latency HLS、DASH-LL等改良方案把延遲壓縮到2-3秒WebRTC也在通過(guò)SFU、Simulcast、Intel Cascade等技術(shù)逐步擴(kuò)大單房間容量。了解這些演進(jìn)方向才能真正站在2024年的技術(shù)節(jié)點(diǎn)上做好選型而不是拿著十年前的教科書(shū)知識(shí)做決策。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 延遲到底差多少數(shù)字背后的真實(shí)體驗(yàn)先上一組我實(shí)測(cè)過(guò)的干貨數(shù)據(jù)。傳統(tǒng)HLS在標(biāo)準(zhǔn)配置下切片時(shí)長(zhǎng)6秒播放器buffer 3個(gè)切片端到端延遲通常穩(wěn)定在20到30秒之間。這個(gè)數(shù)字在純技術(shù)人眼里可能沒(méi)法接受但在實(shí)際觀看場(chǎng)景中看球賽直播的觀眾并不在意自己比現(xiàn)場(chǎng)晚了半分鐘只要大家看到的是同一刻的畫(huà)面就行這就是準(zhǔn)實(shí)時(shí)的概念。LL-HLS通過(guò)將切片時(shí)長(zhǎng)縮短到1-2秒、允許播放器在部分切片未下載完成時(shí)就啟動(dòng)解碼可以將端到端延遲壓縮到3到5秒代價(jià)是實(shí)現(xiàn)復(fù)雜度上了一個(gè)臺(tái)階而且很多舊版本播放器并不支持。WebRTC的延遲表現(xiàn)則是另一個(gè)量級(jí)。同一網(wǎng)絡(luò)環(huán)境下我用WebRTC做P2P視頻通話(huà)屏幕上的秒表和攝像頭對(duì)著同一個(gè)時(shí)鐘拍攝肉眼可見(jiàn)的延遲大概只有100到300毫秒。經(jīng)過(guò)SFU服務(wù)器中轉(zhuǎn)、再疊加跨地域網(wǎng)絡(luò)損耗正常情況下也能壓在500毫秒以?xún)?nèi)。這就是實(shí)時(shí)互動(dòng)的門(mén)檻——超過(guò)400毫秒人就能感覺(jué)到對(duì)話(huà)里的不自然空隙超過(guò)700毫秒雙方基本沒(méi)法順暢交談。這里面有個(gè)很隱蔽的坑很多人測(cè)延遲是拿手機(jī)看著屏幕掐秒表這個(gè)方法根本測(cè)不準(zhǔn)。更靠譜的做法是拍一段攝像頭的畫(huà)面用秒表計(jì)時(shí)間差或者用SMPTE時(shí)間碼疊畫(huà)比對(duì)誤差能控制在幾十毫秒以?xún)?nèi)。我在做直播延遲測(cè)試時(shí)最常用的是把手機(jī)、電腦攝像頭對(duì)準(zhǔn)一個(gè)毫秒級(jí)計(jì)時(shí)器同時(shí)錄下兩端的畫(huà)面在剪輯軟件里對(duì)齊逐幀數(shù)差值這是拿到真實(shí)延遲數(shù)據(jù)最笨也最可靠的方案。2.2 兼容性與解碼鏈路誰(shuí)支持得更廣、更省心HLS的兼容性我用一個(gè)詞形容無(wú)腦。iOS的Safari從iOS 3.0開(kāi)始原生支持HLSAndroid的Chrome和各家WebView也早已把HLS播放內(nèi)核內(nèi)置了這意味著只要你的用戶(hù)用的是智能手機(jī)、裝上任何一個(gè)現(xiàn)代瀏覽器HLS基本上都能直接播放不需要引入任何額外的SDK。視頻編碼上HLS兼容H.264和HEVC加上蘋(píng)果推的HDR和杜比視界支持觀看體驗(yàn)的上限很高。出于這個(gè)兼容性?xún)?yōu)勢(shì)我的建議是任何做純直播分發(fā)觀看的產(chǎn)品HLS都應(yīng)當(dāng)作為保底方案它不會(huì)讓你在播放器層面被用戶(hù)卡住。WebRTC的兼容性也遠(yuǎn)比很多人想象中好。Chrome、Firefox、Safari、Edge等現(xiàn)代瀏覽器基本都實(shí)現(xiàn)了WebRTC標(biāo)準(zhǔn)移動(dòng)端也普遍支持。但這里有一個(gè)真實(shí)的坑Safari對(duì)WebRTC的實(shí)現(xiàn)有自己的一套脾氣尤其是Codec支持上Safari對(duì)VP8和VP9的支持時(shí)好時(shí)壞對(duì)AV1也是最近幾個(gè)大版本才跟上。我在某個(gè)跨平臺(tái)項(xiàng)目里遇到過(guò)Safari上VP8畫(huà)面花屏、VP9硬解不啟動(dòng)、只有H.264能穩(wěn)定工作的怪問(wèn)題最后是全平臺(tái)統(tǒng)一走H.264baseline profile才解決。所以WebRTC雖然入門(mén)簡(jiǎn)單但真要鋪開(kāi)到全終端編碼兼容測(cè)試表是必不可少的一門(mén)功課。2.3 弱網(wǎng)表現(xiàn)與擁塞控制誰(shuí)的抗打擊能力更強(qiáng)弱網(wǎng)是檢驗(yàn)流媒體方案含金量的試金石。HLS走TCP協(xié)議擁塞時(shí)表現(xiàn)是寧可等也不丟結(jié)果就是卡頓、持續(xù)緩沖、體驗(yàn)斷崖式下跌。用戶(hù)抱怨怎么一直轉(zhuǎn)圈基本就是這個(gè)原因。但換個(gè)角度TCP的重傳機(jī)制保證了只要最終數(shù)據(jù)能到畫(huà)面就是完整的不花屏不破碎。所以HLS在弱網(wǎng)下的關(guān)鍵詞是會(huì)卡但不會(huì)花。WebRTC在弱網(wǎng)下的表現(xiàn)則復(fù)雜得多。它默認(rèn)走UDP網(wǎng)絡(luò)抖動(dòng)時(shí)丟包是常態(tài)但應(yīng)用層的NACK重傳策略會(huì)傾向于重傳關(guān)鍵幀而不是每一個(gè)包這樣到了觀眾那邊就表現(xiàn)為偶爾的一幀畫(huà)面馬賽克或跳幀但視頻整體仍然在推進(jìn)聲音不會(huì)因?yàn)榫W(wǎng)絡(luò)丟掉幾十毫秒的數(shù)據(jù)就斷掉。對(duì)于人能持續(xù)對(duì)話(huà)這個(gè)核心訴求來(lái)說(shuō)WebRTC的弱網(wǎng)韌性其實(shí)是更符合直覺(jué)的。我實(shí)際測(cè)過(guò)兩個(gè)信號(hào)較差的會(huì)議室里開(kāi)視頻會(huì)同樣的Wi-Fi環(huán)境HLS的投屏演示卡成PPTWebRTC的視頻會(huì)雖然局部花了一下但語(yǔ)音始終保持清晰整體交流沒(méi)有中斷。這說(shuō)明在實(shí)時(shí)互動(dòng)優(yōu)先的場(chǎng)合WebRTC的容錯(cuò)策略是明顯占優(yōu)的。而如果你的訴求是畫(huà)面完美無(wú)瑕但不那么Care延遲HLS的TCP可靠傳輸反而是優(yōu)勢(shì)。2.4 數(shù)據(jù)容量與并發(fā)能力各自的天花板在哪里并發(fā)能力是這個(gè)對(duì)比里最容易被低估的維度。HLS的分發(fā)本質(zhì)是靜態(tài)文件下載天然適合跑在CDN上。你不需要在源站維護(hù)復(fù)雜的連接狀態(tài)每個(gè)觀看者只是在自己適合的時(shí)間去拉取不同的分片文件。CDN的邊緣節(jié)點(diǎn)可以把大多數(shù)請(qǐng)求消化在距離用戶(hù)最近的機(jī)房源站帶寬壓力被大幅攤平。我之前參與過(guò)一個(gè)小體量直播產(chǎn)品源站只有兩臺(tái)普通云服務(wù)器沒(méi)做什么特殊優(yōu)化靠著CDN輕輕松松扛住了四萬(wàn)多人同時(shí)在線觀看。所以幾百萬(wàn)人同時(shí)看一場(chǎng)直播HLS架構(gòu)上是完全可行的瓶頸只在帶寬成本和預(yù)算。WebRTC則不一樣。它本質(zhì)上是連接導(dǎo)向的即使走SFU轉(zhuǎn)發(fā)每一個(gè)參與的終端都與SFU建立了一條實(shí)時(shí)的UDP連接SFU需要為每一路流做轉(zhuǎn)發(fā)決策、碼率適配、丟包重傳還要管理狀態(tài)CPU和帶寬開(kāi)銷(xiāo)遠(yuǎn)高于HLS的靜態(tài)文件分發(fā)。我做過(guò)幾個(gè)WebRTC萬(wàn)人直播間方案最終都不得不采用將WebRTC流轉(zhuǎn)封裝為HLS再分發(fā)到更多觀看端的混合架構(gòu)。具體做法是主播端走WebRTC低延遲上行通過(guò)SFU后轉(zhuǎn)推給一個(gè)轉(zhuǎn)封裝網(wǎng)關(guān)網(wǎng)關(guān)把RTP流轉(zhuǎn)成HLS分片推給CDN觀看端再走標(biāo)準(zhǔn)HLS。這個(gè)架構(gòu)在互動(dòng)層和分發(fā)層各取所長(zhǎng)是目前大規(guī)?;?dòng)直播事實(shí)上的標(biāo)準(zhǔn)解法。3. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 方案選型決策流程一張路線圖幫你快速判斷我不喜歡直接告訴你用這個(gè)或那個(gè)而是給你一個(gè)可執(zhí)行的決策流程。當(dāng)你接到一個(gè)項(xiàng)目首先要問(wèn)三個(gè)問(wèn)題觀眾是否需要與主播或彼此實(shí)時(shí)互動(dòng)比如連麥、字幕、白板、更進(jìn)一步的舉手發(fā)言需要就是WebRTC的主場(chǎng)否則HLS足夠。端到端允許的最大延遲是多少能用5秒以上做緩沖首選HLS開(kāi)發(fā)量和成本都低要是必須壓在500毫秒內(nèi)直接放棄HLS。預(yù)期在線人數(shù)和互動(dòng)人數(shù)分別是什么量級(jí)大規(guī)模觀看少量互動(dòng)立刻走WebRTC上行HLS下發(fā)混合架構(gòu)純大規(guī)模觀看直接上HLSCDN。我最近在做的一個(gè)遠(yuǎn)程手術(shù)示教項(xiàng)目就是這樣手術(shù)室端通過(guò)WebRTC推流延遲要求低于300毫秒因?yàn)閹Ы汤蠋熞鶕?jù)畫(huà)面實(shí)時(shí)下達(dá)操作指令但宏大教室里的幾百個(gè)學(xué)生只是觀看不需要也無(wú)法實(shí)時(shí)互動(dòng)所以接收端統(tǒng)一走了HLS。同一個(gè)項(xiàng)目里兩種技術(shù)同時(shí)存在各管一段和諧得很。3.2 搭建最小可用的HLS直播鏈路從攝像頭到播放器很多人上來(lái)就整大方案結(jié)果出了問(wèn)題不知道怎么排查。我的建議是先搭一個(gè)最小可用的全鏈路demo跑通了再往上加?xùn)|西。HLS鏈路最小架構(gòu)是攝像頭或桌面錄屏→ 編碼器 → HLS切片器 → Web服務(wù)器或CDN→ 播放器。在Linux服務(wù)器上我用過(guò)一段非常順手的組合ffmpeg負(fù)責(zé)采集和編碼工具自帶的hls muxer直接輸出m3u8和ts分片再用nginx起一個(gè)靜態(tài)文件服務(wù)播放器直接拉m3u8地址。關(guān)鍵命令大致是這樣的ffmpeg -f v4l2 -i /dev/video0 -c:v libx264 -preset veryfast -tune zerolatency \ -pix_fmt yuv420p -g 60 -sc_threshold 0 \ -f hls -hls_time 2 -hls_list_size 0 -hls_segment_type mpegts \ /var/www/live/stream.m3u8這里有幾個(gè)參數(shù)是要點(diǎn)-g 60是設(shè)置GOP關(guān)鍵幀間隔為60幀如果幀率是30fps就是每隔2秒一個(gè)關(guān)鍵幀這正好匹配-hls_time 2的切片時(shí)長(zhǎng)保證每個(gè)分片都從關(guān)鍵幀開(kāi)始播放器切換切片時(shí)不需要等待關(guān)鍵幀解碼起播更快-sc_threshold 0是禁止場(chǎng)景切換自動(dòng)插關(guān)鍵幀保證GOP固定-hls_list_size 0表示不限制m3u8列表長(zhǎng)度適合做7×24小時(shí)的直播但如果做長(zhǎng)期錄制會(huì)產(chǎn)生無(wú)限增長(zhǎng)的索引需要自己做輪轉(zhuǎn)清理。切片時(shí)長(zhǎng)建議設(shè)在1-6秒之間1秒延遲最低但m3u8里索引文件刷新頻繁邊緣節(jié)點(diǎn)緩存壓力大6秒切片則兼容性最好但延遲飆到30秒。對(duì)普通直播來(lái)說(shuō)2-4秒是比較舒服的中間值。播放器端如果用hls.js要注意在video標(biāo)簽上設(shè)置一個(gè)足夠大的緩存時(shí)長(zhǎng)防止網(wǎng)絡(luò)抖動(dòng)導(dǎo)致頻繁卡頓。另外一個(gè)容易被忽略的細(xì)節(jié)是如果播放器頁(yè)面和m3u8地址跨域服務(wù)器必須配置正確的CORS頭否則在現(xiàn)代瀏覽器里hls.js會(huì)直接報(bào)Failed to fetch這問(wèn)題特別容易在調(diào)試階段被當(dāng)成后端故障排查半天。3.3 搭建最小可用的WebRTC視頻通話(huà)信令和媒體鏈路分離WebRTC的demo搭建邏輯和HLS是完全相反的。HLS只需要單向的數(shù)據(jù)下載WebRTC則要先握手、協(xié)商、建立連接、才能傳輸媒體流整個(gè)過(guò)程分幾步信令交換SDP和ICE候選→ P2P連接或者走TURN中繼→ 媒體流傳輸。最痛苦的是信令服務(wù)器因?yàn)閃ebRTC底層協(xié)議并沒(méi)有定義信令的格式你需要自己實(shí)現(xiàn)一個(gè)消息轉(zhuǎn)發(fā)服務(wù)可以用WebSocket或者普通的HTTP POST輪詢(xún)。很多初學(xué)者第一步就栽在本地打開(kāi)兩個(gè)頁(yè)面卻連不通上信令通了、SDP也交換了但I(xiàn)CE候選收集為空。原因極大概率是瀏覽器默認(rèn)只收集了host類(lèi)型的candidate而在同一臺(tái)機(jī)器上測(cè)試時(shí)loopback網(wǎng)卡地址往往拿不到。解決方法是啟動(dòng)瀏覽器時(shí)加上--force-fieldtrialsWebRTC-IPv6Default/Disabled或者完全不設(shè)過(guò)濾也可以用--allow-loopback-in-peer-connection這個(gè)flag允許本地loopback地址參與ICE。我調(diào)試時(shí)有點(diǎn)小技巧直接用帶about:config調(diào)優(yōu)的Firefox配合about:webrtc頁(yè)面看ICE狀態(tài)比Chrome的chrome://webrtc-internals更直觀。信令服務(wù)的最小實(shí)現(xiàn)可以這樣一個(gè)基于Node.js加ws庫(kù)的WebSocket消息中轉(zhuǎn)每個(gè)客戶(hù)端連接時(shí)注冊(cè)一個(gè)隨機(jī)ID發(fā)送消息時(shí)帶上對(duì)端ID服務(wù)器查表轉(zhuǎn)發(fā)不做任何業(yè)務(wù)邏輯。麻雀雖小五臟俱全跑通了這個(gè)流程后面換成任何生產(chǎn)級(jí)的信令服務(wù)都是順理成章的事。3.4 混合架構(gòu)實(shí)戰(zhàn)WebRTC上行HLS分發(fā)如果你的業(yè)務(wù)是互動(dòng)直播強(qiáng)烈建議直接照著這個(gè)混合架構(gòu)設(shè)計(jì)而不是在純WebRTC或純HLS上死磕。我在某教育直播項(xiàng)目中驗(yàn)證過(guò)的鏈路是這樣教師端WebRTC推流→ SFU選擇性轉(zhuǎn)發(fā)→ 轉(zhuǎn)封裝網(wǎng)關(guān)RTP轉(zhuǎn)HLS→ CDN → 學(xué)生端HLS播放。SFU可以選成熟的開(kāi)源方案轉(zhuǎn)封裝網(wǎng)關(guān)用ffmpeg配合腳本監(jiān)聽(tīng)RTP流并切片輸出到CDN的存儲(chǔ)。這個(gè)架構(gòu)天然解決了三個(gè)問(wèn)題學(xué)生端兼容性不需要安裝任何WebRTC插件觀看人數(shù)擴(kuò)展性海量學(xué)生走CDN靜態(tài)分發(fā)互動(dòng)延遲教師端到互動(dòng)學(xué)生端的通信還是低延遲WebRTC。代價(jià)是架構(gòu)里有三個(gè)協(xié)作組件運(yùn)維復(fù)雜度上來(lái)了。我踩過(guò)一個(gè)最大的坑轉(zhuǎn)封裝網(wǎng)關(guān)如果進(jìn)程崩潰學(xué)生端會(huì)黑屏很久因?yàn)閙3u8索引列表更新停住了但CDN邊緣節(jié)點(diǎn)還在按TTL緩存舊的分片。后來(lái)我在網(wǎng)關(guān)上加了健康檢查和看門(mén)狗一旦切片產(chǎn)出超過(guò)預(yù)期間隔就自動(dòng)重啟并通告CDN刷新這個(gè)問(wèn)題才算根治。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 HLS延遲高到離譜先從切片和播放器兩頭查很多人用了HLS之后抱怨不是說(shuō)好的低延遲嗎怎么還有十幾秒問(wèn)題多半出在播放器端的緩沖策略上。ffmpeg輸出2秒切片播放器的buffer設(shè)的卻是30秒那延遲就是被播放器自己吃掉的跟HLS切片本身無(wú)關(guān)。排查方法是在開(kāi)發(fā)者工具里看network面板對(duì)比m3u8請(qǐng)求時(shí)間與video當(dāng)前播放時(shí)間算出實(shí)際的buffer深度。常見(jiàn)的坑還包括某些hls.js配置項(xiàng)里maxLiveSyncPlaybackRate設(shè)置過(guò)小導(dǎo)致直播模式下播放器追趕慢延遲越來(lái)越大。另一種好用的經(jīng)驗(yàn)把hls_time從默認(rèn)的6秒改到2秒后延遲會(huì)立刻大幅下降但m3u8刷新頻率變高邊緣節(jié)點(diǎn)回源次數(shù)增加CDN成本會(huì)有所上升。對(duì)低延遲有執(zhí)念的項(xiàng)目還可以開(kāi)啟hls_flags independent_segments和hls_flags program_date_time前者告訴播放器所有切片都從關(guān)鍵幀開(kāi)始后者強(qiáng)制m3u8攜帶時(shí)間戳便于播放器做精確的延遲控制。這兩個(gè)flag在ffmpeg官方文檔里只是輕描淡寫(xiě)但實(shí)際效果非常顯著。4.2 WebRTC連接一直失敗先從ICE和端口排查WebRTC連接失敗九成是ICE Candidate協(xié)商不過(guò)關(guān)。生產(chǎn)環(huán)境一定要部署STUN/TURN服務(wù)器STUN用來(lái)探測(cè)公網(wǎng)地址和端口映射關(guān)系TURN則作為最后的中繼兜底。很多開(kāi)發(fā)環(huán)境很簡(jiǎn)單家里NAT、公司W(wǎng)i-Fi都沒(méi)通P2P能通但在復(fù)雜的對(duì)稱(chēng)型NAT環(huán)境下比如很多企業(yè)內(nèi)部網(wǎng)絡(luò)STUN會(huì)失敗必須依賴(lài)TURN中繼。另一個(gè)高頻問(wèn)題是防火墻封了UDP端口段。WebRTC默認(rèn)使用UDP如果防火墻只放行TCP端口媒體流根本過(guò)不去。我有個(gè)客戶(hù)的會(huì)議系統(tǒng)在客戶(hù)現(xiàn)場(chǎng)死活連不通最后排查了一圈才發(fā)現(xiàn)是客戶(hù)的安全策略把UDP全屏蔽了改成強(qiáng)制走TCP relay后才恢復(fù)但畫(huà)質(zhì)和延遲都比UDP差了一截這種場(chǎng)景下只能跟客戶(hù)單獨(dú)溝通放行UDP端口段。專(zhuān)門(mén)寫(xiě)一段排查ICE的命令在Chrome的chrome://webrtc-internals里可以看到所有ICE候選的類(lèi)型host/srflx/relay如果只有host說(shuō)明STUN沒(méi)有配置或配置錯(cuò)了如果只有srflx沒(méi)有relay說(shuō)明TURN服務(wù)器不可達(dá)如果relay的候選也不少但連接還是失敗就要檢查T(mén)URN服務(wù)器的用戶(hù)名/密碼和端口對(duì)不對(duì)。Firefox上對(duì)應(yīng)的是about:webrtc頁(yè)面看candidate列表和selected pair也能快速定位。4.3 弱網(wǎng)下WebRTC瘋狂馬賽克帶寬估計(jì)器和編碼參數(shù)一起調(diào)WebRTC有一個(gè)內(nèi)置的帶寬估計(jì)器GCC/REMB會(huì)根據(jù)丟包率和延遲動(dòng)態(tài)調(diào)整發(fā)送碼率但它的反應(yīng)有時(shí)候慢半拍尤其在突然掉網(wǎng)速的時(shí)候畫(huà)面會(huì)先崩潰再恢復(fù)。我自己做項(xiàng)目時(shí)會(huì)在發(fā)布流之前做一次網(wǎng)絡(luò)預(yù)檢用RTCPeerConnection.createDataChannel測(cè)試網(wǎng)絡(luò)往返延遲和丟包率如果指標(biāo)太差就直接提示用戶(hù)當(dāng)前網(wǎng)絡(luò)不適合開(kāi)啟視頻而不是硬著頭皮讓W(xué)ebRTC去適應(yīng)惡劣信道。還有一招很實(shí)用把視頻編碼器的maxBitrate和minBitrate約束在一個(gè)合理的范圍內(nèi)避免WebRTC在弱網(wǎng)下把碼率調(diào)到過(guò)低的水平導(dǎo)致馬賽克爆炸。比如720p視頻通話(huà)我一般設(shè)定最低300kbps、最高1.2Mbps這樣不管網(wǎng)絡(luò)怎么抖動(dòng)編碼器都會(huì)在這條帶內(nèi)自適應(yīng)用戶(hù)觀感至少不會(huì)出現(xiàn)那種大面積的色塊糊屏。如果你對(duì)清晰度的要求更高可以調(diào)高minBitrate但要同時(shí)調(diào)高丟包保護(hù)參數(shù)比如開(kāi)啟ULPFEC不然弱網(wǎng)下流暢度又會(huì)崩。4.4 音頻回聲和音畫(huà)不同步這是實(shí)時(shí)鏈路上被忽視的老大難WebRTC的通話(huà)質(zhì)量里音頻回聲處理的優(yōu)先級(jí)比什么編解碼器都高。瀏覽器雖然自帶了回聲消除AEC模塊但在PC端外放音箱的場(chǎng)景下回聲抑制效果經(jīng)常不夠干凈。我遇到過(guò)最夸張的情況是視頻會(huì)議里一個(gè)人開(kāi)著揚(yáng)聲器說(shuō)話(huà)整個(gè)房間都在嗡嗡回響。解決思路有三個(gè)層面第一在使用getUserMedia時(shí)明確要求瀏覽器啟用回聲消除和降噪Chrome下設(shè)置echoCancellation: true, noiseSuppression: true, autoGainControl: true第二用戶(hù)的音頻設(shè)備盡量使用耳機(jī)物理隔絕回聲第三在SFU層面可以對(duì)音頻做AGC自動(dòng)增益控制補(bǔ)償讓遠(yuǎn)端響度統(tǒng)一。音畫(huà)不同步的排查則要分清是哪一端引入的。一般用時(shí)間戳對(duì)齊法判斷播放端緩存了太多音頻數(shù)據(jù)導(dǎo)致聲音像慢半拍還是視頻解碼端因?yàn)閬G幀追幀導(dǎo)致畫(huà)面跟不上聲音。WebRTC內(nèi)部有RTCP SR發(fā)送方報(bào)告的機(jī)制來(lái)同步不同RTP流的時(shí)鐘但瀏覽器端偶爾也會(huì)出bug。我的土辦法是讓說(shuō)話(huà)人同時(shí)敲桌子并晃動(dòng)手機(jī)觀察畫(huà)面里手停下來(lái)的那一刻和響聲是否對(duì)齊多點(diǎn)幾次就能感受出偏移量。工程上要根治的話(huà)還是要在播放端啟用音頻時(shí)鐘作為主時(shí)鐘視頻幀按時(shí)間戳對(duì)齊到音頻時(shí)鐘上犧牲少量視頻流暢度換取音畫(huà)一致。4.5 我整理的一個(gè)常見(jiàn)問(wèn)題速查表為了讓你快速定位問(wèn)題我把這兩年在不同項(xiàng)目里反復(fù)遇到的坑匯總成了一張速查表。它不解決所有問(wèn)題但能解決大多數(shù)為什么我的視頻流不工作的初級(jí)疑難?,F(xiàn)象可能原因快速排查手段HLS播放器一直轉(zhuǎn)圈跨域未配置CORS頭開(kāi)發(fā)者工具Network里看m3u8請(qǐng)求是否被攔截HLS延遲20秒以上播放器緩存過(guò)大/切片時(shí)長(zhǎng)過(guò)長(zhǎng)調(diào)整hls.js配置或把hls_time降到2秒HLS直播中途黑屏轉(zhuǎn)封裝網(wǎng)關(guān)宕機(jī)或切片斷供檢查m3u8最后修改時(shí)間設(shè)置看門(mén)狗自動(dòng)重啟WebRTC連接失敗防火墻封UDP / ICE候選不全查看webrtc-internals確認(rèn)是否有relay候選WebRTC畫(huà)面馬賽克弱網(wǎng)丟包/碼率估計(jì)滯后約束minBitrate開(kāi)啟ULPFEC做網(wǎng)絡(luò)預(yù)檢WebRTC回聲嚴(yán)重AEC沒(méi)生效/揚(yáng)聲器外放getUserMedia開(kāi)啟回聲消除建議用戶(hù)戴耳機(jī)音畫(huà)不同步播放端緩存/時(shí)鐘不同步用敲桌子法判斷偏移方向調(diào)整主時(shí)鐘對(duì)齊策略5. 場(chǎng)景化選型建議與工程落地要點(diǎn)5.1 直播平臺(tái)與大規(guī)?;顒?dòng)直播HLS仍是壓艙石如果你做的是秀場(chǎng)直播、電商直播、大型活動(dòng)直播觀眾只負(fù)責(zé)看下一步的選型并不難HLS加CDN。原因很簡(jiǎn)單這類(lèi)產(chǎn)品的商業(yè)目標(biāo)是大規(guī)模觸達(dá)用戶(hù)播放兼容性排在絕對(duì)優(yōu)先級(jí)延遲的感知被弱化。主播和觀眾的互動(dòng)也僅限于彈幕和評(píng)論而這些本身就不是通過(guò)視頻流承載的跟實(shí)時(shí)視頻傳輸沒(méi)有必然關(guān)系。在這種場(chǎng)景下我的建議是把主要精力放在編碼參數(shù)和CDN調(diào)度上而不是糾結(jié)要不要換成WebRTC。比如H.264編碼可以適當(dāng)增加關(guān)鍵幀間隔用更高效的編碼參數(shù)換取同碼率下更好的畫(huà)質(zhì)開(kāi)啟B幀和動(dòng)態(tài)碼率VBR通常可以在帶寬不變的情況下帶來(lái)肉眼可見(jiàn)的清晰度提升。CDN側(cè)則要提前做好節(jié)點(diǎn)預(yù)熱的策略尤其是頭部主播的直播間避免開(kāi)播瞬間大量用戶(hù)同時(shí)拉取導(dǎo)致邊緣節(jié)點(diǎn)回源風(fēng)暴。5.2 視頻會(huì)議與互動(dòng)教學(xué)WebRTC當(dāng)仁不讓從需求來(lái)講視頻會(huì)議和互動(dòng)教學(xué)最核心的體驗(yàn)指標(biāo)是自然的對(duì)話(huà)節(jié)奏和實(shí)時(shí)反饋那WebRTC就是最合適的技術(shù)底座。這類(lèi)項(xiàng)目我的建議是實(shí)現(xiàn)上盡量用成熟的WebRTC通信云服務(wù)而不是自己從零搭建SFU集群因?yàn)閷?shí)時(shí)通信的坑太多信令服務(wù)、SFU選型、錄制合規(guī)、弱網(wǎng)優(yōu)化每一樣都需要多年積累。自己搭不是不可以但沒(méi)有必要重復(fù)造輪子省下時(shí)間打磨業(yè)務(wù)才是關(guān)鍵。另外如果規(guī)模在幾十人到幾百人之間建議采用MCU混流或SFU轉(zhuǎn)發(fā)的混合方案發(fā)言人數(shù)少的場(chǎng)合只混流需要展示的視頻源減少下行帶寬大規(guī)模直播課時(shí)非互動(dòng)學(xué)員走HLS觀看課堂內(nèi)的小組討論切回WebRTC架構(gòu)上可以做一套動(dòng)態(tài)路由來(lái)切換。這樣實(shí)際成本可控用戶(hù)體驗(yàn)也接近最佳。5.3 連麥互動(dòng)PK與實(shí)時(shí)競(jìng)技一體化混合方案是趨勢(shì)現(xiàn)在最火熱的連麥PK和互動(dòng)直播玩法背后基本上都是混合架構(gòu)WebRTC保證主播和連麥嘉賓的低延遲互動(dòng)再將混流后的畫(huà)面轉(zhuǎn)封裝成HLS分發(fā)給所有觀眾。這種架構(gòu)的關(guān)鍵在于互動(dòng)流和分發(fā)流的橋接質(zhì)量。我強(qiáng)烈建議在橋接點(diǎn)做好音視頻時(shí)間戳的統(tǒng)一因?yàn)橹鞑ザ撕陀^眾端看到的是兩條不同的流如果不做時(shí)鐘同步觀眾會(huì)明顯感覺(jué)連麥嘉賓的口型和聲音對(duì)不上這是一種比卡頓還糟糕的體驗(yàn)。具體實(shí)施上橋接網(wǎng)關(guān)建議用獨(dú)立的進(jìn)程組來(lái)處理轉(zhuǎn)封裝不要和信令服務(wù)放在一起避免瞬時(shí)高負(fù)載互相拖累。網(wǎng)關(guān)轉(zhuǎn)封裝時(shí)推薦開(kāi)啟GPU加速多路1080p轉(zhuǎn)碼和切片時(shí)CPU會(huì)吃掉大量資源純軟編的轉(zhuǎn)封裝網(wǎng)關(guān)很容易在主播PK高峰期崩潰。我之前做的一個(gè)活動(dòng)中因?yàn)檗D(zhuǎn)封裝網(wǎng)關(guān)軟編扛不住結(jié)果一開(kāi)連麥就直接黑屏當(dāng)晚技術(shù)群里全是用戶(hù)哀嚎這件事讓我徹底記住了把轉(zhuǎn)封裝獨(dú)立成高性能服務(wù)的重要性。5.4 技術(shù)演進(jìn)的新變量LL-HLS、WebCodecs與QUIC站在2024年往后看HLS和WebRTC的邊界并不會(huì)永遠(yuǎn)涇渭分明。LL-HLS已經(jīng)把HLS的延遲降到能接受的互動(dòng)預(yù)覽水平WebCodecs讓瀏覽器底層的編碼解碼能力被暴露給JavaScript意味著WebRTC的媒體處理能力可以被靈活地自定義。更值得關(guān)注的是QUIC和WebTransport它把HTTP3的低延遲可靠傳輸帶給了實(shí)時(shí)流媒體未來(lái)出現(xiàn)一種架構(gòu)上更接近HLS分發(fā)模式、延遲卻逼近WebRTC的傳輸方案并非不可能。如果你正在規(guī)劃新項(xiàng)目我的建議是架構(gòu)上盡可能把媒體傳輸層和業(yè)務(wù)邏輯解耦留出未來(lái)切換到新協(xié)議的余地。比如播放器層封裝統(tǒng)一接口底層是HLS還是LL-HLS還是WebTransport由適配層動(dòng)態(tài)決定推流端也可以抽象一層采集編碼模塊后續(xù)接入新的編碼器或傳輸標(biāo)準(zhǔn)不需要推倒重來(lái)。好的架構(gòu)不是選對(duì)了一個(gè)協(xié)議就一勞永逸而是在協(xié)議演進(jìn)時(shí)能平滑過(guò)渡這才是長(zhǎng)期競(jìng)爭(zhēng)力的來(lái)源。5.5 如何快速評(píng)估一個(gè)實(shí)時(shí)視頻流方案評(píng)估方案永遠(yuǎn)是實(shí)驗(yàn)優(yōu)先于口說(shuō)。拿到一個(gè)具體場(chǎng)景我會(huì)先搭建一個(gè)幾十行代碼的最小驗(yàn)證demoHLS方案直接用ffmpeg推出一個(gè)測(cè)試流用hls.js在瀏覽器里播放手工測(cè)量延遲WebRTC方案則用兩個(gè)頁(yè)面開(kāi)Video Chat記錄連接建立時(shí)間和ICE類(lèi)型手動(dòng)拔網(wǎng)線模擬弱網(wǎng)觀察恢復(fù)時(shí)間。這兩組數(shù)據(jù)出來(lái)之后方案的適用性其實(shí)就已經(jīng)清晰了大半。還有個(gè)小技巧做對(duì)比驗(yàn)證時(shí)一定要在相同的網(wǎng)絡(luò)環(huán)境和編碼參數(shù)下測(cè)試。我看過(guò)太多人對(duì)比延遲時(shí)HLS流只給500kbps碼率WebRTC卻用了2Mbps往上推最后得出HLS畫(huà)質(zhì)差的結(jié)論這完全不公平。統(tǒng)一到相同碼率、相同分辨率、相同攝像頭光線條件下延遲和畫(huà)質(zhì)的差異才是技術(shù)本身導(dǎo)致的差異而不是資源投入不同的結(jié)果。6. 寫(xiě)在最后我自己在流媒體這個(gè)領(lǐng)域摸爬滾打這么些年最大的感受是不要成為某個(gè)協(xié)議的原教旨主義者。HLS的工程化成熟度、兼容性和分發(fā)能力是WebRTC短期很難完全替代的WebRTC的實(shí)時(shí)性、互動(dòng)性和弱網(wǎng)韌性也是HLS怎么打補(bǔ)丁都追不上的。聰明的方案是讓它們各司其職在架構(gòu)層面做到無(wú)縫協(xié)同而不是強(qiáng)迫一個(gè)技術(shù)去覆蓋所有場(chǎng)景。如果你現(xiàn)在正站在選型的路口我的建議是根據(jù)業(yè)務(wù)真實(shí)需求去定延遲預(yù)算延遲預(yù)算定技術(shù)路線技術(shù)路線定人員投入和基礎(chǔ)設(shè)施預(yù)算。別讓別人都在用成為你選型的主要理由。你可以在自己的測(cè)試環(huán)境里把兩種方案都跑起來(lái)用秒表測(cè)一測(cè)你真正在意的指標(biāo)用模擬弱網(wǎng)工具壓一壓它們的抗性用真實(shí)設(shè)備的兼容性清單篩一遍播放端。做完這些功課那個(gè)最佳方案會(huì)自己浮出水面而不需要任何人替你拍板。