2020-10-23 netEQ

webRTC中音频相关的netEQ(一):概述

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

NetEQ算法中集成了自适应抖动控制算法以及语音包丢失隐藏算法。这项技术使其能够快速且高解析度地适应不断变化的网络环境,确保音质优美且缓冲延迟最小。

研究的重点是 NetEQ 模块,其中所涉及的处理过程包括抖动消除、丢包补偿和压缩解码。

抖动消除原理

抖动通常采用抖动缓冲技术来消除,即在接收方建立一个缓冲区,语音包到达接收端时首先进人缓冲区暂存,随后系统再以稳定平滑的速率将语音包从缓冲

区提取出来,经解压后从声卡播出。

4 个语音数据包(A、B、C、D)以 30ms 为间隔进行发送,即发送时间分别为 30,60,90,120ms,网络延迟分别为10,30,10,10ms。到达时间为40,90,100,130ms,所以需要在抖动缓冲中分别缓冲60,90,120,150ms。

1.静态抖动缓冲控制算法

2.自适应抖动缓冲控制算法

丢包隐藏原理

iLBC 的丢包隐藏只是在解码端进行处理,即在解码端根据收到的比特流逐帧进行解码的过程中,iLBC解码器首先拿到每帧的比特流时判断当前帧是否完整,如果没有问题则按照正常的 iLBC解码流程重建语音信号,如果发现语音数据包丢失,那么就进入 PLC 单元进行处理。见算法步骤13

MCU(Micro Control Unit)模块是抖动缓冲区的微控制单元,由于抖动缓冲区作用是暂存接收到的数据包,因此 MCU 的主要作用是安排数据包的插入并控制数据包的输出。数据包的插入主要是确定来自网络的新到达的数据包在缓冲区中的插入位置,而控制数据包的输出则要考虑什么时候需要输出数据,以及输出哪一个插槽的数据包。抖动消除的算法思路在 MCU 控制模块中得以体现。

DSP 模块主要负责对从 MCU 中提取出来的 PCM 源数据包进行数字信号处理,

包括解码、信号处理、数据输出等几个部分。丢包隐藏操作即在 DSP 模块中完成。

NetEQ 模块框图

网络数据包进入抖动缓冲区的过程,其基本步骤如下,

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:解析数据包并将其插入到抖动缓冲区中( Parse the payload and insert it into the buffer)。WebRtcNetEQ_SplitAndInsertPayload

WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, &localFlushed);循环将输入数据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 中的参数,重点计算网络延迟的统计值 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 数据到声卡的算法过程

3:将 DSP 的 endTimeStamp 赋值给 playedOutTS(用于提取数据的参考条件)并记录语音缓冲区中等待播放的样本数 sampleLeft。Write status data to shared memory

4:从抖动缓冲区提取数据。WebRtcNetEQ_DSP2MCUinterrupt

5:遍历查找抖动缓冲区中数据包的时间戳。WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst,inst->timeStamp, &uw32_availableTS, &i_bufferpos, 1, &payloadType);

5.1

5.2

6:统计进入接收端的 NetEQ 模块但仍未被播放的数据量,记为 bufsize。calculate total current buffer size (in ms*8), including sync buffer

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

7.根据 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.根据 BLo(optBufferLevel)、BLc(bufferLevelFilt)、bufsize、playedOutTS、availableTS 及 NetEQ 的上一播放模式进行 MCU 控制命令的判断

9.根据 MCU 的控制命令及当前语音缓冲区中解码后未被播放的数据量sampleLeft 进行判断考虑是否需要从抖动缓冲区取数据. Check sync buffer size(Step 11)

10.提取一个数据包送入共享内存暂存器.WebRtcNetEQ_PacketBufferExtract

11.根据从抖动缓冲区取数据之前的 MCU 的控制命令得到相应的 DSP的处理命令。 Step 13

12.解码取到的数据(Do decoding).inst->codec_ptr_inst.funcDecode()

13.丢包补偿.inst->codec_ptr_inst.funcDecodePLC

14.根据 DSP 操作命令进入相应的的播放模式对解码数据及语音缓冲区中数据进行相关操作。Step 15

15.从语音缓冲区的 curPosition 为起始位置取 10ms 数据传输到声卡。WEBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1);


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

语音通信中终端上的时延(latency)及减小方法)说从本篇开始会切入webRTC中的netEQ主题,netEQ是webRTC中音频技术方面的两大核心技术之一(另一核心技术是音频的前后处理,包括AEC、ANS、AGC等,俗称3A算法)。webRTC是Google收购GIPS重新包装后开源出来的,目前已是有巨大影响力的实时音视频通信解决方案。国内的互联网公司,要做实时音视频通信产品,绝大多数都是基于webRTC来做的,有的是直接用webRTC的解决方案,有的是用webRTC里的核心技术,比如3A算法。不仅互联网公司,其他类型公司(比如通信公司),也会把webRTC里的精华用到自己的产品中。我刚开始做voice engine时,webRTC还未开源,但那时就知道了GIPS是一家做实时语音通信的顶级公司。webRTC开源后,一开始没机会用,后来做OTT语音(APP语音)时用了webRTC里的3A算法。在做了Android手机平台上的音频开发后,用了webRTC上的netEQ,不过用的是较早的C语言版本,不是C++版本,并且只涉及了netEQ中的DSP模块(netEQ有两大模块,MCU(micro control unit, 微控制单元)和DSP(digital signal processing, 信号处理单元),MCU负责控制从网络收到的语音包在jitter  buffer里的插入和提取,同时控制DSP模块用哪种算法处理解码后的PCM数据,DSP负责解码以及解码后的PCM信号处理,主要PCM信号处理算法有加速、减速、丢包补偿、融合等),MCU模块在CP (communication processor, 通讯处理器)上做,两个模块之间通过消息交互。DSP模块经过调试,基本上掌握了机制。MCU模块由于在CP上做,没有source code,我就从网上找来了我们用的版本相对应的webRTC的开源版本,经过了一段时间的理解后,也基本上搞清楚了机制。从本篇开始,我将花几篇讲netEQ(基于我用的早期C语言版本)。这里需要说明的是每个产品在使用webRTC上的代码时都会根据自己产品的特点做一定的修改,我做的产品也不例外。我在讲时一些细节不会涉及,主要讲机制。本篇先对netEQ做一个概述。


实时IP语音通信的软件架构框图通常如下图:

上图中发送方(或叫上行、TX)将从MIC采集到的语音数据先做前处理,然后编码得到码流,再用RTP打包通过UDP socket发送到网络中给对方。接收方(或叫下行、RX)通过UDP socket收语音包,解析RTP包后放入jitter buffer中,要播放时每隔一定时间从jitter buffer中取出包并解码得到PCM数据,做后处理后送给播放器播放出来。


netEQ模块在接收侧,它是把jitter buffer和decoder综合起来并加入解码后的PCM信号处理形成,即netEQ = jitter buffer + decoder + PCM信号处理。这样上图中的软件架构框图就变成下图:


上文说过netEQ模块主要包括MCU和DSP两大单元。它的软件框图如下图:

从上两图看出,jitter buffer(也就是packet  buffer,后面就跟netEQ一致,表述成packet buffer,用于去除网络抖动)模块在MCU单元内,decoder和PCM信号处理模块在DSP单元内。MCU单元主要负责把从网络侧收到的语音包经过RTP解析后往packet  buffer里插入(insert),以及从packet buffer 里提取(extract)语音包给DSP单元做解码、信号处理等,同时也算网络延时(optBufLevel)和抖动缓冲延时(buffLevelFilt),根据网络延时和抖动缓冲延时以及其他因素(上一帧的处理方式等)决定给DSP单元发什么信号处理命令。主要的信号处理命令有5种,一是正常播放,即不需要做信号处理。二是加速播放,用于通话延时较大的情况,通过加速算法使语音信息不丢而减少语音时长,从而减少延时。三是减速播放,用于语音断续情况,通过减速算法使语音信息不丢而增加语音时长,从而减少语音断续。四是丢包补偿,用于丢包情况,通过丢包补偿算法把丢掉的语音补偿回来。五是融合(merge),用于前一帧丢包而当前包正常收到的情况,由于前一包丢失用丢包补偿算法补回了语音,与当前包之间需要做融合处理来平滑上一补偿的包和当前正常收到的语音包。以上几种信号处理提高了在恶劣网络环境下的语音质量,增强了用户体验。可以说是在目前公开的处理语音的网络丢包、延时和抖动的方案中是最佳的了。


DSP单元主要负责解码和PCM信号处理。从packet buffer提取出来的码流解码成PCM数据放进decoded_buffer中,然后根据MCU给出的命令做信号处理,处理结果放在algorithm_buffer中,最后将algorithm_buffer中的数据放进speech_buffer待取走播放。Speech_buffer中数据分两块,一块是已播放过的数据(playedOut),另一块是未播放的数据(sampleLeft), curPosition就是这两种数据的分割点。另外还有一个变量endTimestamps用于记录最后一个样本的时间戳,并报告给MCU,让MCU根据endTimestamps和packet buffer里包的timestamp决定是否要能取出包以及是否要取出包。


这里先简要介绍一下netEQ的处理过程,后面文章中会详细讲。处理过程主要分两部分,一是把RTP语音包插入packet packet的过程,二是从packet buffer中提取语音包解码和PCM信号处理的过程。先看把RTP语音包插入packet packet的过程,主要有三步:

1,在收到第一个RTP语音包后初始化netEQ。

2,解析RTP语音包,将其插入到packet buffer中。在插入时根据收到包的顺序依次插入,到尾部后再从头上继续插入。这是一种简单的插入方法。

3,计算网络延时optBufLevel。

再看怎么提取语音包并解码和PCM信号处理,主要有六步:

1,将DSP模块的endTimeStamp赋给playedOutTS,和sampleLeft(语音缓冲区中未播放的样本数)一同传给MCU,告诉MCU当前DSP模块的播放状况。

2,看是否要从packet buffer里取出语音包,以及是否能取出语音包。取出包时用的是遍历整个packet buffer的方法,根据playedOutTS找到最小的大于等于playedOutTS的时间戳,记为availableTS,将其取出来。如果包丢了就取不到包。

3,算抖动缓冲延时buffLevelFilt。

4,根据网络延时抖动缓冲延时以及上一帧的处理方式等决定本次的MCU控制命令。

5,如果有从packet buffer里提取到包就解码,否则不解码。

6,根据MCU给的控制命令对解码后的以及语音缓冲区里的数据做信号处理。


在我个人看来,netEQ有两大核心技术点。一是计算当前网络延时和抖动缓冲延时的算法。要根据网络延时、抖动缓冲延时和其他因素决定信号处理命令,信号处理命令对了能提高音质,相反则会降低音质,所以说信号处理命令的决策非常关键。二是各种信号处理算法,主要有加速(accelerate)、减速(preemptive expand)、丢包补偿(PLC)、融合(merge)和背景噪声生成(BNG),这些都是非常专业的算法。

分类: 传统音频

好文要顶 关注我 收藏该文  

davidtym

关注 - 6

粉丝 - 133

+加关注

3

0

« 上一篇: 语音通信中终端上的时延(latency)及减小方法

» 下一篇: webRTC中音频相关的netEQ(二):数据结构

posted on 2018-07-16 08:29 davidtym阅读(7970)  评论(4) 编辑 收藏

评论

#1楼 2019-11-02 11:51 Piasy

楼主你好!最近我在研究 WebRTC 最新代码 NetEQ 这部分的内容,找到了你的博客。你的这几篇博客写得非常好!你的其他的博客我也看了一些,越看越希望能和你建立联系,我也写博客,这是我的个人介绍页: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 算法中集成了自适应抖动控制算法以及语音包丢失隐藏算法。这项技术使其能够快速且高解析度地适应不断变化的网络环境,确保音质优美且缓冲延迟最小。

研究的重点是 NetEQ 模块,其中所涉及的处理过程包括抖动消除、丢包补偿和压缩解码。

抖动消除原理

抖动通常采用抖动缓冲技术来消除,即在接收方建立一个缓冲区,语音包到达接收端时首先进人缓冲区暂存,随后系统再以稳定平滑的速率将语音包从缓冲

区提取出来,经解压后从声卡播出。

4 个语音数据包(A、B、C、D)以 30ms 为间隔进行发送,即发送时间分别为 30,60,90,120ms,网络延迟分别为10,30,10,10ms。到达时间为40,90,100,130ms,所以需要在抖动缓冲中分别缓冲60,90,120,150ms。

1.静态抖动缓冲控制算法

2.自适应抖动缓冲控制算法

丢包隐藏原理

iLBC 的丢包隐藏只是在解码端进行处理,即在解码端根据收到的比特流逐帧进行解码的过程中,iLBC  解码器首先拿到每帧的比特流时判断当前帧是否完整,如果没有问题则按照正常的 iLBC  解码流程重建语音信号,如果发现语音数据包丢失,那么就进入 PLC 单元进行处理。见算法步骤13


MCU(Micro Control Unit)模块是抖动缓冲区的微控制单元,由于抖动缓冲区作用是暂存接收到的数据包,因此 MCU 的主要作用是安排数据包的插入并控制数据包的输出。数据包的插入主要是确定来自网络的新到达的数据包在缓冲区中的插入位置,而控制数据包的输出则要考虑什么时候需要输出数据,以及输出哪一个插槽的数据包。抖动消除的算法思路在 MCU 控制模块中得以体现。

DSP 模块主要负责对从 MCU 中提取出来的 PCM 源数据包进行数字信号处理,

包括解码、信号处理、数据输出等几个部分。丢包隐藏操作即在 DSP 模块中完成。

NetEQ 模块框图


网络数据包进入抖动缓冲区的过程,其基本步骤如下,


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

{

...

memcpy(payloadincoming_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:解析数据包并将其插入到抖动缓冲区中( Parse the payload and insert it into the buffer)。WebRtcNetEQ_SplitAndInsertPayload

WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, &localFlushed);循环将输入数据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 中的参数,重点计算网络延迟的统计值 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 数据到声卡的算法过程

3:将 DSP 的 endTimeStamp 赋值给 playedOutTS(用于提取数据的参考条件)并记录语音缓冲区中等待播放的样本数 sampleLeft。Write status data to shared memory

4:从抖动缓冲区提取数据。WebRtcNetEQ_DSP2MCUinterrupt

5:遍历查找抖动缓冲区中数据包的时间戳。 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:统计进入接收端的 NetEQ 模块但仍未被播放的数据量,记为 bufsize。calculate total current buffer size (in ms*8), including sync buffer

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

7.根据 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.根据 BLo(optBufferLevel)、BLc(bufferLevelFilt)、bufsize、playedOutTS、availableTS 及 NetEQ 的上一播放模式进行 MCU 控制命令的判断

9.根据 MCU 的控制命令及当前语音缓冲区中解码后未被播放的数据量sampleLeft 进行判断考虑是否需要从抖动缓冲区取数据.  Check sync buffer size     (Step 11) 

10.提取一个数据包送入共享内存暂存器.WebRtcNetEQ_PacketBufferExtract

11.根据从抖动缓冲区取数据之前的 MCU 的控制命令得到相应的 DSP的处理命令。 Step 13

12.解码取到的数据(Do decoding).inst->codec_ptr_inst.funcDecode()

13.丢包补偿.inst->codec_ptr_inst.funcDecodePLC

14.根据 DSP 操作命令进入相应的的播放模式对解码数据及语音缓冲区中数据进行相关操作。Step 15

15.从语音缓冲区的 curPosition 为起始位置取 10ms 数据传输到声卡。WEBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1);

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352