Implement Opus bandwidth adjustment behind a FieldTrial

Bug: webrtc:8522
Change-Id: I3a32ebfecd27ff74b507c2cee9e16aab17153442
Reviewed-on: https://webrtc-review.googlesource.com/22210
Commit-Queue: Alejandro Luebs <aluebs@webrtc.org>
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20799}
diff --git a/modules/audio_coding/BUILD.gn b/modules/audio_coding/BUILD.gn
index 0fed244..871cbe9 100644
--- a/modules/audio_coding/BUILD.gn
+++ b/modules/audio_coding/BUILD.gn
@@ -2067,6 +2067,7 @@
       "codecs/isac/unittest.cc",
       "codecs/legacy_encoded_audio_frame_unittest.cc",
       "codecs/opus/audio_encoder_opus_unittest.cc",
+      "codecs/opus/opus_bandwidth_unittest.cc",
       "codecs/opus/opus_unittest.cc",
       "codecs/red/audio_encoder_copy_red_unittest.cc",
       "neteq/audio_multi_vector_unittest.cc",
@@ -2140,6 +2141,7 @@
       "../../api/audio_codecs:audio_codecs_api",
       "../../api/audio_codecs:builtin_audio_decoder_factory",
       "../../api/audio_codecs:builtin_audio_encoder_factory",
+      "../../api/audio_codecs/opus:audio_decoder_opus",
       "../../api/audio_codecs/opus:audio_encoder_opus",
       "../../common_audio",
       "../../common_audio:mock_common_audio",
diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/modules/audio_coding/codecs/opus/audio_encoder_opus.cc
index f07cd42..4b655df 100644
--- a/modules/audio_coding/codecs/opus/audio_encoder_opus.cc
+++ b/modules/audio_coding/codecs/opus/audio_encoder_opus.cc
@@ -330,6 +330,28 @@
   }
 }
 
+rtc::Optional<int> AudioEncoderOpusImpl::GetNewBandwidth(
+    const AudioEncoderOpusConfig& config,
+    OpusEncInst* inst) {
+  constexpr int kMinWidebandBitrate = 8000;
+  constexpr int kMaxNarrowbandBitrate = 9000;
+  constexpr int kAutomaticThreshold = 11000;
+  RTC_DCHECK(config.IsOk());
+  const int bitrate = GetBitrateBps(config);
+  if (bitrate > kAutomaticThreshold) {
+    return rtc::Optional<int>(OPUS_AUTO);
+  }
+  const int bandwidth = WebRtcOpus_GetBandwidth(inst);
+  RTC_DCHECK_GE(bandwidth, 0);
+  if (bitrate > kMaxNarrowbandBitrate && bandwidth < OPUS_BANDWIDTH_WIDEBAND) {
+    return rtc::Optional<int>(OPUS_BANDWIDTH_WIDEBAND);
+  } else if (bitrate < kMinWidebandBitrate &&
+             bandwidth > OPUS_BANDWIDTH_NARROWBAND) {
+    return rtc::Optional<int>(OPUS_BANDWIDTH_NARROWBAND);
+  }
+  return rtc::Optional<int>();
+}
+
 class AudioEncoderOpusImpl::PacketLossFractionSmoother {
  public:
   explicit PacketLossFractionSmoother()
@@ -376,6 +398,9 @@
     : payload_type_(payload_type),
       send_side_bwe_with_overhead_(
           webrtc::field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")),
+      adjust_bandwidth_(
+          webrtc::field_trial::IsEnabled("WebRTC-AdjustOpusBandwidth")),
+      bitrate_changed_(true),
       packet_loss_rate_(0.0),
       inst_(nullptr),
       packet_loss_fraction_smoother_(new PacketLossFractionSmoother()),
@@ -609,6 +634,14 @@
   // Will use new packet size for next encoding.
   config_.frame_size_ms = next_frame_length_ms_;
 
+  if (adjust_bandwidth_ && bitrate_changed_) {
+    const auto bandwidth = GetNewBandwidth(config_, inst_);
+    if (bandwidth) {
+      RTC_CHECK_EQ(0, WebRtcOpus_SetBandwidth(inst_, *bandwidth));
+    }
+    bitrate_changed_ = false;
+  }
+
   info.encoded_timestamp = first_timestamp_in_buffer_;
   info.payload_type = payload_type_;
   info.send_even_if_empty = true;  // Allows Opus to send empty packets.
@@ -672,6 +705,7 @@
   // window.
   complexity_ = GetNewComplexity(config).value_or(config.complexity);
   RTC_CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, complexity_));
+  bitrate_changed_ = true;
   if (config.dtx_enabled) {
     RTC_CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_));
   } else {
@@ -727,6 +761,7 @@
     complexity_ = *new_complexity;
     RTC_CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, complexity_));
   }
+  bitrate_changed_ = true;
 }
 
 void AudioEncoderOpusImpl::ApplyAudioNetworkAdaptor() {
diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus.h b/modules/audio_coding/codecs/opus/audio_encoder_opus.h
index 22967c4..49c5207 100644
--- a/modules/audio_coding/codecs/opus/audio_encoder_opus.h
+++ b/modules/audio_coding/codecs/opus/audio_encoder_opus.h
@@ -43,6 +43,13 @@
   static rtc::Optional<int> GetNewComplexity(
       const AudioEncoderOpusConfig& config);
 
+  // Returns OPUS_AUTO if the the current bitrate is above wideband threshold.
+  // Returns empty if it is below, but bandwidth coincides with the desired one.
+  // Otherwise returns the desired bandwidth.
+  static rtc::Optional<int> GetNewBandwidth(
+      const AudioEncoderOpusConfig& config,
+      OpusEncInst* inst);
+
   using AudioNetworkAdaptorCreator =
       std::function<std::unique_ptr<AudioNetworkAdaptor>(const std::string&,
                                                          RtcEventLog*)>;
@@ -148,6 +155,8 @@
   AudioEncoderOpusConfig config_;
   const int payload_type_;
   const bool send_side_bwe_with_overhead_;
+  const bool adjust_bandwidth_;
+  bool bitrate_changed_;
   float packet_loss_rate_;
   std::vector<int16_t> input_buffer_;
   OpusEncInst* inst_;
diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc b/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
index c3ad488..dfef682 100644
--- a/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
+++ b/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
@@ -17,6 +17,7 @@
 #include "common_types.h"  // NOLINT(build/include)
 #include "modules/audio_coding/audio_network_adaptor/mock/mock_audio_network_adaptor.h"
 #include "modules/audio_coding/codecs/opus/audio_encoder_opus.h"
+#include "modules/audio_coding/codecs/opus/opus_interface.h"
 #include "modules/audio_coding/neteq/tools/audio_loop.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/fakeclock.h"
@@ -473,6 +474,64 @@
   EXPECT_EQ(6, AudioEncoderOpusImpl::GetNewComplexity(config));
 }
 
+// Verifies that the bandwidth adaptation in the config works as intended.
+TEST(AudioEncoderOpusTest, ConfigBandwidthAdaptation) {
+  AudioEncoderOpusConfig config;
+  // Sample rate of Opus.
+  constexpr size_t kOpusRateKhz = 48;
+  std::vector<int16_t> silence(
+      kOpusRateKhz * config.frame_size_ms * config.num_channels, 0);
+  constexpr size_t kMaxBytes = 1000;
+  uint8_t bitstream[kMaxBytes];
+
+  OpusEncInst* inst;
+  EXPECT_EQ(0, WebRtcOpus_EncoderCreate(
+                   &inst, config.num_channels,
+                   config.application ==
+                           AudioEncoderOpusConfig::ApplicationMode::kVoip
+                       ? 0
+                       : 1));
+
+  // Bitrate below minmum wideband. Expect narrowband.
+  config.bitrate_bps = rtc::Optional<int>(7999);
+  auto bandwidth = AudioEncoderOpusImpl::GetNewBandwidth(config, inst);
+  EXPECT_EQ(rtc::Optional<int>(OPUS_BANDWIDTH_NARROWBAND), bandwidth);
+  WebRtcOpus_SetBandwidth(inst, *bandwidth);
+  // It is necessary to encode here because Opus has some logic in the encoder
+  // that goes from the user-set bandwidth to the used and returned one.
+  WebRtcOpus_Encode(inst, silence.data(),
+                    rtc::CheckedDivExact(silence.size(), config.num_channels),
+                    kMaxBytes, bitstream);
+
+  // Bitrate not yet above maximum narrowband. Expect empty.
+  config.bitrate_bps = rtc::Optional<int>(9000);
+  bandwidth = AudioEncoderOpusImpl::GetNewBandwidth(config, inst);
+  EXPECT_EQ(rtc::Optional<int>(), bandwidth);
+
+  // Bitrate above maximum narrowband. Expect wideband.
+  config.bitrate_bps = rtc::Optional<int>(9001);
+  bandwidth = AudioEncoderOpusImpl::GetNewBandwidth(config, inst);
+  EXPECT_EQ(rtc::Optional<int>(OPUS_BANDWIDTH_WIDEBAND), bandwidth);
+  WebRtcOpus_SetBandwidth(inst, *bandwidth);
+  // It is necessary to encode here because Opus has some logic in the encoder
+  // that goes from the user-set bandwidth to the used and returned one.
+  WebRtcOpus_Encode(inst, silence.data(),
+                    rtc::CheckedDivExact(silence.size(), config.num_channels),
+                    kMaxBytes, bitstream);
+
+  // Bitrate not yet below minimum wideband. Expect empty.
+  config.bitrate_bps = rtc::Optional<int>(8000);
+  bandwidth = AudioEncoderOpusImpl::GetNewBandwidth(config, inst);
+  EXPECT_EQ(rtc::Optional<int>(), bandwidth);
+
+  // Bitrate above automatic threshold. Expect automatic.
+  config.bitrate_bps = rtc::Optional<int>(12001);
+  bandwidth = AudioEncoderOpusImpl::GetNewBandwidth(config, inst);
+  EXPECT_EQ(rtc::Optional<int>(OPUS_AUTO), bandwidth);
+
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(inst));
+}
+
 TEST(AudioEncoderOpusTest, EmptyConfigDoesNotAffectEncoderSettings) {
   auto states = CreateCodec(2);
   states.encoder->EnableAudioNetworkAdaptor("", nullptr);
diff --git a/modules/audio_coding/codecs/opus/opus_bandwidth_unittest.cc b/modules/audio_coding/codecs/opus/opus_bandwidth_unittest.cc
new file mode 100644
index 0000000..4394949
--- /dev/null
+++ b/modules/audio_coding/codecs/opus/opus_bandwidth_unittest.cc
@@ -0,0 +1,151 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/audio_codecs/opus/audio_decoder_opus.h"
+#include "api/audio_codecs/opus/audio_encoder_opus.h"
+#include "common_audio/include/audio_util.h"
+#include "common_audio/lapped_transform.h"
+#include "common_audio/window_generator.h"
+#include "modules/audio_coding/neteq/tools/audio_loop.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+#include "test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace {
+
+constexpr size_t kNumChannels = 1u;
+constexpr int kSampleRateHz = 48000;
+constexpr size_t kMaxLoopLengthSamples = kSampleRateHz * 50;  // 50 seconds.
+constexpr size_t kInputBlockSizeSamples = 10 * kSampleRateHz / 1000;   // 10 ms
+constexpr size_t kOutputBlockSizeSamples = 20 * kSampleRateHz / 1000;  // 20 ms
+constexpr size_t kFftSize = 1024;
+constexpr size_t kNarrowbandSize = 4000 * kFftSize / kSampleRateHz;
+constexpr float kKbdAlpha = 1.5f;
+
+class PowerRatioEstimator : public LappedTransform::Callback {
+ public:
+  PowerRatioEstimator() : low_pow_(0.f), high_pow_(0.f) {
+    WindowGenerator::KaiserBesselDerived(kKbdAlpha, kFftSize, window_);
+    transform_.reset(new LappedTransform(kNumChannels, 0u,
+                                         kInputBlockSizeSamples, window_,
+                                         kFftSize, kFftSize / 2, this));
+  }
+
+  void ProcessBlock(float* data) { transform_->ProcessChunk(&data, nullptr); }
+
+  float PowerRatio() { return high_pow_ / low_pow_; }
+
+ protected:
+  void ProcessAudioBlock(const std::complex<float>* const* input,
+                         size_t num_input_channels,
+                         size_t num_freq_bins,
+                         size_t num_output_channels,
+                         std::complex<float>* const* output) override {
+    float low_pow = 0.f;
+    float high_pow = 0.f;
+    for (size_t i = 0u; i < num_input_channels; ++i) {
+      for (size_t j = 0u; j < kNarrowbandSize; ++j) {
+        float low_mag = std::abs(input[i][j]);
+        low_pow += low_mag * low_mag;
+        float high_mag = std::abs(input[i][j + kNarrowbandSize]);
+        high_pow += high_mag * high_mag;
+      }
+    }
+    low_pow_ += low_pow / (num_input_channels * kFftSize);
+    high_pow_ += high_pow / (num_input_channels * kFftSize);
+  }
+
+ private:
+  std::unique_ptr<LappedTransform> transform_;
+  float window_[kFftSize];
+  float low_pow_;
+  float high_pow_;
+};
+
+float EncodedPowerRatio(AudioEncoder* encoder,
+                        AudioDecoder* decoder,
+                        test::AudioLoop* audio_loop) {
+  // Encode and decode.
+  uint32_t rtp_timestamp = 0u;
+  constexpr size_t kBufferSize = 500;
+  rtc::Buffer encoded(kBufferSize);
+  std::vector<int16_t> decoded(kOutputBlockSizeSamples);
+  std::vector<float> decoded_float(kOutputBlockSizeSamples);
+  AudioDecoder::SpeechType speech_type = AudioDecoder::kSpeech;
+  PowerRatioEstimator power_ratio_estimator;
+  for (size_t i = 0; i < 1000; ++i) {
+    encoded.Clear();
+    AudioEncoder::EncodedInfo encoder_info =
+        encoder->Encode(rtp_timestamp, audio_loop->GetNextBlock(), &encoded);
+    rtp_timestamp += kInputBlockSizeSamples;
+    if (encoded.size() > 0) {
+      int decoder_info = decoder->Decode(
+          encoded.data(), encoded.size(), kSampleRateHz,
+          decoded.size() * sizeof(decoded[0]), decoded.data(), &speech_type);
+      if (decoder_info > 0) {
+        S16ToFloat(decoded.data(), decoded.size(), decoded_float.data());
+        power_ratio_estimator.ProcessBlock(decoded_float.data());
+      }
+    }
+  }
+  return power_ratio_estimator.PowerRatio();
+}
+
+}  // namespace
+
+TEST(BandwidthAdaptationTest, BandwidthAdaptationTest) {
+  test::ScopedFieldTrials override_field_trials(
+      "WebRTC-AdjustOpusBandwidth/Enabled/");
+
+  constexpr float kMaxNarrowbandRatio = 0.003f;
+  constexpr float kMinWidebandRatio = 0.03f;
+
+  // Create encoder.
+  AudioEncoderOpusConfig enc_config;
+  enc_config.bitrate_bps = rtc::Optional<int>(7999);
+  enc_config.num_channels = kNumChannels;
+  constexpr int payload_type = 17;
+  auto encoder = AudioEncoderOpus::MakeAudioEncoder(enc_config, payload_type);
+
+  // Create decoder.
+  AudioDecoderOpus::Config dec_config;
+  dec_config.num_channels = kNumChannels;
+  auto decoder = AudioDecoderOpus::MakeAudioDecoder(dec_config);
+
+  // Open speech file.
+  const std::string kInputFileName =
+      webrtc::test::ResourcePath("audio_coding/speech_mono_32_48kHz", "pcm");
+  test::AudioLoop audio_loop;
+  EXPECT_EQ(kSampleRateHz, encoder->SampleRateHz());
+  ASSERT_TRUE(audio_loop.Init(kInputFileName, kMaxLoopLengthSamples,
+                              kInputBlockSizeSamples));
+
+  EXPECT_LT(EncodedPowerRatio(encoder.get(), decoder.get(), &audio_loop),
+            kMaxNarrowbandRatio);
+
+  encoder->OnReceivedTargetAudioBitrate(9000);
+  EXPECT_LT(EncodedPowerRatio(encoder.get(), decoder.get(), &audio_loop),
+            kMaxNarrowbandRatio);
+
+  encoder->OnReceivedTargetAudioBitrate(9001);
+  EXPECT_GT(EncodedPowerRatio(encoder.get(), decoder.get(), &audio_loop),
+            kMinWidebandRatio);
+
+  encoder->OnReceivedTargetAudioBitrate(8000);
+  EXPECT_GT(EncodedPowerRatio(encoder.get(), decoder.get(), &audio_loop),
+            kMinWidebandRatio);
+
+  encoder->OnReceivedTargetAudioBitrate(12001);
+  EXPECT_GT(EncodedPowerRatio(encoder.get(), decoder.get(), &audio_loop),
+            kMinWidebandRatio);
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_coding/codecs/opus/opus_interface.c b/modules/audio_coding/codecs/opus/opus_interface.c
index 5166f4c..d219098 100644
--- a/modules/audio_coding/codecs/opus/opus_interface.c
+++ b/modules/audio_coding/codecs/opus/opus_interface.c
@@ -11,7 +11,6 @@
 #include "modules/audio_coding/codecs/opus/opus_interface.h"
 
 #include "rtc_base/checks.h"
-#include "modules/audio_coding/codecs/opus/opus_inst.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -229,6 +228,27 @@
   }
 }
 
+int32_t WebRtcOpus_GetBandwidth(OpusEncInst* inst) {
+  if (!inst) {
+    return -1;
+  }
+  int32_t bandwidth;
+  if (opus_encoder_ctl(inst->encoder, OPUS_GET_BANDWIDTH(&bandwidth)) == 0) {
+    return bandwidth;
+  } else {
+    return -1;
+  }
+
+}
+
+int16_t WebRtcOpus_SetBandwidth(OpusEncInst* inst, int32_t bandwidth) {
+  if (inst) {
+    return opus_encoder_ctl(inst->encoder, OPUS_SET_BANDWIDTH(bandwidth));
+  } else {
+    return -1;
+  }
+}
+
 int16_t WebRtcOpus_SetForceChannels(OpusEncInst* inst, size_t num_channels) {
   if (!inst)
     return -1;
diff --git a/modules/audio_coding/codecs/opus/opus_interface.h b/modules/audio_coding/codecs/opus/opus_interface.h
index 13e2ee3..4b8e892 100644
--- a/modules/audio_coding/codecs/opus/opus_interface.h
+++ b/modules/audio_coding/codecs/opus/opus_interface.h
@@ -13,6 +13,7 @@
 
 #include <stddef.h>
 
+#include "modules/audio_coding/codecs/opus/opus_inst.h"
 #include "typedefs.h"  // NOLINT(build/include)
 
 #ifdef __cplusplus
@@ -222,6 +223,40 @@
 int16_t WebRtcOpus_SetComplexity(OpusEncInst* inst, int32_t complexity);
 
 /*
+ * WebRtcOpus_GetBandwidth(...)
+ *
+ * This function returns the current bandwidth.
+ *
+ * Input:
+ *      - inst               : Encoder context
+ *
+ * Return value              :  Bandwidth - Success
+ *                             -1 - Error
+ */
+int32_t WebRtcOpus_GetBandwidth(OpusEncInst* inst);
+
+/*
+ * WebRtcOpus_SetBandwidth(...)
+ *
+ * By default Opus decides which bandwidth to encode the signal in depending on
+ * the the bitrate. This function overrules the previous setting and forces the
+ * encoder to encode in narrowband/wideband/fullband/etc.
+ *
+ * Input:
+ *      - inst               : Encoder context
+ *      - bandwidth          : New target bandwidth. Valid values are:
+ *                             OPUS_BANDWIDTH_NARROWBAND
+ *                             OPUS_BANDWIDTH_MEDIUMBAND
+ *                             OPUS_BANDWIDTH_WIDEBAND
+ *                             OPUS_BANDWIDTH_SUPERWIDEBAND
+ *                             OPUS_BANDWIDTH_FULLBAND
+ *
+ * Return value              :  0 - Success
+ *                             -1 - Error
+ */
+int16_t WebRtcOpus_SetBandwidth(OpusEncInst* inst, int32_t bandwidth);
+
+/*
  * WebRtcOpus_SetForceChannels(...)
  *
  * If the encoder is initialized as a stereo encoder, Opus will by default
diff --git a/modules/audio_coding/codecs/opus/opus_speed_test.cc b/modules/audio_coding/codecs/opus/opus_speed_test.cc
index 0590a1e..ca46aa1 100644
--- a/modules/audio_coding/codecs/opus/opus_speed_test.cc
+++ b/modules/audio_coding/codecs/opus/opus_speed_test.cc
@@ -81,10 +81,11 @@
   return 1000.0 * clocks / CLOCKS_PER_SEC;
 }
 
+/* Test audio length in second. */
+constexpr size_t kDurationSec = 400;
+
 #define ADD_TEST(complexity) \
 TEST_P(OpusSpeedTest, OpusSetComplexityTest##complexity) { \
-  /* Test audio length in second. */ \
-  size_t kDurationSec = 400; \
   /* Set complexity. */ \
   printf("Setting complexity to %d ...\n", complexity); \
   EXPECT_EQ(0, WebRtcOpus_SetComplexity(opus_encoder_, complexity)); \
@@ -103,6 +104,20 @@
 ADD_TEST(1);
 ADD_TEST(0);
 
+#define ADD_BANDWIDTH_TEST(bandwidth)                                \
+  TEST_P(OpusSpeedTest, OpusSetBandwidthTest##bandwidth) {           \
+    /* Set bandwidth. */                                             \
+    printf("Setting bandwidth to %d ...\n", bandwidth);              \
+    EXPECT_EQ(0, WebRtcOpus_SetBandwidth(opus_encoder_, bandwidth)); \
+    EncodeDecode(kDurationSec);                                      \
+  }
+
+ADD_BANDWIDTH_TEST(OPUS_BANDWIDTH_NARROWBAND);
+ADD_BANDWIDTH_TEST(OPUS_BANDWIDTH_MEDIUMBAND);
+ADD_BANDWIDTH_TEST(OPUS_BANDWIDTH_WIDEBAND);
+ADD_BANDWIDTH_TEST(OPUS_BANDWIDTH_SUPERWIDEBAND);
+ADD_BANDWIDTH_TEST(OPUS_BANDWIDTH_FULLBAND);
+
 // List all test cases: (channel, bit rat, filename, extension).
 const coding_param param_set[] = {
     std::make_tuple(1,
diff --git a/modules/audio_coding/codecs/opus/opus_unittest.cc b/modules/audio_coding/codecs/opus/opus_unittest.cc
index c9e6ad1..be0530b 100644
--- a/modules/audio_coding/codecs/opus/opus_unittest.cc
+++ b/modules/audio_coding/codecs/opus/opus_unittest.cc
@@ -458,6 +458,45 @@
   EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
 }
 
+TEST_P(OpusTest, OpusSetBandwidth) {
+  PrepareSpeechData(channels_, 20, 20);
+
+  int16_t audio_type;
+  std::unique_ptr<int16_t[]> output_data_decode(
+      new int16_t[kOpus20msFrameSamples * channels_]());
+
+  // Test without creating encoder memory.
+  EXPECT_EQ(-1,
+            WebRtcOpus_SetBandwidth(opus_encoder_, OPUS_BANDWIDTH_NARROWBAND));
+  EXPECT_EQ(-1, WebRtcOpus_GetBandwidth(opus_encoder_));
+
+  // Create encoder memory, try with different bandwidths.
+  EXPECT_EQ(0,
+            WebRtcOpus_EncoderCreate(&opus_encoder_, channels_, application_));
+  EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
+
+  EXPECT_EQ(-1, WebRtcOpus_SetBandwidth(opus_encoder_,
+                                        OPUS_BANDWIDTH_NARROWBAND - 1));
+  EXPECT_EQ(0,
+            WebRtcOpus_SetBandwidth(opus_encoder_, OPUS_BANDWIDTH_NARROWBAND));
+  EncodeDecode(opus_encoder_, speech_data_.GetNextBlock(), opus_decoder_,
+               output_data_decode.get(), &audio_type);
+  EXPECT_EQ(OPUS_BANDWIDTH_NARROWBAND, WebRtcOpus_GetBandwidth(opus_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_SetBandwidth(opus_encoder_, OPUS_BANDWIDTH_FULLBAND));
+  EncodeDecode(opus_encoder_, speech_data_.GetNextBlock(), opus_decoder_,
+               output_data_decode.get(), &audio_type);
+  EXPECT_EQ(OPUS_BANDWIDTH_FULLBAND, WebRtcOpus_GetBandwidth(opus_encoder_));
+  EXPECT_EQ(
+      -1, WebRtcOpus_SetBandwidth(opus_encoder_, OPUS_BANDWIDTH_FULLBAND + 1));
+  EncodeDecode(opus_encoder_, speech_data_.GetNextBlock(), opus_decoder_,
+               output_data_decode.get(), &audio_type);
+  EXPECT_EQ(OPUS_BANDWIDTH_FULLBAND, WebRtcOpus_GetBandwidth(opus_encoder_));
+
+  // Free memory.
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
+}
+
 TEST_P(OpusTest, OpusForceChannels) {
   // Test without creating encoder memory.
   EXPECT_EQ(-1, WebRtcOpus_SetForceChannels(opus_encoder_, 1));