上篇(webRTC中音頻相關的netEQ(一):概述)是netEQ的概述,知道了它主要是用於解決網絡延時抖動丟包等問題提升語音質量的,也知道了它有兩大單元MCU和DSP組成。MCU 主要是把從網絡收到的語音RTP包放進packet buffer內,同時也會根據計算出來的網絡延時和抖動緩衝延時以及DSP單元反饋過來的信息決定給DSP發什麼控制命令(命令主要有正常播放、加速、減速、丟包補償、融合等),也會把語音包從packet buffer裏取出來給DSP單元處理。DSP主要是對取出來的語音包解碼並根據MCU給出的控制命令作信號處理。本篇咱們繼續講netEQ,講主要的數據結構。html
要研究一個模塊,首先得搞清楚它的數據結構。netEQ中最頂層的結構體是MainInst_t(也就是netEQ結構體),它主要包含兩個成員變量,一個是DSPInst_t,另外一個是MCUInst_t,正好對應netEQ的兩個單元DSP和MCU。具體見圖1(這裏把次要的成員變量忽略掉了,下同)。在netEQ初始化時生成netEQ的實例,實例中包含DSP和MCU兩個子實例。web
圖 1算法
先看DSP結構體,見圖1。pw16_readAddress 和pw16_writeAddress用於與MCU交互數據。MCU中也有這兩個成員變量,先說一下MCU和DSP是怎麼交互的。MCU會給DSP發控制命令執行何種信號處理算法,就把命令相關的數據寫在本身的pw16_writeAddress的地址上,讓 DSP到這個地址上取數據,即DSP的pw16_readAddress就是MCU的pw16_writeAddress。DSP處理完一幀後會給MCU發反饋數據,反饋數據就寫在本身的pw16_writeAddress地址上,MCU就從這個地址上去讀反饋數據, 即MCU的pw16_readAddress就是DSP的pw16_writeAddress。main_inst指向父結構體netEQ(MainInst_t),這是一種常見的操做手法,便於找到父結構體實例。speechBuffer(語音buffer)用於存放通過解碼和信號處理過的語音數據。它分兩塊,一塊是已經播放過的語音數據,另外一塊是未播放過將要播放的語音數據,成員變量curPosition是分界點。另外一成員變量endPosition表示語音buffer的大小,這依據採樣率來定。能夠用圖2表示這三個成員變量的關係:數組
圖 2網絡
endTimestamp用於記錄語音buffer中未播放的語音數據的最後的時間戳(MCU給DSP的控制命令中會帶當前幀的時間戳,解碼後經過換算就能夠獲得endTimestamp)。fs是採樣率。w16_frameLen是每幀的採樣點數。w16_mode是當前幀的處理方法(是加速處理仍是減速處理等),這個值會帶給MCU,MCU根據這和網絡延時抖動緩衝延時等決定下一幀的處理命令。pw16_speechHistory和w16_speechHistoryLen用於丟包補償(PLC,控制命令是EXPAND),pw16_speechHistory放最近播放過的歷史語音數據,要作PLC時就以這些歷史語音數據做爲參考數據產生補償的語音數據。w16_speechHistoryLen是放歷史語音數據的buffer(即pw16_speechHistory)的長度,是個定值,依採樣率而定。DSP結構體中還有幾個子實例,主要有decoder實例(CodecFuncInst_t)、丟包補償實例(ExpandInst_t)和背景噪聲生成實例(BGNInst_t)。數據結構
上面說到MCU和DSP的數據交互,咱們看一下交互的是什麼數據。MCU發給DSP的是控制命令,控制命令數據佔3個short大小,第一個short是命令相關的,第二三個是timestamp的高16位和低16位。DSP發給MCU的是反饋數據,反饋數據的結構體如圖3:post
圖 3url
playedOutTS表示語音buffer中最後數據的時間戳,等於DSP結構體中的endTimestamp。samplesLeft表示語音buffer總未播放的數據長度。lastMode表示上一幀的處理方法,等於DSP結構體中的w16_mode。frameLen表示上一幀解碼後成長度。3d
再看MCU結構體,見圖1。first_packet在初始化時置成1,後來收到包後置成0。用它主要是對收到第一個包後給MCU的一些成員變量(好比SSRC)賦值。pw16_readAddress 和pw16_writeAddress用於與DSP交互數據,同DSP中的同樣。main_inst也同DSP中的同樣。MCU中也有兩個主要的實例,一個是PacketBuffer_inst,它用於存放從網絡收到的語音包。另外一個是BufferStat_inst,它用於統計網絡延時等。這兩個均是很是重要的結構體。先說PacketBuffer_inst,它的定義如圖4:code
圖 4
初始化時會分配一塊能夠存放最大個數(最大個數事先定義好了)語音包的buffer。存放的內容有timeStamp/payloadLocation(包的payload放的地址,指向payload)/seqNumber/payloadType/payloadLengthBytes/rcuPlCntr/waitingTime/payload等(見上圖的紅框內部分)。存放時並非每一個包的timestamp/payload等放在一塊兒,而是全部包的timestamp放在一塊兒,全部包的sequence number放在一塊兒,其餘也是,這樣就獲得了以下的buffer分佈圖5:
圖 5
圖5看起來不直觀。netEQ中有slot的概念,每一個包的timestamp/payload等放在同一個slot內,這樣圖5就能夠表示成圖6(圖中每塊 buffer都是連續的,上一塊buffer的尾部就是下一塊buffer的首部,好比timestamp的尾部就是payload location的首部),這樣看起來就直觀多了。要獲取某個包的屬性或者payload,就能夠經過slot_index獲得。好比要獲取第0個包的timestamp,就能夠表示成timestamp[0]。怎樣存放包搞清楚了就能夠很好的理解這個結構體內的其餘成員變量了。packSizeSamples表示上一解碼的包有多少個採樣點。startPayloadMemory表示放payload的起始地址。memorySizeW16表示分配的buffer還剩下的memory size。currentMemoryPos表示放下一個包的payload的起始地址,這個值會放在下一個包的對應的payloadLocation裏。等下一個包來後,currentMemoryPos會加上這個包的payload長度從新賦給currentMemoryPos,並做爲放下下個包的payload的起始地址。numPacketsInBuffer表示packet buffer裏放了多少個包。insertPosition表示下一個包要放的位置。maxInsertPositions表示packet buffer最大可放的包個數。discardedPackets表示主動丟棄的包個數。
圖 6
再說BufferStat_inst,它的定義如圖7:
圖 7
w16_noExpand表示上一包的處理是否是EXPAND。avgDelayMsQ8表示平均緩衝延時。maxDelayMs表示最大緩衝延時。AutomodeInst_t是BufferStat_inst的子實例,,主要用於計算網絡延時和抖動緩衝延時。它的定義如圖8:
圖 8
成員變量主要分三部分,一是iat(inter-arrival time,相鄰包到的時間間隔)統計相關的, iat以包個數爲單位,假設一個包20Ms,兩個相鄰包到的時間間隔是40Ms,iat就爲2。有一個大小爲65的數組來存放iat(iat從0到64)個數統計的值,基於這些值算網絡延遲統計值。二是iat峯值統計相關的,用兩個長度爲8的數組來存放iat的峯值,一個用來存放峯值幅度,另外一個用來存放峯值間隔。峯值間隔是automode結構體中另外一個參數peakIatCountSamp,它用於統計當前探測到的峯值距離上次峯值的時間間隔,以採樣點個數爲單位。三是包相關的,有lastSeqNo(上一個收到包的sequence number)、lastTimeStamp(上一個收到包的timestamp)等。這些都是爲了計算optBufLevel(網絡延時)和buffLevelFilt(抖動緩衝延時)。MCU發給DSP的控制命令就是根據網絡延時和抖動緩衝延時以及上一次的處理方式等獲得的。須要注意的是這些變量中一些是以Q格式(Q格式相關的能夠見我前面的文章Android手機上Audio DSP頻率低 memory小的應對措施)表示的,算網絡延時和抖動緩衝延時都是用Q格式算的,這加大了理解的難度。