原文鏈接:http://blog.csdn.net/u014338577/article/details/46010983
NetEQ算法中集成了自適應(yīng)抖動(dòng)控制算法以及語(yǔ)音包丟失隱藏算法。這項(xiàng)技術(shù)使其能夠快速且高解析度地適應(yīng)不斷變化的網(wǎng)絡(luò)環(huán)境,確保音質(zhì)優(yōu)美且緩沖延遲最小。
研究的重點(diǎn)是 NetEQ 模塊,其中所涉及的處理過(guò)程包括抖動(dòng)消除、丟包補(bǔ)償和壓縮解碼。
抖動(dòng)消除原理
抖動(dòng)通常采用抖動(dòng)緩沖技術(shù)來(lái)消除,即在接收方建立一個(gè)緩沖區(qū),語(yǔ)音包到達(dá)接收端時(shí)首先進(jìn)人緩沖區(qū)暫存,隨后系統(tǒng)再以穩(wěn)定平滑的速率將語(yǔ)音包從緩沖
區(qū)提取出來(lái),經(jīng)解壓后從聲卡播出。
4 個(gè)語(yǔ)音數(shù)據(jù)包(A、B、C、D)以 30ms 為間隔進(jìn)行發(fā)送,即發(fā)送時(shí)間分別為 30,60,90,120ms,網(wǎng)絡(luò)延遲分別為10,30,10,10ms。到達(dá)時(shí)間為40,90,100,130ms,所以需要在抖動(dòng)緩沖中分別緩沖60,90,120,150ms。
1.靜態(tài)抖動(dòng)緩沖控制算法
2.自適應(yīng)抖動(dòng)緩沖控制算法
丟包隱藏原理
iLBC 的丟包隱藏只是在解碼端進(jìn)行處理,即在解碼端根據(jù)收到的比特流逐幀進(jìn)行解碼的過(guò)程中,iLBC解碼器首先拿到每幀的比特流時(shí)判斷當(dāng)前幀是否完整,如果沒(méi)有問(wèn)題則按照正常的 iLBC解碼流程重建語(yǔ)音信號(hào),如果發(fā)現(xiàn)語(yǔ)音數(shù)據(jù)包丟失,那么就進(jìn)入 PLC 單元進(jìn)行處理。見(jiàn)算法步驟13
MCU(Micro Control Unit)模塊是抖動(dòng)緩沖區(qū)的微控制單元,由于抖動(dòng)緩沖區(qū)作用是暫存接收到的數(shù)據(jù)包,因此 MCU 的主要作用是安排數(shù)據(jù)包的插入并控制數(shù)據(jù)包的輸出。數(shù)據(jù)包的插入主要是確定來(lái)自網(wǎng)絡(luò)的新到達(dá)的數(shù)據(jù)包在緩沖區(qū)中的插入位置,而控制數(shù)據(jù)包的輸出則要考慮什么時(shí)候需要輸出數(shù)據(jù),以及輸出哪一個(gè)插槽的數(shù)據(jù)包。抖動(dòng)消除的算法思路在 MCU 控制模塊中得以體現(xiàn)。
DSP 模塊主要負(fù)責(zé)對(duì)從 MCU 中提取出來(lái)的 PCM 源數(shù)據(jù)包進(jìn)行數(shù)字信號(hào)處理,
包括解碼、信號(hào)處理、數(shù)據(jù)輸出等幾個(gè)部分。丟包隱藏操作即在 DSP 模塊中完成。
NetEQ 模塊框圖
網(wǎng)絡(luò)數(shù)據(jù)包進(jìn)入抖動(dòng)緩沖區(qū)的過(guò)程,其基本步驟如下,
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ù)包并將其插入到抖動(dòng)緩沖區(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 的實(shí)例中
?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ù),重點(diǎn)計(jì)算網(wǎng)絡(luò)延遲的統(tǒng)計(jì)值 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ù)到聲卡的算法過(guò)程
3:將 DSP 的 endTimeStamp 賦值給 playedOutTS(用于提取數(shù)據(jù)的參考條件)并記錄語(yǔ)音緩沖區(qū)中等待播放的樣本數(shù) sampleLeft。Write status data to shared memory
4:從抖動(dòng)緩沖區(qū)提取數(shù)據(jù)。WebRtcNetEQ_DSP2MCUinterrupt
5:遍歷查找抖動(dòng)緩沖區(qū)中數(shù)據(jù)包的時(shí)間戳。WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst,inst->timeStamp, &uw32_availableTS, &i_bufferpos, 1, &payloadType);
5.1
5.2
6:統(tǒng)計(jì)進(jìn)入接收端的 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 計(jì)算 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 的上一播放模式進(jìn)行 MCU 控制命令的判斷
9.根據(jù) MCU 的控制命令及當(dāng)前語(yǔ)音緩沖區(qū)中解碼后未被播放的數(shù)據(jù)量sampleLeft 進(jìn)行判斷考慮是否需要從抖動(dòng)緩沖區(qū)取數(shù)據(jù).?Check sync buffer size(Step 11)
10.提取一個(gè)數(shù)據(jù)包送入共享內(nèi)存暫存器.WebRtcNetEQ_PacketBufferExtract
11.根據(jù)從抖動(dòng)緩沖區(qū)取數(shù)據(jù)之前的 MCU 的控制命令得到相應(yīng)的 DSP的處理命令。 Step 13
12.解碼取到的數(shù)據(jù)(Do decoding).inst->codec_ptr_inst.funcDecode()
13.丟包補(bǔ)償.inst->codec_ptr_inst.funcDecodePLC
14.根據(jù) DSP 操作命令進(jìn)入相應(yīng)的的播放模式對(duì)解碼數(shù)據(jù)及語(yǔ)音緩沖區(qū)中數(shù)據(jù)進(jìn)行相關(guān)操作。Step 15
15.從語(yǔ)音緩沖區(qū)的 curPosition 為起始位置取 10ms 數(shù)據(jù)傳輸?shù)铰暱?。WEBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1);
https://www.cnblogs.com/talkaudiodev/p/9142192.html
(語(yǔ)音通信中終端上的時(shí)延(latency)及減小方法)說(shuō)從本篇開(kāi)始會(huì)切入webRTC中的netEQ主題,netEQ是webRTC中音頻技術(shù)方面的兩大核心技術(shù)之一(另一核心技術(shù)是音頻的前后處理,包括AEC、ANS、AGC等,俗稱3A算法)。webRTC是Google收購(gòu)GIPS重新包裝后開(kāi)源出來(lái)的,目前已是有巨大影響力的實(shí)時(shí)音視頻通信解決方案。國(guó)內(nèi)的互聯(lián)網(wǎng)公司,要做實(shí)時(shí)音視頻通信產(chǎn)品,絕大多數(shù)都是基于webRTC來(lái)做的,有的是直接用webRTC的解決方案,有的是用webRTC里的核心技術(shù),比如3A算法。不僅互聯(lián)網(wǎng)公司,其他類型公司(比如通信公司),也會(huì)把webRTC里的精華用到自己的產(chǎn)品中。我剛開(kāi)始做voice engine時(shí),webRTC還未開(kāi)源,但那時(shí)就知道了GIPS是一家做實(shí)時(shí)語(yǔ)音通信的頂級(jí)公司。webRTC開(kāi)源后,一開(kāi)始沒(méi)機(jī)會(huì)用,后來(lái)做OTT語(yǔ)音(APP語(yǔ)音)時(shí)用了webRTC里的3A算法。在做了Android手機(jī)平臺(tái)上的音頻開(kāi)發(fā)后,用了webRTC上的netEQ,不過(guò)用的是較早的C語(yǔ)言版本,不是C++版本,并且只涉及了netEQ中的DSP模塊(netEQ有兩大模塊,MCU(micro control unit, 微控制單元)和DSP(digital signal processing, 信號(hào)處理單元),MCU負(fù)責(zé)控制從網(wǎng)絡(luò)收到的語(yǔ)音包在jitter? buffer里的插入和提取,同時(shí)控制DSP模塊用哪種算法處理解碼后的PCM數(shù)據(jù),DSP負(fù)責(zé)解碼以及解碼后的PCM信號(hào)處理,主要PCM信號(hào)處理算法有加速、減速、丟包補(bǔ)償、融合等),MCU模塊在CP (communication processor, 通訊處理器)上做,兩個(gè)模塊之間通過(guò)消息交互。DSP模塊經(jīng)過(guò)調(diào)試,基本上掌握了機(jī)制。MCU模塊由于在CP上做,沒(méi)有source code,我就從網(wǎng)上找來(lái)了我們用的版本相對(duì)應(yīng)的webRTC的開(kāi)源版本,經(jīng)過(guò)了一段時(shí)間的理解后,也基本上搞清楚了機(jī)制。從本篇開(kāi)始,我將花幾篇講netEQ(基于我用的早期C語(yǔ)言版本)。這里需要說(shuō)明的是每個(gè)產(chǎn)品在使用webRTC上的代碼時(shí)都會(huì)根據(jù)自己產(chǎn)品的特點(diǎn)做一定的修改,我做的產(chǎn)品也不例外。我在講時(shí)一些細(xì)節(jié)不會(huì)涉及,主要講機(jī)制。本篇先對(duì)netEQ做一個(gè)概述。
實(shí)時(shí)IP語(yǔ)音通信的軟件架構(gòu)框圖通常如下圖:

上圖中發(fā)送方(或叫上行、TX)將從MIC采集到的語(yǔ)音數(shù)據(jù)先做前處理,然后編碼得到碼流,再用RTP打包通過(guò)UDP socket發(fā)送到網(wǎng)絡(luò)中給對(duì)方。接收方(或叫下行、RX)通過(guò)UDP socket收語(yǔ)音包,解析RTP包后放入jitter buffer中,要播放時(shí)每隔一定時(shí)間從jitter buffer中取出包并解碼得到PCM數(shù)據(jù),做后處理后送給播放器播放出來(lái)。
netEQ模塊在接收側(cè),它是把jitter buffer和decoder綜合起來(lái)并加入解碼后的PCM信號(hào)處理形成,即netEQ = jitter buffer + decoder + PCM信號(hào)處理。這樣上圖中的軟件架構(gòu)框圖就變成下圖:

上文說(shuō)過(guò)netEQ模塊主要包括MCU和DSP兩大單元。它的軟件框圖如下圖:

從上兩圖看出,jitter buffer(也就是packet? buffer,后面就跟netEQ一致,表述成packet buffer,用于去除網(wǎng)絡(luò)抖動(dòng))模塊在MCU單元內(nèi),decoder和PCM信號(hào)處理模塊在DSP單元內(nèi)。MCU單元主要負(fù)責(zé)把從網(wǎng)絡(luò)側(cè)收到的語(yǔ)音包經(jīng)過(guò)RTP解析后往packet? buffer里插入(insert),以及從packet buffer 里提取(extract)語(yǔ)音包給DSP單元做解碼、信號(hào)處理等,同時(shí)也算網(wǎng)絡(luò)延時(shí)(optBufLevel)和抖動(dòng)緩沖延時(shí)(buffLevelFilt),根據(jù)網(wǎng)絡(luò)延時(shí)和抖動(dòng)緩沖延時(shí)以及其他因素(上一幀的處理方式等)決定給DSP單元發(fā)什么信號(hào)處理命令。主要的信號(hào)處理命令有5種,一是正常播放,即不需要做信號(hào)處理。二是加速播放,用于通話延時(shí)較大的情況,通過(guò)加速算法使語(yǔ)音信息不丟而減少語(yǔ)音時(shí)長(zhǎng),從而減少延時(shí)。三是減速播放,用于語(yǔ)音斷續(xù)情況,通過(guò)減速算法使語(yǔ)音信息不丟而增加語(yǔ)音時(shí)長(zhǎng),從而減少語(yǔ)音斷續(xù)。四是丟包補(bǔ)償,用于丟包情況,通過(guò)丟包補(bǔ)償算法把丟掉的語(yǔ)音補(bǔ)償回來(lái)。五是融合(merge),用于前一幀丟包而當(dāng)前包正常收到的情況,由于前一包丟失用丟包補(bǔ)償算法補(bǔ)回了語(yǔ)音,與當(dāng)前包之間需要做融合處理來(lái)平滑上一補(bǔ)償?shù)陌彤?dāng)前正常收到的語(yǔ)音包。以上幾種信號(hào)處理提高了在惡劣網(wǎng)絡(luò)環(huán)境下的語(yǔ)音質(zhì)量,增強(qiáng)了用戶體驗(yàn)??梢哉f(shuō)是在目前公開(kāi)的處理語(yǔ)音的網(wǎng)絡(luò)丟包、延時(shí)和抖動(dòng)的方案中是最佳的了。
DSP單元主要負(fù)責(zé)解碼和PCM信號(hào)處理。從packet buffer提取出來(lái)的碼流解碼成PCM數(shù)據(jù)放進(jìn)decoded_buffer中,然后根據(jù)MCU給出的命令做信號(hào)處理,處理結(jié)果放在algorithm_buffer中,最后將algorithm_buffer中的數(shù)據(jù)放進(jìn)speech_buffer待取走播放。Speech_buffer中數(shù)據(jù)分兩塊,一塊是已播放過(guò)的數(shù)據(jù)(playedOut),另一塊是未播放的數(shù)據(jù)(sampleLeft), curPosition就是這兩種數(shù)據(jù)的分割點(diǎn)。另外還有一個(gè)變量endTimestamps用于記錄最后一個(gè)樣本的時(shí)間戳,并報(bào)告給MCU,讓MCU根據(jù)endTimestamps和packet buffer里包的timestamp決定是否要能取出包以及是否要取出包。
這里先簡(jiǎn)要介紹一下netEQ的處理過(guò)程,后面文章中會(huì)詳細(xì)講。處理過(guò)程主要分兩部分,一是把RTP語(yǔ)音包插入packet packet的過(guò)程,二是從packet buffer中提取語(yǔ)音包解碼和PCM信號(hào)處理的過(guò)程。先看把RTP語(yǔ)音包插入packet packet的過(guò)程,主要有三步:
1,在收到第一個(gè)RTP語(yǔ)音包后初始化netEQ。
2,解析RTP語(yǔ)音包,將其插入到packet buffer中。在插入時(shí)根據(jù)收到包的順序依次插入,到尾部后再?gòu)念^上繼續(xù)插入。這是一種簡(jiǎn)單的插入方法。
3,計(jì)算網(wǎng)絡(luò)延時(shí)optBufLevel。
再看怎么提取語(yǔ)音包并解碼和PCM信號(hào)處理,主要有六步:
1,將DSP模塊的endTimeStamp賦給playedOutTS,和sampleLeft(語(yǔ)音緩沖區(qū)中未播放的樣本數(shù))一同傳給MCU,告訴MCU當(dāng)前DSP模塊的播放狀況。
2,看是否要從packet buffer里取出語(yǔ)音包,以及是否能取出語(yǔ)音包。取出包時(shí)用的是遍歷整個(gè)packet buffer的方法,根據(jù)playedOutTS找到最小的大于等于playedOutTS的時(shí)間戳,記為availableTS,將其取出來(lái)。如果包丟了就取不到包。
3,算抖動(dòng)緩沖延時(shí)buffLevelFilt。
4,根據(jù)網(wǎng)絡(luò)延時(shí)抖動(dòng)緩沖延時(shí)以及上一幀的處理方式等決定本次的MCU控制命令。
5,如果有從packet buffer里提取到包就解碼,否則不解碼。
6,根據(jù)MCU給的控制命令對(duì)解碼后的以及語(yǔ)音緩沖區(qū)里的數(shù)據(jù)做信號(hào)處理。
在我個(gè)人看來(lái),netEQ有兩大核心技術(shù)點(diǎn)。一是計(jì)算當(dāng)前網(wǎng)絡(luò)延時(shí)和抖動(dòng)緩沖延時(shí)的算法。要根據(jù)網(wǎng)絡(luò)延時(shí)、抖動(dòng)緩沖延時(shí)和其他因素決定信號(hào)處理命令,信號(hào)處理命令對(duì)了能提高音質(zhì),相反則會(huì)降低音質(zhì),所以說(shuō)信號(hào)處理命令的決策非常關(guān)鍵。二是各種信號(hào)處理算法,主要有加速(accelerate)、減速(preemptive expand)、丟包補(bǔ)償(PLC)、融合(merge)和背景噪聲生成(BNG),這些都是非常專業(yè)的算法。
分類:?傳統(tǒng)音頻



3
0
??上一篇:?語(yǔ)音通信中終端上的時(shí)延(latency)及減小方法
??下一篇:?webRTC中音頻相關(guān)的netEQ(二):數(shù)據(jù)結(jié)構(gòu)
posted on?2018-07-16 08:29?davidtym閱讀(7970)? 評(píng)論(4)?編輯?收藏
評(píng)論
樓主你好!最近我在研究 WebRTC 最新代碼 NetEQ 這部分的內(nèi)容,找到了你的博客。你的這幾篇博客寫(xiě)得非常好!你的其他的博客我也看了一些,越看越希望能和你建立聯(lián)系,我也寫(xiě)博客,這是我的個(gè)人介紹頁(yè):blog.piasy.com/about/index.html
希望能和你加個(gè)微信,我的微信號(hào)是 piasy_umumu ,非常感謝!
#2樓?[樓主]?2019-11-04 18:36?davidtym
@?Piasy
看了你的博客,挺牛的!加你微信了,歡迎溝通!
#3樓?2019-11-10 14:30?fanwenyao
樓主C語(yǔ)言版本 neteq 下載地址可否提供下, 感謝
#4樓?[樓主]?2019-11-12 15:48?davidtym
@?fanwenyao
我找到了一個(gè)C版本的地址:?https://chromium.googlesource.com/external/webrtc/stable/src/+/b34066b0ebe4a9adc6df603090afdf6a2b2a986b/modules/audio_coding/neteq?, 不過(guò)要翻墻的。謝謝!
NetEQ 算法. https://www.cnblogs.com/mengnan/p/11637449.html
NetEQ 算法中集成了自適應(yīng)抖動(dòng)控制算法以及語(yǔ)音包丟失隱藏算法。這項(xiàng)技術(shù)使其能夠快速且高解析度地適應(yīng)不斷變化的網(wǎng)絡(luò)環(huán)境,確保音質(zhì)優(yōu)美且緩沖延遲最小。
研究的重點(diǎn)是 NetEQ 模塊,其中所涉及的處理過(guò)程包括抖動(dòng)消除、丟包補(bǔ)償和壓縮解碼。
抖動(dòng)消除原理
抖動(dòng)通常采用抖動(dòng)緩沖技術(shù)來(lái)消除,即在接收方建立一個(gè)緩沖區(qū),語(yǔ)音包到達(dá)接收端時(shí)首先進(jìn)人緩沖區(qū)暫存,隨后系統(tǒng)再以穩(wěn)定平滑的速率將語(yǔ)音包從緩沖
區(qū)提取出來(lái),經(jīng)解壓后從聲卡播出。
4 個(gè)語(yǔ)音數(shù)據(jù)包(A、B、C、D)以 30ms 為間隔進(jìn)行發(fā)送,即發(fā)送時(shí)間分別為 30,60,90,120ms,網(wǎng)絡(luò)延遲分別為10,30,10,10ms。到達(dá)時(shí)間為40,90,100,130ms,所以需要在抖動(dòng)緩沖中分別緩沖60,90,120,150ms。
1.靜態(tài)抖動(dòng)緩沖控制算法
2.自適應(yīng)抖動(dòng)緩沖控制算法
丟包隱藏原理
iLBC 的丟包隱藏只是在解碼端進(jìn)行處理,即在解碼端根據(jù)收到的比特流逐幀進(jìn)行解碼的過(guò)程中,iLBC? 解碼器首先拿到每幀的比特流時(shí)判斷當(dāng)前幀是否完整,如果沒(méi)有問(wèn)題則按照正常的 iLBC? 解碼流程重建語(yǔ)音信號(hào),如果發(fā)現(xiàn)語(yǔ)音數(shù)據(jù)包丟失,那么就進(jìn)入 PLC 單元進(jìn)行處理。見(jiàn)算法步驟13
MCU(Micro Control Unit)模塊是抖動(dòng)緩沖區(qū)的微控制單元,由于抖動(dòng)緩沖區(qū)作用是暫存接收到的數(shù)據(jù)包,因此 MCU 的主要作用是安排數(shù)據(jù)包的插入并控制數(shù)據(jù)包的輸出。數(shù)據(jù)包的插入主要是確定來(lái)自網(wǎng)絡(luò)的新到達(dá)的數(shù)據(jù)包在緩沖區(qū)中的插入位置,而控制數(shù)據(jù)包的輸出則要考慮什么時(shí)候需要輸出數(shù)據(jù),以及輸出哪一個(gè)插槽的數(shù)據(jù)包。抖動(dòng)消除的算法思路在 MCU 控制模塊中得以體現(xiàn)。
DSP 模塊主要負(fù)責(zé)對(duì)從 MCU 中提取出來(lái)的 PCM 源數(shù)據(jù)包進(jìn)行數(shù)字信號(hào)處理,
包括解碼、信號(hào)處理、數(shù)據(jù)輸出等幾個(gè)部分。丟包隱藏操作即在 DSP 模塊中完成。
NetEQ 模塊框圖
網(wǎng)絡(luò)數(shù)據(jù)包進(jìn)入抖動(dòng)緩沖區(qū)的過(guò)程,其基本步驟如下,
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ù)包并將其插入到抖動(dòng)緩沖區(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 的實(shí)例中
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ù),重點(diǎn)計(jì)算網(wǎng)絡(luò)延遲的統(tǒng)計(jì)值 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ù)到聲卡的算法過(guò)程
3:將 DSP 的 endTimeStamp 賦值給 playedOutTS(用于提取數(shù)據(jù)的參考條件)并記錄語(yǔ)音緩沖區(qū)中等待播放的樣本數(shù) sampleLeft。Write status data to shared memory
4:從抖動(dòng)緩沖區(qū)提取數(shù)據(jù)。WebRtcNetEQ_DSP2MCUinterrupt
5:遍歷查找抖動(dòng)緩沖區(qū)中數(shù)據(jù)包的時(shí)間戳。?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)計(jì)進(jìn)入接收端的 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 計(jì)算 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 的上一播放模式進(jìn)行 MCU 控制命令的判斷
9.根據(jù) MCU 的控制命令及當(dāng)前語(yǔ)音緩沖區(qū)中解碼后未被播放的數(shù)據(jù)量sampleLeft 進(jìn)行判斷考慮是否需要從抖動(dòng)緩沖區(qū)取數(shù)據(jù).? Check sync buffer size???? (Step 11)?
10.提取一個(gè)數(shù)據(jù)包送入共享內(nèi)存暫存器.WebRtcNetEQ_PacketBufferExtract
11.根據(jù)從抖動(dòng)緩沖區(qū)取數(shù)據(jù)之前的 MCU 的控制命令得到相應(yīng)的 DSP的處理命令。 Step 13
12.解碼取到的數(shù)據(jù)(Do decoding).inst->codec_ptr_inst.funcDecode()
13.丟包補(bǔ)償.inst->codec_ptr_inst.funcDecodePLC
14.根據(jù) DSP 操作命令進(jìn)入相應(yīng)的的播放模式對(duì)解碼數(shù)據(jù)及語(yǔ)音緩沖區(qū)中數(shù)據(jù)進(jìn)行相關(guān)操作。Step 15
15.從語(yǔ)音緩沖區(qū)的 curPosition 為起始位置取 10ms 數(shù)據(jù)傳輸?shù)铰暱āEBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1);