2020-10-23 netEQ

webRTC中音頻相關(guān)的netEQ(一):概述

原文鏈接:http://blog.csdn.net/u014338577/article/details/46010983

NetEQ算法中集成了自適應(yīng)抖動控制算法以及語音包丟失隱藏算法殴蹄。這項技術(shù)使其能夠快速且高解析度地適應(yīng)不斷變化的網(wǎng)絡(luò)環(huán)境菩收,確保音質(zhì)優(yōu)美且緩沖延遲最小。

研究的重點是 NetEQ 模塊师枣,其中所涉及的處理過程包括抖動消除斟叼、丟包補償和壓縮解碼丧凤。

抖動消除原理

抖動通常采用抖動緩沖技術(shù)來消除利虫,即在接收方建立一個緩沖區(qū),語音包到達接收端時首先進人緩沖區(qū)暫存献宫,隨后系統(tǒng)再以穩(wěn)定平滑的速率將語音包從緩沖

區(qū)提取出來钥平,經(jīng)解壓后從聲卡播出。

4 個語音數(shù)據(jù)包(A遵蚜、B帖池、C、D)以 30ms 為間隔進行發(fā)送吭净,即發(fā)送時間分別為 30睡汹,60,90寂殉,120ms囚巴,網(wǎng)絡(luò)延遲分別為10,30,10,10ms友扰。到達時間為40,90,100,130ms彤叉,所以需要在抖動緩沖中分別緩沖60,90,120,150ms。

1.靜態(tài)抖動緩沖控制算法

2.自適應(yīng)抖動緩沖控制算法

丟包隱藏原理

iLBC 的丟包隱藏只是在解碼端進行處理村怪,即在解碼端根據(jù)收到的比特流逐幀進行解碼的過程中秽浇,iLBC解碼器首先拿到每幀的比特流時判斷當(dāng)前幀是否完整,如果沒有問題則按照正常的 iLBC解碼流程重建語音信號甚负,如果發(fā)現(xiàn)語音數(shù)據(jù)包丟失柬焕,那么就進入 PLC 單元進行處理审残。見算法步驟13

MCU(Micro Control Unit)模塊是抖動緩沖區(qū)的微控制單元,由于抖動緩沖區(qū)作用是暫存接收到的數(shù)據(jù)包斑举,因此 MCU 的主要作用是安排數(shù)據(jù)包的插入并控制數(shù)據(jù)包的輸出搅轿。數(shù)據(jù)包的插入主要是確定來自網(wǎng)絡(luò)的新到達的數(shù)據(jù)包在緩沖區(qū)中的插入位置,而控制數(shù)據(jù)包的輸出則要考慮什么時候需要輸出數(shù)據(jù)富玷,以及輸出哪一個插槽的數(shù)據(jù)包璧坟。抖動消除的算法思路在 MCU 控制模塊中得以體現(xiàn)。

DSP 模塊主要負(fù)責(zé)對從 MCU 中提取出來的 PCM 源數(shù)據(jù)包進行數(shù)字信號處理赎懦,

包括解碼雀鹃、信號處理、數(shù)據(jù)輸出等幾個部分铲敛。丟包隱藏操作即在 DSP 模塊中完成褐澎。

NetEQ 模塊框圖

網(wǎng)絡(luò)數(shù)據(jù)包進入抖動緩沖區(qū)的過程,其基本步驟如下伐蒋,

AudioCodingModuleImpl::IncomingPacket(const uint8_t*incoming_payload,const int32_t payload_length,const WebRtcRTPHeader& rtp_info)

{

...

memcpy(payload,incoming_payload, payload_length);

codecs_[current_receive_codec_idx_]->SplitStereoPacket(payload, &length);

rtp_header.type.Audio.channel = 2;

per_neteq_payload_length = length / 2;

// Insert packet into NetEQ.

if (neteq_.RecIn(payload, length, rtp_header,

last_receive_timestamp_) < 0)

...

}

ACMNetEQ::RecIn(const uint8_t*incoming_payload,const int32_t length_payload,const WebRtcRTPHeader& rtp_info,uint32_t receive_timestamp)

{

...

WebRtcNetEQ_RecInRTPStruct(inst_[0], &neteq_rtpinfo,incoming_payload, payload_length,receive_timestamp);

}

int WebRtcNetEQ_RecInRTPStruct(void *inst, WebRtcNetEQ_RTPInfo *rtpInfo,

const uint8_t *payloadPtr, int16_t payloadLenBytes,

uint32_t uw32_timeRec)

{

...

?RTPPacket_t RTPpacket;

...

RTPpacket.payloadType = rtpInfo->payloadType;

RTPpacket.seqNumber = rtpInfo->sequenceNumber;

RTPpacket.timeStamp = rtpInfo->timeStamp;

RTPpacket.ssrc = rtpInfo->SSRC;

RTPpacket.payload = (const int16_t*)payloadPtr;

RTPpacket.payloadLen = payloadLenBytes;

RTPpacket.starts_byte1 = 0;

WebRtcNetEQ_RecInInternal(&NetEqMainInst->MCUinst, &RTPpacket, uw32_timeRec);

}

int WebRtcNetEQ_RecInInternal(MCUInst_t *MCU_inst, RTPPacket_t *RTPpacketInput,uint32_t uw32_timeRec)

{

...

WebRtcNetEQ_PacketBufferInsert(&MCU_inst->PacketBuffer_inst,&RTPpacket[i_k], &flushed, MCU_inst->av_sync);

...

WebRtcNetEQ_SplitAndInsertPayload(&RTPpacket[i_k],&MCU_inst->PacketBuffer_inst, &MCU_inst->PayloadSplit_inst,&flushed, MCU_inst->av_sync);

...

}

intWebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *RTPpacket,

int16_t *flushed, int av_sync)

{//This function inserts an RTP packet into the packet buffer.

...

bufferInst->payloadLocation[bufferInst->insertPosition] = bufferInst->currentMemoryPos;

bufferInst->payloadLengthBytes[bufferInst->insertPosition] =RTPpacket->payloadLen;

bufferInst->payloadType[bufferInst->insertPosition] =RTPpacket->payloadType;

bufferInst->seqNumber[bufferInst->insertPosition] =RTPpacket->seqNumber;

bufferInst->timeStamp[bufferInst->insertPosition] =RTPpacket->timeStamp;

bufferInst->rcuPlCntr[bufferInst->insertPosition] =RTPpacket->rcuPlCntr;

bufferInst->waitingTime[bufferInst->insertPosition] = 0;

...

}

intWebRtcNetEQ_SplitAndInsertPayload(RTPPacket_t* packet,PacketBuf_t* Buffer_inst,SplitInfo_t* split_inst,int16_t* flushed,int av_sync)

{

}

1:解析數(shù)據(jù)包并將其插入到抖動緩沖區(qū)中( Parse the payload and insert it into the buffer)。WebRtcNetEQ_SplitAndInsertPayload

WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, &localFlushed);循環(huán)將輸入數(shù)據(jù)packets split到PacketBuf_t 的實例中

?while (len >= split_inst->deltaBytes)

{

....

?i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, &localFlushed);

...

}

??if (RTPpacket->starts_byte1 == 0)

??{


??WEBRTC_SPL_MEMCPY_W16(bufferInst->currentMemoryPos,

????RTPpacket->payload, (RTPpacket->payloadLen + 1) >> 1);

??}

??else

??{


???for (i = 0; i < RTPpacket->payloadLen; i++)

???{


????WEBRTC_SPL_SET_BYTE(bufferInst->currentMemoryPos,

?????(WEBRTC_SPL_GET_BYTE(RTPpacket->payload, (i + 1))), i);

???}

??}

2:更新 automode 中的參數(shù)迁酸,重點計算網(wǎng)絡(luò)延遲的統(tǒng)計值 BLo(optBufferLevel)先鱼。WebRtcNetEQ_UpdateIatStatistics

2.1

timeIat = WebRtcSpl_DivW32W16(inst->packetIatCountSamp, packetLenSamp);

2.2

2.3

tempvar = (int32_t) WebRtcNetEQ_CalcOptimalBufLvl(inst, fsHz, mdCodec, timeIat,

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

提取 10ms 數(shù)據(jù)到聲卡的算法過程

3:將 DSP 的 endTimeStamp 賦值給 playedOutTS(用于提取數(shù)據(jù)的參考條件)并記錄語音緩沖區(qū)中等待播放的樣本數(shù) sampleLeft。Write status data to shared memory

4:從抖動緩沖區(qū)提取數(shù)據(jù)奸鬓。WebRtcNetEQ_DSP2MCUinterrupt

5:遍歷查找抖動緩沖區(qū)中數(shù)據(jù)包的時間戳焙畔。WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst,inst->timeStamp, &uw32_availableTS, &i_bufferpos, 1, &payloadType);

5.1

5.2

6:統(tǒng)計進入接收端的 NetEQ 模塊但仍未被播放的數(shù)據(jù)量,記為 bufsize串远。calculate total current buffer size (in ms*8), including sync buffer

w32_bufsize = WebRtcSpl_DivW32W16((w32_bufsize + dspInfo.samplesLeft), fs_mult)

7.根據(jù) bufsize 計算 BLc(bufferLevelFilt).WebRtcNetEQ_BufferLevelFilter

* Current buffer level in packet lengths

* = (curSizeMs8 * fsMult) / packetSpeechLenSamp

curSizeFrames = Sb/Lp;

Sb = Np*Lp+sampleLeft;

?if (inst->levelFiltFact > 0)

?{

inst->buffLevelFilt = ((inst->levelFiltFact * inst->buffLevelFilt) >> 8) +

(256 - inst->levelFiltFact) * curSizeFrames;

?}

8.根據(jù) BLo(optBufferLevel)宏多、BLc(bufferLevelFilt)、bufsize澡罚、playedOutTS伸但、availableTS 及 NetEQ 的上一播放模式進行 MCU 控制命令的判斷

9.根據(jù) MCU 的控制命令及當(dāng)前語音緩沖區(qū)中解碼后未被播放的數(shù)據(jù)量sampleLeft 進行判斷考慮是否需要從抖動緩沖區(qū)取數(shù)據(jù).?Check sync buffer size(Step 11)

10.提取一個數(shù)據(jù)包送入共享內(nèi)存暫存器.WebRtcNetEQ_PacketBufferExtract

11.根據(jù)從抖動緩沖區(qū)取數(shù)據(jù)之前的 MCU 的控制命令得到相應(yīng)的 DSP的處理命令。 Step 13

12.解碼取到的數(shù)據(jù)(Do decoding).inst->codec_ptr_inst.funcDecode()

13.丟包補償.inst->codec_ptr_inst.funcDecodePLC

14.根據(jù) DSP 操作命令進入相應(yīng)的的播放模式對解碼數(shù)據(jù)及語音緩沖區(qū)中數(shù)據(jù)進行相關(guān)操作留搔。Step 15

15.從語音緩沖區(qū)的 curPosition 為起始位置取 10ms 數(shù)據(jù)傳輸?shù)铰暱ǜ帧EBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1);


https://www.cnblogs.com/talkaudiodev/p/9142192.html

語音通信中終端上的時延(latency)及減小方法)說從本篇開始會切入webRTC中的netEQ主題,netEQ是webRTC中音頻技術(shù)方面的兩大核心技術(shù)之一(另一核心技術(shù)是音頻的前后處理隔显,包括AEC却妨、ANS、AGC等括眠,俗稱3A算法)彪标。webRTC是Google收購GIPS重新包裝后開源出來的,目前已是有巨大影響力的實時音視頻通信解決方案掷豺。國內(nèi)的互聯(lián)網(wǎng)公司捞烟,要做實時音視頻通信產(chǎn)品薄声,絕大多數(shù)都是基于webRTC來做的,有的是直接用webRTC的解決方案坷襟,有的是用webRTC里的核心技術(shù)奸柬,比如3A算法。不僅互聯(lián)網(wǎng)公司婴程,其他類型公司(比如通信公司)廓奕,也會把webRTC里的精華用到自己的產(chǎn)品中。我剛開始做voice engine時档叔,webRTC還未開源桌粉,但那時就知道了GIPS是一家做實時語音通信的頂級公司。webRTC開源后衙四,一開始沒機會用铃肯,后來做OTT語音(APP語音)時用了webRTC里的3A算法。在做了Android手機平臺上的音頻開發(fā)后传蹈,用了webRTC上的netEQ押逼,不過用的是較早的C語言版本,不是C++版本惦界,并且只涉及了netEQ中的DSP模塊(netEQ有兩大模塊挑格,MCU(micro control unit, 微控制單元)和DSP(digital signal processing, 信號處理單元),MCU負(fù)責(zé)控制從網(wǎng)絡(luò)收到的語音包在jitter? buffer里的插入和提取沾歪,同時控制DSP模塊用哪種算法處理解碼后的PCM數(shù)據(jù)漂彤,DSP負(fù)責(zé)解碼以及解碼后的PCM信號處理,主要PCM信號處理算法有加速灾搏、減速挫望、丟包補償、融合等)狂窑,MCU模塊在CP (communication processor, 通訊處理器)上做媳板,兩個模塊之間通過消息交互。DSP模塊經(jīng)過調(diào)試蕾域,基本上掌握了機制拷肌。MCU模塊由于在CP上做,沒有source code旨巷,我就從網(wǎng)上找來了我們用的版本相對應(yīng)的webRTC的開源版本巨缘,經(jīng)過了一段時間的理解后,也基本上搞清楚了機制采呐。從本篇開始若锁,我將花幾篇講netEQ(基于我用的早期C語言版本)。這里需要說明的是每個產(chǎn)品在使用webRTC上的代碼時都會根據(jù)自己產(chǎn)品的特點做一定的修改斧吐,我做的產(chǎn)品也不例外又固。我在講時一些細節(jié)不會涉及仲器,主要講機制。本篇先對netEQ做一個概述仰冠。


實時IP語音通信的軟件架構(gòu)框圖通常如下圖:

上圖中發(fā)送方(或叫上行乏冀、TX)將從MIC采集到的語音數(shù)據(jù)先做前處理,然后編碼得到碼流洋只,再用RTP打包通過UDP socket發(fā)送到網(wǎng)絡(luò)中給對方辆沦。接收方(或叫下行、RX)通過UDP socket收語音包识虚,解析RTP包后放入jitter buffer中肢扯,要播放時每隔一定時間從jitter buffer中取出包并解碼得到PCM數(shù)據(jù),做后處理后送給播放器播放出來担锤。


netEQ模塊在接收側(cè)蔚晨,它是把jitter buffer和decoder綜合起來并加入解碼后的PCM信號處理形成,即netEQ = jitter buffer + decoder + PCM信號處理肛循。這樣上圖中的軟件架構(gòu)框圖就變成下圖:


上文說過netEQ模塊主要包括MCU和DSP兩大單元铭腕。它的軟件框圖如下圖:

從上兩圖看出,jitter buffer(也就是packet? buffer多糠,后面就跟netEQ一致谨履,表述成packet buffer,用于去除網(wǎng)絡(luò)抖動)模塊在MCU單元內(nèi)熬丧,decoder和PCM信號處理模塊在DSP單元內(nèi)。MCU單元主要負(fù)責(zé)把從網(wǎng)絡(luò)側(cè)收到的語音包經(jīng)過RTP解析后往packet? buffer里插入(insert)怀挠,以及從packet buffer 里提任龊(extract)語音包給DSP單元做解碼、信號處理等绿淋,同時也算網(wǎng)絡(luò)延時(optBufLevel)和抖動緩沖延時(buffLevelFilt)闷畸,根據(jù)網(wǎng)絡(luò)延時和抖動緩沖延時以及其他因素(上一幀的處理方式等)決定給DSP單元發(fā)什么信號處理命令。主要的信號處理命令有5種吞滞,一是正常播放佑菩,即不需要做信號處理。二是加速播放裁赠,用于通話延時較大的情況殿漠,通過加速算法使語音信息不丟而減少語音時長,從而減少延時佩捞。三是減速播放绞幌,用于語音斷續(xù)情況,通過減速算法使語音信息不丟而增加語音時長一忱,從而減少語音斷續(xù)莲蜘。四是丟包補償谭确,用于丟包情況,通過丟包補償算法把丟掉的語音補償回來票渠。五是融合(merge)逐哈,用于前一幀丟包而當(dāng)前包正常收到的情況,由于前一包丟失用丟包補償算法補回了語音问顷,與當(dāng)前包之間需要做融合處理來平滑上一補償?shù)陌彤?dāng)前正常收到的語音包昂秃。以上幾種信號處理提高了在惡劣網(wǎng)絡(luò)環(huán)境下的語音質(zhì)量,增強了用戶體驗择诈⌒堤#可以說是在目前公開的處理語音的網(wǎng)絡(luò)丟包、延時和抖動的方案中是最佳的了羞芍。


DSP單元主要負(fù)責(zé)解碼和PCM信號處理哗戈。從packet buffer提取出來的碼流解碼成PCM數(shù)據(jù)放進decoded_buffer中,然后根據(jù)MCU給出的命令做信號處理荷科,處理結(jié)果放在algorithm_buffer中唯咬,最后將algorithm_buffer中的數(shù)據(jù)放進speech_buffer待取走播放。Speech_buffer中數(shù)據(jù)分兩塊畏浆,一塊是已播放過的數(shù)據(jù)(playedOut)胆胰,另一塊是未播放的數(shù)據(jù)(sampleLeft), curPosition就是這兩種數(shù)據(jù)的分割點。另外還有一個變量endTimestamps用于記錄最后一個樣本的時間戳刻获,并報告給MCU蜀涨,讓MCU根據(jù)endTimestamps和packet buffer里包的timestamp決定是否要能取出包以及是否要取出包。


這里先簡要介紹一下netEQ的處理過程蝎毡,后面文章中會詳細講厚柳。處理過程主要分兩部分,一是把RTP語音包插入packet packet的過程沐兵,二是從packet buffer中提取語音包解碼和PCM信號處理的過程别垮。先看把RTP語音包插入packet packet的過程,主要有三步:

1扎谎,在收到第一個RTP語音包后初始化netEQ碳想。

2,解析RTP語音包毁靶,將其插入到packet buffer中胧奔。在插入時根據(jù)收到包的順序依次插入,到尾部后再從頭上繼續(xù)插入老充。這是一種簡單的插入方法葡盗。

3,計算網(wǎng)絡(luò)延時optBufLevel。

再看怎么提取語音包并解碼和PCM信號處理觅够,主要有六步:

1胶背,將DSP模塊的endTimeStamp賦給playedOutTS,和sampleLeft(語音緩沖區(qū)中未播放的樣本數(shù))一同傳給MCU喘先,告訴MCU當(dāng)前DSP模塊的播放狀況钳吟。

2,看是否要從packet buffer里取出語音包窘拯,以及是否能取出語音包红且。取出包時用的是遍歷整個packet buffer的方法,根據(jù)playedOutTS找到最小的大于等于playedOutTS的時間戳涤姊,記為availableTS暇番,將其取出來。如果包丟了就取不到包思喊。

3壁酬,算抖動緩沖延時buffLevelFilt。

4恨课,根據(jù)網(wǎng)絡(luò)延時抖動緩沖延時以及上一幀的處理方式等決定本次的MCU控制命令舆乔。

5,如果有從packet buffer里提取到包就解碼剂公,否則不解碼希俩。

6,根據(jù)MCU給的控制命令對解碼后的以及語音緩沖區(qū)里的數(shù)據(jù)做信號處理纲辽。


在我個人看來,netEQ有兩大核心技術(shù)點拖吼。一是計算當(dāng)前網(wǎng)絡(luò)延時和抖動緩沖延時的算法。要根據(jù)網(wǎng)絡(luò)延時、抖動緩沖延時和其他因素決定信號處理命令橘原,信號處理命令對了能提高音質(zhì),相反則會降低音質(zhì)趾断,所以說信號處理命令的決策非常關(guān)鍵。二是各種信號處理算法增显,主要有加速(accelerate)脐帝、減速(preemptive expand)同云、丟包補償(PLC)糖权、融合(merge)和背景噪聲生成(BNG),這些都是非常專業(yè)的算法炸站。

分類:?傳統(tǒng)音頻

好文要頂?關(guān)注我?收藏該文??

davidtym

關(guān)注 - 6

粉絲 - 133

+加關(guān)注

3

0

??上一篇:?語音通信中終端上的時延(latency)及減小方法

??下一篇:?webRTC中音頻相關(guān)的netEQ(二):數(shù)據(jù)結(jié)構(gòu)

posted on?2018-07-16 08:29?davidtym閱讀(7970)? 評論(4)?編輯?收藏

評論

#1樓?2019-11-02 11:51?Piasy

樓主你好星澳!最近我在研究 WebRTC 最新代碼 NetEQ 這部分的內(nèi)容,找到了你的博客旱易。你的這幾篇博客寫得非常好禁偎!你的其他的博客我也看了一些,越看越希望能和你建立聯(lián)系阀坏,我也寫博客如暖,這是我的個人介紹頁:blog.piasy.com/about/index.html

希望能和你加個微信,我的微信號是 piasy_umumu 忌堂,非常感謝盒至!

支持(0)?反對(0)

#2樓?[樓主]?2019-11-04 18:36?davidtym

@?Piasy

看了你的博客,挺牛的浸船!加你微信了妄迁,歡迎溝通!

支持(0)?反對(0)

#3樓?2019-11-10 14:30?fanwenyao

樓主C語言版本 neteq 下載地址可否提供下李命, 感謝

支持(0)?反對(0)

#4樓?[樓主]?2019-11-12 15:48?davidtym

@?fanwenyao

我找到了一個C版本的地址:?https://chromium.googlesource.com/external/webrtc/stable/src/+/b34066b0ebe4a9adc6df603090afdf6a2b2a986b/modules/audio_coding/neteq?登淘, 不過要翻墻的。謝謝封字!

支持(0)?反對(0)



NetEQ 算法. https://www.cnblogs.com/mengnan/p/11637449.html

NetEQ 算法中集成了自適應(yīng)抖動控制算法以及語音包丟失隱藏算法黔州。這項技術(shù)使其能夠快速且高解析度地適應(yīng)不斷變化的網(wǎng)絡(luò)環(huán)境,確保音質(zhì)優(yōu)美且緩沖延遲最小阔籽。

研究的重點是 NetEQ 模塊流妻,其中所涉及的處理過程包括抖動消除、丟包補償和壓縮解碼笆制。

抖動消除原理

抖動通常采用抖動緩沖技術(shù)來消除在辆,即在接收方建立一個緩沖區(qū),語音包到達接收端時首先進人緩沖區(qū)暫存浑度,隨后系統(tǒng)再以穩(wěn)定平滑的速率將語音包從緩沖

區(qū)提取出來箩张,經(jīng)解壓后從聲卡播出饮笛。

4 個語音數(shù)據(jù)包(A缎浇、B素跺、C指厌、D)以 30ms 為間隔進行發(fā)送踩验,即發(fā)送時間分別為 30箕憾,60袭异,90御铃,120ms上真,網(wǎng)絡(luò)延遲分別為10,30,10,10ms陵像。到達時間為40,90,100,130ms蠢壹,所以需要在抖動緩沖中分別緩沖60,90,120,150ms。

1.靜態(tài)抖動緩沖控制算法

2.自適應(yīng)抖動緩沖控制算法

丟包隱藏原理

iLBC 的丟包隱藏只是在解碼端進行處理疏日,即在解碼端根據(jù)收到的比特流逐幀進行解碼的過程中沟优,iLBC? 解碼器首先拿到每幀的比特流時判斷當(dāng)前幀是否完整挠阁,如果沒有問題則按照正常的 iLBC? 解碼流程重建語音信號侵俗,如果發(fā)現(xiàn)語音數(shù)據(jù)包丟失增拥,那么就進入 PLC 單元進行處理掌栅。見算法步驟13


MCU(Micro Control Unit)模塊是抖動緩沖區(qū)的微控制單元猾封,由于抖動緩沖區(qū)作用是暫存接收到的數(shù)據(jù)包忘衍,因此 MCU 的主要作用是安排數(shù)據(jù)包的插入并控制數(shù)據(jù)包的輸出枚钓。數(shù)據(jù)包的插入主要是確定來自網(wǎng)絡(luò)的新到達的數(shù)據(jù)包在緩沖區(qū)中的插入位置搀捷,而控制數(shù)據(jù)包的輸出則要考慮什么時候需要輸出數(shù)據(jù)嫩舟,以及輸出哪一個插槽的數(shù)據(jù)包家厌。抖動消除的算法思路在 MCU 控制模塊中得以體現(xiàn)饭于。

DSP 模塊主要負(fù)責(zé)對從 MCU 中提取出來的 PCM 源數(shù)據(jù)包進行數(shù)字信號處理掰吕,

包括解碼殖熟、信號處理钳榨、數(shù)據(jù)輸出等幾個部分重绷。丟包隱藏操作即在 DSP 模塊中完成昭卓。

NetEQ 模塊框圖


網(wǎng)絡(luò)數(shù)據(jù)包進入抖動緩沖區(qū)的過程候醒,其基本步驟如下倒淫,


AudioCodingModuleImpl::IncomingPacket(const uint8_t*incoming_payload,const int32_t payload_length,const WebRtcRTPHeader& rtp_info)

{

...

memcpy(payload,?incoming_payload, payload_length);

codecs_[current_receive_codec_idx_]->SplitStereoPacket(payload, &length);

rtp_header.type.Audio.channel = 2;

per_neteq_payload_length = length / 2;

// Insert packet into NetEQ.

if (neteq_.RecIn(payload, length, rtp_header,

last_receive_timestamp_) < 0)

...

}

ACMNetEQ::RecIn(const uint8_t*incoming_payload,const int32_t length_payload,const WebRtcRTPHeader& rtp_info,uint32_t receive_timestamp)

{

...

WebRtcNetEQ_RecInRTPStruct(inst_[0], &neteq_rtpinfo,incoming_payload, payload_length,receive_timestamp);

}

int WebRtcNetEQ_RecInRTPStruct(void *inst, WebRtcNetEQ_RTPInfo *rtpInfo,

const uint8_t *payloadPtr, int16_t payloadLenBytes,

uint32_t uw32_timeRec)

{

...

??? RTPPacket_t RTPpacket;

...

/* Load NetEQ's RTP struct from Module RTP struct */

RTPpacket.payloadType = rtpInfo->payloadType;

RTPpacket.seqNumber = rtpInfo->sequenceNumber;

RTPpacket.timeStamp = rtpInfo->timeStamp;

RTPpacket.ssrc = rtpInfo->SSRC;

?RTPpacket.payload = (const int16_t*)?payloadPtr;

RTPpacket.payloadLen = payloadLenBytes;

RTPpacket.starts_byte1 = 0;

?WebRtcNetEQ_RecInInternal(&NetEqMainInst->MCUinst, &RTPpacket, uw32_timeRec);

}

int WebRtcNetEQ_RecInInternal(MCUInst_t *MCU_inst, RTPPacket_t *RTPpacketInput,uint32_t uw32_timeRec)

{

...

WebRtcNetEQ_PacketBufferInsert(&MCU_inst->PacketBuffer_inst,&RTPpacket[i_k], &flushed, MCU_inst->av_sync);

...

WebRtcNetEQ_SplitAndInsertPayload(&RTPpacket[i_k],&MCU_inst->PacketBuffer_inst, &MCU_inst->PayloadSplit_inst,&flushed, MCU_inst->av_sync);

...

}

int?WebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *RTPpacket,

int16_t *flushed, int av_sync)

{//This function inserts an RTP packet into the packet buffer.

...

/* Copy the packet information */

?bufferInst->payloadLocation[bufferInst->insertPosition] = bufferInst->currentMemoryPos;

?bufferInst->payloadLengthBytes[bufferInst->insertPosition] =?RTPpacket->payloadLen;

?bufferInst->payloadType[bufferInst->insertPosition] =?RTPpacket->payloadType;

?bufferInst->seqNumber[bufferInst->insertPosition] =?RTPpacket->seqNumber;

?bufferInst->timeStamp[bufferInst->insertPosition] =?RTPpacket->timeStamp;

?bufferInst->rcuPlCntr[bufferInst->insertPosition] =?RTPpacket->rcuPlCntr;

?bufferInst->waitingTime[bufferInst->insertPosition] = 0;

...

}

int?WebRtcNetEQ_SplitAndInsertPayload(RTPPacket_t* packet,PacketBuf_t* Buffer_inst,SplitInfo_t* split_inst,int16_t* flushed,int av_sync)

{

}

1:解析數(shù)據(jù)包并將其插入到抖動緩沖區(qū)中( Parse the payload and insert it into the buffer)返干。WebRtcNetEQ_SplitAndInsertPayload

WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, &localFlushed);循環(huán)將輸入數(shù)據(jù)packets split到PacketBuf_t 的實例中

while (len >= split_inst->deltaBytes)

{

....

i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, &localFlushed);

...

}

/* Insert packet in the found position */

if (RTPpacket->starts_byte1 == 0)

{

/* Payload is 16-bit aligned => just copy it */

WEBRTC_SPL_MEMCPY_W16(bufferInst->currentMemoryPos,

RTPpacket->payload, (RTPpacket->payloadLen + 1) >> 1);

}

else

{

/* Payload is not 16-bit aligned => align it during copy operation */

for (i = 0; i < RTPpacket->payloadLen; i++)

{

/* copy the (i+1)-th byte to the i-th byte */

WEBRTC_SPL_SET_BYTE(bufferInst->currentMemoryPos,

(WEBRTC_SPL_GET_BYTE(RTPpacket->payload, (i + 1))), i);

}

}

2:更新 automode 中的參數(shù)矩欠,重點計算網(wǎng)絡(luò)延遲的統(tǒng)計值 BLo(optBufferLevel)癌淮。WebRtcNetEQ_UpdateIatStatistics

????? 2.1

/* calculate inter-arrival time in integer packets (rounding down) */

timeIat = WebRtcSpl_DivW32W16(inst->packetIatCountSamp, packetLenSamp);

??? ?2.2??? /* update iatProb = forgetting_factor * iatProb for all elements */

2.3??????? /* Calculate optimal buffer level based on updated statistics */

tempvar = (int32_t) WebRtcNetEQ_CalcOptimalBufLvl(inst, fsHz, mdCodec, timeIat,

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

提取 10ms 數(shù)據(jù)到聲卡的算法過程

3:將 DSP 的 endTimeStamp 賦值給 playedOutTS(用于提取數(shù)據(jù)的參考條件)并記錄語音緩沖區(qū)中等待播放的樣本數(shù) sampleLeft。Write status data to shared memory

4:從抖動緩沖區(qū)提取數(shù)據(jù)虚倒。WebRtcNetEQ_DSP2MCUinterrupt

5:遍歷查找抖動緩沖區(qū)中數(shù)據(jù)包的時間戳。?WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst,inst->timeStamp, &uw32_availableTS, &i_bufferpos, 1, &payloadType);

????? 5.1/* Loop through all slots in buffer. */

?????? 5.2/* If old payloads should be discarded. */

6:統(tǒng)計進入接收端的 NetEQ 模塊但仍未被播放的數(shù)據(jù)量,記為 bufsize捧弃。calculate total current buffer size (in ms*8), including sync buffer

?w32_bufsize = WebRtcSpl_DivW32W16((w32_bufsize + dspInfo.samplesLeft), fs_mult)

7.根據(jù) bufsize 計算 BLc(bufferLevelFilt).WebRtcNetEQ_BufferLevelFilter

* Current buffer level in packet lengths

* = (curSizeMs8 * fsMult) / packetSpeechLenSamp

curSizeFrames = Sb/Lp;

Sb = Np*Lp+sampleLeft;


/* Filter buffer level */

if (inst->levelFiltFact > 0) /* check that filter factor is set */

{

/* Filter:

* buffLevelFilt = levelFiltFact * buffLevelFilt

*????????????????? + (1-levelFiltFact) * curSizeFrames

*

* levelFiltFact is in Q8

*/

inst->buffLevelFilt = ((inst->levelFiltFact * inst->buffLevelFilt) >> 8) +

(256 - inst->levelFiltFact) * curSizeFrames;

}

8.根據(jù) BLo(optBufferLevel)、BLc(bufferLevelFilt)瞬场、bufsize贯被、playedOutTS彤灶、availableTS 及 NetEQ 的上一播放模式進行 MCU 控制命令的判斷

9.根據(jù) MCU 的控制命令及當(dāng)前語音緩沖區(qū)中解碼后未被播放的數(shù)據(jù)量sampleLeft 進行判斷考慮是否需要從抖動緩沖區(qū)取數(shù)據(jù).? Check sync buffer size???? (Step 11)?

10.提取一個數(shù)據(jù)包送入共享內(nèi)存暫存器.WebRtcNetEQ_PacketBufferExtract

11.根據(jù)從抖動緩沖區(qū)取數(shù)據(jù)之前的 MCU 的控制命令得到相應(yīng)的 DSP的處理命令幌陕。 Step 13

12.解碼取到的數(shù)據(jù)(Do decoding).inst->codec_ptr_inst.funcDecode()

13.丟包補償.inst->codec_ptr_inst.funcDecodePLC

14.根據(jù) DSP 操作命令進入相應(yīng)的的播放模式對解碼數(shù)據(jù)及語音緩沖區(qū)中數(shù)據(jù)進行相關(guān)操作棚唆。Step 15

15.從語音緩沖區(qū)的 curPosition 為起始位置取 10ms 數(shù)據(jù)傳輸?shù)铰暱ㄏ琛EBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1);

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摆寄,一起剝皮案震驚了整個濱河市微饥,隨后出現(xiàn)的幾起案子古戴,更是在濱河造成了極大的恐慌现恼,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刽酱,死亡現(xiàn)場離奇詭異棵里,居然都是意外死亡殿怜,警方通過查閱死者的電腦和手機曙砂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門柱告,熙熙樓的掌柜王于貴愁眉苦臉地迎上來末荐,“玉大人新锈,你說我怎么就攤上這事妹笆∪” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哲鸳。 經(jīng)常有香客問我讯沈,道長婿奔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任如叼,我火速辦了婚禮穷劈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雕沿。我一直安慰自己审轮,他們只是感情好疾渣,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布榴捡。 她就那樣靜靜地躺著吊圾,像睡著了一般项乒。 火紅的嫁衣襯著肌膚如雪檀何。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天栓辜,我揣著相機與錄音啃憎,去河邊找鬼辛萍。 笑死,一個胖子當(dāng)著我的面吹牛悯许,可吹牛的內(nèi)容都是我干的先壕。 我是一名探鬼主播垃僚,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼谆棺,長吁一口氣:“原來是場噩夢啊……” “哼改淑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起榆纽,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤亮元,失蹤者是張志新(化名)和其女友劉穎爆捞,沒想到半個月后煮甥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成肘,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡双霍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年染坯,在試婚紗的時候發(fā)現(xiàn)自己被綠了丘逸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仲锄。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡儒喊,死狀恐怖澄惊,靈堂內(nèi)的尸體忽然破棺而出鸵赖,到底是詐尸還是另有隱情蛤奢,我是刑警寧澤啤贩,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站枉氮,受9級特大地震影響聊替,放射性物質(zhì)發(fā)生泄漏春叫。R本人自食惡果不足惜暂殖,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一央星、第九天 我趴在偏房一處隱蔽的房頂上張望霞怀。 院中可真熱鬧,春花似錦莉给、人聲如沸毙石。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徐矩。三九已至,卻和暖如春叁幢,著一層夾襖步出監(jiān)牢的瞬間滤灯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工曼玩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓陶舞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親厅贪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351