ULPFEC在WebRTC中的實(shí)現(xiàn)

1. 前言

在基于IP網(wǎng)絡(luò)的多媒體通信系統(tǒng)(比如WebRTC)中,網(wǎng)絡(luò)丟包對(duì)多媒體通信質(zhì)量有非常嚴(yán)重的影響:例如造成視頻的馬賽克、圖像模糊、幀率下降等問題,造成音頻的聲音失真、噪聲干擾、音頻中斷等問題。這都會(huì)嚴(yán)重影響系統(tǒng)的通信質(zhì)量,造成非常差的用戶體驗(yàn)。

WebRTC主要采取兩種手段對(duì)抗網(wǎng)絡(luò)丟包:丟包重傳(NACK)和前向糾錯(cuò)(FEC)。丟包重傳在之前文章中有專門論述[1],本文集中注意力于FEC。FEC是一種前向糾錯(cuò)技術(shù),發(fā)送端將負(fù)載數(shù)據(jù)加上一定的冗余糾錯(cuò)碼一起發(fā)送,接收端根據(jù)接收到的糾錯(cuò)碼對(duì)數(shù)據(jù)進(jìn)行差錯(cuò)檢測(cè),如果發(fā)現(xiàn)差錯(cuò),則利用糾錯(cuò)碼進(jìn)行糾錯(cuò)。而ULPFEC(Uneven Level Protection FEC,直譯為非均等保護(hù)前向糾錯(cuò))則是WebRTC實(shí)現(xiàn)的FEC方案之一,本文深入學(xué)習(xí)ULPFEC的理論基礎(chǔ)和實(shí)現(xiàn)細(xì)節(jié)。

2. ULPFEC理論學(xué)習(xí)

ULPFEC由RFC5109[2]定義,在WebRTC中以RED格式進(jìn)一步封裝在RTP中傳輸。該標(biāo)準(zhǔn)使用XOR操作基于多個(gè)多媒體數(shù)據(jù)包生成FEC數(shù)據(jù)包,然后在接收端根據(jù)FEC數(shù)據(jù)包和已接收數(shù)據(jù)包恢復(fù)丟失的數(shù)據(jù)包。ULPFEC能夠針對(duì)不同的數(shù)據(jù)包提供不同的保護(hù)級(jí)別,從而對(duì)重要的數(shù)據(jù)包提供更多的保護(hù)[3]。

2.1 ULPFEC基本概念

ULPFEC數(shù)據(jù)包中包含發(fā)送端需要告知接收端的一些重要信息,包括本FEC數(shù)據(jù)包所保護(hù)的媒體數(shù)據(jù)、保護(hù)級(jí)別和每個(gè)級(jí)別的保護(hù)長(zhǎng)度。特別地,F(xiàn)EC數(shù)據(jù)包針對(duì)每個(gè)保護(hù)級(jí)別k設(shè)置一個(gè)偏移量掩碼m(k),如果m(k)的第i位被設(shè)置為1,則序列號(hào)為N+i的媒體數(shù)據(jù)包在本FEC包的第k級(jí)別被保護(hù)。其中N為基準(zhǔn)序列號(hào),在本FEC包中設(shè)置。第k級(jí)別保護(hù)的媒體數(shù)據(jù)大小由L(k)指示,該值也在FEC包中設(shè)置。以上保護(hù)長(zhǎng)度、偏移量掩碼、負(fù)載類型和基準(zhǔn)序列號(hào)能夠完全確定生成FEC數(shù)據(jù)包中的奇偶校驗(yàn)碼[2]。

一般來(lái)說,F(xiàn)EC是帶寬和保護(hù)力度的權(quán)衡,針對(duì)同樣的媒體數(shù)據(jù),更多的FEC數(shù)據(jù)包意味著更有力的抗丟包保護(hù),但同時(shí)也會(huì)消耗更多的帶寬。通常情況下,對(duì)于媒體數(shù)據(jù)包,不同部分的重要程度不一樣。因此,我們可以針對(duì)數(shù)據(jù)包的不同部分實(shí)施不同程度保護(hù)(即非均等保護(hù)前向糾錯(cuò)),以充分利用帶寬資源。更多帶寬花費(fèi)在更重要的數(shù)據(jù)部分,相反,較少帶寬花費(fèi)在不那么重要的數(shù)據(jù)部分。媒體數(shù)據(jù)包根據(jù)重要程度劃分為若干部分,每個(gè)部分就是我們所說的保護(hù)級(jí)別,每個(gè)部分的長(zhǎng)度即為保護(hù)長(zhǎng)度,每個(gè)FEC包可攜帶多個(gè)保護(hù)級(jí)別的奇偶校驗(yàn)碼。根據(jù)數(shù)據(jù)包不同部分重要程度進(jìn)行保護(hù)的算法,就是所謂的ULPFEC非均等保護(hù)前向糾錯(cuò)。

圖1 ULP非均等保護(hù)前向糾錯(cuò).png

圖1很好說明了ULP的概念:FEC包1在L0級(jí)保護(hù)數(shù)據(jù)包A和B,F(xiàn)EC包2在L0級(jí)保護(hù)數(shù)據(jù)包C和D,同時(shí)在L1級(jí)保護(hù)數(shù)據(jù)包A、B、C和D。注意FEC包1和FEC包2的保護(hù)數(shù)據(jù)包集合不一樣大,同時(shí)保護(hù)長(zhǎng)度也不一樣長(zhǎng)。

2.2 ULPFEC報(bào)文格式

ULPFEC報(bào)文由一個(gè)頭部和多個(gè)保護(hù)級(jí)別組成,每個(gè)保護(hù)級(jí)別包含級(jí)別頭部和負(fù)載,如圖2、3、4所示:

圖2 FEC報(bào)文格式.png

RTP頭部只有在FEC報(bào)文通過單獨(dú)數(shù)據(jù)流發(fā)送時(shí)才用到,這里的RTP頭部格式遵循RFC3550的定義[4]。

圖3 FEC頭部格式.png

FEC頭部為10字節(jié),包含內(nèi)容如下:
E flag:擴(kuò)展位,供將來(lái)使用,當(dāng)前設(shè)置為0。
L flag:指示長(zhǎng)偏移掩碼是否使用,0表示偏移掩碼為16位,1表示為48位。
P/X/CC/M/PT recovery field:由本FEC包所保護(hù)的所有媒體數(shù)據(jù)包的RTP頭部的P/X/CC/M/PT flag位經(jīng)XOR操作后得到。
SN base:本FEC包所保護(hù)的媒體數(shù)據(jù)包的RTP報(bào)文的序列號(hào)最小值。
TS recovery field: 由本FEC包所保護(hù)的所有媒體數(shù)據(jù)包的RTP頭部中的Timestamp字段經(jīng)XOR操作后得到。
Length recovery field: 由本FEC包所保護(hù)的所有媒體數(shù)據(jù)包的負(fù)載長(zhǎng)度(包括CSRC、RTP頭部擴(kuò)展、負(fù)載和padding的長(zhǎng)度之和,以16位無(wú)符號(hào)網(wǎng)絡(luò)序表示)經(jīng)XOR操作后得到。

圖4 ULP級(jí)別頭部格式.png

根據(jù)FEC頭部中E flag是否設(shè)置,F(xiàn)EC級(jí)別頭部長(zhǎng)度為4字節(jié)或8字節(jié)。Protection length為2字節(jié)表示本級(jí)別所保護(hù)的媒體數(shù)據(jù)的長(zhǎng)度;mask為2字節(jié)(如E flag設(shè)置則為6字節(jié))表示偏移掩碼,指示本級(jí)別所保護(hù)的媒體數(shù)據(jù)包的分布情況。如果偏移掩碼的第i位置為1,則表示第N+i個(gè)媒體數(shù)據(jù)包在本級(jí)別中受保護(hù),其中N為FEC頭部中的媒體數(shù)據(jù)基準(zhǔn)序列號(hào)。

偏移掩碼的設(shè)置遵循以下規(guī)則:

a)媒體數(shù)據(jù)包在高于0級(jí)別的等級(jí)中只能被保護(hù)一次,但是可以在0級(jí)別中被多個(gè)FEC包保護(hù),只要這些FEC包在0級(jí)別的保護(hù)長(zhǎng)度相等。
b)如果媒體數(shù)據(jù)包在p級(jí)別被保護(hù),那么它也必須在p-1級(jí)別被保護(hù)。注意保護(hù)p級(jí)別的FEC包和保護(hù)p-1級(jí)別的FEC包可能不是同一個(gè)。
c)如果FEC包包含p級(jí)別保護(hù),那么它也必須包含p-1級(jí)別保護(hù)。注意p級(jí)別保護(hù)的數(shù)據(jù)包可能和p-1級(jí)別保護(hù)的數(shù)據(jù)包不是同一個(gè)。

規(guī)則a)把多重保護(hù)限定在0級(jí)別,高于0級(jí)別的多重保護(hù)會(huì)減小保護(hù)效果并且增大接收端恢復(fù)數(shù)據(jù)的復(fù)雜度。規(guī)則b)限定媒體數(shù)據(jù)包受保護(hù)的連續(xù)性,即不存在中間某段數(shù)據(jù)不受保護(hù)的媒體數(shù)據(jù)包。規(guī)則c)限定FEC數(shù)據(jù)包保護(hù)級(jí)別的連續(xù)性,即不存在中間某個(gè)級(jí)別不保護(hù)數(shù)據(jù)的FEC數(shù)據(jù)包。

下面以圖5來(lái)簡(jiǎn)單說明,我們可以看到,F(xiàn)EC包1在0級(jí)別保護(hù)了數(shù)據(jù)包A、B,而FEC包2在0級(jí)別保護(hù)了數(shù)據(jù)包C、D,同時(shí)在1級(jí)別保護(hù)了數(shù)據(jù)包A、B、C和D。這都符合上述三條規(guī)則。

圖5 ULP非均等保護(hù)組合舉例.png
2.3 ULPFEC報(bào)文構(gòu)造

通過對(duì)比RFC3550中RTP頭部的定義我們可以發(fā)現(xiàn),F(xiàn)EC頭部和RTP頭部非常類似:除E/L flag之外,F(xiàn)EC頭部前8字節(jié)基本上和RTP頭部前8字節(jié)定義相同,而且其數(shù)據(jù)也來(lái)源于媒體數(shù)據(jù)包的RTP頭部(經(jīng)XOR運(yùn)算后得到)。而后兩字節(jié)length recovery也是對(duì)媒體數(shù)據(jù)RTP負(fù)載長(zhǎng)度計(jì)算得到的。因此,F(xiàn)EC頭部就是它所保護(hù)的所有RTP報(bào)文的頭部經(jīng)XOR計(jì)算后得到的。

據(jù)此,我們很容易得到FEC頭部的構(gòu)造辦法:對(duì)于本FEC包保護(hù)的所有媒體數(shù)據(jù)包,針對(duì)其RTP頭部的前8字節(jié)進(jìn)行XOR運(yùn)算,最終結(jié)果根據(jù)格式定義填入到FEC頭部中。具體細(xì)節(jié)在此不展開,詳情可參考RFC5109[2]。需要注意的是FEC頭部只保護(hù)RTP頭部的前12字節(jié),對(duì)于CSRC和RTP Extentions部分,F(xiàn)EC將其視為RTP負(fù)載部分進(jìn)行保護(hù)。

對(duì)于保護(hù)級(jí)別的頭部,根據(jù)預(yù)先確定的本級(jí)別保護(hù)長(zhǎng)度和保護(hù)媒體數(shù)據(jù)集合,分別填入保護(hù)長(zhǎng)度字段和設(shè)置偏移掩碼字段。對(duì)于保護(hù)級(jí)別的負(fù)載部分,則是由本級(jí)別保護(hù)的媒體數(shù)據(jù)包的對(duì)應(yīng)部分進(jìn)行XOR運(yùn)算后得到,然后填入負(fù)載位置。

2.4 ULPFEC報(bào)文發(fā)送

ULPFEC報(bào)文可采取兩種方式發(fā)送:1)使用獨(dú)立的RTP流發(fā)送;2)封裝在RED報(bào)文中隨源媒體數(shù)據(jù)一起發(fā)送。WebRTC采用第二種方式。RED(Redundant Coding)是針對(duì)RTP負(fù)載數(shù)據(jù)的二次封裝,所以叫冗余編碼,其定義在RFC2198[5]中。RED有兩種數(shù)據(jù)封裝格式:Primary Data Block和Redundant Data Block,分別如圖6、圖7所示:

圖6 Redundant Data Block.png

F flag:指示本Block后續(xù)是否還有其他Block跟隨,1表示有,0表示無(wú)。
block PT:本Block 的Payload Type,也即原始RTP負(fù)載數(shù)據(jù)的PT。
Timestamp offset:本Block相對(duì)于原始RTP頭部中timestamp的偏移量。
Block length:本Block的長(zhǎng)度。

圖7 Primary Data Block.png

Primary Data Block表示本Block之后再無(wú)Block。

FEC報(bào)文在構(gòu)造之后,會(huì)封裝為RED格式,然后再進(jìn)一步封裝為RTP報(bào)文,最后隨其他RTP報(bào)文(也已經(jīng)封裝為RED格式)一起發(fā)送到網(wǎng)絡(luò)。在接收端,RTP報(bào)文首先根據(jù)負(fù)載判斷為RED報(bào)文后,進(jìn)行解包操作,得到原始RTP/FEC報(bào)文,然后繼續(xù)接下來(lái)的流程。

2.5 ULPFEC報(bào)文恢復(fù)

報(bào)文恢復(fù)即是報(bào)文構(gòu)造的逆過程,在接收端RTP數(shù)據(jù)包經(jīng)過RED解包操作后,得到原始RTP包或者FEC包,前者進(jìn)一步發(fā)送到VCM模塊并存儲(chǔ)在FEC處理模塊,后者則進(jìn)行丟包檢測(cè)和數(shù)據(jù)包恢復(fù)工作。FEC包能夠在媒體數(shù)據(jù)包丟失的情況下恢復(fù),丟失的媒體數(shù)據(jù)包可以部分或全部恢復(fù),這取決于實(shí)際的數(shù)據(jù)包丟失情況。丟失媒體數(shù)據(jù)包恢復(fù)需要兩步:1)確定恢復(fù)丟失媒體數(shù)據(jù)包所需要的FEC數(shù)據(jù)包和未丟失媒體數(shù)據(jù)包的集合,有多種算法可確定這個(gè)數(shù)據(jù)包集合。2)重建丟失的媒體數(shù)據(jù)包,這又包括重建RTP頭部和RTP負(fù)載。下面分別描述之。

重建RTP頭部。設(shè)S是在0級(jí)別恢復(fù)數(shù)據(jù)包xi RTP頭部所需要的FEC數(shù)據(jù)包和媒體數(shù)據(jù)包的集合,則重建xi的RTP頭部的過程如下:1)對(duì)于S中所有的媒體數(shù)據(jù)包,針對(duì)其前10字節(jié)執(zhí)行XOR運(yùn)算得到媒體比特流M(最后2字節(jié)基于數(shù)據(jù)包長(zhǎng)度進(jìn)行XOR運(yùn)算);FEC比特流F則為S中FEC數(shù)據(jù)包的前10字節(jié)。2)針對(duì)媒體比特流M和FEC比特流F執(zhí)行XOR運(yùn)算,得到恢復(fù)比特流R。3)新建一個(gè)標(biāo)準(zhǔn)RTP數(shù)據(jù)包,其頭部長(zhǎng)度為12字節(jié)。4)根據(jù)RTP頭部和FEC頭部的關(guān)系,用恢復(fù)比特流R填充RTP頭部 ,其中RTP頭部最后4字節(jié)為SSRC已經(jīng)預(yù)先知道,直接填充即可。

重建RTP負(fù)載。設(shè)S是在n級(jí)別恢復(fù)數(shù)據(jù)包xi負(fù)載所需要的FEC數(shù)據(jù)包和媒體數(shù)據(jù)包的集合,則重建xi的RTP負(fù)載的過程如下:1)從FEC包中獲取n級(jí)別的負(fù)載數(shù)據(jù)保護(hù)長(zhǎng)度Ln。2)定義FEC比特流Fn為FEC包在n級(jí)別的FEC負(fù)載。3)根據(jù)n級(jí)別在FEC包中的位置,可計(jì)算得到S中的媒體數(shù)據(jù)包受本FEC包保護(hù)的負(fù)載數(shù)據(jù)段的起始位置,綜合所有媒體數(shù)據(jù)包的負(fù)載數(shù)據(jù)段執(zhí)行XOR運(yùn)算,得到媒體比特流Mn。4)計(jì)算恢復(fù)比特流Rn為媒體比特流Fn和FEC比特流Mn的XOR運(yùn)算結(jié)果。5)根據(jù)當(dāng)前保護(hù)級(jí)別n和保護(hù)長(zhǎng)度Ln,把恢復(fù)比特流Rn填充到恢復(fù)媒體數(shù)據(jù)包的相應(yīng)位置。至此,丟失數(shù)據(jù)包在當(dāng)前n保護(hù)級(jí)別的負(fù)載數(shù)據(jù)得到恢復(fù);針對(duì)每個(gè)保護(hù)級(jí)別都執(zhí)行上述操作,最終即可恢復(fù)整個(gè)丟失數(shù)據(jù)包。

更多ULPFEC報(bào)文構(gòu)造和恢復(fù)的例子,可參考RFC5109[2]的第10節(jié)。

3. ULPFEC在WebRTC中實(shí)現(xiàn)

本節(jié)在深度分析WebRTC 60 Codebase中ULPFEC的相關(guān)代碼,總結(jié)出其實(shí)現(xiàn)算法和細(xì)節(jié)。下面以Video為例,從FEC報(bào)文構(gòu)建、FEC掩碼構(gòu)造和丟失數(shù)據(jù)包恢復(fù)三個(gè)方面分析ULPFEC在WebRTC中的實(shí)現(xiàn)。

3.1 ULPFEC報(bào)文構(gòu)建

WebRTC中ULPFEC報(bào)文構(gòu)建的流程如圖8所示:

圖8 FEC報(bào)文構(gòu)建和發(fā)送流程.png

FEC報(bào)文構(gòu)建開始于編碼線程編碼完一幀數(shù)據(jù)的后處理,控制流程經(jīng)PayloadRouter到達(dá)RtpSenderVideo::SendVideo()函數(shù)。如果當(dāng)前會(huì)話配置了red和ulpfec,則調(diào)用SendVideoPacketAsRedMaybeWithUlpfec(),該函數(shù)主要做四件事:1)把本rtp數(shù)據(jù)包送入U(xiǎn)lpfecGenerator,并嘗試構(gòu)造FEC包;2)獲取1)構(gòu)造的FEC包(已經(jīng)封裝為RED包)列表;3)把本RTP數(shù)據(jù)包封裝為RED包并發(fā)送到網(wǎng)絡(luò);4)把FEC包列表發(fā)送到網(wǎng)絡(luò)。其中2)構(gòu)造RED包的過程很簡(jiǎn)單,按照RFC2198的定義填充字段即可,3)和4)發(fā)送RED包到網(wǎng)絡(luò)也很簡(jiǎn)單,在此不再過多論述。接下來(lái)重點(diǎn)分析2)的FEC包的構(gòu)造過程。

步驟2)會(huì)觸發(fā)ForwardErrorCorrection::EncodeFec()函數(shù),該函數(shù)流程偽代碼如下所示:

ForwardErrorCorrection::EncodeFec(media_packets,protection_factor, 
  num_important_packets, use_unequal_protection, fec_mask_type, fec_packets) {
  // step 1, 根據(jù)媒體數(shù)據(jù)包個(gè)數(shù)和保護(hù)因子,確定需要生成的fec數(shù)據(jù)包個(gè)數(shù)并初始化。
  int num_fec_packets = NumFecPackets(num_media_packets, protection_factor);
  for (int i = 0; i < num_fec_packets; ++i) {
    memset(generated_fec_packets_[i].data, 0, IP_PACKET_SIZE);
    fec_packets->push_back(&generated_fec_packets_[i]); 
  }

  // step2, 構(gòu)建fec掩碼表,并從中獲取構(gòu)造fec包需要的掩碼,存儲(chǔ)在packet_mask_中。
  // 這一步是最關(guān)鍵的,packet_masks_決定媒體數(shù)據(jù)包在FEC包中受保護(hù)的分布情況。
  const internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
  internal::GeneratePacketMasks(num_media_packets, num_fec_packets,
     num_important_packets, use_unequal_protection,  mask_table, packet_masks_);

  // 步驟3,以packet_masks_和packet_mask_size_,media_packets和num_fec_packets
  // 為輸入,生成FEC包集合generated_fec_packets_,包括半成品的頭部和成型的負(fù)載。
  GenerateFecPayloads(media_packets, num_fec_packets);

  // 步驟4,填充并修正生成生成FEC數(shù)據(jù)包的頭部,這很簡(jiǎn)單,按照RFC填充即可。 
  FinalizeFecHeaders(num_fec_packets, media_ssrc, seq_num_base);
}

FEC構(gòu)造過程首先會(huì)根據(jù)輸入媒體數(shù)據(jù)包的個(gè)數(shù)m和保護(hù)因子factor,確定需要生成的FEC包的個(gè)數(shù)num_fec = m * factor / 256,然后初始化這num_fec個(gè)FEC包。接下來(lái)是關(guān)鍵的一步,創(chuàng)建掩碼表并獲取num_fec個(gè)FEC包所需要的掩碼數(shù)據(jù),存儲(chǔ)在packet_masks_。掩碼表是預(yù)先定義好的一張三維表格,用以模擬不同情況下媒體數(shù)據(jù)包在FEC包中的保護(hù)分布情況,3.2節(jié)會(huì)針對(duì)該問題進(jìn)一步分析。

接下來(lái),函數(shù)以掩碼信息packet_masks_,媒體數(shù)據(jù)包media_packets和fec包個(gè)數(shù)num_fec為輸入調(diào)用GenerateFecPayloads()函數(shù)生成FEC數(shù)據(jù)包,此時(shí)FEC包中的負(fù)載部分已經(jīng)成型,但是FEC頭部需要進(jìn)一步修正,這需要最后一步調(diào)用FinalizeFecHeaders()解決。

至此,F(xiàn)EC包的構(gòu)造過程分析完畢。需要注意的是,WebRTC僅僅使用ULPFEC的Level 0對(duì)媒體數(shù)據(jù)包進(jìn)行保護(hù),也即FEC包中只有一個(gè)FEC Level。另外,在WebRTC內(nèi)部,保護(hù)RTP頭部的FEC頭部稱之為L(zhǎng)evel 0,而保護(hù)RTP負(fù)載的FEC Level部分稱之為L(zhǎng)evel 1。這里和FEC5109文檔中所定義的稍有不同,需要注意一下。

3.2 ULPFEC掩碼表和掩碼

根據(jù)RFC5109中相關(guān)定義可知,一個(gè)媒體數(shù)據(jù)包可以由多個(gè)FEC包保護(hù),而一個(gè)FEC包也可以多保護(hù)多個(gè)媒體數(shù)據(jù)包。假設(shè)m個(gè)媒體數(shù)據(jù)包需要n個(gè)FEC數(shù)據(jù)包保護(hù),則可以定義一個(gè)二維的m * n零一矩陣來(lái)描述媒體數(shù)據(jù)包在fec包中的保護(hù)分布情況:矩陣中元素m[i, j]置1表示第i個(gè)媒體數(shù)據(jù)包需要第j個(gè)FEC包保護(hù)。從行角度來(lái)看,第i行元素表示第i個(gè)FEC包保護(hù)的媒體數(shù)據(jù)包的集合;從列角度講,第j列元素表示保護(hù)第j個(gè)媒體數(shù)據(jù)包的FEC包的集合。由于該矩陣是零一矩陣,因此在存儲(chǔ)上可以采用掩碼來(lái)存儲(chǔ)。這個(gè)掩碼也就是FEC Level Header中所定義的掩碼。

那么掩碼中的1該如何分布。我們知道,現(xiàn)實(shí)世界中網(wǎng)絡(luò)丟包分為隨機(jī)丟包和突發(fā)丟包兩種情況,F(xiàn)EC包需要能夠針對(duì)這兩種情況對(duì)媒體數(shù)據(jù)包進(jìn)行保護(hù)。WebRTC預(yù)先構(gòu)造兩個(gè)掩碼表kPacketMaskRandomTbl和kPacketMaskBurstyTbl,以模擬在隨機(jī)情況和突發(fā)情況下媒體數(shù)據(jù)包在FEC包中的保護(hù)分配情況。假設(shè)在隨機(jī)丟包場(chǎng)景下,對(duì)于m * n的情況,我們只需要從kPacketMaskRandomTbl[m][n]就可以獲取FEC包所需要的全部掩碼,然后該掩碼為基礎(chǔ),構(gòu)造FEC數(shù)據(jù)包。

根據(jù)ULP思想,F(xiàn)EC包可以對(duì)媒體數(shù)據(jù)包集合中的不同數(shù)據(jù)包實(shí)施不同的保護(hù)力度。這源于一幀視頻數(shù)據(jù)編碼后生成的一系列RTP數(shù)據(jù)包中,其重要性是不一樣的,比如開始幾個(gè)RTP包的重要性更大一些。因此,WebRTC在構(gòu)造FEC包的掩碼時(shí),有均勻保護(hù)和非均勻保護(hù)兩種策略。

對(duì)于均勻保護(hù),所有RTP包的重要性一樣,F(xiàn)EC包對(duì)他們進(jìn)行平等的均勻的保護(hù)。對(duì)于m * n,F(xiàn)EC包使用的掩碼即為kPacketMaskRandomTbl[m][n]。對(duì)于非均勻保護(hù),RTP包集合被非為重要數(shù)據(jù)包集合S1和普通數(shù)據(jù)包集合S2,分配較多個(gè)FEC包來(lái)保護(hù)S1,較少個(gè)FEC包保護(hù)S2。WebRTC定義了三種模式針對(duì)實(shí)現(xiàn)非均勻保護(hù):

1)kModeNoOverlay:非疊加保護(hù),保護(hù)S1的掩碼和S2的掩碼相互分離。
2)kModeOverlay:疊加保護(hù),保護(hù)S1的掩碼和S2的掩碼疊加在一起。
3)kModeBiasFirstPacket:在均勻保護(hù)的基礎(chǔ)上,所有FEC包都保護(hù)第一個(gè)包。

假設(shè)保護(hù)場(chǎng)景為(m, n),其中重要數(shù)據(jù)包為前k個(gè),分配給重要數(shù)據(jù)包的FEC包個(gè)數(shù)為t,掩碼表為mask_table。則三種場(chǎng)景下最終掩碼的確定如下:

1)kModeNoOverlay:mask_table[k][t]和mask_table[m-k][n-t]的移位組合。
2)kModeOverlay: mask_table[k][t]和mask_table[m][n-t]的拼接。
3)kModeBiasFirstPacket:mask_table[m][n],再第一列全部置1。

至此,關(guān)于ULPFEC的掩碼表和掩碼分析完畢。

3.3 ULPFEC報(bào)文接收和數(shù)據(jù)包恢復(fù)

ULPFEC報(bào)文接收和丟失數(shù)據(jù)包恢復(fù)是ULPFEC報(bào)文構(gòu)造和發(fā)送的逆過程,該過程分為三個(gè)子過程:1)把接收到的RED包進(jìn)行解包得到RTP包或FEC包。2)把RTP/FEC包插入到FEC處理模塊的合適列表中。3)發(fā)起數(shù)據(jù)包恢復(fù)嘗試。圖9描述這個(gè)過程。

圖 9 ULPFEC接收數(shù)據(jù)包和丟失數(shù)據(jù)包恢復(fù).png

RTP數(shù)據(jù)包首先到達(dá)RtpStreamReceiver::ReceivePacket(),判斷出該RTP包為RED包后,調(diào)用ParseAndHandleEncapsulatingHeader()。該函數(shù)主要做了兩件事:RED解包和處理包。前者很簡(jiǎn)單,就是按照RFC2198去掉RED頭部,得到純RTP報(bào)文或者FEC報(bào)文。后者深入到UlpfecReceiver中進(jìn)一步處理RTP/FEC報(bào)文。

在ProcessReceivedFec()函數(shù)中,若接收到的是RTP包,則首先調(diào)用callback的OnRecoveredPacket()把RTP包發(fā)送到VCM模塊,以不耽誤解碼過程。接下來(lái)調(diào)用DecodeFec()執(zhí)行FEC恢復(fù)數(shù)據(jù)包過程。最后,把新恢復(fù)的數(shù)據(jù)包送到VCM模塊進(jìn)行解碼。

DecodeFec()函數(shù)做兩件事情:把數(shù)據(jù)包插入到合適列表,并發(fā)起丟失包恢復(fù)過程。FEC包插入到Fec列表,RTP包插入到Media列表。對(duì)于FEC包,還要根據(jù)其掩碼遍歷Media列表,把所有本FEC包保護(hù)的RTP包掛在自己名下的列表中。對(duì)于RTP包,還要根據(jù)自己的序列號(hào)遍歷Fec列表,把本RTP包掛在所有保護(hù)自己的FEC包名下的列表中。

接下來(lái)是嘗試恢復(fù)丟失數(shù)據(jù)包過程AttemptRecovery:對(duì)于Fec列表中的每一個(gè)FEC包,判斷其目前的丟包數(shù):如果未丟包則無(wú)需恢復(fù)操作,刪除本FEC包繼續(xù)下一個(gè);如果丟包數(shù)大于1,則條件還不成熟,跳過本FEC包繼續(xù)下一個(gè);如果丟包數(shù)等于1,則調(diào)用RecoveryPacket()恢復(fù)一個(gè)RTP數(shù)據(jù)包。然后把該恢復(fù)包插入到Media列表中,并根據(jù)該RTP包的序列號(hào)遍歷Fec列表,把該RTP包掛在所有保護(hù)自己的FEC包名下的列表中。

最后是真正執(zhí)行恢復(fù)數(shù)據(jù)包過程的RecoveryPacket()。流程走到這里,所有條件都已經(jīng)成熟,按照RFC5109的定義按部就班恢復(fù)數(shù)據(jù)包即可:1)準(zhǔn)備階段:把本FEC包中的頭部數(shù)據(jù)和payload數(shù)據(jù)拷貝到recover_packet中作為基準(zhǔn)數(shù)據(jù)。2)恢復(fù)階段:針對(duì)本FEC包保護(hù)的每個(gè)已收到RTP包,recover_packet包與之執(zhí)行XOR操作,結(jié)果仍然存儲(chǔ)在recover_packet中。全部RTP包執(zhí)行完XOR操作以后,recover_packet中的負(fù)載部分即為待恢復(fù)RTP包的負(fù)載數(shù)據(jù)。3)收尾階段:修正recover_packet的RTP頭部,得到最終恢復(fù)的RTP包。

至此,一個(gè)完整的接收端恢復(fù)丟失數(shù)據(jù)包的過程分析完畢。

4. 總結(jié)

本文首先從理論上學(xué)習(xí)RFC5109關(guān)于ULPFEC的定義,然后深入分析WebRTC源碼,學(xué)習(xí)其ULPFEC的實(shí)現(xiàn)細(xì)節(jié)。通過本次學(xué)習(xí),對(duì)ULPFEC有深入徹底的理解,為后續(xù)學(xué)習(xí)音視頻技術(shù)和WebRTC其他模塊奠定基礎(chǔ)。

參考文獻(xiàn)

[1] WebRTC中丟包重傳NACK實(shí)現(xiàn)分析
?????? http://www.lxweimin.com/p/a7f6ec0c9273
[2] RTP Payload Format for Generic Forward Error Correction
?????? https://tools.ietf.org/html/rfc5109
[3] ULPFEC (Uneven Level Protection Forward Error Correction)
?????? https://webrtcglossary.com/ulpfec/
[4] RTP: A Transport Protocol for Real-Time Applications
?????? https://tools.ietf.org/html/rfc3550
[5] RTP Payload for Redundant Audio Data
?????? https://tools.ietf.org/html/rfc2198

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容