这几天在做一些WebRTC音频改进方面的调查工作,在阅读Chromium源码的过程中,就顺便记录下来,便于日后回顾。本文基于Chromium 85源码分析,由于Chromium的快速发展,很有可能不适合于跨度太大的Chromium版本。
大家知道Opus内置了两种编码器:CELT和SILK,并且可以针对采样率、采样间隔、码率、通道数……等属性进行设置。创建的参数设置,是从sdp来的。本文的主要目的,是来看看sdp中的信息,是如何对应到native世界里的代码的。
Native调用序列:
上图中,我保留的起点是从channel.cc中的VoiceChannel::SetRemoteContent_w() 方法被调用开始的,再往上的调用栈就没有画了。整个序列中,需要关注的主要是 AudioEncoderOpusImpl 的 SdpToConfig 和 RecreateEncoderInstance 这两个方法。
SdpToConfig,顾名思义,它会把sdp的内容(SdpAudioFormat)转换成 AudioEncoderOpusConfig 对象。AudioEncoderOpusConfig类的头文件件位于:webrtc\api\audio_codecs\opus\audio_encoder_opus_config.h。它定义了十几个Opus的相关属性,如采样率sample_rate_hz、帧长度frame_size_ms、通道数num_channels、音频模式application(决定内部编码使用celt还是silk)、fec_enabled、cbr_enabled……等等。
OK,接下来,让我们拿一个具体的例子来展开说明。假设SDP中音频部分是这样:
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:d3VL
a=ice-pwd:5ccIbts7ExcJCOlQXdRs+lzp
a=ice-options:trickle
a=fingerprint:sha-256 67:E5:3E:05:AC:F4:CE:56:06:4B:7B:74:AB:DA:92:D2:CE:88:1E:2E:78:13:49:69:EA:F3:2B:A0:BB:04:40:DA
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;stereo=1; sprop-stereo=1;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:286098906 cname:/7O5RyfmfX+yNmDJ
a=ssrc:286098906 msid:Cs59oGPYCoRopRCJ6lNE8Rw2B5QuuBRD0Dqq 4db71fa8-4266-409a-869c-d6eecc7e98b8
a=ssrc:286098906 mslabel:Cs59oGPYCoRopRCJ6lNE8Rw2B5QuuBRD0Dqq
a=ssrc:286098906 label:4db71fa8-4266-409a-869c-d6eecc7e98b8
让我们根据这份SDP,来分析一下它里面几个关键的部分,对应native的实现是怎么样的。
a=rtpmap:111 opus/48000/2
这一行是固定格式不能更改。因为从 SdpToConfig 函数的开头就能看出来:
if (!absl::EqualsIgnoreCase(format.name, "opus") ||
format.clockrate_hz != kRtpTimestampRateHz || format.num_channels != 2) {
return absl::nullopt;
}
其中 kRtpTimestampRateHz = 48000。所以,SDP中这一行,并不代表默认Opus就是使用48KHz和双通道来创建编码器,这个只是一个约束而已。
a=rtcp-fb:111 transport-cc
这一行是启用RTCP的一种反馈机制。它对应的文档规范URL在sdp中是这个:
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
但似乎这个链接已经失效了,最新的是这个:https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=fmtp:111 minptime=10;stereo=1; sprop-stereo=1;useinbandfec=1
这一行比较重要。我们如果需要额外设置Opus参数,基本上就是要修改这一行SDP来达到我们的目的。例子SDP中,主要设置了4个属性: minptime,stereo,sprop-stereo,useinbandfec。其实还有很多,都有哪些Opus属性可以设置呢?参考 RFC7587(RTP Payload Format for the Opus Speech and Audio Codec) 的 6.1 Opus Media Type Registration 这一节。列举如下:
SDP属性 | 含义(谷歌翻译) |
---|---|
maxplaybackrate | 接收器能够呈现的最大输出采样率,以Hz为单位。 解码器必须能够解码任何音频带宽,但是由于硬件限制,只能播放指定采样率以下的信号。 发送具有更高音频带宽的信号会导致超出必要的网络使用率和编码复杂度,因此编码器不应编码高于maxplaybackrate指定的音频带宽的频率。 尽管通常该值将与Opus带宽之一匹配(表1),但是该参数可以采用8000到48000之间的任何值。 缺省情况下,假设接收机没有限制,即48000。 |
sprop-maxcapturerate | 发送方可能产生的最大输入采样率。 这不能保证发送方永远不会发送任何更高的带宽(例如,它可以发送使用更高带宽的预先录制的提示),但可以向接收方指示可以安全地丢弃高于此最大值的频率。 通过以高于必要的速率操作音频处理管道(例如回声消除)来避免浪费接收机资源,该参数是有用的。 尽管通常该值将与Opus带宽之一匹配(表1),但该参数可以采用8000到48000之间的任何值。 缺省情况下,假设发送方没有限制,即48000。 |
maxptime | 解码器要接收的数据包所代表的最大媒体持续时间(根据[RFC4566]第6节),以毫秒为单位,四舍五入到下一个完整整数值。 可能的值为3、5、10、20、40、60或Opus帧大小的任意倍数,四舍五入为下一个完整整数值,最大为120,如第4节中所定义。 指定,默认值为120。 |
minptime | 数据包所代表的媒体的最短持续时间(根据[RFC4566]的第6节),应该封装在接收到的数据包中,以毫秒为单位,四舍五入到下一个完整整数值。 可能的值为3、5、10、20、40和60,或者Opus帧大小的任意倍数舍入为第4节中定义的下一个完整整数值,最大为120。 默认值为3。此值是解码端的建议,以确保解码器具有最佳性能。 解码器必须能够接受任何允许的包大小,以确保最大的兼容性。 |
ptime | 解码器想要接收的由数据包表示的媒体的首选持续时间(根据[RFC4566]第6节),以毫秒为单位,四舍五入到下一个完整整数值。 可能的值为3、5、10、20、40、60或Opus帧大小的任意倍数,四舍五入为下一个完整整数值,最大为120,如第4节中所定义。 指定,默认值为20。 |
maxaveragebitrate | 指定会话的最大平均平均接收比特率,以每秒比特数(b / s)为单位。 比特率的实际值可能会有所不同,因为它取决于数据包中媒体的特性。 注意最大平均比特率可以在会话期间动态修改。 允许使用任何正整数,但应忽略范围在6000到510000之间的值。 如果未指定任何值,则默认值是在3.1.1节中为相应的Opus模式指定的最大值和相应的maxplaybackrate。 |
stereo | 指定解码器是喜欢接收立体声信号还是单声道信号。 可能的值为1和0,其中1表示首选立体声信号,0表示仅首选单声道信号。 每个接收器都必须独立于立体声参数而能够接收和解码立体声信号,但是将立体声信号发送给接收器,该信号指示对单声道信号的偏爱可能会导致网络利用率和编码复杂性高于必要水平。 如果未指定任何值,则默认值为0(单声道) |
sprop-stereo | 指定发送者是否可能产生立体声音频。 可能的值是1和0,其中1指定可能发送立体声信号,0指定发送方可能仅发送单声道。 这不能保证发送方将永远不会发送立体声音频(例如,它可以发送使用立体声的预先录制的提示),但是可以向接收方指示可以安全地将接收到的信号降混为单声道。 该参数对于避免不必要时通过以立体声操作音频处理管道(例如回声消除)来避免浪费接收机资源很有用。 如果未指定任何值,则默认值为0(单声道) |
cbr | 指定解码器是否更喜欢使用恒定比特率而不是可变比特率。 可能的值为1和0,其中1表示恒定比特率,0表示可变比特率。 如果未指定任何值,则默认值为0(vbr)。 当cbr为1时,最大平均比特率仍然可以更改,例如 适应不断变化的网络条件 |
useinbandfec | 指定解码器具有利用Opus带内FEC的能力。 可能的值为1和0。建议在接收方无法使用FEC时提供0。 如果未指定任何值,则useinbandfec假定为0。此参数仅是一个首选项,并且即使表示FEC部分已被丢弃,接收方也必须能够处理包含FEC信息的数据包。 |
usedtx | 指定解码器是否更喜欢使用DTX。 可能的值为1和0。如果未指定任何值,则默认值为0。 |
OK,我们来看看这些属性和native的 AudioEncoderOpusConfig 的对应关系:
SDP属性 | AudioEncoderOpusConfig对应的成员变量 |
---|---|
maxplaybackrate | int max_playback_rate_hz; //默认48000 |
sprop-maxcapturerate | 没有找到相关native属性对应 |
minptime, maxptime | std::vector supported_frame_lengths_ms; // 20,40,60中的1个或全部 |
ptime | int frame_size_ms; //默认20 |
maxaveragebitrate | 参考CalculateBitrate函数实现。如果不设置,会根据通道数和maxplaybackrate来计算。例如,如果maxplaybackrate是48000,单通道,码率是32000。双通道则是64000。 |
stereo | int num_channels; //stereo设置为1时,通道数是2 |
sprop-stereo | 没有找到相关native属性对应 |
cbr | bool cbr_enabled; |
useinbandfec | bool fec_enabled; |
usedtx | bool dtx_enabled; |
除了上面一些属性以外,AudioEncoderOpusConfig 还有一个 ApplicationMode {kVoip, kAudio}的属性。当通道数是1时,取值kVoip(语音),否则是kAudio(音频)。它其实对应的是Opus的application属性。关于这个属性,参考:https://www.opus-codec.org/docs/html_api/group__opusencoder.html。其实Opus一共有三种application模式:
OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice signals. It enhances the input signal by high-pass filtering and emphasizing formants and harmonics. Optionally it includes in-band forward error correction to protect against packet loss. Use this mode for typical VoIP applications. Because of the enhancement, even at high bitrates the output may sound different from the input. 可在给定的比特率下为语音信号提供最佳质量。它通过高通滤波并强调共振峰和谐波来增强输入信号。可选地,它包括带内前向纠错,以防止数据包丢失。对典型的VoIP应用程序使用此模式。由于增强,即使在高比特率下,输出听起来也可能与输入不同。
OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most non-voice signals like music. Use this mode for music and mixed (music/voice) content, broadcast, and applications requiring less than 15 ms of coding delay. 可在给定的比特率下为音乐等大多数非语音信号提供最佳质量。对于音乐和混合(音乐/声音)内容,广播以及要求少于15 ms编码延迟的应用程序,请使用此模式。
OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay. 配置低延迟模式,该模式禁用语音优化模式,以换取稍微减少的延迟。
而第三种WebRTC并没有使用。因此,我们可以看出,WebRTC默认是单通道走VoIP模式,如果想采用Audio模式,则只需要设置双通道(sdp中 stereo设置为1)即可。因此,根据你的使用场景,例如是音乐老师教乐器,建议还是在sdp中设置 stereo为1。如果只是日常沟通,如视频会议,默认的 VoIP就足够啦。
好了,到这里为止,我们已经十分清楚了SDP中的设置和native层的对应关系。接下来如何根据需要改变Opus编码器参数设置,想必就是一件很容易的事了。不过,到这里,还有一些疑问我还没有找到答案:
- Opus 的两种 application mode (VoIP、Audio),在RTC应用中真实的差别到底有多大?
- 弱网下如何改进音频表现?除了NACK和FEC,是否需要动态调整音频码率?双通道改为单通道?
- maxptime给不同的取值,对真实使用中,尤其复杂网络条件下的影响几何?
- 6kbps - 510kbps的码率允许范围内,不同的码率,如果在相同的弱网条件下,WebRTC解码端的表现和码率之间的关系是怎样的?
……
这些问题,还需要做一些工作和试验才能找到答案了。