Photo by Vlad Alexandru Popa from Pexels算法
SRT協議是基於UDT的傳輸協議,保留了UDT的核心思想和機制,抗丟包能力強,適用於複雜的網絡。在LiveVideoStack線上分享中,新浪音視頻架構師 施維對SRT協議的原理、優缺點特性以及在流媒體中的應用進行了詳細解析。
文 / 施維緩存
整理 / LiveVideoStack服務器
視頻回放網絡
https://www2.tutormeetplus.co...數據結構
毋庸置疑,現今存量最大的直播協議是RTMP,但隨着新技術的不斷髮展與使用場景的不斷拓展,繼續使用RTMP會使人感到有些力不從心。RTMP協議的缺陷主要有如下四個方面:架構
首先,RTMP協議太老,且最後一次更新是在2012年;同時HEVC/H.265/AV1等視頻格式都沒有官方定義,以致於須要國內CDN廠商自行定義。socket
RTMP鏈接過程較長,因爲RTMP基於TCP(TCP存在三次握手),除此以外,其自己又存在c0/s0到c2/s2的三次握手,再加上connection,createstream,play/publish,總地來講RTMP完成一次建連須要進行9次會話,用於PC端勉強可以接受,對於移動端網絡質量的要求則很高。ide
RTMP的擁塞控制徹底依賴傳輸層,即徹底依賴於TCP傳輸層的擁塞控制算法來進行擁塞管理,幾乎沒有什麼優化;RTMP自己基於TCP傳輸,沒法提供帶寬自適應的算法。性能
在此背景下,衆多廠商開始着手提供一些新的直播協議供行業參考。如QUIC、SRT等。本次咱們將重點講述SRT的特色與應用。測試
Haivision聯手Wowza在UDT的基礎上針對音視頻實時性提出了SRT協議。SRT是基於UDT的協議(UDT協議是基於UDP的傳輸協議,在IETF已經提交了4個版本),具備很是良好的丟包重傳機制,丟包重傳的控制消息很是豐富,同時支持ACK、ACKACK、NACK。
咱們都知道音視頻對於時間這一點很是在乎,而SRT基於時間的報文發送,使其具備良好的防止流量突發的能力。SRT對上層提供了豐富的擁塞控制統計信息,包括RTT、丟包率、inflight、send/receive bitrate等。利用這些豐富的信息,咱們能夠實現帶寬預測,並根據帶寬的變化在編碼層去作自適應動態編碼與擁塞控制。
上圖能夠涵蓋SRT的基本思想:對比編碼後的視音頻碼流(左側綠色折線「Source」)與通過公網傳輸後的碼流(紅色折線「NetworkTransmission」),能夠看到編碼後的源視音頻碼流具備固定的幀間隔與必定特性的可變比特率,但通過公網傳輸後的碼流,其幀間隔變得不固定且碼率特性也被徹底改變,解碼這樣的信號是一項十分艱鉅的挑戰,甚至根本沒法解碼。
而若是使用加入糾錯的SRT協議進行公共互聯網傳輸,儘管編碼後的視音頻碼流通過公網傳輸幀間隔變得不固定,但因爲SRT協議封裝中包含精準的時間戳,解碼接收端能夠經過該時間戳重現固定的幀間隔。更重要的是,經過規定延時量,同時劃定了send buffer(發送端緩衝區)與receive buffer(接收端緩衝區),來自接收端的反饋信號(能夠理解爲後向糾錯)經過一系列的設置、糾錯、流量控制,使編碼後的視音頻碼流擁有與原碼流幾乎同樣的碼率特性。
Encoder編碼器處理完成的數據會被輸入send buffer,send buffer會對數據進行時間校驗,也就是按照必定的時間順序編碼並經過internet發送至receivebuffer,receive buffer按照報文上的時間戳一點點處理,確保生成的數據與源碼流基本一致。
整個SRT協議的思想基於編碼,具備抗丟包、抗擁塞、抗抖動等特性。
SRT的報文基礎也就是其交互過程依次爲以上五個步驟:握手(Handshake)、重要信息(Capability)、媒體(Media)、控制信息(Control)與關閉(Shutdown)。與RTMP的九次握手不一樣,SRT的建連過程僅需兩個RTT。也就是Handshake Request與Capability Announce。Caller與Listener之間的進行的Handshake Request是一個簡化版的握手,用以提升效率;握手以後Caller與Listener間進行重要信息的交換,整個SRT的第一項關鍵步驟就是Caller與Listener在這裏交換了延時量與緩衝區的大小;隨後Caller與Listener之間開始媒體數據的傳輸,同時也傳輸了爲恢復幀間隔而封裝的精準時間戳;SRT的第二項關鍵步驟是Listener向Caller同步控制信息,用以對抗公網中的抖動、丟包等一系列突發情況;最後,待至媒體數據傳輸完畢以後關閉傳輸。
SRT的報文格式較爲簡單,分爲數據報文與控制報文。上圖展現了數據報文的數據結構,觀察結構咱們能夠發現上方兩層爲UDP部分,而在這之下是UDT部分。若是初始化爲0,則認爲其是數據報文。FF表示報文的序列, 0b10是分片的第一個報文,0b00是分片的中間報文,0b01是分片的最後一個報文,0b11表示單個報文並無分片。KK表示是否加密,R表示是否屬於重傳報文,Timestamp表示時間戳,Destination Socket ID是SRT自定義的一個socket id。由該數據結構咱們能夠看出:SRT的數據報文擁有精準的32位時間戳,package sequence number絕對夠用,且結構很是簡單。
下圖展現的則是控制報文的數據結構,右側表格展現了控制報文的常規類型,其中值得重點關注的有ACK、NACK、ACKACK等。
SRT在接收和發送端都有receive buffer與send buffer,send Buffer嚴格按照時間戳間隔來發送,定時器默認10毫秒。
丟包重傳最多見的即是ACK機制。以上圖爲例,假設發送端緩衝區發送五個數據包:一、二、三、四、5給接收端緩衝區,接收端緩衝區成功接收到這些數據包以後會向發送端緩衝區發送ACK——確定應答已表示數據包被成功接收,發送端接收到ACK——確定應答以後回收空間,刪除一、二、三、四、5這五個數據包並準備發送數據包6。
send buffer徹底按照時間戳間隔處理數據,在肯定的間隔(與ACKs,ACKACKs 和 Round Trip Time相關),接收方發送ACK給發送方,使得發送方把收到ack的packet從sender buffer中移除,其在buffer中的空間點將被回收。接收端的receivebuffer中存在Latency Window也就是延遲窗口,其做用爲按照時間戳一點點上送數據。並嚴格按照時間戳檢測,檢測週期默認爲10毫秒。
接收端在收到數據包後會向發送端反饋表示成功接收的ACK,例如上圖左側,接收端在收到第十一個數據包後向發送端反饋ACK(11),而發送端在收到來自接收端的ACK(11)以後會向對端再發送一個ACKACK用以表示收到ACK(11)。這一過程最大的意義在於可以讓接收端計算出RTT,ACK發送的時間與對應ACKACK收到的時間之差就是RTT——Round Trip Time(RTT)是時間的度量,表示報文一個來回的耗時。SRT不能測量單方向的耗時,因此只能用RTT/2來表示單方向耗時。一個ACK(從接收方)會觸發ACKACK(從發送方)的發送,幾乎不帶其餘延時。RTT可評估當前網絡質量,RTT高表示整個網絡的延遲很大,RTT低則說明網絡延遲較低。RTT由接收端計算而出,計算得出的RTT會經過ACK發送至發送端,發送端就可獲知當前網絡的質量如何。帶寬狀況是不斷變化的,所以RTT也是一個隨網絡環境不斷變化的實時動態值。
ACK報文包含了豐富的關鍵信息。其中Last Acknowledge Packet Sequence Number表示當前收到第幾個包,RTT信息便於發送端評估網絡的質量,RTT的變化表示網絡變化狀況。Available Buffer Size表示接收端緩存可用率,SRT是能夠用做文件傳輸的,Available Buffer Size對文件傳輸的擁塞控制很是有用。當文件傳輸過快時接收端的內存緩存沒法成功接收,此時發送端除了考慮網絡還須要考慮接收端的性能與緩存是否有足夠的空間與能力在短期內接收龐大的文件。Packets Receiving Rate表示每秒鐘的收包率,這裏統計的是包的個數;而ReceivingRate表示接收包的比特率。
總而言之,接收端以10毫秒爲週期向發送端發送ACK,其中包括網絡RTT信息、接收端緩存信息、接收端比特率等關鍵數據,其中以上三個數據相當重要,直接反映出發送端到接收端之間的網絡環境狀態。
常規狀況下QUIC或TCP僅提供ACK方式,也就是接收端向發送端反饋哪些包成功接收;而WebRTC的RTP或RTCP經常使用選擇NACK,也就是接收端向發送端反饋哪些包沒有成功接收。NACK回傳的是接收端沒收到的數據包的列表。
SRT同時支持ACK與NACK,這樣設定的緣由在我看來是帶寬搶佔的強勢。例如,接收端向發送端發送ACK表示該數據包已經成功接收,不排除該ACK報文自己丟失,致使發送端覺得數據報文丟失而要超時重發,實際接收端對該數據包已經成功接收了。
還有一種狀況是:NACK的週期性發送可能會形成發送端多發報文,例如發送端成功發送5號與6號數據包,卻一直未收到來自接收端的ACK消息,當達到重發的時間時發送端再次發送5號與6號數據包,在此以後發送端收到了來自接收端的NACK消息,表示接收端未成功接收5號數據包與6號數據包;因而發送端第三次發送5號數據包與6號數據包。一個週期下來,發送端發了兩次數據包。從協議原理角度分析,咱們發現 SRT在丟包過程當中對帶寬的消耗比QUIC與其餘協議要高。 一旦出現丟包,SRT的重傳要多於其餘協議,SRT以此來搶佔帶寬從而達到音視頻的同步效果。
按時間發送是一個標準的音視頻傳輸特性。咱們知道編碼器發送是以編碼器的速度向外發送數據,但有時編碼器會太樂觀,徹底按照編碼輸出向外發送會致使輸出超過預期太高的比特率。在這種狀況下SRT Packet就不會足夠快地輸出,由於SRT會最終被太低的錯誤配置影響到。
SRT中包含三個配置選項:INPUTBW表示編碼器輸入帶寬,MAXBW表示最大帶寬,以及OVERHEAD(%)表示過載率,配置原則如上圖所示:
若是不配置INPUTBW與OVERHEAD(%)而僅配置MAXBW,則當前編碼器輸出的比特率是<mbw>也就是MAXBW;
若是不配置MAXBW與INPUTBW而僅配置OVERHEAD(%),則當前編碼器輸出的比特率是<smpinpbw>(1080+<oh>)/100,默認爲25%;
第三種狀況是不配置MAXBW而配置OVERHEAD(%)與INPUTBW,最大輸出比特率便爲<ibw>(100+<oh>)/100。
SRT最大的特色即是可配置。在有配置的狀況下按照配置來作,在無配置的狀況下按實際測量的比特率來作。常規狀況下咱們不選擇進行配置,尤爲是針對互聯網而言,由於單一配置沒法保證可以在不一樣時段應對不一樣境況的網絡。一般咱們選擇使用實際測量出的編碼比特率計算:<smpinpbw>*(1080+<oh>)/100。
接下來咱們討論一下SRT的流控。咱們知道在丟包重傳機制下,若是報文在網絡傳輸中丟失,則發送端會從新發送。例如在某網絡環境下發送端與接收端之間的帶寬僅有1M,如今因爲背景流量與噪聲等影響,原來1M的帶寬減小100k變成了900k,此時就會出現10%的丟包;發送端未收到來自接收端的ACK或者收到NACK認爲出現丟包,因而嘗試重傳這10%的數據;但實際上發送端與接收端之間的帶寬就剩下900k了,而加上丟包重傳的發送量是1.1M,帶寬變窄發送的流量不降反增,就會致使網絡狀況愈來愈糟糕,最後形成網絡擁塞卡頓頻繁,最後網絡崩潰。SRT單純地丟包重傳不能解決網絡擁塞的問題,出現網絡擁塞的最好解決方案是限流,下降帶寬流量壓力。
SRT的擁塞控制過於簡單,僅在congctrl.cpp中實現。其包括如下幾個變量:
M_iFlowWindowSize表示接收方的緩存大小,m_dCWndSize等於1秒內發送的bytes數據 / (RTT + 10),sendbufer表示發送方未發送buffer大小。其中這裏的1秒內發送的bytes數據,具體是指一個RTT內發送的數據。總結算法以下:m_iFlowWindowSize用以衡量接收方緩存是否耗盡,同時監測發送方每RTT發送數據size是否大於發送buffer大小。接收方緩存通常在傳輸文件時起做用。
在我看來,SRT在擁塞控制方面作得還遠遠不夠。
SRT在快速鏈接方面有明顯優點,兩次握手成功便可建連;SRT的丟包重傳策略出色,ACK、ACKACK、NACK等提供了豐富的控制消息,還有RTT、Receive比特率等。可是同時咱們通過測試也發現SRT在丟包時,發送數據的帶寬佔用率仍是有些大,丟包率越高發送帶寬佔用越大。
SRT按照時間發送音視頻數據,根據實際的編碼比特率來發送音視頻數據;但SRT的擁塞控制過於簡單,只針對接收方緩存是否有能力接收與編碼比特率是否發送過快。
咱們推薦從編碼器到推流至邊緣節點的部分使用SRT。其目的主要有兩個:提升源流質量與基於SRT自適應比特率編碼,發送端和接收端配合從而解決最後一千米的推流問題。是本身對srt的理解。SRS支持 SRT的接收端推流,邊緣SRS在接收到來自推流端的SRT推流以後會將其轉爲RTMP並分發給中央節點,然後全部的邊緣節點均可以進行RTMP拉流。
做爲一個傳輸協議,SRT的一個弊端在於給出一個未定義的地址,咱們不清楚這到底是推流地址仍是拉流地址,那麼如何進行匹配?
爲方便SRT編碼器推流,SRS4.0支持編碼器的簡單配置。如服務器IP、服務器Port以及streamid。根據SRT的官方文檔,SRT經過StreamID進行標識,簡配地址格式如上圖所示,帶有vhost虛擬主機配置的地址格式以下圖所示,其中m=publish/request表示推/拉流地址。顯而易見的是,對比RTMP,SRT地址的可讀性並很差,地址長而冗雜。
測試各類網絡狀況下的SRT咱們不難發現,丟包率增長致使帶寬消耗增長,網絡情況不良或發生擁塞時,發送端會發送更多的數據,這便會致使網絡情況愈發惡化,丟包率變得更高,並以此惡性循環;除此以外,RTT增長也會致使延時增長,同樣會致使丟包率增長,帶寬消耗更大。
這裏咱們提出的解決方案是預測網絡帶寬——經過當前的send bitrate、RTT、inflight等數據預測網絡帶寬;同時動態調整編碼比特率,根據預測的帶寬動態調整編碼比特率1來適應實時的帶寬,避免發生擁塞並提升視頻流暢度。
上圖展現的就是谷歌擁塞控制算法GCC的架構圖。如圖所示:發送端將報文發給接收端,接收端由幾個部分組成,其中計算接收報文Delay,也就是計算基於D(i,j) = (Rj-Sj) - (Ri-Si) 的變化的導數,獲得m(ti)。除此以外,接收端設置的卡曼濾波算法會計算出一個門限值,經過該卡曼濾波的計算與比對,決定是增長碼率仍是下降碼率,最終得出下一次網絡帶寬的預估值(Ar),並將數據返回給發送方。
基於SRT自適應碼率編碼的關鍵參數如上圖左側所示:rtt_min表示1s內的RTT最小值,send_bitrate_max表示1s內最大的發送bitrate,常規狀況咱們大概200~300毫秒統計一次;inflight表示已經發送出去但還未接收到ACK的報文的個數,這種狀況可能意味着報文還在傳輸鏈路的途中;BDP(bandwidth-delay product) BDP = send_bitrate_max*rtt_min表示一個RTT內發送的最大字節數。若是BDP大於(1.2 x inflight)表示網絡情況良好,能夠增長編碼碼率;若是BDP小於(0.8 x inflight)則意味着網絡情況不佳須要減小編碼碼率,其餘狀況則維持現有編碼碼率不變。這樣便能較爲有效地避免網絡擁塞的狀況發生。
咱們以測試實例來驗證效果:經過Internet推流,來自於美國的SRT編碼器的數據推至杭州的節點,創建SRS4.0。該測試開源地址如圖中所示,使用FFmpeg配置編碼碼率爲1000kbps,傳輸過程當中根據實際出口帶寬動態調整編碼碼率。由上圖右側圖線1咱們能夠看到自適應碼率最高可達到1400kbps,有良好的自適應效果;實際測試感覺來看,視頻播放平滑無明顯卡頓,即使偶爾出現帶寬抖動也可以迅速恢復。
接下來咱們對比SRT與QUIC,總結兩者特色。
SRT1.4的優缺點能夠簡單歸納爲如下內容:SRT的優勢在於基於音視頻按照時間戳進行收發,可有效保證音視頻,同時ACK/ACKACK/NACK多種丟包糾正機制可有效下降延時與丟包率。SRT對上層提供豐富的傳輸層數據信息如RTT、lost packet rate、receive rate等。固然,SRT的缺點也不容忽視,如SRT的擁塞控制過於簡單,須要在傳輸層合入BBR算法,原生SRT不支持鏈接遷移等。
基於以上特色,我認爲SRT更加適合編碼器到最近節點的傳輸,也就是經過SRT探測出的RTT等相關信息實現自適應碼率編碼;除此以外,SRT也適合網絡節點固定、網絡狀況固定的環境,合理配置lantency、send/recv buffer、ohead rate等SRT參數可達到事半功倍的效果。
QUIC的優勢是鏈接快,同時具備可插拔的擁塞控制,包括CUBIC、BBR;另外在丟包重傳方面,QUIC擁有更多的ACK block,最大可達256個,而且對於RTT的計算更加準確(QUIC中存在獨立的Packet number,包括重傳包在內的Packet number也都不相同,很是有利於RTT的精確計算);最後還有QUIC支持鏈接遷移。
固然,QUIC一樣存在缺點:第一,報文頭大在發送報文中所佔比例較高。第二,丟包重傳只有ACK原生;第三,QUIC不支持丟包,一旦出現丟包,若沒有在timeout前恢復就會斷開鏈接。
基於以上優缺點,我認爲QUIC更適合運用在網絡丟包率較高的環境,由於QUIC具備0RTT快速鏈接能力、丟包重傳中ACK回覆的block較大而且在擁塞控制方面表現很是優秀。除此以外QUIC也適合用於長距離傳輸當中,由於網絡傳輸RTT較高,QUIC在鏈接斷開後重連是0RTT,傳輸數據更加高效。
Mediago具備支持QUIC協議來傳輸RTMP直播流的特性,如RTMP over TCP推拉流、RTMP over QUIC推拉流以及對FLV的支持。服務器之間則支持基於TCP服務器間RTMP回源與基於QUIC服務器間RTMP回源,上圖中有測試連接,感興趣的朋友能夠本身嘗試一下。