Adding SetOpusMaxBandwidth in VoE and ACM

This is a step to solve
https://code.google.com/p/webrtc/issues/detail?id=1906

In particular, we add an API in VoE and ACM to call Opus's API of setting maximum bandwidth.

TEST = added a test in voe_cmd_test and listened to the result

BUG=
R=henrika@google.com, henrika@webrtc.org, turaj@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/21129004

git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@6869 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/modules/audio_coding/main/acm2/acm_generic_codec.cc b/modules/audio_coding/main/acm2/acm_generic_codec.cc
index db776d2..565d291 100644
--- a/modules/audio_coding/main/acm2/acm_generic_codec.cc
+++ b/modules/audio_coding/main/acm2/acm_generic_codec.cc
@@ -1000,6 +1000,12 @@
   return -1;
 }
 
+int ACMGenericCodec::SetOpusMaxBandwidth(int /* max_bandwidth */) {
+  WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, unique_id_,
+               "The send-codec is not Opus, failed to set maximum bandwidth.");
+  return -1;
+}
+
 }  // namespace acm2
 
 }  // namespace webrtc
diff --git a/modules/audio_coding/main/acm2/acm_generic_codec.h b/modules/audio_coding/main/acm2/acm_generic_codec.h
index 80f239a..b88e28f 100644
--- a/modules/audio_coding/main/acm2/acm_generic_codec.h
+++ b/modules/audio_coding/main/acm2/acm_generic_codec.h
@@ -538,6 +538,23 @@
                                  int16_t* payload_len_bytes);
 
   ///////////////////////////////////////////////////////////////////////////
+  // int SetOpusMaxBandwidth()
+  // Sets maximum required encoding bandwidth for Opus. This is to tell Opus
+  // that it is enough to code the input audio up to a bandwidth. A use case of
+  // this is when the receiver cannot render the full band. Opus can take this
+  // information to optimize the bit rate and increase the computation
+  // efficiency.
+  //
+  // Input:
+  //   -max_bandwidth      : maximum required bandwidth.
+  //
+  // Return value:
+  //   -1 if failed or on codecs other than Opus
+  //    0 if succeeded.
+  //
+  virtual int SetOpusMaxBandwidth(int /* max_bandwidth */);
+
+  ///////////////////////////////////////////////////////////////////////////
   // HasFrameToEncode()
   // Returns true if there is enough audio buffered for encoding, such that
   // calling Encode() will return a payload.
diff --git a/modules/audio_coding/main/acm2/acm_opus.cc b/modules/audio_coding/main/acm2/acm_opus.cc
index f75a348..c778982 100644
--- a/modules/audio_coding/main/acm2/acm_opus.cc
+++ b/modules/audio_coding/main/acm2/acm_opus.cc
@@ -261,6 +261,11 @@
   return -1;
 }
 
+int ACMOpus::SetOpusMaxBandwidth(int max_bandwidth) {
+  // Ask the encoder to change the maximum required bandwidth.
+  return WebRtcOpus_SetMaxBandwidth(encoder_inst_ptr_, max_bandwidth);
+}
+
 #endif  // WEBRTC_CODEC_OPUS
 
 }  // namespace acm2
diff --git a/modules/audio_coding/main/acm2/acm_opus.h b/modules/audio_coding/main/acm2/acm_opus.h
index b94adc4..8c2882c 100644
--- a/modules/audio_coding/main/acm2/acm_opus.h
+++ b/modules/audio_coding/main/acm2/acm_opus.h
@@ -38,6 +38,8 @@
 
   virtual int SetPacketLossRate(int loss_rate) OVERRIDE;
 
+  virtual int SetOpusMaxBandwidth(int max_bandwidth) OVERRIDE;
+
  protected:
   void DestructEncoderSafe();
 
diff --git a/modules/audio_coding/main/acm2/audio_coding_module_impl.cc b/modules/audio_coding/main/acm2/audio_coding_module_impl.cc
index 26f5b54..164c0bb 100644
--- a/modules/audio_coding/main/acm2/audio_coding_module_impl.cc
+++ b/modules/audio_coding/main/acm2/audio_coding_module_impl.cc
@@ -1911,6 +1911,15 @@
       frame_size_ms, rate_bit_per_sec, enforce_frame_size);
 }
 
+// Informs Opus encoder about the maximum audio bandwidth needs to be encoded.
+int AudioCodingModuleImpl::SetOpusMaxBandwidth(int bandwidth_hz) {
+  CriticalSectionScoped lock(acm_crit_sect_);
+  if (!HaveValidEncoder("SetOpusMaxBandwidth")) {
+    return -1;
+  }
+  return codecs_[current_send_codec_idx_]->SetOpusMaxBandwidth(bandwidth_hz);
+}
+
 int AudioCodingModuleImpl::PlayoutTimestamp(uint32_t* timestamp) {
   return receiver_.GetPlayoutTimestamp(timestamp) ? 0 : -1;
 }
diff --git a/modules/audio_coding/main/acm2/audio_coding_module_impl.h b/modules/audio_coding/main/acm2/audio_coding_module_impl.h
index 02290da..9e5cc37 100644
--- a/modules/audio_coding/main/acm2/audio_coding_module_impl.h
+++ b/modules/audio_coding/main/acm2/audio_coding_module_impl.h
@@ -232,6 +232,10 @@
                                    int rate_bit_per_sec,
                                    bool enforce_frame_size = false);
 
+  // If current send codec is Opus, informs it about the maximum audio
+  // bandwidth needs to be encoded.
+  int SetOpusMaxBandwidth(int bandwidth_hz);
+
   int UnregisterReceiveCodec(uint8_t payload_type);
 
   int EnableNack(size_t max_nack_list_size);
diff --git a/modules/audio_coding/main/interface/audio_coding_module.h b/modules/audio_coding/main/interface/audio_coding_module.h
index 4827004..aae7ead 100644
--- a/modules/audio_coding/main/interface/audio_coding_module.h
+++ b/modules/audio_coding/main/interface/audio_coding_module.h
@@ -916,6 +916,23 @@
       bool enforce_frame_size = false) = 0;
 
   ///////////////////////////////////////////////////////////////////////////
+  // int SetOpusMaxBandwidth()
+  // If current send codec is Opus, informs it about maximum audio bandwidth
+  // needs to be encoded. A use case of this is when the receiver can only play
+  // audio up to frequency limit. Opus can use this information to optimize
+  // the bit rate and increase the computation efficiency.
+  //
+  // Input:
+  //   -banbwidth_hz            : maximum bandwidth in Hz.
+  //
+  // Return value:
+  //   -1 if current send codec is not Opus or
+  //      error occurred in setting the bandwidth,
+  //    0 maximum bandwidth is set successfully.
+  //
+  virtual int SetOpusMaxBandwidth(int banbwidth_hz) = 0;
+
+  ///////////////////////////////////////////////////////////////////////////
   //   statistics
   //
 
diff --git a/voice_engine/channel.cc b/voice_engine/channel.cc
index c9364ef..3f13ba2 100644
--- a/voice_engine/channel.cc
+++ b/voice_engine/channel.cc
@@ -1749,6 +1749,19 @@
     return 0;
 }
 
+int Channel::SetOpusMaxBandwidth(int bandwidth_hz) {
+  WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, _channelId),
+               "Channel::SetOpusMaxBandwidth()");
+
+  if (audio_coding_->SetOpusMaxBandwidth(bandwidth_hz) != 0) {
+    _engineStatisticsPtr->SetLastError(
+        VE_AUDIO_CODING_MODULE_ERROR, kTraceError,
+        "SetOpusMaxBandwidth() failed to set maximum encoding bandwidth");
+    return -1;
+  }
+  return 0;
+}
+
 int32_t Channel::RegisterExternalTransport(Transport& transport)
 {
     WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, _channelId),
diff --git a/voice_engine/channel.h b/voice_engine/channel.h
index 8385ccc..a4f7ecd 100644
--- a/voice_engine/channel.h
+++ b/voice_engine/channel.h
@@ -207,6 +207,7 @@
     int32_t SetRecPayloadType(const CodecInst& codec);
     int32_t GetRecPayloadType(CodecInst& codec);
     int32_t SetSendCNPayloadType(int type, PayloadFrequencies frequency);
+    int SetOpusMaxBandwidth(int bandwidth_hz);
 
     // VoE dual-streaming.
     int SetSecondarySendCodec(const CodecInst& codec, int red_payload_type);
diff --git a/voice_engine/include/voe_codec.h b/voice_engine/include/voe_codec.h
index 34a2232..56835f7 100644
--- a/voice_engine/include/voe_codec.h
+++ b/voice_engine/include/voe_codec.h
@@ -126,6 +126,14 @@
     virtual int GetVADStatus(int channel, bool& enabled, VadModes& mode,
                              bool& disabledDTX) = 0;
 
+    // Sets the maximum audio bandwidth needs to be encoded in Hz,
+    // |bandwidth_hz|, for the Opus encoder on a specific |channel|.
+    // TODO(minyue): Make SetOpusMaxBandwidth() pure virtual when
+    // fakewebrtcvoiceengine in talk is ready.
+    virtual int SetOpusMaxBandwidth(int channel, int bandwidth_hz) {
+      return -1;
+    }
+
     // Don't use. To be removed.
     virtual int SetAMREncFormat(int channel, AmrMode mode) { return -1; }
     virtual int SetAMRDecFormat(int channel, AmrMode mode) { return -1; }
diff --git a/voice_engine/test/auto_test/standard/codec_test.cc b/voice_engine/test/auto_test/standard/codec_test.cc
index 2970ab3..8ca4328 100644
--- a/voice_engine/test/auto_test/standard/codec_test.cc
+++ b/voice_engine/test/auto_test/standard/codec_test.cc
@@ -130,6 +130,33 @@
   EXPECT_EQ(webrtc::kVadConventional, vad_mode);
 }
 
+TEST_F(CodecTest, OpusMaxBandwidthCanBeSet) {
+  for (int i = 0; i < voe_codec_->NumOfCodecs(); ++i) {
+    voe_codec_->GetCodec(i, codec_instance_);
+    if (_stricmp("opus", codec_instance_.plname)) {
+      continue;
+    }
+    voe_codec_->SetSendCodec(channel_, codec_instance_);
+    // SetOpusMaxBandwidth can handle any integer as the bandwidth. Following
+    // tests some most commonly used numbers.
+    EXPECT_EQ(0, voe_codec_->SetOpusMaxBandwidth(channel_, 24000));
+    EXPECT_EQ(0, voe_codec_->SetOpusMaxBandwidth(channel_, 16000));
+    EXPECT_EQ(0, voe_codec_->SetOpusMaxBandwidth(channel_, 8000));
+    EXPECT_EQ(0, voe_codec_->SetOpusMaxBandwidth(channel_, 4000));
+  }
+}
+
+TEST_F(CodecTest, OpusMaxBandwidthCannotBeSetForNonOpus) {
+  for (int i = 0; i < voe_codec_->NumOfCodecs(); ++i) {
+    voe_codec_->GetCodec(i, codec_instance_);
+    if (!_stricmp("opus", codec_instance_.plname)) {
+      continue;
+    }
+    voe_codec_->SetSendCodec(channel_, codec_instance_);
+    EXPECT_EQ(-1, voe_codec_->SetOpusMaxBandwidth(channel_, 16000));
+  }
+}
+
 // TODO(xians, phoglund): Re-enable when issue 372 is resolved.
 TEST_F(CodecTest, DISABLED_ManualVerifySendCodecsForAllPacketSizes) {
   for (int i = 0; i < voe_codec_->NumOfCodecs(); ++i) {
diff --git a/voice_engine/test/cmd_test/voe_cmd_test.cc b/voice_engine/test/cmd_test/voe_cmd_test.cc
index a30764a..5e63645 100644
--- a/voice_engine/test/cmd_test/voe_cmd_test.cc
+++ b/voice_engine/test/cmd_test/voe_cmd_test.cc
@@ -445,6 +445,7 @@
       printf("%i. Remove a file-playing channel \n", option_index++);
       printf("%i. Toggle Opus stereo (Opus must be selected again to apply "
              "the setting) \n", option_index++);
+      printf("%i. Set Opus maximum audio bandwidth \n", option_index++);
 
       printf("Select action or %i to stop the call: ", option_index);
       int option_selection;
@@ -757,6 +758,12 @@
         else
           printf("\n Opus mono enabled (select Opus again to apply the "
                  "setting). \n");
+      } else if (option_selection == option_index++) {
+        printf("\n Input bandwidth in Hz: ");
+        int max_playback_rate;
+        ASSERT_EQ(1, scanf("%i", &max_playback_rate));
+        res = codec->SetOpusMaxBandwidth(chan, max_playback_rate);
+        VALIDATE;
       } else {
         break;
       }
diff --git a/voice_engine/voe_codec_impl.cc b/voice_engine/voe_codec_impl.cc
index 4aa0556..5099407 100644
--- a/voice_engine/voe_codec_impl.cc
+++ b/voice_engine/voe_codec_impl.cc
@@ -418,6 +418,24 @@
     return 0;
 }
 
+int VoECodecImpl::SetOpusMaxBandwidth(int channel, int bandwidth_hz) {
+  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
+               "SetOpusMaxBandwidth(channel=%d, bandwidth_hz=%d)", channel,
+               bandwidth_hz);
+  if (!_shared->statistics().Initialized()) {
+    _shared->SetLastError(VE_NOT_INITED, kTraceError);
+    return -1;
+  }
+  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
+  voe::Channel* channelPtr = ch.channel();
+  if (channelPtr == NULL) {
+    _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
+                          "SetOpusMaxBandwidth failed to locate channel");
+    return -1;
+  }
+  return channelPtr->SetOpusMaxBandwidth(bandwidth_hz);
+}
+
 void VoECodecImpl::ACMToExternalCodecRepresentation(CodecInst& toInst,
                                                     const CodecInst& fromInst)
 {
diff --git a/voice_engine/voe_codec_impl.h b/voice_engine/voe_codec_impl.h
index 1b9f00e..498854d 100644
--- a/voice_engine/voe_codec_impl.h
+++ b/voice_engine/voe_codec_impl.h
@@ -54,6 +54,8 @@
                              VadModes& mode,
                              bool& disabledDTX);
 
+    virtual int SetOpusMaxBandwidth(int channel, int bandwidth_hz);
+
     // Dual-streaming
     virtual int SetSecondarySendCodec(int channel, const CodecInst& codec,
                                       int red_payload_type);