两种Opus编码器image.png

当我们使用单通道或双通道的时候就使用webrtc_opus,超过2个通道的音频就选择webrtc_multiopus。

Opus编码器API

image.png
下面的api介绍来自《webrtc音频QOS方法二(opus编码器自适应网络参数调整功能)》
https://blog.csdn.net/CrystalShaw/article/details/106940151

H:\webrtc-20210315\webrtc-20210315\webrtc\webrtc-checkout\src\modules\audio_coding\codecs\opus\opus_interface.h

1、WebRtcOpus_SetBitRate

  1. Opus支持码率从6 kbit/s510 kbit/s的切换功能,以适应这种网络状态。以20ms单帧数据编码为例,下面是各种配置的Opus的比特率最佳点。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2691823/1650167432742-fcf8c296-4f55-468e-8e53-21742b8385d9.png#clientId=u97970bc3-39da-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u8c943f04&margin=%5Bobject%20Object%5D&name=image.png&originHeight=249&originWidth=529&originalType=url&ratio=1&rotation=0&showTitle=false&size=35469&status=done&style=none&taskId=u13597730-2a2b-4e23-8eef-f92d36b9d5d&title=)

2、WebRtcOpus_SetPacketLossRate

  1. 动态配置丢包率,是为了动态调整opus FEC的冗余度。opus编码器自带inband FEC冗余算法,增强抗丢包能力。大概使用的是非对称冗余协议。将一些关键信息多次编码重传。

3、WebRtcOpus_EnableFec/DisableFec

  1. 开启或者关闭inband FEC功能。<br /> 走读opus代码,发现只有silk编码支持inband FEC。函数实现调用栈如下:
  1. opus_encode_native
  2. ->silk_Encode
  3. ->silk_encode_frame_Fxx
  4. ->silk_encode_frame_FLP
  5. ->silk_LBRR_encode_FLP

celt不支持inband FEC。猜测celt是通过改变参考帧长度,来增强抗网络丢包能力。

4、WebRtcOpus_EnableDtx/DisableDtx

  1. DTXDiscontinuous Transmission。不同于music场景,在voip场景下,声音不是持续的,会有一段一段的间歇期。这个间歇期若是也正常编码音频数据,对带宽有些浪费。所以opus支持DTX功能,若是检测当前会议没有明显通话声音,仅定期发送(400ms)静音指示报文给对方。对方收到静音指示报文可以补舒适噪音包(opus不支持CNG,不能补舒适噪音包)或者静音包给音频渲染器。

5、WebRtcOpus_EnableCbr/DisableCbr

opus支持恒定码率和变码率两种编码方式。一般流媒体使用CBR,voip场景使用VBR。

6、WebRtcOpus_SetMaxPlaybackRate

  1. 根据采样率调整算法bandwidth参数。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2691823/1650167505340-e1ff24cf-3a38-4729-ac35-c09527578f7e.png#clientId=u97970bc3-39da-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u34c85553&margin=%5Bobject%20Object%5D&name=image.png&originHeight=282&originWidth=663&originalType=url&ratio=1&rotation=0&showTitle=false&size=33019&status=done&style=none&taskId=u56d8546a-ebaa-4fd1-83fc-82f492702bb&title=)

7、WebRtcOpus_SetComplexity

  1. 取值范围0-10。值越大代码复杂度越高,音质越好。webrtc里面只有安卓、IOSARM支持复杂度切换功能。windows系统默认都是9

8、WebRtcOpus_SetForceChannels

  1. opus支持单双声道切换功能。当传入数据是双声道,解码器是单声道,解码器会average左右声道数据,以单声道数据输出。

当传入数据是单声道,解码器是双声道,解码器会给左右声道输出同一份数据。一般voip使用单声道传输,music使用双声道,这种单双声道切换,主要提升music场景下抗弱网能力。

9、WebRtcOpus_EncoderCreate.application

  1. #define OPUS_APPLICATION_VOIP 2048<br /> #define OPUS_APPLICATION_AUDIO 2049<br /> #define OPUS_APPLICATION_RESTRICTED_LOWDELAY 2051<br />application有三种模式:voipmusiclowdelay三种模式。<br />voip主要使用SILK编码,music主要使用CELT编码。lowdelay取消voip场景的一些优化方案,换取一丢丢低延时。

SetupSendCodec代码调用流程

image.png

代码分析

AudioSendStream::SetupSendCodec

  1. bool AudioSendStream::SetupSendCodec(const Config& new_config) {
  2. RTC_DCHECK(new_config.send_codec_spec);
  3. const auto& spec = *new_config.send_codec_spec;
  4. RTC_DCHECK(new_config.encoder_factory);
  5. std::unique_ptr<AudioEncoder> encoder =
  6. new_config.encoder_factory->MakeAudioEncoder(
  7. spec.payload_type, spec.format, new_config.codec_pair_id);
  8. if (!encoder) {
  9. RTC_DLOG(LS_ERROR) << "Unable to create encoder for "
  10. << rtc::ToString(spec.format);
  11. return false;
  12. }
  13. // If a bitrate has been specified for the codec, use it over the
  14. // codec's default.
  15. if (spec.target_bitrate_bps) {
  16. encoder->OnReceivedTargetAudioBitrate(*spec.target_bitrate_bps);
  17. }
  18. // Enable ANA if configured (currently only used by Opus).
  19. if (new_config.audio_network_adaptor_config) {
  20. if (encoder->EnableAudioNetworkAdaptor(
  21. *new_config.audio_network_adaptor_config, event_log_)) {
  22. RTC_LOG(LS_INFO) << "Audio network adaptor enabled on SSRC "
  23. << new_config.rtp.ssrc;
  24. } else {
  25. RTC_LOG(LS_INFO) << "Failed to enable Audio network adaptor on SSRC "
  26. << new_config.rtp.ssrc;
  27. }
  28. }
  29. // Wrap the encoder in an AudioEncoderCNG, if VAD is enabled.
  30. if (spec.cng_payload_type) {
  31. AudioEncoderCngConfig cng_config;
  32. cng_config.num_channels = encoder->NumChannels();
  33. cng_config.payload_type = *spec.cng_payload_type;
  34. cng_config.speech_encoder = std::move(encoder);
  35. cng_config.vad_mode = Vad::kVadNormal;
  36. encoder = CreateComfortNoiseEncoder(std::move(cng_config));
  37. RegisterCngPayloadType(*spec.cng_payload_type,
  38. new_config.send_codec_spec->format.clockrate_hz);
  39. }
  40. // Wrap the encoder in a RED encoder, if RED is enabled.
  41. if (spec.red_payload_type) {
  42. AudioEncoderCopyRed::Config red_config;
  43. red_config.payload_type = *spec.red_payload_type;
  44. red_config.speech_encoder = std::move(encoder);
  45. encoder = std::make_unique<AudioEncoderCopyRed>(std::move(red_config));
  46. }
  47. // Set currently known overhead (used in ANA, opus only).
  48. // If overhead changes later, it will be updated in UpdateOverheadForEncoder.
  49. {
  50. MutexLock lock(&overhead_per_packet_lock_);
  51. size_t overhead = GetPerPacketOverheadBytes();
  52. if (overhead > 0) {
  53. encoder->OnReceivedOverhead(overhead);
  54. }
  55. }
  56. StoreEncoderProperties(encoder->SampleRateHz(), encoder->NumChannels());
  57. channel_send_->SetEncoder(new_config.send_codec_spec->payload_type,
  58. std::move(encoder));
  59. return true;
  60. }

image.png
断点调试,当前选择的就是opus
image.png
获取编码器参数后,就会调用MakeAudioEncoder创建opus编码器
—》

MakeAudioEncoder

-》

  1. std::unique_ptr<AudioEncoder> MakeAudioEncoder(
  2. int payload_type,
  3. const SdpAudioFormat& format,
  4. absl::optional<AudioCodecPairId> codec_pair_id) override {
  5. // 模板递归创建音频编码器
  6. return Helper<Ts...>::MakeAudioEncoder(payload_type, format, codec_pair_id);
  7. }
  8. --》
  9. static std::unique_ptr<AudioEncoder> MakeAudioEncoder(
  10. int payload_type,
  11. const SdpAudioFormat& format,
  12. absl::optional<AudioCodecPairId> codec_pair_id) {
  13. auto opt_config = T::SdpToConfig(format);
  14. if (opt_config) {
  15. return T::MakeAudioEncoder(*opt_config, payload_type, codec_pair_id);
  16. } else {
  17. return Helper<Ts...>::MakeAudioEncoder(payload_type, format,
  18. codec_pair_id);
  19. }
  20. }

—》

AudioEncoderOpusImpl::MakeAudioEncoder

H:\webrtc-20210315\webrtc-20210315\webrtc\webrtc-checkout\src\modules\audio_coding\codecs\opus\audio_encoder_opus.cc

  1. std::unique_ptr<AudioEncoder> AudioEncoderOpusImpl::MakeAudioEncoder(
  2. const AudioEncoderOpusConfig& config,
  3. int payload_type) {
  4. RTC_DCHECK(config.IsOk());
  5. return std::make_unique<AudioEncoderOpusImpl>(config, payload_type);
  6. }

image.png
这里音频的每帧帧大小为20ms,opus支持最大为120ms,意思就是一帧最大可以播放120ms的音频。
—》

  1. AudioEncoderOpusImpl::AudioEncoderOpusImpl(const AudioEncoderOpusConfig& config,
  2. int payload_type)
  3. : AudioEncoderOpusImpl(
  4. config,
  5. payload_type,
  6. [this](const std::string& config_string, RtcEventLog* event_log) {
  7. return DefaultAudioNetworkAdaptorCreator(config_string, event_log);
  8. },
  9. // We choose 5sec as initial time constant due to empirical data.
  10. std::make_unique<SmoothingFilterImpl>(5000)) {}
  11. -》
  12. AudioEncoderOpusImpl::AudioEncoderOpusImpl(
  13. const AudioEncoderOpusConfig& config,
  14. int payload_type,
  15. const AudioNetworkAdaptorCreator& audio_network_adaptor_creator,
  16. std::unique_ptr<SmoothingFilter> bitrate_smoother)
  17. : payload_type_(payload_type),
  18. send_side_bwe_with_overhead_(
  19. !webrtc::field_trial::IsDisabled("WebRTC-SendSideBwe-WithOverhead")),
  20. use_stable_target_for_adaptation_(!webrtc::field_trial::IsDisabled(
  21. "WebRTC-Audio-StableTargetAdaptation")),
  22. adjust_bandwidth_(
  23. webrtc::field_trial::IsEnabled("WebRTC-AdjustOpusBandwidth")),
  24. bitrate_changed_(true),
  25. bitrate_multipliers_(GetBitrateMultipliers()),
  26. packet_loss_rate_(0.0),
  27. inst_(nullptr),
  28. packet_loss_fraction_smoother_(new PacketLossFractionSmoother()),
  29. audio_network_adaptor_creator_(audio_network_adaptor_creator),
  30. bitrate_smoother_(std::move(bitrate_smoother)),
  31. consecutive_dtx_frames_(0) {
  32. RTC_DCHECK(0 <= payload_type && payload_type <= 127);
  33. // Sanity check of the redundant payload type field that we want to get rid
  34. // of. See https://bugs.chromium.org/p/webrtc/issues/detail?id=7847
  35. RTC_CHECK(config.payload_type == -1 || config.payload_type == payload_type);
  36. RTC_CHECK(RecreateEncoderInstance(config));
  37. SetProjectedPacketLossRate(packet_loss_rate_);
  38. }

真正创建编码器的地方RecreateEncoderInstance
—》

AudioEncoderOpusImpl::RecreateEncoderInstance

  1. // If the given config is OK, recreate the Opus encoder instance with those
  2. // settings, save the config, and return true. Otherwise, do nothing and return
  3. // false.
  4. bool AudioEncoderOpusImpl::RecreateEncoderInstance(
  5. const AudioEncoderOpusConfig& config) {
  6. if (!config.IsOk())
  7. return false;
  8. config_ = config;
  9. if (inst_)
  10. RTC_CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_));
  11. input_buffer_.clear();
  12. input_buffer_.reserve(Num10msFramesPerPacket() * SamplesPer10msFrame());
  13. RTC_CHECK_EQ(0, WebRtcOpus_EncoderCreate(
  14. &inst_, config.num_channels,
  15. config.application ==
  16. AudioEncoderOpusConfig::ApplicationMode::kVoip
  17. ? 0
  18. : 1,
  19. config.sample_rate_hz));
  20. const int bitrate = GetBitrateBps(config);
  21. RTC_CHECK_EQ(0, WebRtcOpus_SetBitRate(inst_, bitrate));
  22. RTC_LOG(LS_VERBOSE) << "Set Opus bitrate to " << bitrate << " bps.";
  23. if (config.fec_enabled) {
  24. RTC_CHECK_EQ(0, WebRtcOpus_EnableFec(inst_));
  25. } else {
  26. RTC_CHECK_EQ(0, WebRtcOpus_DisableFec(inst_));
  27. }
  28. RTC_CHECK_EQ(
  29. 0, WebRtcOpus_SetMaxPlaybackRate(inst_, config.max_playback_rate_hz));
  30. // Use the default complexity if the start bitrate is within the hysteresis
  31. // window.
  32. complexity_ = GetNewComplexity(config).value_or(config.complexity);
  33. RTC_CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, complexity_));
  34. bitrate_changed_ = true;
  35. if (config.dtx_enabled) {
  36. RTC_CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_));
  37. } else { // 这里不启动dtx_enabled
  38. RTC_CHECK_EQ(0, WebRtcOpus_DisableDtx(inst_));
  39. }
  40. RTC_CHECK_EQ(0,
  41. WebRtcOpus_SetPacketLossRate(
  42. inst_, static_cast<int32_t>(packet_loss_rate_ * 100 + .5))); //千分之五
  43. if (config.cbr_enabled) {
  44. RTC_CHECK_EQ(0, WebRtcOpus_EnableCbr(inst_));
  45. } else {// 这里不启动cbr_enabled
  46. RTC_CHECK_EQ(0, WebRtcOpus_DisableCbr(inst_));
  47. }
  48. num_channels_to_encode_ = NumChannels();
  49. next_frame_length_ms_ = config_.frame_size_ms;
  50. return true;
  51. }

-》WebRtcOpus_EncoderCreate

WebRtcOpus_EncoderCreate

  1. int16_t WebRtcOpus_EncoderCreate(OpusEncInst** inst,
  2. size_t channels,
  3. int32_t application,
  4. int sample_rate_hz) {
  5. int opus_app;
  6. if (!inst)
  7. return -1;
  8. switch (application) {
  9. case 0: // 当前是这里
  10. opus_app = OPUS_APPLICATION_VOIP;
  11. break;
  12. case 1:
  13. opus_app = OPUS_APPLICATION_AUDIO;
  14. break;
  15. default:
  16. return -1;
  17. }
  18. OpusEncInst* state =
  19. reinterpret_cast<OpusEncInst*>(calloc(1, sizeof(OpusEncInst)));
  20. RTC_DCHECK(state);
  21. int error;
  22. state->encoder = opus_encoder_create(
  23. sample_rate_hz, static_cast<int>(channels), opus_app, &error);
  24. if (error != OPUS_OK || (!state->encoder && !state->multistream_encoder)) {
  25. WebRtcOpus_EncoderFree(state);
  26. return -1;
  27. }
  28. state->in_dtx_mode = 0;
  29. state->channels = channels;
  30. state->sample_rate_hz = sample_rate_hz;
  31. state->smooth_energy_non_active_frames = 0.0f;
  32. state->avoid_noise_pumping_during_dtx =
  33. webrtc::field_trial::IsEnabled(kAvoidNoisePumpingDuringDtxFieldTrial);
  34. *inst = state;
  35. return 0;
  36. }

创建OpusEncInst实例,然后调用了opus库的opus_encoder_create函数,将创建好的编码器赋值给OpusEncInst实例的encoder变量,设置好其他参数后,返回该实例。

AudioSendStream::SetupSendCodec中最后面调用了
channelsend->SetEncoder(new_config.send_codec_spec->payload_type, std::move(encoder));。

ChannelSend::SetEncoder

  1. void ChannelSend::SetEncoder(int payload_type,
  2. std::unique_ptr<AudioEncoder> encoder) {
  3. RTC_DCHECK_RUN_ON(&worker_thread_checker_);
  4. RTC_DCHECK_GE(payload_type, 0);
  5. RTC_DCHECK_LE(payload_type, 127);
  6. // The RTP/RTCP module needs to know the RTP timestamp rate (i.e. clockrate)
  7. // as well as some other things, so we collect this info and send it along.
  8. rtp_rtcp_->RegisterSendPayloadFrequency(payload_type,
  9. encoder->RtpTimestampRateHz());
  10. rtp_sender_audio_->RegisterAudioPayload("audio", payload_type,
  11. encoder->RtpTimestampRateHz(),
  12. encoder->NumChannels(), 0);
  13. audio_coding_->SetEncoder(std::move(encoder));
  14. }
  15. -》
  16. // Utility method for simply replacing the existing encoder with a new one.
  17. void SetEncoder(std::unique_ptr<AudioEncoder> new_encoder) {
  18. ModifyEncoder([&](std::unique_ptr<AudioEncoder>* encoder) {
  19. *encoder = std::move(new_encoder);
  20. });
  21. }

问题

为什么用户传入的通道是2, 创建的时候却为1了呢?
这时候需要进入static std::unique_ptr MakeAudioEncoder函数的 T::SdpToConfig(format);中

  1. absl::optional<AudioEncoderOpusConfig> AudioEncoderOpusImpl::SdpToConfig(
  2. const SdpAudioFormat& format) {
  3. if (!absl::EqualsIgnoreCase(format.name, "opus") ||
  4. format.clockrate_hz != kRtpTimestampRateHz || format.num_channels != 2) {
  5. return absl::nullopt;
  6. }
  7. AudioEncoderOpusConfig config;
  8. config.num_channels = GetChannelCount(format);
  9. config.frame_size_ms = GetFrameSizeMs(format); // 默认返回AudioEncoderOpusConfig::kDefaultFrameSizeMs=20
  10. config.max_playback_rate_hz = GetMaxPlaybackRate(format);
  11. config.fec_enabled = (GetFormatParameter(format, "useinbandfec") == "1");
  12. config.dtx_enabled = (GetFormatParameter(format, "usedtx") == "1");
  13. config.cbr_enabled = (GetFormatParameter(format, "cbr") == "1");
  14. config.bitrate_bps =
  15. CalculateBitrate(config.max_playback_rate_hz, config.num_channels,
  16. GetFormatParameter(format, "maxaveragebitrate"));
  17. config.application = config.num_channels == 1
  18. ? AudioEncoderOpusConfig::ApplicationMode::kVoip
  19. : AudioEncoderOpusConfig::ApplicationMode::kAudio;
  20. constexpr int kMinANAFrameLength = kANASupportedFrameLengths[0];
  21. constexpr int kMaxANAFrameLength =
  22. kANASupportedFrameLengths[arraysize(kANASupportedFrameLengths) - 1];
  23. // For now, minptime and maxptime are only used with ANA. If ptime is outside
  24. // of this range, it will get adjusted once ANA takes hold. Ideally, we'd know
  25. // if ANA was to be used when setting up the config, and adjust accordingly.
  26. const int min_frame_length_ms =
  27. GetFormatParameter<int>(format, "minptime").value_or(kMinANAFrameLength);
  28. const int max_frame_length_ms =
  29. GetFormatParameter<int>(format, "maxptime").value_or(kMaxANAFrameLength);
  30. FindSupportedFrameLengths(min_frame_length_ms, max_frame_length_ms,
  31. &config.supported_frame_lengths_ms);
  32. RTC_DCHECK(config.IsOk());
  33. return config;
  34. }

这里看 config.num_channels = GetChannelCount(format); 获取通道数。

  1. int GetChannelCount(const SdpAudioFormat& format) {
  2. const auto param = GetFormatParameter(format, "stereo");
  3. if (param == "1") {
  4. return 2;
  5. } else {
  6. return 1;
  7. }
  8. }

因为format参数中没有stereo参数,所以会返回1。如果要使用双声道就需要设置该参数。
image.png
这里并没有使用num_channels参数。
最后,可以看到AudioEncoderOpusImpl::SdpToConfig打印的config值。
image.png