参考: https://www.cnblogs.com/talkaudiodev/p/9142192.html
https://www.cnblogs.com/talkaudiodev/p/9185067.html
https://www.cnblogs.com/talkaudiodev/p/9231526.html
https://www.cnblogs.com/talkaudiodev/p/9459010.html
https://www.cnblogs.com/talkaudiodev/p/9501438.html
随着WebRTC版本的不断更新,内部代码结构也有了很大的变化,但是对于底层的音视频引擎来讲,却没有什么太大的变化,因为引擎早很早之前就已经达到了一个非常成熟稳定的状态(早期的QQ音频就已经使用了GIPS的NetEQ)。读研期间我就研究过NetEQ中的一些理论,不过那时候研究的很浅,主要是通过查找各种资料来学习NetEQ,其实我也很奇怪,如此有大名鼎鼎的技术,并且已经开源了,为什么网上研究它的资料少之又少,除了西安电子大学吴江锐的那篇硕士论文,基本上就没有任何有价值的资料了。估计自己以后还会持续的研究NetEQ,所以准备一边研究一边记录。
NetEQ是GIPS公司的核心音频引擎技术,后来GIPS公司被Google收购,这项技术落到了Google手里并随着WebRTC的开源一起被公之于众。NetEQ是从接收端来处理语音包的,主要的功能是抖动消除,丢包隐藏,在网络延迟大的时候降低丢包率,在网络条件好的时候减小时延。
M66版本的WebRTC中NetEQ代码接口的定义在src/modules/audio_coding/neteq/include/neteq.h头文件中,而具体的实现是在src/modules/audio_coding/neteq/neteq_impl.cc文件中,neteq.h文件中定义了相当多的接口,但其实最重要的接口就只有两个,第一个是向NetEQ模块中插入解析过后的从网络中来到的RTP数据包,二是从NetEQ模块中取解码过后的pcm音频数据。所以其实我们可以很简单的将NetEQ看成是一个黑盒,我们往里面扔网络中接收到的RTP数据包,它会给我们吐出解码或者经过其它处理过后的pcm音频数据,然后我们拿去播放,至于中间的一些过程,例如抖动消除,解码,丢包隐藏,语音的拉伸和压缩以及它们之间如何配合,都是我们可以不用去关心的,其实也就是NetEQ真正的价值所在。
neteq.h的中两个重要函数分别是InsertPacket()和GetAudio(),在neteq_impl.cc中的具体实现InsertPacketInternal()和GetAudioInternal(),与前面的塞包和取包的描述一一对应。下面主要介绍这两个函数的主要流程,这两个函数的流程也就体现了NetEQ整个处理语音包的流程。
int NetEqImpl::InsertPacketInternal(const RTPHeader& rtp_header,
rtc::ArrayView<const uint8_t> payload,
uint32_t receive_timestamp) {
if (payload.empty()) {
RTC_LOG_F(LS_ERROR) << "payload is empty";
return kInvalidPointer;
}
PacketList packet_list;
// Insert packet in a packet list.
packet_list.push_back([&rtp_header, &payload] {
// Convert to Packet.
Packet packet;
packet.payload_type = rtp_header.payloadType;
packet.sequence_number = rtp_header.sequenceNumber;
packet.timestamp = rtp_header.timestamp;
packet.payload.SetData(payload.data(), payload.size());
// Waiting