Adding "usedtx" as Opus codec parameter.

This is according to https://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03

Specifically,

usedtx: specifies if the decoder prefers the use of DTX. values are 1 and 0. If no value is specified, usedtx is assumed to be 0.

BUG=1014
R=juberti@webrtc.org, tina.legrand@webrtc.org

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

Cr-Commit-Position: refs/heads/master@{#8872}
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
index be1fe47..72a72bf 100644
--- a/talk/app/webrtc/webrtcsdp.cc
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -69,6 +69,7 @@
 using cricket::kCodecParamStartBitrate;
 using cricket::kCodecParamStereo;
 using cricket::kCodecParamUseInbandFec;
+using cricket::kCodecParamUseDtx;
 using cricket::kCodecParamSctpProtocol;
 using cricket::kCodecParamSctpStreams;
 using cricket::kCodecParamMaxAverageBitrate;
@@ -1560,9 +1561,9 @@
 bool IsFmtpParam(const std::string& name) {
   const char* kFmtpParams[] = {
     kCodecParamMinPTime, kCodecParamSPropStereo,
-    kCodecParamStereo, kCodecParamUseInbandFec, kCodecParamStartBitrate,
-    kCodecParamMaxBitrate, kCodecParamMinBitrate, kCodecParamMaxQuantization,
-    kCodecParamSctpProtocol, kCodecParamSctpStreams,
+    kCodecParamStereo, kCodecParamUseInbandFec, kCodecParamUseDtx,
+    kCodecParamStartBitrate, kCodecParamMaxBitrate, kCodecParamMinBitrate,
+    kCodecParamMaxQuantization, kCodecParamSctpProtocol, kCodecParamSctpStreams,
     kCodecParamMaxAverageBitrate, kCodecParamMaxPlaybackRate,
     kCodecParamAssociatedPayloadType
   };
diff --git a/talk/media/base/constants.cc b/talk/media/base/constants.cc
index 4d41add..6712a3f 100644
--- a/talk/media/base/constants.cc
+++ b/talk/media/base/constants.cc
@@ -62,6 +62,7 @@
 const char kCodecParamSPropStereo[] = "sprop-stereo";
 const char kCodecParamStereo[] = "stereo";
 const char kCodecParamUseInbandFec[] = "useinbandfec";
+const char kCodecParamUseDtx[] = "usedtx";
 const char kCodecParamMaxAverageBitrate[] = "maxaveragebitrate";
 const char kCodecParamMaxPlaybackRate[] = "maxplaybackrate";
 
@@ -77,6 +78,7 @@
 const int kOpusDefaultSPropStereo = 0;
 const int kOpusDefaultStereo = 0;
 const int kOpusDefaultUseInbandFec = 0;
+const int kOpusDefaultUseDtx = 0;
 const int kOpusDefaultMaxPlaybackRate = 48000;
 
 const int kPreferredMaxPTime = 60;
diff --git a/talk/media/base/constants.h b/talk/media/base/constants.h
index 93198fa..a67c667 100644
--- a/talk/media/base/constants.h
+++ b/talk/media/base/constants.h
@@ -68,6 +68,7 @@
 extern const char kCodecParamSPropStereo[];
 extern const char kCodecParamStereo[];
 extern const char kCodecParamUseInbandFec[];
+extern const char kCodecParamUseDtx[];
 extern const char kCodecParamMaxAverageBitrate[];
 extern const char kCodecParamMaxPlaybackRate[];
 extern const char kCodecParamSctpProtocol[];
@@ -87,6 +88,7 @@
 extern const int kOpusDefaultSPropStereo;
 extern const int kOpusDefaultStereo;
 extern const int kOpusDefaultUseInbandFec;
+extern const int kOpusDefaultUseDtx;
 extern const int kOpusDefaultMaxPlaybackRate;
 
 // Prefered values in this code base. Note that they may differ from the default
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
index 960bad3..c394d0c 100644
--- a/talk/media/webrtc/fakewebrtcvoiceengine.h
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -314,6 +314,9 @@
   bool GetVAD(int channel) {
     return channels_[channel]->vad;
   }
+  bool GetOpusDtx(int channel) {
+    return channels_[channel]->opus_dtx;
+  }
   bool GetRED(int channel) {
     return channels_[channel]->red;
   }
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index f20b5e1..16e0ee8 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -79,7 +79,6 @@
   { kIlbcCodecName,   8000,  1, 102, false, { 20, 30, 40, 60 } },
   { kPcmuCodecName,   8000,  1, 0,   false, { 10, 20, 30, 40, 50, 60 } },
   { kPcmaCodecName,   8000,  1, 8,   false, { 10, 20, 30, 40, 50, 60 } },
-  { kCnCodecName,     48000, 1, 107, false, { } },
   { kCnCodecName,     32000, 1, 106, false, { } },
   { kCnCodecName,     16000, 1, 105, false, { } },
   { kCnCodecName,     8000,  1, 13,  false, { } },
@@ -161,6 +160,7 @@
      << " (" << codec.id << ")";
   return ss.str();
 }
+
 static std::string ToString(const webrtc::CodecInst& codec) {
   std::stringstream ss;
   ss << codec.plname << "/" << codec.plfreq << "/" << codec.channels
@@ -191,9 +191,17 @@
   return filter;
 }
 
+static bool IsCodec(const AudioCodec& codec, const char* ref_name) {
+  return (_stricmp(codec.name.c_str(), ref_name) == 0);
+}
+
+static bool IsCodec(const webrtc::CodecInst& codec, const char* ref_name) {
+  return (_stricmp(codec.plname, ref_name) == 0);
+}
+
 static bool IsCodecMultiRate(const webrtc::CodecInst& codec) {
   for (size_t i = 0; i < ARRAY_SIZE(kCodecPrefs); ++i) {
-    if (_stricmp(kCodecPrefs[i].name, codec.plname) == 0 &&
+    if (IsCodec(codec, kCodecPrefs[i].name) &&
         kCodecPrefs[i].clockrate == codec.plfreq) {
       return kCodecPrefs[i].is_multi_rate;
     }
@@ -201,18 +209,6 @@
   return false;
 }
 
-static bool IsTelephoneEventCodec(const std::string& name) {
-  return _stricmp(name.c_str(), "telephone-event") == 0;
-}
-
-static bool IsCNCodec(const std::string& name) {
-  return _stricmp(name.c_str(), "CN") == 0;
-}
-
-static bool IsRedCodec(const std::string& name) {
-  return _stricmp(name.c_str(), "red") == 0;
-}
-
 static bool FindCodec(const std::vector<AudioCodec>& codecs,
                       const AudioCodec& codec,
                       AudioCodec* found_codec) {
@@ -248,9 +244,9 @@
 // TODO(Brave): Query supported packet sizes from ACM when the API is ready.
 static bool SetPTimeAsPacketSize(webrtc::CodecInst* codec, int ptime_ms) {
   for (const CodecPref& codec_pref : kCodecPrefs) {
-    if ((_stricmp(codec_pref.name, codec->plname) == 0 &&
+    if ((IsCodec(*codec, codec_pref.name) &&
         codec_pref.clockrate == codec->plfreq) ||
-        _stricmp(codec_pref.name, kG722CodecName) == 0) {
+        IsCodec(*codec, kG722CodecName)) {
       int packet_size_ms = SelectPacketSize(codec_pref, ptime_ms);
       if (packet_size_ms) {
         // Convert unit from milli-seconds to samples.
@@ -262,6 +258,87 @@
   return false;
 }
 
+// Return true if codec.params[feature] == "1", false otherwise.
+static bool IsCodecFeatureEnabled(const AudioCodec& codec,
+                                  const char* feature) {
+  int value;
+  return codec.GetParam(feature, &value) && value == 1;
+}
+
+// Use params[kCodecParamMaxAverageBitrate] if it is defined, use codec.bitrate
+// otherwise. If the value (either from params or codec.bitrate) <=0, use the
+// default configuration. If the value is beyond feasible bit rate of Opus,
+// clamp it. Returns the Opus bit rate for operation.
+static int GetOpusBitrate(const AudioCodec& codec, int max_playback_rate) {
+  int bitrate = 0;
+  bool use_param = true;
+  if (!codec.GetParam(kCodecParamMaxAverageBitrate, &bitrate)) {
+    bitrate = codec.bitrate;
+    use_param = false;
+  }
+  if (bitrate <= 0) {
+    if (max_playback_rate <= 8000) {
+      bitrate = kOpusBitrateNb;
+    } else if (max_playback_rate <= 16000) {
+      bitrate = kOpusBitrateWb;
+    } else {
+      bitrate = kOpusBitrateFb;
+    }
+
+    if (IsCodecFeatureEnabled(codec, kCodecParamStereo)) {
+      bitrate *= 2;
+    }
+  } else if (bitrate < kOpusMinBitrate || bitrate > kOpusMaxBitrate) {
+    bitrate = (bitrate < kOpusMinBitrate) ? kOpusMinBitrate : kOpusMaxBitrate;
+    std::string rate_source =
+        use_param ? "Codec parameter \"maxaveragebitrate\"" :
+            "Supplied Opus bitrate";
+    LOG(LS_WARNING) << rate_source
+                    << " is invalid and is replaced by: "
+                    << bitrate;
+  }
+  return bitrate;
+}
+
+// Returns kOpusDefaultPlaybackRate if params[kCodecParamMaxPlaybackRate] is not
+// defined. Returns the value of params[kCodecParamMaxPlaybackRate] otherwise.
+static int GetOpusMaxPlaybackRate(const AudioCodec& codec) {
+  int value;
+  if (codec.GetParam(kCodecParamMaxPlaybackRate, &value)) {
+    return value;
+  }
+  return kOpusDefaultMaxPlaybackRate;
+}
+
+static void GetOpusConfig(const AudioCodec& codec, webrtc::CodecInst* voe_codec,
+                          bool* enable_codec_fec, int* max_playback_rate,
+                          bool* enable_codec_dtx) {
+  *enable_codec_fec = IsCodecFeatureEnabled(codec, kCodecParamUseInbandFec);
+  *enable_codec_dtx = IsCodecFeatureEnabled(codec, kCodecParamUseDtx);
+  *max_playback_rate = GetOpusMaxPlaybackRate(codec);
+
+  // If OPUS, change what we send according to the "stereo" codec
+  // parameter, and not the "channels" parameter.  We set
+  // voe_codec.channels to 2 if "stereo=1" and 1 otherwise.  If
+  // the bitrate is not specified, i.e. is <= zero, we set it to the
+  // appropriate default value for mono or stereo Opus.
+
+  voe_codec->channels = IsCodecFeatureEnabled(codec, kCodecParamStereo) ? 2 : 1;
+  voe_codec->rate = GetOpusBitrate(codec, *max_playback_rate);
+}
+
+// Changes RTP timestamp rate of G722. This is due to the "bug" in the RFC
+// which says that G722 should be advertised as 8 kHz although it is a 16 kHz
+// codec.
+static void MaybeFixupG722(webrtc::CodecInst* voe_codec, int new_plfreq) {
+  if (IsCodec(*voe_codec, kG722CodecName)) {
+    // If the ASSERT triggers, the codec definition in WebRTC VoiceEngine
+    // has changed, and this special case is no longer needed.
+    ASSERT(voe_codec->plfreq != new_plfreq);
+    voe_codec->plfreq = new_plfreq;
+  }
+}
+
 // Gets the default set of options applied to the engine. Historically, these
 // were supplied as a combination of flags from the channel manager (ec, agc,
 // ns, and highpass) and the rest hardcoded in InitInternal.
@@ -283,100 +360,8 @@
   return options;
 }
 
-static bool IsOpus(const AudioCodec& codec) {
-  return (_stricmp(codec.name.c_str(), kOpusCodecName) == 0);
-}
-
-static bool IsIsac(const AudioCodec& codec) {
-  return (_stricmp(codec.name.c_str(), kIsacCodecName) == 0);
-}
-
-// True if params["stereo"] == "1"
-static bool IsOpusStereoEnabled(const AudioCodec& codec) {
-  int value;
-  return codec.GetParam(kCodecParamStereo, &value) && value == 1;
-}
-
-// Use params[kCodecParamMaxAverageBitrate] if it is defined, use codec.bitrate
-// otherwise. If the value (either from params or codec.bitrate) <=0, use the
-// default configuration. If the value is beyond feasible bit rate of Opus,
-// clamp it. Returns the Opus bit rate for operation.
-static int GetOpusBitrate(const AudioCodec& codec, int max_playback_rate) {
-  int bitrate = 0;
-  bool use_param = true;
-  if (!codec.GetParam(kCodecParamMaxAverageBitrate, &bitrate)) {
-    bitrate = codec.bitrate;
-    use_param = false;
-  }
-  if (bitrate <= 0) {
-    if (max_playback_rate <= 8000) {
-      bitrate = kOpusBitrateNb;
-    }
-    else if (max_playback_rate <= 16000) {
-      bitrate = kOpusBitrateWb;
-    }
-    else {
-      bitrate = kOpusBitrateFb;
-    }
-
-    if (IsOpusStereoEnabled(codec)) {
-      bitrate *= 2;
-    }
-  }
-  else if (bitrate < kOpusMinBitrate || bitrate > kOpusMaxBitrate) {
-    bitrate = (bitrate < kOpusMinBitrate) ? kOpusMinBitrate : kOpusMaxBitrate;
-    std::string rate_source =
-        use_param ? "Codec parameter \"maxaveragebitrate\"" :
-        "Supplied Opus bitrate";
-    LOG(LS_WARNING) << rate_source
-                    << " is invalid and is replaced by: "
-                    << bitrate;
-  }
-  return bitrate;
-}
-
-// Return true if params[kCodecParamUseInbandFec] == "1", false
-// otherwise.
-static bool IsOpusFecEnabled(const AudioCodec& codec) {
-  int value;
-  return codec.GetParam(kCodecParamUseInbandFec, &value) && value == 1;
-}
-
-// Returns kOpusDefaultPlaybackRate if params[kCodecParamMaxPlaybackRate] is not
-// defined. Returns the value of params[kCodecParamMaxPlaybackRate] otherwise.
-static int GetOpusMaxPlaybackRate(const AudioCodec& codec) {
-  int value;
-  if (codec.GetParam(kCodecParamMaxPlaybackRate, &value)) {
-    return value;
-  }
-  return kOpusDefaultMaxPlaybackRate;
-}
-
-static void GetOpusConfig(const AudioCodec& codec, webrtc::CodecInst* voe_codec,
-  bool* enable_codec_fec, int* max_playback_rate) {
-  *enable_codec_fec = IsOpusFecEnabled(codec);
-  *max_playback_rate = GetOpusMaxPlaybackRate(codec);
-
-  // If OPUS, change what we send according to the "stereo" codec
-  // parameter, and not the "channels" parameter.  We set
-  // voe_codec.channels to 2 if "stereo=1" and 1 otherwise.  If
-  // the bitrate is not specified, i.e. is <= zero, we set it to the
-  // appropriate default value for mono or stereo Opus.
-
-  voe_codec->channels = IsOpusStereoEnabled(codec) ? 2 : 1;
-  voe_codec->rate = GetOpusBitrate(codec, *max_playback_rate);
-}
-
-// Changes RTP timestamp rate of G722. This is due to the "bug" in the RFC
-// which says that G722 should be advertised as 8 kHz although it is a 16 kHz
-// codec.
-static void MaybeFixupG722(webrtc::CodecInst* voe_codec, int new_plfreq) {
-  if (_stricmp(voe_codec->plname, kG722CodecName) == 0) {
-    // If the ASSERT triggers, the codec definition in WebRTC VoiceEngine
-    // has changed, and this special case is no longer needed.
-    ASSERT(voe_codec->plfreq != new_plfreq);
-    voe_codec->plfreq = new_plfreq;
-  }
+static std::string GetEnableString(bool enable) {
+  return enable ? "enable" : "disable";
 }
 
 class WebRtcSoundclipMedia : public SoundclipMedia {
@@ -535,13 +520,13 @@
     webrtc::CodecInst voe_codec;
     if (GetVoeCodec(i, &voe_codec)) {
       // Skip uncompressed formats.
-      if (_stricmp(voe_codec.plname, kL16CodecName) == 0) {
+      if (IsCodec(voe_codec, kL16CodecName)) {
         continue;
       }
 
       const CodecPref* pref = NULL;
       for (size_t j = 0; j < ARRAY_SIZE(kCodecPrefs); ++j) {
-        if (_stricmp(kCodecPrefs[j].name, voe_codec.plname) == 0 &&
+        if (IsCodec(voe_codec, kCodecPrefs[j].name) &&
             kCodecPrefs[j].clockrate == voe_codec.plfreq &&
             kCodecPrefs[j].channels == voe_codec.channels) {
           pref = &kCodecPrefs[j];
@@ -556,11 +541,11 @@
                          voe_codec.rate, voe_codec.channels,
                          ARRAY_SIZE(kCodecPrefs) - (pref - kCodecPrefs));
         LOG(LS_INFO) << ToString(codec);
-        if (IsIsac(codec)) {
+        if (IsCodec(codec, kIsacCodecName)) {
           // Indicate auto-bitrate in signaling.
           codec.bitrate = 0;
         }
-        if (IsOpus(codec)) {
+        if (IsCodec(codec, kOpusCodecName)) {
           // Only add fmtp parameters that differ from the spec.
           if (kPreferredMinPTime != kOpusDefaultMinPTime) {
             codec.params[kCodecParamMinPTime] =
@@ -1323,7 +1308,7 @@
           MaybeFixupG722(&voe_codec, 16000);
 
           // Apply codec-specific settings.
-          if (IsIsac(codec)) {
+          if (IsCodec(codec, kIsacCodecName)) {
             // If ISAC and an explicit bitrate is not specified,
             // enable auto bitrate adjustment.
             voe_codec.rate = (in.bitrate > 0) ? in.bitrate : -1;
@@ -2120,7 +2105,7 @@
 
   bool nack_enabled = nack_enabled_;
   bool enable_codec_fec = false;
-
+  bool enable_opus_dtx = false;
   int opus_max_playback_rate = 0;
 
   // Set send codec (the first non-telephone-event/CN codec)
@@ -2134,7 +2119,7 @@
       continue;
     }
 
-    if (IsTelephoneEventCodec(it->name) || IsCNCodec(it->name)) {
+    if (IsCodec(*it, kDtmfCodecName) || IsCodec(*it, kCnCodecName)) {
       // Skip telephone-event/CN codec, which will be handled later.
       continue;
     }
@@ -2143,7 +2128,7 @@
     // Be sure to use the payload type requested by the remote side.
     // "red", for RED audio, is a special case where the actual codec to be
     // used is specified in params.
-    if (IsRedCodec(it->name)) {
+    if (IsCodec(*it, kRedCodecName)) {
       // Parse out the RED parameters. If we fail, just ignore RED;
       // we don't support all possible params/usage scenarios.
       if (!GetRedSendCodec(*it, codecs, &send_codec)) {
@@ -2160,11 +2145,11 @@
     } else {
       send_codec = voe_codec;
       nack_enabled = IsNackEnabled(*it);
-      // For Opus as the send codec, we are to enable inband FEC if requested
-      // and set maximum playback rate.
-      if (IsOpus(*it)) {
+      // For Opus as the send codec, we are to determine inband FEC, maximum
+      // playback rate, and opus internal dtx.
+      if (IsCodec(*it, kOpusCodecName)) {
         GetOpusConfig(*it, &send_codec, &enable_codec_fec,
-                      &opus_max_playback_rate);
+                      &opus_max_playback_rate, &enable_opus_dtx);
       }
 
       // Set packet size if the AudioCodec param kCodecParamPTime is set.
@@ -2207,17 +2192,32 @@
     }
   }
 
-  // maxplaybackrate should be set after SetSendCodec.
-  // If opus_max_playback_rate <= 0, the default maximum playback rate of 48 kHz
-  // will be used.
-  if (opus_max_playback_rate > 0) {
-    LOG(LS_INFO) << "Attempt to set maximum playback rate to "
-                 << opus_max_playback_rate
-                 << " Hz on channel "
+  if (IsCodec(send_codec, kOpusCodecName)) {
+    // DTX and maxplaybackrate should be set after SetSendCodec. Because current
+    // send codec has to be Opus.
+
+    // Set Opus internal DTX.
+    LOG(LS_INFO) << "Attempt to "
+                 << GetEnableString(enable_opus_dtx)
+                 << " Opus DTX on channel "
                  << channel;
-    if (engine()->voe()->codec()->SetOpusMaxPlaybackRate(
-        channel, opus_max_playback_rate) == -1) {
-      LOG(LS_WARNING) << "Could not set maximum playback rate.";
+    if (engine()->voe()->codec()->SetOpusDtx(channel, enable_opus_dtx)) {
+      LOG_RTCERR2(SetOpusDtx, channel, enable_opus_dtx);
+      return false;
+    }
+
+    // If opus_max_playback_rate <= 0, the default maximum playback rate
+    // (48 kHz) will be used.
+    if (opus_max_playback_rate > 0) {
+      LOG(LS_INFO) << "Attempt to set maximum playback rate to "
+                   << opus_max_playback_rate
+                   << " Hz on channel "
+                   << channel;
+      if (engine()->voe()->codec()->SetOpusMaxPlaybackRate(
+          channel, opus_max_playback_rate) == -1) {
+        LOG_RTCERR2(SetOpusMaxPlaybackRate, channel, opus_max_playback_rate);
+        return false;
+      }
     }
   }
 
@@ -2241,13 +2241,13 @@
 
     // Find the DTMF telephone event "codec" and tell VoiceEngine channels
     // about it.
-    if (IsTelephoneEventCodec(it->name)) {
+    if (IsCodec(*it, kDtmfCodecName)) {
       if (engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
               channel, it->id) == -1) {
         LOG_RTCERR2(SetSendTelephoneEventPayloadType, channel, it->id);
         return false;
       }
-    } else if (IsCNCodec(it->name)) {
+    } else if (IsCodec(*it, kCnCodecName)) {
       // Turn voice activity detection/comfort noise on if supported.
       // Set the wideband CN payload type appropriately.
       // (narrowband always uses the static payload type 13).
@@ -2285,7 +2285,9 @@
       }
       // Only turn on VAD if we have a CN payload type that matches the
       // clockrate for the codec we are going to use.
-      if (it->clockrate == send_codec.plfreq) {
+      if (it->clockrate == send_codec.plfreq && send_codec.channels != 2) {
+        // TODO(minyue): If CN frequency == 48000 Hz is allowed, consider the
+        // interaction between VAD and Opus FEC.
         LOG(LS_INFO) << "Enabling VAD";
         if (engine()->voe()->codec()->SetVADStatus(channel, true) == -1) {
           LOG_RTCERR2(SetVADStatus, channel, true);
@@ -2303,8 +2305,7 @@
   for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
        it != codecs.end(); ++it) {
     // Find the DTMF telephone event "codec".
-    if (_stricmp(it->name.c_str(), "telephone-event") == 0 ||
-        _stricmp(it->name.c_str(), "audio/telephone-event") == 0) {
+    if (IsCodec(*it, kDtmfCodecName)) {
       dtmf_allowed_ = true;
     }
   }
diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
index 59388bc..7895023 100644
--- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc
+++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
@@ -1402,6 +1402,53 @@
             voe_.GetMaxEncodingBandwidth(channel_num));
 }
 
+// Test that with usedtx=0, Opus DTX is off.
+TEST_F(WebRtcVoiceEngineTestFake, DisableOpusDtxOnOpus) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].params["usedtx"] = "0";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_FALSE(voe_.GetOpusDtx(channel_num));
+}
+
+// Test that with usedtx=1, Opus DTX is on.
+TEST_F(WebRtcVoiceEngineTestFake, EnableOpusDtxOnOpus) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].params["usedtx"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetOpusDtx(channel_num));
+  EXPECT_FALSE(voe_.GetVAD(channel_num));  // Opus DTX should not affect VAD.
+}
+
+// Test that usedtx=1 works with stereo Opus.
+TEST_F(WebRtcVoiceEngineTestFake, EnableOpusDtxOnOpusStereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].params["usedtx"] = "1";
+  codecs[0].params["stereo"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetOpusDtx(channel_num));
+  EXPECT_FALSE(voe_.GetVAD(channel_num));   // Opus DTX should not affect VAD.
+}
+
+// Test that usedtx=1 does not work with non Opus.
+TEST_F(WebRtcVoiceEngineTestFake, CannotEnableOpusDtxOnNonOpus) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs[0].params["usedtx"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_FALSE(voe_.GetOpusDtx(channel_num));
+}
+
 // Test that we can switch back and forth between Opus and ISAC with CN.
 TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsIsacOpusSwitching) {
   EXPECT_TRUE(SetupEngine());