Index: webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc |
diff --git a/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc |
index 37ce8733fed21268db49a942eb3cb1c7a7a1b76b..e750aa1323b9bfdfa1b42be7a7869202d41307c6 100644 |
--- a/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc |
+++ b/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc |
@@ -11,6 +11,7 @@ |
#include "webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h" |
#include "webrtc/base/checks.h" |
+#include "webrtc/base/safe_conversions.h" |
#include "webrtc/common_types.h" |
#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" |
@@ -18,36 +19,63 @@ namespace webrtc { |
namespace { |
+const int kSampleRateHz = 48000; |
const int kMinBitrateBps = 500; |
const int kMaxBitrateBps = 512000; |
-// TODO(tlegrand): Remove this code when we have proper APIs to set the |
-// complexity at a higher level. |
-#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) || defined(WEBRTC_ARCH_ARM) |
-// If we are on Android, iOS and/or ARM, use a lower complexity setting as |
-// default, to save encoder complexity. |
-const int kDefaultComplexity = 5; |
-#else |
-const int kDefaultComplexity = 9; |
-#endif |
+AudioEncoderOpus::Config CreateConfig(const CodecInst& codec_inst) { |
+ AudioEncoderOpus::Config config; |
+ config.frame_size_ms = rtc::CheckedDivExact(codec_inst.pacsize, 48); |
+ config.num_channels = codec_inst.channels; |
+ config.bitrate_bps = codec_inst.rate; |
+ config.payload_type = codec_inst.pltype; |
+ config.application = config.num_channels == 1 ? AudioEncoderOpus::kVoip |
+ : AudioEncoderOpus::kAudio; |
+ return config; |
+} |
-// We always encode at 48 kHz. |
-const int kSampleRateHz = 48000; |
+// Optimize the loss rate to configure Opus. Basically, optimized loss rate is |
+// the input loss rate rounded down to various levels, because a robustly good |
+// audio quality is achieved by lowering the packet loss down. |
+// Additionally, to prevent toggling, margins are used, i.e., when jumping to |
+// a loss rate from below, a higher threshold is used than jumping to the same |
+// level from above. |
+double OptimizePacketLossRate(double new_loss_rate, double old_loss_rate) { |
+ DCHECK_GE(new_loss_rate, 0.0); |
+ DCHECK_LE(new_loss_rate, 1.0); |
+ DCHECK_GE(old_loss_rate, 0.0); |
+ DCHECK_LE(old_loss_rate, 1.0); |
+ const double kPacketLossRate20 = 0.20; |
+ const double kPacketLossRate10 = 0.10; |
+ const double kPacketLossRate5 = 0.05; |
+ const double kPacketLossRate1 = 0.01; |
+ const double kLossRate20Margin = 0.02; |
+ const double kLossRate10Margin = 0.01; |
+ const double kLossRate5Margin = 0.01; |
+ if (new_loss_rate >= |
+ kPacketLossRate20 + |
+ kLossRate20Margin * |
+ (kPacketLossRate20 - old_loss_rate > 0 ? 1 : -1)) { |
+ return kPacketLossRate20; |
+ } else if (new_loss_rate >= |
+ kPacketLossRate10 + |
+ kLossRate10Margin * |
+ (kPacketLossRate10 - old_loss_rate > 0 ? 1 : -1)) { |
+ return kPacketLossRate10; |
+ } else if (new_loss_rate >= |
+ kPacketLossRate5 + |
+ kLossRate5Margin * |
+ (kPacketLossRate5 - old_loss_rate > 0 ? 1 : -1)) { |
+ return kPacketLossRate5; |
+ } else if (new_loss_rate >= kPacketLossRate1) { |
+ return kPacketLossRate1; |
+ } else { |
+ return 0.0; |
+ } |
+} |
} // namespace |
-AudioEncoderOpus::Config::Config() |
- : frame_size_ms(20), |
- num_channels(1), |
- payload_type(120), |
- application(kVoip), |
- bitrate_bps(64000), |
- fec_enabled(false), |
- max_playback_rate_hz(48000), |
- complexity(kDefaultComplexity), |
- dtx_enabled(false) { |
-} |
- |
bool AudioEncoderOpus::Config::IsOk() const { |
if (frame_size_ms <= 0 || frame_size_ms % 10 != 0) |
return false; |
@@ -61,119 +89,45 @@ bool AudioEncoderOpus::Config::IsOk() const { |
} |
AudioEncoderOpus::AudioEncoderOpus(const Config& config) |
- : num_10ms_frames_per_packet_( |
- static_cast<size_t>(rtc::CheckedDivExact(config.frame_size_ms, 10))), |
- num_channels_(config.num_channels), |
- payload_type_(config.payload_type), |
- application_(config.application), |
- dtx_enabled_(config.dtx_enabled), |
- samples_per_10ms_frame_(static_cast<size_t>( |
- rtc::CheckedDivExact(kSampleRateHz, 100) * num_channels_)), |
- packet_loss_rate_(0.0) { |
- CHECK(config.IsOk()); |
- input_buffer_.reserve(num_10ms_frames_per_packet_ * samples_per_10ms_frame_); |
- CHECK_EQ(0, WebRtcOpus_EncoderCreate(&inst_, num_channels_, application_)); |
- SetTargetBitrate(config.bitrate_bps); |
- if (config.fec_enabled) { |
- CHECK_EQ(0, WebRtcOpus_EnableFec(inst_)); |
- } else { |
- CHECK_EQ(0, WebRtcOpus_DisableFec(inst_)); |
- } |
- CHECK_EQ(0, |
- WebRtcOpus_SetMaxPlaybackRate(inst_, config.max_playback_rate_hz)); |
- CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, config.complexity)); |
- if (config.dtx_enabled) { |
- CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_)); |
- } else { |
- CHECK_EQ(0, WebRtcOpus_DisableDtx(inst_)); |
- } |
+ : packet_loss_rate_(0.0), inst_(nullptr) { |
+ CHECK(RecreateEncoderInstance(config)); |
} |
+AudioEncoderOpus::AudioEncoderOpus(const CodecInst& codec_inst) |
+ : AudioEncoderOpus(CreateConfig(codec_inst)) {} |
+ |
AudioEncoderOpus::~AudioEncoderOpus() { |
CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_)); |
} |
-int AudioEncoderOpus::SampleRateHz() const { |
- return kSampleRateHz; |
-} |
- |
-int AudioEncoderOpus::NumChannels() const { |
- return num_channels_; |
-} |
- |
size_t AudioEncoderOpus::MaxEncodedBytes() const { |
// Calculate the number of bytes we expect the encoder to produce, |
// then multiply by two to give a wide margin for error. |
- size_t bytes_per_millisecond = |
- static_cast<size_t>(bitrate_bps_ / (1000 * 8) + 1); |
- size_t approx_encoded_bytes = |
- num_10ms_frames_per_packet_ * 10 * bytes_per_millisecond; |
+ const size_t bytes_per_millisecond = |
+ static_cast<size_t>(config_.bitrate_bps / (1000 * 8) + 1); |
+ const size_t approx_encoded_bytes = |
+ Num10msFramesPerPacket() * 10 * bytes_per_millisecond; |
return 2 * approx_encoded_bytes; |
} |
-size_t AudioEncoderOpus::Num10MsFramesInNextPacket() const { |
- return num_10ms_frames_per_packet_; |
+int AudioEncoderOpus::SampleRateHz() const { |
+ return kSampleRateHz; |
} |
-size_t AudioEncoderOpus::Max10MsFramesInAPacket() const { |
- return num_10ms_frames_per_packet_; |
+int AudioEncoderOpus::NumChannels() const { |
+ return config_.num_channels; |
} |
-int AudioEncoderOpus::GetTargetBitrate() const { |
- return bitrate_bps_; |
+size_t AudioEncoderOpus::Num10MsFramesInNextPacket() const { |
+ return Num10msFramesPerPacket(); |
} |
-void AudioEncoderOpus::SetTargetBitrate(int bits_per_second) { |
- bitrate_bps_ = std::max(std::min(bits_per_second, kMaxBitrateBps), |
- kMinBitrateBps); |
- CHECK_EQ(WebRtcOpus_SetBitRate(inst_, bitrate_bps_), 0); |
+size_t AudioEncoderOpus::Max10MsFramesInAPacket() const { |
+ return Num10msFramesPerPacket(); |
} |
-void AudioEncoderOpus::SetProjectedPacketLossRate(double fraction) { |
- DCHECK_GE(fraction, 0.0); |
- DCHECK_LE(fraction, 1.0); |
- // Optimize the loss rate to configure Opus. Basically, optimized loss rate is |
- // the input loss rate rounded down to various levels, because a robustly good |
- // audio quality is achieved by lowering the packet loss down. |
- // Additionally, to prevent toggling, margins are used, i.e., when jumping to |
- // a loss rate from below, a higher threshold is used than jumping to the same |
- // level from above. |
- const double kPacketLossRate20 = 0.20; |
- const double kPacketLossRate10 = 0.10; |
- const double kPacketLossRate5 = 0.05; |
- const double kPacketLossRate1 = 0.01; |
- const double kLossRate20Margin = 0.02; |
- const double kLossRate10Margin = 0.01; |
- const double kLossRate5Margin = 0.01; |
- double opt_loss_rate; |
- if (fraction >= |
- kPacketLossRate20 + |
- kLossRate20Margin * |
- (kPacketLossRate20 - packet_loss_rate_ > 0 ? 1 : -1)) { |
- opt_loss_rate = kPacketLossRate20; |
- } else if (fraction >= |
- kPacketLossRate10 + |
- kLossRate10Margin * |
- (kPacketLossRate10 - packet_loss_rate_ > 0 ? 1 : -1)) { |
- opt_loss_rate = kPacketLossRate10; |
- } else if (fraction >= |
- kPacketLossRate5 + |
- kLossRate5Margin * |
- (kPacketLossRate5 - packet_loss_rate_ > 0 ? 1 : -1)) { |
- opt_loss_rate = kPacketLossRate5; |
- } else if (fraction >= kPacketLossRate1) { |
- opt_loss_rate = kPacketLossRate1; |
- } else { |
- opt_loss_rate = 0; |
- } |
- |
- if (packet_loss_rate_ != opt_loss_rate) { |
- // Ask the encoder to change the target packet loss rate. |
- CHECK_EQ(WebRtcOpus_SetPacketLossRate( |
- inst_, static_cast<int32_t>(opt_loss_rate * 100 + .5)), |
- 0); |
- packet_loss_rate_ = opt_loss_rate; |
- } |
+int AudioEncoderOpus::GetTargetBitrate() const { |
+ return config_.bitrate_bps; |
} |
AudioEncoder::EncodedInfo AudioEncoderOpus::EncodeInternal( |
@@ -184,75 +138,118 @@ AudioEncoder::EncodedInfo AudioEncoderOpus::EncodeInternal( |
if (input_buffer_.empty()) |
first_timestamp_in_buffer_ = rtp_timestamp; |
input_buffer_.insert(input_buffer_.end(), audio, |
- audio + samples_per_10ms_frame_); |
+ audio + SamplesPer10msFrame()); |
if (input_buffer_.size() < |
- (num_10ms_frames_per_packet_ * samples_per_10ms_frame_)) { |
+ (static_cast<size_t>(Num10msFramesPerPacket()) * SamplesPer10msFrame())) { |
return EncodedInfo(); |
} |
- CHECK_EQ(input_buffer_.size(), |
- num_10ms_frames_per_packet_ * samples_per_10ms_frame_); |
+ CHECK_EQ(input_buffer_.size(), static_cast<size_t>(Num10msFramesPerPacket()) * |
+ SamplesPer10msFrame()); |
int status = WebRtcOpus_Encode( |
inst_, &input_buffer_[0], |
rtc::CheckedDivExact(input_buffer_.size(), |
- static_cast<size_t>(num_channels_)), |
- max_encoded_bytes, encoded); |
+ static_cast<size_t>(config_.num_channels)), |
+ rtc::saturated_cast<int16_t>(max_encoded_bytes), encoded); |
CHECK_GE(status, 0); // Fails only if fed invalid data. |
input_buffer_.clear(); |
EncodedInfo info; |
info.encoded_bytes = static_cast<size_t>(status); |
info.encoded_timestamp = first_timestamp_in_buffer_; |
- info.payload_type = payload_type_; |
+ info.payload_type = config_.payload_type; |
info.send_even_if_empty = true; // Allows Opus to send empty packets. |
info.speech = (status > 0); |
return info; |
} |
-namespace { |
-AudioEncoderOpus::Config CreateConfig(const CodecInst& codec_inst) { |
- AudioEncoderOpus::Config config; |
- config.frame_size_ms = rtc::CheckedDivExact(codec_inst.pacsize, 48); |
- config.num_channels = codec_inst.channels; |
- config.bitrate_bps = codec_inst.rate; |
- config.payload_type = codec_inst.pltype; |
- config.application = (config.num_channels == 1 ? AudioEncoderOpus::kVoip |
- : AudioEncoderOpus::kAudio); |
- return config; |
+void AudioEncoderOpus::Reset() { |
+ CHECK(RecreateEncoderInstance(config_)); |
} |
-} // namespace |
-AudioEncoderMutableOpus::AudioEncoderMutableOpus(const CodecInst& codec_inst) |
- : AudioEncoderMutableImpl<AudioEncoderOpus>(CreateConfig(codec_inst)) { |
-} |
- |
-bool AudioEncoderMutableOpus::SetFec(bool enable) { |
- auto conf = config(); |
+bool AudioEncoderOpus::SetFec(bool enable) { |
+ auto conf = config_; |
conf.fec_enabled = enable; |
- return Reconstruct(conf); |
+ return RecreateEncoderInstance(conf); |
} |
-bool AudioEncoderMutableOpus::SetDtx(bool enable) { |
- auto conf = config(); |
+bool AudioEncoderOpus::SetDtx(bool enable) { |
+ auto conf = config_; |
conf.dtx_enabled = enable; |
- return Reconstruct(conf); |
+ return RecreateEncoderInstance(conf); |
} |
-bool AudioEncoderMutableOpus::SetApplication(Application application) { |
- auto conf = config(); |
+bool AudioEncoderOpus::SetApplication(Application application) { |
+ auto conf = config_; |
switch (application) { |
- case kApplicationSpeech: |
+ case Application::kSpeech: |
conf.application = AudioEncoderOpus::kVoip; |
break; |
- case kApplicationAudio: |
+ case Application::kAudio: |
conf.application = AudioEncoderOpus::kAudio; |
break; |
} |
- return Reconstruct(conf); |
+ return RecreateEncoderInstance(conf); |
} |
-bool AudioEncoderMutableOpus::SetMaxPlaybackRate(int frequency_hz) { |
- auto conf = config(); |
+bool AudioEncoderOpus::SetMaxPlaybackRate(int frequency_hz) { |
+ auto conf = config_; |
conf.max_playback_rate_hz = frequency_hz; |
- return Reconstruct(conf); |
+ return RecreateEncoderInstance(conf); |
+} |
+ |
+void AudioEncoderOpus::SetProjectedPacketLossRate(double fraction) { |
+ double opt_loss_rate = OptimizePacketLossRate(fraction, packet_loss_rate_); |
+ if (packet_loss_rate_ != opt_loss_rate) { |
+ packet_loss_rate_ = opt_loss_rate; |
+ CHECK_EQ(0, WebRtcOpus_SetPacketLossRate( |
+ inst_, static_cast<int32_t>(packet_loss_rate_ * 100 + .5))); |
+ } |
+} |
+ |
+void AudioEncoderOpus::SetTargetBitrate(int bits_per_second) { |
+ config_.bitrate_bps = |
+ std::max(std::min(bits_per_second, kMaxBitrateBps), kMinBitrateBps); |
+ DCHECK(config_.IsOk()); |
+ CHECK_EQ(0, WebRtcOpus_SetBitRate(inst_, config_.bitrate_bps)); |
+} |
+ |
+int AudioEncoderOpus::Num10msFramesPerPacket() const { |
+ return rtc::CheckedDivExact(config_.frame_size_ms, 10); |
+} |
+ |
+int AudioEncoderOpus::SamplesPer10msFrame() const { |
+ return rtc::CheckedDivExact(kSampleRateHz, 100) * config_.num_channels; |
+} |
+ |
+// If the given config is OK, recreate the Opus encoder instance with those |
+// settings, save the config, and return true. Otherwise, do nothing and return |
+// false. |
+bool AudioEncoderOpus::RecreateEncoderInstance(const Config& config) { |
+ if (!config.IsOk()) |
+ return false; |
+ if (inst_) |
+ CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_)); |
+ input_buffer_.clear(); |
+ input_buffer_.reserve(Num10msFramesPerPacket() * SamplesPer10msFrame()); |
+ CHECK_EQ(0, WebRtcOpus_EncoderCreate(&inst_, config.num_channels, |
+ config.application)); |
+ CHECK_EQ(0, WebRtcOpus_SetBitRate(inst_, config.bitrate_bps)); |
+ if (config.fec_enabled) { |
+ CHECK_EQ(0, WebRtcOpus_EnableFec(inst_)); |
+ } else { |
+ CHECK_EQ(0, WebRtcOpus_DisableFec(inst_)); |
+ } |
+ CHECK_EQ(0, |
+ WebRtcOpus_SetMaxPlaybackRate(inst_, config.max_playback_rate_hz)); |
+ CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, config.complexity)); |
+ if (config.dtx_enabled) { |
+ CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_)); |
+ } else { |
+ CHECK_EQ(0, WebRtcOpus_DisableDtx(inst_)); |
+ } |
+ CHECK_EQ(0, WebRtcOpus_SetPacketLossRate( |
+ inst_, static_cast<int32_t>(packet_loss_rate_ * 100 + .5))); |
+ config_ = config; |
+ return true; |
} |
} // namespace webrtc |