- Remove calls to VoEDtmf from WVoE/MC.
- Flatten logic and make the relevant calls on VoE::Channel from AudioSendStream::SendTelephoneEvent().
- Store current payload type for telephone events in WVoMC, instead of setting it on the Channel. This should be refactored to be an AudioSendStream::Config parameter when we redo WVoMC::SetSendCodecs().

BUG=webrtc:4690
R=pthatcher@webrtc.org, tina.legrand@webrtc.org

Review URL: https://codereview.webrtc.org/1491743004 .

Cr-Commit-Position: refs/heads/master@{#10895}
diff --git a/talk/media/webrtc/fakewebrtccall.cc b/talk/media/webrtc/fakewebrtccall.cc
index d86bfb5..bf51fb3 100644
--- a/talk/media/webrtc/fakewebrtccall.cc
+++ b/talk/media/webrtc/fakewebrtccall.cc
@@ -39,14 +39,27 @@
   RTC_DCHECK(config.voe_channel_id != -1);
 }
 
+const webrtc::AudioSendStream::Config&
+    FakeAudioSendStream::GetConfig() const {
+  return config_;
+}
+
 void FakeAudioSendStream::SetStats(
     const webrtc::AudioSendStream::Stats& stats) {
   stats_ = stats;
 }
 
-const webrtc::AudioSendStream::Config&
-    FakeAudioSendStream::GetConfig() const {
-  return config_;
+FakeAudioSendStream::TelephoneEvent
+    FakeAudioSendStream::GetLatestTelephoneEvent() const {
+  return latest_telephone_event_;
+}
+
+bool FakeAudioSendStream::SendTelephoneEvent(int payload_type, uint8_t event,
+                                             uint32_t duration_ms) {
+  latest_telephone_event_.payload_type = payload_type;
+  latest_telephone_event_.event_code = event;
+  latest_telephone_event_.duration_ms = duration_ms;
+  return true;
 }
 
 webrtc::AudioSendStream::Stats FakeAudioSendStream::GetStats() const {
diff --git a/talk/media/webrtc/fakewebrtccall.h b/talk/media/webrtc/fakewebrtccall.h
index 2e70390..024c50d 100644
--- a/talk/media/webrtc/fakewebrtccall.h
+++ b/talk/media/webrtc/fakewebrtccall.h
@@ -49,10 +49,17 @@
 namespace cricket {
 class FakeAudioSendStream final : public webrtc::AudioSendStream {
  public:
+  struct TelephoneEvent {
+    int payload_type = -1;
+    uint8_t event_code = 0;
+    uint32_t duration_ms = 0;
+  };
+
   explicit FakeAudioSendStream(const webrtc::AudioSendStream::Config& config);
 
   const webrtc::AudioSendStream::Config& GetConfig() const;
   void SetStats(const webrtc::AudioSendStream::Stats& stats);
+  TelephoneEvent GetLatestTelephoneEvent() const;
 
  private:
   // webrtc::SendStream implementation.
@@ -64,8 +71,11 @@
   }
 
   // webrtc::AudioSendStream implementation.
+  bool SendTelephoneEvent(int payload_type, uint8_t event,
+                          uint32_t duration_ms) override;
   webrtc::AudioSendStream::Stats GetStats() const override;
 
+  TelephoneEvent latest_telephone_event_;
   webrtc::AudioSendStream::Config config_;
   webrtc::AudioSendStream::Stats stats_;
 };
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
index 9209863..65c3deb 100644
--- a/talk/media/webrtc/fakewebrtcvoiceengine.h
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -145,20 +145,11 @@
 
 class FakeWebRtcVoiceEngine
     : public webrtc::VoEAudioProcessing,
-      public webrtc::VoEBase, public webrtc::VoECodec, public webrtc::VoEDtmf,
+      public webrtc::VoEBase, public webrtc::VoECodec,
       public webrtc::VoEHardware,
       public webrtc::VoENetwork, public webrtc::VoERTP_RTCP,
       public webrtc::VoEVolumeControl {
  public:
-  struct DtmfInfo {
-    DtmfInfo()
-      : dtmf_event_code(-1),
-        dtmf_out_of_band(false),
-        dtmf_length_ms(-1) {}
-    int dtmf_event_code;
-    bool dtmf_out_of_band;
-    int dtmf_length_ms;
-  };
   struct Channel {
     explicit Channel()
         : external_transport(false),
@@ -173,7 +164,6 @@
           nack(false),
           cn8_type(13),
           cn16_type(105),
-          dtmf_type(106),
           red_type(117),
           nack_max_packets(0),
           send_ssrc(0),
@@ -195,12 +185,10 @@
     bool nack;
     int cn8_type;
     int cn16_type;
-    int dtmf_type;
     int red_type;
     int nack_max_packets;
     uint32_t send_ssrc;
     int associate_send_channel;
-    DtmfInfo dtmf_info;
     std::vector<webrtc::CodecInst> recv_codecs;
     webrtc::CodecInst send_codec;
     webrtc::PacketTime last_rtp_packet_time;
@@ -281,9 +269,6 @@
         channels_[channel]->cn16_type :
         channels_[channel]->cn8_type;
   }
-  int GetSendTelephoneEventPayloadType(int channel) {
-    return channels_[channel]->dtmf_type;
-  }
   int GetSendREDPayloadType(int channel) {
     return channels_[channel]->red_type;
   }
@@ -552,26 +537,6 @@
     return 0;
   }
 
-  // webrtc::VoEDtmf
-  WEBRTC_FUNC(SendTelephoneEvent, (int channel, int event_code,
-      bool out_of_band = true, int length_ms = 160, int attenuation_db = 10)) {
-    channels_[channel]->dtmf_info.dtmf_event_code = event_code;
-    channels_[channel]->dtmf_info.dtmf_out_of_band = out_of_band;
-    channels_[channel]->dtmf_info.dtmf_length_ms = length_ms;
-    return 0;
-  }
-  WEBRTC_FUNC(SetSendTelephoneEventPayloadType,
-      (int channel, unsigned char type)) {
-    channels_[channel]->dtmf_type = type;
-    return 0;
-  };
-  WEBRTC_STUB(GetSendTelephoneEventPayloadType,
-      (int channel, unsigned char& type));
-  WEBRTC_STUB(SetDtmfFeedbackStatus, (bool enable, bool directFeedback));
-  WEBRTC_STUB(GetDtmfFeedbackStatus, (bool& enabled, bool& directFeedback));
-  WEBRTC_STUB(PlayDtmfTone,
-      (int event_code, int length_ms = 200, int attenuation_db = 10));
-
   // webrtc::VoEHardware
   WEBRTC_FUNC(GetNumOfRecordingDevices, (int& num)) {
     return GetNumDevices(num);
@@ -831,15 +796,6 @@
   void EnableStereoChannelSwapping(bool enable) {
     stereo_swapping_enabled_ = enable;
   }
-  bool WasSendTelephoneEventCalled(int channel, int event_code, int length_ms) {
-    return (channels_[channel]->dtmf_info.dtmf_event_code == event_code &&
-            channels_[channel]->dtmf_info.dtmf_out_of_band == true &&
-            channels_[channel]->dtmf_info.dtmf_length_ms == length_ms);
-  }
-  bool WasPlayDtmfToneCalled(int event_code, int length_ms) {
-    return (dtmf_info_.dtmf_event_code == event_code &&
-            dtmf_info_.dtmf_length_ms == length_ms);
-  }
   int GetNetEqCapacity() const {
     auto ch = channels_.find(last_channel_);
     ASSERT(ch != channels_.end());
@@ -910,7 +866,6 @@
   int send_fail_channel_;
   int recording_sample_rate_;
   int playout_sample_rate_;
-  DtmfInfo dtmf_info_;
   FakeAudioProcessing audio_processing_;
 };
 
diff --git a/talk/media/webrtc/webrtcvoe.h b/talk/media/webrtc/webrtcvoe.h
index 1e104b4..aa705a0 100644
--- a/talk/media/webrtc/webrtcvoe.h
+++ b/talk/media/webrtc/webrtcvoe.h
@@ -36,7 +36,6 @@
 #include "webrtc/voice_engine/include/voe_audio_processing.h"
 #include "webrtc/voice_engine/include/voe_base.h"
 #include "webrtc/voice_engine/include/voe_codec.h"
-#include "webrtc/voice_engine/include/voe_dtmf.h"
 #include "webrtc/voice_engine/include/voe_errors.h"
 #include "webrtc/voice_engine/include/voe_hardware.h"
 #include "webrtc/voice_engine/include/voe_network.h"
@@ -91,14 +90,13 @@
  public:
   VoEWrapper()
       : engine_(webrtc::VoiceEngine::Create()), processing_(engine_),
-        base_(engine_), codec_(engine_), dtmf_(engine_),
+        base_(engine_), codec_(engine_),
         hw_(engine_), network_(engine_),
         rtp_(engine_), volume_(engine_) {
   }
   VoEWrapper(webrtc::VoEAudioProcessing* processing,
              webrtc::VoEBase* base,
              webrtc::VoECodec* codec,
-             webrtc::VoEDtmf* dtmf,
              webrtc::VoEHardware* hw,
              webrtc::VoENetwork* network,
              webrtc::VoERTP_RTCP* rtp,
@@ -107,7 +105,6 @@
         processing_(processing),
         base_(base),
         codec_(codec),
-        dtmf_(dtmf),
         hw_(hw),
         network_(network),
         rtp_(rtp),
@@ -118,7 +115,6 @@
   webrtc::VoEAudioProcessing* processing() const { return processing_.get(); }
   webrtc::VoEBase* base() const { return base_.get(); }
   webrtc::VoECodec* codec() const { return codec_.get(); }
-  webrtc::VoEDtmf* dtmf() const { return dtmf_.get(); }
   webrtc::VoEHardware* hw() const { return hw_.get(); }
   webrtc::VoENetwork* network() const { return network_.get(); }
   webrtc::VoERTP_RTCP* rtp() const { return rtp_.get(); }
@@ -130,7 +126,6 @@
   scoped_voe_ptr<webrtc::VoEAudioProcessing> processing_;
   scoped_voe_ptr<webrtc::VoEBase> base_;
   scoped_voe_ptr<webrtc::VoECodec> codec_;
-  scoped_voe_ptr<webrtc::VoEDtmf> dtmf_;
   scoped_voe_ptr<webrtc::VoEHardware> hw_;
   scoped_voe_ptr<webrtc::VoENetwork> network_;
   scoped_voe_ptr<webrtc::VoERTP_RTCP> rtp_;
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index d1f76ef..d70c864 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -134,6 +134,12 @@
 const char kAecDumpByAudioOptionFilename[] = "audio.aecdump";
 #endif
 
+// Constants from voice_engine_defines.h.
+const int kMinTelephoneEventCode = 0;           // RFC4733 (Section 2.3.1)
+const int kMaxTelephoneEventCode = 255;
+const int kMinTelephoneEventDuration = 100;
+const int kMaxTelephoneEventDuration = 60000;   // Actual limit is 2^16
+
 bool ValidateStreamParams(const StreamParams& sp) {
   if (sp.ssrcs.empty()) {
     LOG(LS_ERROR) << "No SSRCs in stream parameters: " << sp.ToString();
@@ -582,12 +588,6 @@
     LOG(LS_INFO) << ToString(codec);
   }
 
-  // Disable the DTMF playout when a tone is sent.
-  // PlayDtmfTone will be used if local playout is needed.
-  if (voe_wrapper_->dtmf()->SetDtmfFeedbackStatus(false) == -1) {
-    LOG_RTCERR1(SetDtmfFeedbackStatus, false);
-  }
-
   initialized_ = true;
   return true;
 }
@@ -1258,6 +1258,13 @@
     RTC_CHECK(stream_);
   }
 
+  bool SendTelephoneEvent(int payload_type, uint8_t event,
+                          uint32_t duration_ms) {
+    RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+    RTC_DCHECK(stream_);
+    return stream_->SendTelephoneEvent(payload_type, event, duration_ms);
+  }
+
   webrtc::AudioSendStream::Stats GetStats() const {
     RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
     RTC_DCHECK(stream_);
@@ -1612,7 +1619,7 @@
   engine()->voe()->codec()->SetFECStatus(channel, false);
 
   // Scan through the list to figure out the codec to use for sending, along
-  // with the proper configuration for VAD and DTMF.
+  // with the proper configuration for VAD.
   bool found_send_codec = false;
   webrtc::CodecInst send_codec;
   memset(&send_codec, 0, sizeof(send_codec));
@@ -1741,7 +1748,7 @@
     SetSendBitrateInternal(send_bitrate_bps_);
   }
 
-  // Loop through the codecs list again to config the telephone-event/CN codec.
+  // Loop through the codecs list again to config the CN codec.
   for (const AudioCodec& codec : codecs) {
     // Ignore codecs we don't know about. The negotiation step should prevent
     // this, but double-check to be sure.
@@ -1751,15 +1758,7 @@
       continue;
     }
 
-    // Find the DTMF telephone event "codec" and tell VoiceEngine channels
-    // about it.
-    if (IsCodec(codec, kDtmfCodecName)) {
-      if (engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
-              channel, codec.id) == -1) {
-        LOG_RTCERR2(SetSendTelephoneEventPayloadType, channel, codec.id);
-        return false;
-      }
-    } else if (IsCodec(codec, kCnCodecName)) {
+    if (IsCodec(codec, 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).
@@ -1814,12 +1813,16 @@
 bool WebRtcVoiceMediaChannel::SetSendCodecs(
     const std::vector<AudioCodec>& codecs) {
   RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+  // TODO(solenberg): Validate input - that payload types don't overlap, are
+  //                  within range, filter out codecs we don't support,
+  //                  redundant codecs etc.
 
-  dtmf_allowed_ = false;
+  // Find the DTMF telephone event "codec" payload type.
+  dtmf_payload_type_ = rtc::Optional<int>();
   for (const AudioCodec& codec : codecs) {
-    // Find the DTMF telephone event "codec".
     if (IsCodec(codec, kDtmfCodecName)) {
-      dtmf_allowed_ = true;
+      dtmf_payload_type_ = rtc::Optional<int>(codec.id);
+      break;
     }
   }
 
@@ -2282,38 +2285,34 @@
 }
 
 bool WebRtcVoiceMediaChannel::CanInsertDtmf() {
-  return dtmf_allowed_;
+  return dtmf_payload_type_ ? true : false;
 }
 
 bool WebRtcVoiceMediaChannel::InsertDtmf(uint32_t ssrc, int event,
                                          int duration) {
   RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
-  if (!dtmf_allowed_) {
+  LOG(LS_INFO) << "WebRtcVoiceMediaChannel::InsertDtmf";
+  if (!dtmf_payload_type_) {
     return false;
   }
 
-  // Send the event.
-  int channel = -1;
-  if (ssrc == 0) {
-    if (send_streams_.size() > 0) {
-      channel = send_streams_.begin()->second->channel();
-    }
-  } else {
-    channel = GetSendChannelId(ssrc);
-  }
-  if (channel == -1) {
-    LOG(LS_WARNING) << "InsertDtmf - The specified ssrc "
-                    << ssrc << " is not in use.";
+  // Figure out which WebRtcAudioSendStream to send the event on.
+  auto it = ssrc != 0 ? send_streams_.find(ssrc) : send_streams_.begin();
+  if (it == send_streams_.end()) {
+    LOG(LS_WARNING) << "The specified ssrc " << ssrc << " is not in use.";
     return false;
   }
-  // Send DTMF using out-of-band DTMF. ("true", as 3rd arg)
-  if (engine()->voe()->dtmf()->SendTelephoneEvent(
-          channel, event, true, duration) == -1) {
-    LOG_RTCERR4(SendTelephoneEvent, channel, event, true, duration);
+  if (event < kMinTelephoneEventCode ||
+      event > kMaxTelephoneEventCode) {
+    LOG(LS_WARNING) << "DTMF event code " << event << " out of range.";
     return false;
   }
-
-  return true;
+  if (duration < kMinTelephoneEventDuration ||
+      duration > kMaxTelephoneEventDuration) {
+    LOG(LS_WARNING) << "DTMF event duration " << duration << " out of range.";
+    return false;
+  }
+  return it->second->SendTelephoneEvent(*dtmf_payload_type_, event, duration);
 }
 
 void WebRtcVoiceMediaChannel::OnPacketReceived(
diff --git a/talk/media/webrtc/webrtcvoiceengine.h b/talk/media/webrtc/webrtcvoiceengine.h
index 843ff79..89cf25c 100644
--- a/talk/media/webrtc/webrtcvoiceengine.h
+++ b/talk/media/webrtc/webrtcvoiceengine.h
@@ -265,7 +265,7 @@
   bool send_bitrate_setting_ = false;
   int send_bitrate_bps_ = 0;
   AudioOptions options_;
-  bool dtmf_allowed_ = false;
+  rtc::Optional<int> dtmf_payload_type_;
   bool desired_playout_ = false;
   bool nack_enabled_ = false;
   bool playout_ = false;
diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
index 61f17db..1a27a4c 100644
--- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc
+++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
@@ -64,7 +64,6 @@
       : cricket::VoEWrapper(engine,  // processing
                             engine,  // base
                             engine,  // codec
-                            engine,  // dtmf
                             engine,  // hw
                             engine,  // network
                             engine,  // rtp
@@ -121,6 +120,12 @@
     engine_.Terminate();
   }
 
+  const cricket::FakeAudioSendStream& GetSendStream(uint32_t ssrc) {
+    const auto* send_stream = call_.GetAudioSendStream(ssrc);
+    EXPECT_TRUE(send_stream);
+    return *send_stream;
+  }
+
   const webrtc::AudioSendStream::Config& GetSendStreamConfig(uint32_t ssrc) {
     const auto* send_stream = call_.GetAudioSendStream(ssrc);
     EXPECT_TRUE(send_stream);
@@ -163,11 +168,15 @@
     // Check we fail if the ssrc is invalid.
     EXPECT_FALSE(channel_->InsertDtmf(-1, 1, 111));
 
-    // Test send
-    int channel_id = voe_.GetLastChannel();
-    EXPECT_FALSE(voe_.WasSendTelephoneEventCalled(channel_id, 2, 123));
+    // Test send.
+    cricket::FakeAudioSendStream::TelephoneEvent telephone_event =
+        GetSendStream(kSsrc1).GetLatestTelephoneEvent();
+    EXPECT_EQ(-1, telephone_event.payload_type);
     EXPECT_TRUE(channel_->InsertDtmf(ssrc, 2, 123));
-    EXPECT_TRUE(voe_.WasSendTelephoneEventCalled(channel_id, 2, 123));
+    telephone_event = GetSendStream(kSsrc1).GetLatestTelephoneEvent();
+    EXPECT_EQ(kTelephoneEventCodec.id, telephone_event.payload_type);
+    EXPECT_EQ(2, telephone_event.event_code);
+    EXPECT_EQ(123, telephone_event.duration_ms);
   }
 
   // Test that send bandwidth is set correctly.
@@ -766,7 +775,7 @@
   EXPECT_FALSE(voe_.GetRED(channel_num));
   EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
   EXPECT_EQ(105, voe_.GetSendCNPayloadType(channel_num, true));
-  EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
+  EXPECT_FALSE(channel_->CanInsertDtmf());
 }
 
 // Test that VoE Channel doesn't call SetSendCodec again if same codec is tried
@@ -1607,7 +1616,7 @@
   EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
   EXPECT_EQ(96, gcodec.pltype);
   EXPECT_STREQ("ISAC", gcodec.plname);
-  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+  EXPECT_TRUE(channel_->CanInsertDtmf());
 }
 
 // Test that we can set send codecs even with CN codec as the first
@@ -1653,7 +1662,7 @@
   EXPECT_FALSE(voe_.GetRED(channel_num));
   EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
   EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
-  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+  EXPECT_TRUE(channel_->CanInsertDtmf());
 }
 
 // Test that we set VAD and DTMF types correctly as callee.
@@ -1686,7 +1695,7 @@
   EXPECT_FALSE(voe_.GetRED(channel_num));
   EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
   EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
-  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+  EXPECT_TRUE(channel_->CanInsertDtmf());
 }
 
 // Test that we only apply VAD if we have a CN codec that matches the
@@ -1750,7 +1759,7 @@
   EXPECT_FALSE(voe_.GetRED(channel_num));
   EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
   EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
-  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+  EXPECT_TRUE(channel_->CanInsertDtmf());
 }
 
 // Test that we set up RED correctly as caller.
diff --git a/webrtc/audio/audio_send_stream.cc b/webrtc/audio/audio_send_stream.cc
index 4dd9522..2ff388b 100644
--- a/webrtc/audio/audio_send_stream.cc
+++ b/webrtc/audio/audio_send_stream.cc
@@ -102,6 +102,13 @@
   return false;
 }
 
+bool AudioSendStream::SendTelephoneEvent(int payload_type, uint8_t event,
+                                         uint32_t duration_ms) {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
+  return channel_proxy_->SetSendTelephoneEventPayloadType(payload_type) &&
+         channel_proxy_->SendTelephoneEventOutband(event, duration_ms);
+}
+
 webrtc::AudioSendStream::Stats AudioSendStream::GetStats() const {
   RTC_DCHECK(thread_checker_.CalledOnValidThread());
   webrtc::AudioSendStream::Stats stats;
diff --git a/webrtc/audio/audio_send_stream.h b/webrtc/audio/audio_send_stream.h
index b670efe..88304fd 100644
--- a/webrtc/audio/audio_send_stream.h
+++ b/webrtc/audio/audio_send_stream.h
@@ -37,6 +37,8 @@
   bool DeliverRtcp(const uint8_t* packet, size_t length) override;
 
   // webrtc::AudioSendStream implementation.
+  bool SendTelephoneEvent(int payload_type, uint8_t event,
+                          uint32_t duration_ms) override;
   webrtc::AudioSendStream::Stats GetStats() const override;
 
   const webrtc::AudioSendStream::Config& config() const;
diff --git a/webrtc/audio/audio_send_stream_unittest.cc b/webrtc/audio/audio_send_stream_unittest.cc
index 8dc6da7..c3620b2 100644
--- a/webrtc/audio/audio_send_stream_unittest.cc
+++ b/webrtc/audio/audio_send_stream_unittest.cc
@@ -40,6 +40,9 @@
     1345,  1678,  1901, 1234,  112, 13456, 17890, 1567, -1890, -1123};
 const CodecInst kCodecInst = {-121, "codec_name_send", 48000, -231, -451, -671};
 const ReportBlock kReportBlock = {456, 780, 123, 567, 890, 132, 143, 13354};
+const int kTelephoneEventPayloadType = 123;
+const uint8_t kTelephoneEventCode = 45;
+const uint32_t kTelephoneEventDuration = 6789;
 
 struct ConfigHelper {
   ConfigHelper() : stream_config_(nullptr) {
@@ -79,6 +82,16 @@
   AudioSendStream::Config& config() { return stream_config_; }
   rtc::scoped_refptr<AudioState> audio_state() { return audio_state_; }
 
+  void SetupMockForSendTelephoneEvent() {
+    EXPECT_TRUE(channel_proxy_);
+    EXPECT_CALL(*channel_proxy_,
+        SetSendTelephoneEventPayloadType(kTelephoneEventPayloadType))
+            .WillOnce(Return(true));
+    EXPECT_CALL(*channel_proxy_,
+        SendTelephoneEventOutband(kTelephoneEventCode, kTelephoneEventDuration))
+            .WillOnce(Return(true));
+  }
+
   void SetupMockForGetStats() {
     using testing::DoAll;
     using testing::SetArgReferee;
@@ -142,6 +155,14 @@
   internal::AudioSendStream send_stream(helper.config(), helper.audio_state());
 }
 
+TEST(AudioSendStreamTest, SendTelephoneEvent) {
+  ConfigHelper helper;
+  internal::AudioSendStream send_stream(helper.config(), helper.audio_state());
+  helper.SetupMockForSendTelephoneEvent();
+  EXPECT_TRUE(send_stream.SendTelephoneEvent(kTelephoneEventPayloadType,
+      kTelephoneEventCode, kTelephoneEventDuration));
+}
+
 TEST(AudioSendStreamTest, GetStats) {
   ConfigHelper helper;
   internal::AudioSendStream send_stream(helper.config(), helper.audio_state());
diff --git a/webrtc/audio_send_stream.h b/webrtc/audio_send_stream.h
index 7069c37..dd8d9e9 100644
--- a/webrtc/audio_send_stream.h
+++ b/webrtc/audio_send_stream.h
@@ -89,6 +89,9 @@
     int red_payload_type = -1;  // pt, or -1 to disable REDundant coding.
   };
 
+  // TODO(solenberg): Make payload_type a config property instead.
+  virtual bool SendTelephoneEvent(int payload_type, uint8_t event,
+                                  uint32_t duration_ms) = 0;
   virtual Stats GetStats() const = 0;
 };
 }  // namespace webrtc
diff --git a/webrtc/test/mock_voe_channel_proxy.h b/webrtc/test/mock_voe_channel_proxy.h
index 401a87a..a0f0464 100644
--- a/webrtc/test/mock_voe_channel_proxy.h
+++ b/webrtc/test/mock_voe_channel_proxy.h
@@ -33,6 +33,9 @@
   MOCK_CONST_METHOD0(GetDecodingCallStatistics, AudioDecodingCallStats());
   MOCK_CONST_METHOD0(GetSpeechOutputLevelFullRange, int32_t());
   MOCK_CONST_METHOD0(GetDelayEstimate, uint32_t());
+  MOCK_METHOD1(SetSendTelephoneEventPayloadType, bool(int payload_type));
+  MOCK_METHOD2(SendTelephoneEventOutband, bool(uint8_t event,
+                                               uint32_t duration_ms));
 };
 }  // namespace test
 }  // namespace webrtc
diff --git a/webrtc/voice_engine/channel.cc b/webrtc/voice_engine/channel.cc
index 54aa802..fb98356 100644
--- a/webrtc/voice_engine/channel.cc
+++ b/webrtc/voice_engine/channel.cc
@@ -2372,6 +2372,9 @@
     WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, _channelId),
                "Channel::SendTelephoneEventOutband(..., playDtmfEvent=%d)",
                playDtmfEvent);
+    if (!Sending()) {
+      return -1;
+    }
 
     _playOutbandDtmfEvent = playDtmfEvent;
 
diff --git a/webrtc/voice_engine/channel_proxy.cc b/webrtc/voice_engine/channel_proxy.cc
index 614b7b7..1772ad5 100644
--- a/webrtc/voice_engine/channel_proxy.cc
+++ b/webrtc/voice_engine/channel_proxy.cc
@@ -108,6 +108,18 @@
   return channel()->GetDelayEstimate();
 }
 
+bool ChannelProxy::SetSendTelephoneEventPayloadType(int payload_type) {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
+  return channel()->SetSendTelephoneEventPayloadType(payload_type) == 0;
+}
+
+bool ChannelProxy::SendTelephoneEventOutband(uint8_t event,
+                                             uint32_t duration_ms) {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
+  return
+      channel()->SendTelephoneEventOutband(event, duration_ms, 10, false) == 0;
+}
+
 Channel* ChannelProxy::channel() const {
   RTC_DCHECK(channel_owner_.channel());
   return channel_owner_.channel();
diff --git a/webrtc/voice_engine/channel_proxy.h b/webrtc/voice_engine/channel_proxy.h
index 6b916a5..3668de4 100644
--- a/webrtc/voice_engine/channel_proxy.h
+++ b/webrtc/voice_engine/channel_proxy.h
@@ -51,6 +51,9 @@
   virtual int32_t GetSpeechOutputLevelFullRange() const;
   virtual uint32_t GetDelayEstimate() const;
 
+  virtual bool SetSendTelephoneEventPayloadType(int payload_type);
+  virtual bool SendTelephoneEventOutband(uint8_t event, uint32_t duration_ms);
+
  private:
   Channel* channel() const;