農林漁牧網

您現在的位置是:首頁 > 農業

webRTC中音訊相關的netEQ(三):存取包和延時計算

2022-03-09由 vymeet雲會議 發表于 農業

包數是什麼意思

上篇(webRTC中音訊相關的netEQ(二):資料結構)講了netEQ裡主要的資料結構,為理解netEQ的機制打好了基礎。

本篇主要講MCU中從網路上收到的RTP包是怎麼放進packet buffer和從packet buffer裡取出來,以及網路延時值(optBufLevel)和抖動緩衝延時值(buffLevelFilt)的計算。先看RTP語音包是怎麼放進packet buffer的。

前面說過把從網路收到的RTP包放進packet buffer時有個slot概念,每個slot裡放一個包的屬性(比如timestamp、sequence number等)和payload。

Packet buffer初始化時把numPacketsInBuffer(已有的包個數)和insertPosition(下一個包放的位置)置成0,也把屬性和payload置成合理的值(比如把payloadLengthBytes(payload長度)置成0,把payloadType(負載型別)置成-1)。

當一個RTP包要放進packet buffer時,先要看packetbuffer是否為空(即numPacketsInBuffer是否為零)。如為空,直接把包放在slot 0的位置(把包的屬性以及payload放到slot 0的位置上)。如不為空,insertPosition加1,先看這個slot上是否有包(標誌是payloadLengthBytes是否為零,為零表示沒包。

當這個slot上的包被取走時payloadLengthBytes會被置為零)。如有包說明packet buffer 滿了,需要reset(程式碼中叫flush)packet buffer,然後把當前包放在slot 0的位置(Packet buffer中能放的包個數是一個很大的值,通常不會放滿)。

如未滿,則直接放在下一個slot上。下面舉例說明。假設packet buffer最多可以放240個包,則slot範圍是0—239,如下圖。從網路上收到的第一個包會放在slot 0 的位置,第二個包放在slot1的位置,以此類推,第240個包會放在slot239的位置。

當一個包從packet buffer取出時,相應slot就又被初始化了。當第241個包來時就又放到slot0 的位置。當包放進相應slot時要check這個slot裡是否有包(標準是payloadLengthBytes是否為零),有包則說明packet buffer已滿需要reset/flush,然後這個包放在slot0的位置。

webRTC中音訊相關的netEQ(三):存取包和延時計算

接下來看怎麼從pakcet buffer裡取一個語音包,這要依賴於從DSP模組帶來的timestamp值(記為timestamp_from_dsp)。

遍歷packet buffer裡每個slot,如果這個slot上語音包的timestamp小於timestamp_from_dsp,並且slot上有payload,就可以認為這個包來的太遲應該主動discard掉,包括reset這個slot和packet buffer裡包個數減一等。

遍歷完packet buffer後把離timestamp_from_dsp最近的語音包的timestamp(即語音包的timestamp減去timestamp_from_dsp的值最小)對應的slot作為將要取出來的slot,把語音包從這個slot取出來後同樣要reset這個slot以及packet buffer裡包個數減一等。

從上面的描述可以看出這種放包的方式是比較簡單的,是按照語音包到netEQ的先後順序依次放在buffer裡,而且可能是亂序的(收到包時就有可能是亂序的)。

這就要求在取包時要遍歷buffer把離DSP模組帶過來的timestamp最近的timestamp的包取出來,遍歷要用for迴圈,這就增加了計算量。這跟我以前做的jitter buffer的設計是有很大區別的。

那種思想是把亂序的包排好序後再放在buffer裡,取包時就不需要遍歷buffer,而是從頭上依次向後取。具體怎麼實現的可以看我前面的文章(音訊傳輸之Jitter Buffer設計與實現)。

下面看怎麼計算網路延時統計值(optBufLevel),這是難點之一。假設每包20Ms,理想情況下每隔20Ms從網咯上收到一個語音包。實際情況是網路有延時丟包抖動,導致並不是每隔20Ms收到一個包,而是有時幾十甚至100多毫秒收不到一個包,有時20Ms內收到幾個包。

我們要算出網路延時的統計值,作為產生向DSP發出控制命令的依據之一。怎麼算呢?netEQ用包到的時間間隔來算,它的意思是當前收到的包相對上一個收到的包的時間間隔,以包個數為單位。

當每收到一個包時就把packetIatCountSamp(已取樣點數為單位)清零,以後每取一幀資料播放就把packetIatCountSamp加上一幀的取樣點數(以AMR-WB每幀20Ms為例,每幀有320個取樣點。每取一幀,packetIatCountSamp就增加320),當下一個包到時,拿packetIatCountSamp除以320就可以 算出兩個包之間的間隔了。

下面給出計算網路延時的演算法:

1, 計算當前包絕對到達間隔iat(以資料據包個數為單位),計算公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

根據公式,提前到達的資料包的iat均為0,正常到達的iat為1,延遲一個包時間到達的Iat為2。Iat的最大值為64,即有65種(0—64)種可能。

2, 更新iat在每個值(0—64)上的機率分佈。初始化時每個值(0—64)上的機率均為0,隨著包的到來,每個值上的機率都在動態的改變著。機率更新分以下幾小步:

1) 用遺忘因子f對當前機率進行遺忘,計算公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

這裡有個遺忘因子(forgetting factor)的概念。每個值上的機率均要算一下,得到新的機率。

2) 增大本次計算到的iat的機率,計算公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

3) 更新遺忘因子f,使f為遞增趨勢,即通話時間越長,包間隔iat的機率分佈越穩定。計算公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

4) 調整本次計算到的iat的機率,使整個iat的機率分佈之和近似為1。假設當前機率分佈之和為tempSum,則計算公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

3, 統計滿足95%機率的iat值,記為B。根據下式可以算出B的值。

webRTC中音訊相關的netEQ(三):存取包和延時計算

4, 統計iat的峰值

netEQ中採用兩個長度為8的陣列來統計iat的峰值,一個用來存峰值幅度,另一個用來存峰值間隔。峰值間隔是結構體automode中另一個引數peakIatCountSamp,用於統計當前探測到的峰值距離上次探測到的峰值的間隔,以樣本個數為單位。

當iat的值大於2B時就認為峰值出現了,把當前的iat和peakIatCountSamp值存在數組裡。如果陣列未滿,就放在上一個峰值位置後的空的位置上;如果滿了,就淘汰掉數組裡最早的那個峰值,其他峰值左移,並把新的峰值放在數組裡index為7的位置上。這裡需要說明的是當兩個陣列的值不足8個時,峰值陣列是不起作用的。

5, 計算optBufLevel

當峰值陣列起作用並且當前peakIatCountSamp小於等於峰值間隔陣列中最大的間隔兩倍時,optBufLevel 取峰值陣列中的最大值。否則optBufLevel 就為B。

以上是我照本宣科的把怎麼算網路延時的演算法表述出來。我基本理解了演算法的思想,但是不清楚演算法中的一些係數是怎麼得到的。

用google搜了一下,沒找到相關的文件說明,這也是好多開源軟體的通病,沒有文件。我猜測是相關開發人員用數學建模的方法得到的係數值吧。如果有哪位朋友知道,麻煩給講講,先謝謝了。講講我對這個演算法的理解吧。

算網路延時是基於機率來算的,共有65個樣本(0延時,一個包延時,2個包延時,……。,64個延時)。初始化時各個樣本的機率(佔的百分比)均為0。通話後某個延時值出現了它的機率值就要變大,相應的其他延時值的機率就要變小(已經為零的沒辦法再變小,依舊為零)。

演算法裡先用遺忘因子去減小各個延時值的機率,然後再去變大本次延時值的機率,為了保證機率和為1要做一些微調(也會去更新遺忘因子)。然後從零延時開始把各個延時值的機率加起來,達到95%的值就可初步認為是延時值了。

比如0延時機率為0。1, 1個包延時機率為0。7, 2個包延時機率為0。09,3個包延時機率為0。07,這時機率和為0。96,已達到0。95的線,取網路延時最大的值3,就可初步認為網路延時為3個包的延時。

還要看當前網路狀況,如果一段時間內頻繁出現延時的峰值,說明當前網路環境比較糟糕,為了提高語音質量需要加大網路延時的值,就把峰值數組裡的最大值,作為最終的網路延時值。

算網路延時是在語音包放進packet buffer後。算抖動緩衝延時是在收到DSP模組給MCU模組發反饋資訊後(要用到反饋資訊)以及從packet buffer取語音包前。下面給出計算步驟:

1, 根據packet buffer裡已有的語音包的個數算出已有的樣本數,記為samples_in_packetbuffer,這依賴於取樣率和包時長,以AMR-WB為例,取樣率為16kHZ,包時長為20ms,可算出每包有320個樣本。假設packet buffer裡有5個語音包,則packet buffer裡已有的樣本數為1600 (1600 = 320*5)。

2, 在speech buffer裡未播放的(即sampleLeft)樣本也要算在抖動緩衝延時內。它與packet buffer內的樣本數相加就是實時的抖動緩衝延時(samples_jitter_delay,以樣本個數為單位),即samples_jitter_delay = samples_in_packet_buffer + samplesLeft,再除以每包樣本數samples_per_packet,就可以得到實時抖動緩衝延時值(以包個數為單位)。

3, 計算bufferLevelFilt,公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

這裡計算的是抖動緩衝延時的自適應平均值,f是計算均值的遺忘因子,根據網路狀況自適應的變化,具體取值見下式:

webRTC中音訊相關的netEQ(三):存取包和延時計算

其中B為前面算網路延時時的B值(以包個數為單位)。

4, 如果經過加速或者減速播放,則需要去修正bufferLevelFilt,公式如下:

webRTC中音訊相關的netEQ(三):存取包和延時計算

其中samplesMemory表示加速或減速播放後資料長度的伸縮變化,已樣本個數為單位。若為加速,sampleMemory為正值,bufferLevelFilt減小;若為減速,sampleMemory為負值,bufferLevelFilt變大。

上面講了MCU中網路延時和抖動緩衝延時的計算,MCU也收到了DSP模組發過來的反饋報告。後面MCU就要根據這些來決定給DSP模組發什麼樣的控制命令(加速/減速等),這是下一篇的主要內容。