在WebRTC中,对于音频丢包,目前有三种丢包恢复方案:
- 带内FEC
- 带外FEC
- NACK
本文介绍其中最简单的带内FEC。带内FEC属于WebRTC中默认启用的功能,由Opus编解码器实现,经过我们测试,30%随机丢包率下语音聊天,仍有不错的质量。不过单一靠带内FEC是无法实现更高的抗丢包要求,例如突发丢包环境或者30%以上随机丢包率,这也是各个厂商的优化点。
Opus编解码器
Opus编解码器其实是一种混合音频编解码器,融合了SILK与CELT两种编解码器。
SILK编解码器
- 由Skype开发
- 使用线性预测
- 适合语音
CELT编解码器
- 由Xiph.Org开发
- 使用改进的离散余弦变换
- 适合音乐等音质要求高的
支持特性
- FEC(Forward Error Correction):前向纠错。使用低码率编码前面音频包数据,作为冗余信息。
- DTX(Discontinuous Transmission):非连续传输。在安静的情况时自动降低编码码率。
- PLC(Packet Loss Concealment):丢包隐藏。在解码端实现。通过前面音频数据包和后面音频数据包的相关性来预测当前丢失的音频数据包,对丢失的音频数据包进行补偿,隐藏当前的丢包错误。
关于Opus中SILK与CELT为什么在一起的故事,大家可以看下声网社区分享的这一篇文章:
编解码器杂谈:浅析Opus
带内FEC原理
使用低码率编码前面的历史音频包数据,作为冗余信息插入当前音频包中,同时结合解码端的PLC处理,从而实现不错的丢包恢复效果。
假设当前音频包采样时间为T,采样间隔为Δ,那么某音频编码后数据如下:
1 |
Audio Encoded Data: | T | T - Δ | |
携带T时刻编码数据,以及低码率编码的T - Δ时刻数据。
假设Δ为1,时间戳T从1开始,那么前6个包数据如下:
1 |
|1| | -> |2|1| -> |3|2| -> |4|3| -> |5|4| -> |6|5| |
如果时刻4的包丢了,那么接收端可以从时刻5的数据包恢复时刻4数据。
如果出现突发连续丢包,例如时刻4,5的数据包都丢了,那么时刻4数据就无法还原了,此时就要通过PLC算法进行丢包恢复。当然,如果使用了带外FEC以及NACK,也可以配合带内FEC用于丢包恢复。
代码导读
带内FEC整个闭环处理比较简单:
- 从Receiver Report中获取丢包率
- 平滑处理丢包率
- 平滑后的丢包率反馈给Opus编码器
AudioEncoderOpusImpl::OnReceivedUplinkPacketLossFraction
负责相关处理。
接收通过RTCP反馈的丢包率,传给Opus编码器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void AudioEncoderOpusImpl::OnReceivedUplinkPacketLossFraction( float uplink_packet_loss_fraction) { if (audio_network_adaptor_) { audio_network_adaptor_->SetUplinkPacketLossFraction( uplink_packet_loss_fraction); ApplyAudioNetworkAdaptor(); } // 对丢包率进行平滑处理 packet_loss_fraction_smoother_->AddSample(uplink_packet_loss_fraction); float average_fraction_loss = packet_loss_fraction_smoother_->GetAverage(); SetProjectedPacketLossRate(average_fraction_loss); } void AudioEncoderOpusImpl::SetProjectedPacketLossRate(float fraction) { // 允许的最大丢包率为20% fraction = std::min(std::max(fraction, 0.0f), kMaxPacketLossFraction); if (packet_loss_rate_ != fraction) { packet_loss_rate_ = fraction; // 传递丢包率到Opus编码器 RTC_CHECK_EQ( 0, WebRtcOpus_SetPacketLossRate( inst_, static_cast<int32_t>(packet_loss_rate_ * 100 + .5))); } } |
SDP处理
这里我们看下使用Opus编码器时默认生成的SDP:
1 2 |
a=rtpmap:111 opus/48000/2 a=fmtp:111 minptime=10;useinbandfec=1 |
可以看到useinbandfec=1
字段,表示开启带内FEC,最后通过WebRtcOpus_EnableFec
接口启用带内FEC功能。
优势与不足
由于带内FEC由编解码器实现,所以我们的工作量很少,可以很方便地将音频FEC的能力集成到我们的应用中。不过带内FEC中,音频包的冗余信息用的是低码率编码,所以还原后的音质会降低。
参考
[1] RTP Payload Format for the Opus Speech and Audio Codec.https://tools.ietf.org/html/rfc7587.
文章评论
大佬你好,请教下,你说的音频带外fec+nack的模式,是webrtc源生支持的吗
@ewan 是的,不过WebRTC的实现很初级
博主,想请教一个问题,关于webrtc的fec的rate的设置, 这个函数
bool VCMFecMethod::ProtectionFactor(const VCMProtectionParameters* parameters)
主要会根据当前帧的分辨率,码率和丢包率,从一个表中查到fec应该设的冗余度,但是在计算码率的时候使用了resolnFac对码率进行缩放,不太理解后面的思想,博主能看出来吗
WebRTC的音频带外 FEC 实现指的是哪些类呢?
大佬,你好,请教一下这个默认值为什么最大只允许%20,提高会有什么负面作用吗, 比如%8 90 ? ,这边尝试写死 0.8 抗丢包能力会有大幅提升。
@sola 同样编码码率情况下,提高这个值会导致音质降低,看你们怎么权衡了