Bug 1291715 - Add DTMF support to PeerConnection and AudioConduit; r=jib
☠☠ backed out by 6242662fb324 ☠ ☠
authorDan Minor <dminor@mozilla.com>
Tue, 04 Oct 2016 14:55:27 -0400
changeset 316536 5505acbab65951a624420095d4002981c85cfd3a
parent 316535 50497f2cdd2835221a0b21661ed6cb8b006131a8
child 316537 91c0b78bb4dd5032bef67519c41268623fdac301
push id32865
push userdminor@mozilla.com
push dateWed, 05 Oct 2016 14:36:22 +0000
treeherderautoland@078248a9fdbc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib
bugs1291715
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1291715 - Add DTMF support to PeerConnection and AudioConduit; r=jib MozReview-Commit-ID: LNN1HNJicCj
media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
media/webrtc/signaling/src/media-conduit/AudioConduit.h
media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/Telemetry.h"
 #endif
 
 #include "webrtc/common.h"
 #include "webrtc/modules/audio_processing/include/audio_processing.h"
 #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
 #include "webrtc/voice_engine/include/voe_dtmf.h"
 #include "webrtc/voice_engine/include/voe_errors.h"
+#include "webrtc/voice_engine/voice_engine_impl.h"
 #include "webrtc/system_wrappers/interface/clock.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidJNIWrapper.h"
 #endif
 
 namespace mozilla {
 
@@ -234,16 +235,30 @@ bool WebrtcAudioConduit::SetDtmfPayloadT
   int result = mPtrVoEDtmf->SetSendTelephoneEventPayloadType(mChannel, type);
   if (result == -1) {
     CSFLogError(logTag, "%s Failed call to SetSendTelephoneEventPayloadType",
                         __FUNCTION__);
   }
   return result != -1;
 }
 
+bool WebrtcAudioConduit::InsertDTMFTone(int channel, int eventCode,
+                                        bool outOfBand, int lengthMs,
+                                        int attenuationDb) {
+  NS_ASSERTION(!NS_IsMainThread(), "Do not call on main thread");
+
+  if (!mVoiceEngine || !mDtmfEnabled) {
+    return false;
+  }
+
+  webrtc::VoiceEngineImpl* s = static_cast<webrtc::VoiceEngineImpl*>(mVoiceEngine);
+  int result = s->SendTelephoneEvent(channel, eventCode, outOfBand, lengthMs, attenuationDb);
+  return result != -1;
+}
+
 /*
  * WebRTCAudioConduit Implementation
  */
 MediaConduitErrorCode WebrtcAudioConduit::Init()
 {
   CSFLogDebug(logTag,  "%s this=%p", __FUNCTION__, this);
 
 #ifdef MOZ_WIDGET_ANDROID
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h
@@ -217,16 +217,19 @@ public:
                              uint32_t *cumulativeLost,
                              int32_t* rttMs) override;
   bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp,
                            unsigned int* packetsSent,
                            uint64_t* bytesSent) override;
 
   bool SetDtmfPayloadType(unsigned char type) override;
 
+  bool InsertDTMFTone(int channel, int eventCode, bool outOfBand,
+                      int lengthMs, int attenuationDb) override;
+
 private:
   WebrtcAudioConduit(const WebrtcAudioConduit& other) = delete;
   void operator=(const WebrtcAudioConduit& other) = delete;
 
   //Local database of currently applied receive codecs
   typedef std::vector<AudioCodecConfig* > RecvCodecList;
 
   //Function to convert between WebRTC and Conduit codec structures
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -482,11 +482,14 @@ public:
     * @param enabled: enable extension
     * @param id: id to be used for this rtp header extension
     * NOTE: See AudioConduit for more information
     */
   virtual MediaConduitErrorCode EnableAudioLevelExtension(bool enabled, uint8_t id) = 0;
 
   virtual bool SetDtmfPayloadType(unsigned char type) = 0;
 
+  virtual bool InsertDTMFTone(int channel, int eventCode, bool outOfBand,
+                              int lengthMs, int attenuationDb) = 0;
+
 };
 }
 #endif
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -74,16 +74,18 @@
 #include "nsNetUtil.h"
 #include "nsIURLParser.h"
 #include "nsIDOMDataChannel.h"
 #include "nsIDOMLocation.h"
 #include "nsNullPrincipal.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/dom/RTCCertificate.h"
 #include "mozilla/dom/RTCConfigurationBinding.h"
+#include "mozilla/dom/RTCDTMFSenderBinding.h"
+#include "mozilla/dom/RTCDTMFToneChangeEvent.h"
 #include "mozilla/dom/RTCRtpSenderBinding.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "mozilla/dom/RTCPeerConnectionBinding.h"
 #include "mozilla/dom/PeerConnectionImplBinding.h"
 #include "mozilla/dom/DataChannelBinding.h"
 #include "mozilla/dom/PerformanceTiming.h"
 #include "mozilla/dom/PluginCrashedEvent.h"
 #include "MediaStreamList.h"
@@ -133,17 +135,16 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef PCObserverString ObString;
 
 static const char* logTag = "PeerConnectionImpl";
 
-
 // Getting exceptions back down from PCObserver is generally not harmful.
 namespace {
 // This is a terrible hack.  The problem is that SuppressException is not
 // inline, and we link this file without libxul in some cases (e.g. for our test
 // setup).  So we can't use ErrorResult or IgnoredErrorResult because those call
 // SuppressException...  And we can't use FastErrorResult because we can't
 // include BindingUtils.h, because our linking is completely fucked up.  Use
 // BaseErrorResult directly.  Please do not let me see _anyone_ doing this
@@ -2493,16 +2494,25 @@ PeerConnectionImpl::SelectSsrc(MediaStre
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) {
   PC_AUTO_ENTER_API_CALL(true);
 
   std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
+
+  for (size_t i = 0; i < mDTMFStates.Length(); ++i) {
+    if (mDTMFStates[i].mTrackId == trackId) {
+      mDTMFStates[i].mSendTimer->Cancel();
+      mDTMFStates.RemoveElementAt(i);
+      break;
+    }
+  }
+
   RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId);
 
   if (!info) {
     CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__);
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv =
@@ -2520,34 +2530,135 @@ PeerConnectionImpl::RemoveTrack(MediaStr
 
   aTrack.RemovePrincipalChangeObserver(this);
 
   OnNegotiationNeeded();
 
   return NS_OK;
 }
 
+static int GetDTMFToneCode(uint16_t c)
+{
+  const char* DTMF_TONECODES = "0123456789*#ABCD";
+
+  if (c == ',') {
+    // , is a special character indicating a 2 second delay
+    return -1;
+  }
+
+  const char* i = strchr(DTMF_TONECODES, c);
+  MOZ_ASSERT(i);
+  return i - DTMF_TONECODES;
+}
+
 NS_IMETHODIMP
 PeerConnectionImpl::InsertDTMF(mozilla::dom::RTCRtpSender& sender,
                                const nsAString& tones, uint32_t duration,
                                uint32_t interToneGap) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  PC_AUTO_ENTER_API_CALL(false);
+
+  JSErrorResult jrv;
+
+  // Retrieve track
+  RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv);
+  if (jrv.Failed()) {
+    NS_WARNING("Failed to retrieve track for RTCRtpSender!");
+    return jrv.StealNSResult();
+  }
+
+  std::string senderTrackId = GetTrackId(*mst);
+
+  // Attempt to locate state for the DTMFSender
+  DTMFState* state = nullptr;
+  for (auto& dtmfState : mDTMFStates) {
+    if (dtmfState.mTrackId == senderTrackId) {
+      state = &dtmfState;
+      break;
+    }
+  }
+
+  // No state yet, create a new one
+  if (!state) {
+    state = mDTMFStates.AppendElement();
+    state->mPeerConnectionImpl = this;
+    state->mTrackId = senderTrackId;
+    state->mSendTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    MOZ_ASSERT(state->mSendTimer);
+  }
+  MOZ_ASSERT(state);
+
+  auto trackPairs = mJsepSession->GetNegotiatedTrackPairs();
+  state->mLevel = -1;
+  for (auto& trackPair : trackPairs) {
+    if (trackPair.mSending->GetTrackId() == state->mTrackId) {
+      if (trackPair.mBundleLevel.isSome()) {
+        state->mLevel = *trackPair.mBundleLevel;
+      } else {
+        state->mLevel = trackPair.mLevel;
+      }
+      break;
+    }
+  }
+
+  state->mTones = tones;
+  state->mDuration = duration;
+  state->mInterToneGap = interToneGap;
+  if (!state->mTones.IsEmpty()) {
+    state->mSendTimer->InitWithFuncCallback(DTMFSendTimerCallback_m, state, 0,
+                                            nsITimer::TYPE_ONE_SHOT);
+  }
+#endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::GetDTMFToneBuffer(mozilla::dom::RTCRtpSender& sender,
-                                      nsAString& outTones) {
+                                      nsAString& outToneBuffer) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  PC_AUTO_ENTER_API_CALL(false);
+
+  JSErrorResult jrv;
+
+  // Retrieve track
+  RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv);
+  if (jrv.Failed()) {
+    NS_WARNING("Failed to retrieve track for RTCRtpSender!");
+    return jrv.StealNSResult();
+  }
+
+  std::string senderTrackId = GetTrackId(*mst);
+
+  // Attempt to locate state for the DTMFSender
+  for (auto& dtmfState : mDTMFStates) {
+    if (dtmfState.mTrackId == senderTrackId) {
+      outToneBuffer = dtmfState.mTones;
+      break;
+    }
+  }
+#endif
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack,
                                  MediaStreamTrack& aWithTrack) {
   PC_AUTO_ENTER_API_CALL(true);
 
+  std::string trackId = PeerConnectionImpl::GetTrackId(aThisTrack);
+
+  for (size_t i = 0; i < mDTMFStates.Length(); ++i) {
+    if (mDTMFStates[i].mTrackId == trackId) {
+      mDTMFStates[i].mSendTimer->Cancel();
+      mDTMFStates.RemoveElementAt(i);
+      break;
+    }
+  }
+
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (!pco) {
     return NS_ERROR_UNEXPECTED;
   }
   JSErrorResult jrv;
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   if (&aThisTrack == &aWithTrack) {
@@ -2954,16 +3065,20 @@ PeerConnectionImpl::RecordEndOfCallTelem
 #endif
 }
 
 nsresult
 PeerConnectionImpl::CloseInt()
 {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
+  for (auto& dtmfState : mDTMFStates) {
+    dtmfState.mSendTimer->Cancel();
+  }
+
   // We do this at the end of the call because we want to make sure we've waited
   // for all trickle ICE candidates to come in; this can happen well after we've
   // transitioned to connected. As a bonus, this allows us to detect race
   // conditions where a stats dispatch happens right as the PC closes.
   if (!mPrivateWindow) {
     RecordLongtermICEStatistics();
   }
   RecordEndOfCallTelemetry();
@@ -3996,9 +4111,70 @@ PeerConnectionImpl::GetRemoteStreams(nsT
     result.AppendElement(info->GetMediaStream());
   }
   return NS_OK;
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
+void
+PeerConnectionImpl::DTMFSendTimerCallback_m(nsITimer* timer, void* closure)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  auto state = static_cast<DTMFState*>(closure);
+
+  nsString eventTone;
+  if (!state->mTones.IsEmpty()) {
+    uint16_t toneChar = state->mTones.CharAt(0);
+    int tone = GetDTMFToneCode(toneChar);
+
+    eventTone.Assign(toneChar);
+
+    state->mTones.Cut(0, 1);
+
+    if (tone == -1) {
+      state->mSendTimer->InitWithFuncCallback(DTMFSendTimerCallback_m, state,
+                                              2000, nsITimer::TYPE_ONE_SHOT);
+    } else {
+      // Reset delay if necessary
+      state->mSendTimer->InitWithFuncCallback(DTMFSendTimerCallback_m, state,
+                                              state->mDuration + state->mInterToneGap,
+                                              nsITimer::TYPE_ONE_SHOT);
+
+      RefPtr<AudioSessionConduit> conduit =
+        state->mPeerConnectionImpl->mMedia->GetAudioConduit(state->mLevel);
+
+      if (conduit) {
+        uint32_t duration = state->mDuration;
+        state->mPeerConnectionImpl->mSTSThread->Dispatch(WrapRunnableNM([conduit, tone, duration] () {
+            //Note: We default to channel 0, not inband, and 6dB attenuation.
+            //      here. We might want to revisit these choices in the future.
+            conduit->InsertDTMFTone(0, tone, true, duration, 6);
+          }), NS_DISPATCH_NORMAL);
+      }
+
+    }
+  } else {
+    state->mSendTimer->Cancel();
+  }
+
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(state->mPeerConnectionImpl->mPCObserver);
+  if (!pco) {
+    NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!");
+    return;
+  }
+
+  nsString trackId;
+  trackId.AssignASCII(state->mTrackId.c_str());
+  JSErrorResult jrv;
+  pco->OnDTMFToneChange(trackId, eventTone, jrv);
+
+  if (jrv.Failed()) {
+    NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!");
+    return;
+  }
+#endif
+}
+
 }  // end mozilla namespace
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -71,16 +71,17 @@ class MediaPipeline;
 typedef Fake_DOMMediaStream DOMMediaStream;
 #else
 class DOMMediaStream;
 #endif
 
 namespace dom {
 class RTCCertificate;
 struct RTCConfiguration;
+class RTCDTMFSender;
 struct RTCIceServer;
 struct RTCOfferOptions;
 struct RTCRtpParameters;
 class RTCRtpSender;
 #ifdef USE_FAKE_MEDIA_STREAMS
 typedef Fake_MediaStreamTrack MediaStreamTrack;
 #else
 class MediaStreamTrack;
@@ -433,24 +434,24 @@ public:
   {
     rv = RemoveTrack(aTrack);
   }
 
   nsresult
   AddTrack(mozilla::dom::MediaStreamTrack& aTrack, DOMMediaStream& aStream);
 
   NS_IMETHODIMP_TO_ERRORRESULT(InsertDTMF, ErrorResult &rv,
-                               mozilla::dom::RTCRtpSender& sender,
-                               const nsAString& tones, uint32_t duration,
-                               uint32_t interToneGap) {
+                               dom::RTCRtpSender& sender,
+                               const nsAString& tones,
+                               uint32_t duration, uint32_t interToneGap) {
     rv = InsertDTMF(sender, tones, duration, interToneGap);
   }
 
   NS_IMETHODIMP_TO_ERRORRESULT(GetDTMFToneBuffer, ErrorResult &rv,
-                               mozilla::dom::RTCRtpSender& sender,
+                               dom::RTCRtpSender& sender,
                                nsAString& outToneBuffer) {
     rv = GetDTMFToneBuffer(sender, outToneBuffer);
   }
 
   NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrack, ErrorResult &rv,
                                mozilla::dom::MediaStreamTrack& aThisTrack,
                                mozilla::dom::MediaStreamTrack& aWithTrack)
   {
@@ -846,16 +847,32 @@ private:
   bool mNegotiationNeeded;
 
   bool mPrivateWindow;
 
   // storage for Telemetry data
   uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
   uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
 
+  // DTMF
+  struct DTMFState {
+    PeerConnectionImpl* mPeerConnectionImpl;
+    nsCOMPtr<nsITimer> mSendTimer;
+    std::string mTrackId;
+    nsString mTones;
+    size_t mLevel;
+    uint32_t mDuration;
+    uint32_t mInterToneGap;
+  };
+
+  static void
+  DTMFSendTimerCallback_m(nsITimer* timer, void*);
+
+  nsTArray<DTMFState> mDTMFStates;
+
 public:
   //these are temporary until the DataChannel Listen/Connect API is removed
   unsigned short listenPort;
   unsigned short connectPort;
   char *connectStr; // XXX ownership/free
 };
 
 // This is what is returned when you acquire on a handle