平滑的發(fā)送
Paced sender今豆,也常常被稱為 “pacer”,它是 WebRTC RTP 棧的一部分亚斋,主要用于平緩發(fā)送到網(wǎng)絡(luò)的數(shù)據(jù)包流隙弛。
背景
考慮一個(gè)碼率為 5Mbps 幀率為 30fps 的視頻流饱溢。理想情況下喧伞,這個(gè)視頻流的每個(gè)幀的大小大約 21kB,每個(gè)幀被打包成大約 18 個(gè) RTP 數(shù)據(jù)包绩郎。雖然說(shuō)每秒中滑動(dòng)窗口的平均比特率是正確的 5Mbps潘鲫,但在更短的時(shí)間尺度上,它可以被視為每隔 33 毫秒 167 Mbps 的突發(fā)傳輸肋杖,然后是 32 毫秒的靜默期溉仑。此外,相當(dāng)常見(jiàn)的是状植,在突然移動(dòng)的情況下浊竟,尤其是在處理屏幕共享時(shí),視頻編碼器會(huì)超出目標(biāo)幀大小津畸。比理想大小大 10 倍甚至 100 倍的幀是一個(gè)非常真實(shí)的場(chǎng)景振定。這些數(shù)據(jù)包突發(fā)可能會(huì)導(dǎo)致一些問(wèn)題,例如網(wǎng)絡(luò)擁塞和緩沖區(qū)膨脹肉拓,甚至是數(shù)據(jù)包丟失后频。大多數(shù)會(huì)話都有多個(gè)媒體流,比如一個(gè)視頻軌道和一個(gè)音頻軌道暖途。如果你一次將一個(gè)視頻幀發(fā)送到網(wǎng)絡(luò)上卑惜,并且這些數(shù)據(jù)包需要 100 毫秒到達(dá)對(duì)端 —— 這意味著你現(xiàn)在也阻塞了任何音頻數(shù)據(jù)包及時(shí)到達(dá)遠(yuǎn)端。
Paced sender 通過(guò)使用一個(gè)緩沖區(qū)來(lái)解決這個(gè)問(wèn)題驻售,媒體數(shù)據(jù)包先在這個(gè)緩沖區(qū)里排隊(duì)露久,然后使用一個(gè) 漏桶 算法將它們平緩地發(fā)送到網(wǎng)絡(luò)上。緩沖區(qū)中包含所有媒體軌道的單獨(dú)的 fifo 流欺栗,以便實(shí)現(xiàn)抱环,比如音頻可以優(yōu)先于視頻 - 并且可以以循環(huán)方式發(fā)送優(yōu)先級(jí)相等的流,以避免任何一個(gè)流阻塞其它流纸巷。
由于 pacer 控制在網(wǎng)絡(luò)上發(fā)送的比特率镇草,因此它還用于在需要最小發(fā)送速率的情況下生成填充 - 如果使用比特率探測(cè),則生成數(shù)據(jù)包序列瘤旨。
數(shù)據(jù)包的生命周期
當(dāng)使用 paced sender 時(shí)梯啤,媒體數(shù)據(jù)包的典型路徑看起來(lái)就像這樣:
RTPSenderVideo
或RTPSenderAudio
將媒體數(shù)據(jù)打包成 RTP 數(shù)據(jù)包。數(shù)據(jù)包被發(fā)送給 [RTPSender] 類進(jìn)行傳輸存哲。
pacer 通過(guò) [RtpPacketSender] 接口被調(diào)用以將數(shù)據(jù)包批量入隊(duì)因宇。
數(shù)據(jù)包被放進(jìn) pacer 內(nèi)的隊(duì)列中七婴,等待適當(dāng)?shù)臅r(shí)機(jī)發(fā)送它們。
在計(jì)算好的時(shí)間察滑,pacer 調(diào)用
PacingController::PacketSender()
回調(diào)方法打厘,通常由 [PacketRouter] 類實(shí)現(xiàn)。router 基于數(shù)據(jù)包的 SSRC 將數(shù)據(jù)包轉(zhuǎn)發(fā)到正確的 RTP 模塊贺辰,并在其中的
RTPSenderEgress
類中打上最后的時(shí)間戳户盯,可能會(huì)保存它以進(jìn)行重傳等。數(shù)據(jù)包被發(fā)送到底層的
Transport
接口饲化,之后它現(xiàn)在超出了范圍莽鸭。
與此異步進(jìn)行的是確定估計(jì)的可用發(fā)送帶寬 - 并通過(guò) void SetPacingRates(DataRate pacing_rate, DataRate padding_rate)
方法對(duì) RtpPacketPacker
設(shè)置目標(biāo)發(fā)送速率。
數(shù)據(jù)包優(yōu)先級(jí)
pacer 基于兩個(gè)標(biāo)準(zhǔn)對(duì)數(shù)據(jù)包進(jìn)行優(yōu)先級(jí)排序:
-
數(shù)據(jù)包類型吃靠,優(yōu)先級(jí)從高到低如下:
- 音頻
- 重傳
- 視頻和 FEC
- 填充
入隊(duì)的順序
入隊(duì)順序是在每個(gè)流 (SSRC) 的基礎(chǔ)上執(zhí)行的硫眨。給定相同的優(yōu)先級(jí),[RoundRobinPacketQueue] 在媒體流之間交替巢块,以確保沒(méi)有流不必要地阻塞其它流礁阁。
實(shí)現(xiàn)
當(dāng)前 paced sender 有兩個(gè)實(shí)現(xiàn)(盡管它們通過(guò) PacingController
類共享大量的邏輯)。 傳統(tǒng)的 [PacedSender] 使用專門的線程以 5ms 的間隔輪詢 pacing 控制器族奢,并具有保護(hù)內(nèi)部狀態(tài)的鎖氮兵。顧名思義,更新的 [TaskQueuePacedSender] 使用 [TaskQueue] 來(lái)保護(hù)狀態(tài)歹鱼,并調(diào)度數(shù)據(jù)包處理泣栈,后者動(dòng)態(tài)地基于實(shí)際的發(fā)送速率和約束。避免在新的應(yīng)用中使用傳統(tǒng)的 PacedSender弥姻,我們已經(jīng)在計(jì)劃移除它了南片。
數(shù)據(jù)包路由器
一個(gè)稱為 [PacketRouter] 的相鄰組件用于路由從 pacer 出來(lái)的數(shù)據(jù)包,并進(jìn)入正確的 RTP 模塊庭敦。它具有以下功能:
-
SendPacket
方法查找具有對(duì)應(yīng)于數(shù)據(jù)包的 SSRC 的 RTP 模塊疼进,以進(jìn)一步路由到網(wǎng)絡(luò)。秧廉。 - 如果使用了發(fā)送端帶寬估計(jì)伞广,它會(huì)填充傳輸范圍內(nèi)的序列號(hào)擴(kuò)展。
- 生成填充疼电。支持基于負(fù)載的填充的模塊被優(yōu)先考慮嚼锄,最后一個(gè)發(fā)送媒體的模塊始終是第一選擇。
- 發(fā)送媒體之后返回任何生成的 FEC蔽豺。
- 轉(zhuǎn)發(fā) REMB 和/或 TransportFeedback 消息給適當(dāng)?shù)?RTP 模塊区丑。
目前 FEC 是基于每個(gè) SSRC 生成的,因此總是在發(fā)送媒體后從 RTP 模塊返回。希望有一天沧侥,我們將支持使用單個(gè) FlexFEC 流覆蓋多個(gè)流 - 則數(shù)據(jù)包路由器是這種 FEC 生成器可能存在的地方可霎。它甚至可以用于 FEC 填充,作為 RTX 的替代方案宴杀。
API
這一節(jié)概述了與 pacer 的幾個(gè)不同用例相關(guān)的類和方法
數(shù)據(jù)包發(fā)送
要發(fā)送數(shù)據(jù)包癣朗,可以使用 RtpPacketSender::EnqueuePackets(std::vector<std::unique_ptr<RtpPacketToSend>> packets)
。pacer 接收一個(gè) PacingController::PacketSender
對(duì)象作為構(gòu)造函數(shù)的參數(shù)旺罢,當(dāng)需要實(shí)際發(fā)送數(shù)據(jù)包時(shí)使用這個(gè)回調(diào)旷余。
發(fā)送速率
要控制發(fā)送速率,則使用 void SetPacingRates(DataRate pacing_rate, DataRate padding_rate)
主经。如果數(shù)據(jù)包隊(duì)列變?yōu)榭杖倌海野l(fā)送速率掉到 padding_rate
以下庭惜,則 pacer 將從 PacketRouter
請(qǐng)求填充包罩驻。
要完全掛起/恢復(fù)發(fā)送數(shù)據(jù)(比如,由于網(wǎng)絡(luò)可用性)护赊,則使用 Pause()
和 Resume()
方法惠遏。
在某些情況下,指定的 pacing 速率可能會(huì)被覆蓋骏啰,例如由于極端的編碼器過(guò)沖节吮。使用 void SetQueueTimeLimit(TimeDelta limit)
來(lái)指定你希望數(shù)據(jù)包在 pacer 的隊(duì)列中等待的最長(zhǎng)時(shí)間(暫停除外)。實(shí)際發(fā)送速率可能會(huì)增加到超過(guò) pacing_rate判耕,以嘗試使 平均 排隊(duì)時(shí)間小于請(qǐng)求的限制透绩。這樣做的理由是,如果發(fā)送隊(duì)列長(zhǎng)于三秒壁熄,最好冒丟包的風(fēng)險(xiǎn)帚豪,然后嘗試使用關(guān)鍵幀進(jìn)行恢復(fù),而不是造成嚴(yán)重的延遲草丧。
帶寬估計(jì)
如果帶寬估計(jì)器支持帶寬探測(cè)狸臣,它可能會(huì)請(qǐng)求以指定速率發(fā)送一組數(shù)據(jù)包,以判斷這是否會(huì)導(dǎo)致網(wǎng)絡(luò)延遲/丟失增加昌执。使用 void CreateProbeCluster(DataRate bitrate, int cluster_id)
方法 - 通過(guò)這個(gè) PacketRouter
發(fā)送的數(shù)據(jù)包將在附加的 PacedPacketInfo
結(jié)構(gòu)中用相應(yīng)的 cluster_id 進(jìn)行標(biāo)記烛亦。
如果使用擁塞窗口 pushback,則可以使用 SetCongestionWindow()
和 UpdateOutstandingData()
更新?tīng)顟B(tài)懂拾。
還有一些方法可以幫我們控制如何 pace:
-
SetAccountForAudioPackets()
確定音頻數(shù)據(jù)包是否計(jì)入帶寬消耗煤禽。 -
SetIncludeOverhead()
確定是否把完整 RTP 數(shù)據(jù)包大小計(jì)入帶寬使用(否則只計(jì)算媒體載荷)。 -
SetTransportOverhead()
設(shè)置每個(gè)數(shù)據(jù)包消耗的額外數(shù)據(jù)大小岖赋,表示比如 UDP/IP 頭部呜师。
統(tǒng)計(jì)數(shù)據(jù)
有幾種方法用于在 pacer 狀態(tài)中收集統(tǒng)計(jì)信息:
-
OldestPacketWaitTime()
,自添加進(jìn)隊(duì)列中的最早的數(shù)據(jù)包被添加進(jìn)隊(duì)列以來(lái)的時(shí)間贾节。 -
QueueSizeData()
汁汗,當(dāng)前在隊(duì)列中的總字節(jié)數(shù)衷畦。 -
FirstSentPacketTime()
,發(fā)送第一個(gè)數(shù)據(jù)包的絕對(duì)時(shí)間知牌。 -
ExpectedQueueTime()
祈争,隊(duì)列中的總字節(jié)數(shù)除以發(fā)送速率。
RTPSender
RtpPacketSender
RtpPacketPacer
PacketRouter
PacedSender
TaskQueuePacedSender
RoundRobinPacketQueue