Bug 1291715 - Add DTMF support to PeerConnection and AudioConduit; r?jib draft
authorDan Minor <dminor@mozilla.com>
Mon, 12 Sep 2016 12:33:37 -0400
changeset 413580 794837af48d75fdfc64e3bd26eb6c6186d888db4
parent 413579 b7e349cb7dbd3066001ff7a11ddc26223bba9e3f
child 413581 bc4b591d36d22968104d8d26c0efebc8eca2fb1f
push id29449
push userdminor@mozilla.com
push dateWed, 14 Sep 2016 12:58:37 +0000
reviewersjib
bugs1291715
milestone51.0a1
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
@@ -22,16 +22,17 @@
 #include "Latency.h"
 #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_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 {
 
@@ -215,16 +216,29 @@ bool WebrtcAudioConduit::GetRTCPSenderRe
     *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTPseconds,
                                           senderInfo.NTPfraction);
     *packetsSent = senderInfo.sendPacketCount;
     *bytesSent = senderInfo.sendOctetCount;
    }
    return result;
  }
 
+
+bool
+WebrtcAudioConduit::InsertDTMFTone(int channel, int eventCode, bool outOfBand,
+                                   int lengthMs, int attenuationDb) {
+  if (mVoiceEngine) {
+    webrtc::VoiceEngineImpl* s = static_cast<webrtc::VoiceEngineImpl*>(mVoiceEngine);
+    int result = s->SendTelephoneEvent(channel, eventCode, outOfBand, lengthMs, attenuationDb);
+    return result != -1;
+  }
+
+  return false;
+}
+
 /*
  * 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
@@ -214,16 +214,19 @@ public:
                              uint32_t* packetsReceived,
                              uint64_t* bytesReceived,
                              uint32_t *cumulativeLost,
                              int32_t* rttMs) override;
   bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp,
                            unsigned int* packetsSent,
                            uint64_t* bytesSent) 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
@@ -472,11 +472,14 @@ public:
    /**
     * Function to enable the audio level extension
     * @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 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,17 @@
 #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/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,16 +134,19 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef PCObserverString ObString;
 
 static const char* logTag = "PeerConnectionImpl";
 
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+static const char* DTMF_TONECODES = "0123456789*#ABCD";
+#endif
 
 // 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
@@ -2512,22 +2516,129 @@ PeerConnectionImpl::RemoveTrack(MediaStr
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::InsertDTMF(mozilla::dom::RTCRtpSender& sender,
                                const nsAString& tones, double duration,
                                double interToneGap) {
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  JSErrorResult jrv;
+  RefPtr<dom::RTCDTMFSender> dtmfSender = sender.GetDtmf(jrv);
+  if (jrv.Failed()) {
+    NS_WARNING("Failed to retrieve DTMF object on RTCRtpSender!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Attempt to locate state for the DTMFSender
+  DTMFState *state = nullptr;
+  for (uint32_t i = 0; i < mDTMFStates.Length(); ++i) {
+    if (mDTMFStates[i].mDTMFSender == dtmfSender) {
+      state = &mDTMFStates[i];
+      break;
+    }
+  }
+
+  // No state yet, create a new one
+  if (state == nullptr) {
+    DTMFState newState;
+    newState.mPeerConnectionImpl = this;
+    newState.mDTMFSender = dtmfSender;
+    newState.mSendTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    MOZ_ASSERT(newState.mSendTimer);
+    nsresult timerRv = newState.mSendTimer->SetTarget(mSTSThread);
+    NS_ENSURE_SUCCESS(timerRv, timerRv);
+
+    mDTMFStates.AppendElement(newState);
+    state = &mDTMFStates[mDTMFStates.Length() - 1];
+  }
+
+  MOZ_ASSERT(state != nullptr);
+  state->mSendTimer->Cancel();
+  state->mTones.Clear();
+
+  // Retrieve track
+  RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv);
+  if (jrv.Failed()) {
+    NS_WARNING("Failed to retrieve track for RTCRtpSender!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  auto trackPairs = mJsepSession->GetNegotiatedTrackPairs();
+  state->mLevel = -1;
+  for (auto j = trackPairs.begin(); j != trackPairs.end(); ++j) {
+    JsepTrackPair& trackPair = *j;
+    if (trackPair.mSending->GetTrackId() == GetTrackId(*mst.get())) {
+      if (trackPair.mBundleLevel.isSome()) {
+        state->mLevel = static_cast<uint16_t>(*trackPair.mBundleLevel);
+      } else {
+        state->mLevel = static_cast<uint16_t>(trackPair.mLevel);
+      }
+    }
+    break;
+  }
+
+  for (uint32_t i = 0; i < tones.Length(); ++i) {
+    uint16_t c = tones.CharAt(i);
+    if (c == ',') {
+      // , is a special character indicating a 2 second delay
+      state->mTones.AppendElement(-1);
+    } else {
+      const char* i = strchr(DTMF_TONECODES, c);
+      // the input tones should have been validated prior to calling this function
+      MOZ_ASSERT(i != nullptr);
+      if (i) {
+        state->mTones.AppendElement(i - DTMF_TONECODES);
+      }
+    }
+  }
+  state->mDuration = duration;
+  state->mInterToneGap = interToneGap;
+  if (!state->mTones.IsEmpty()) {
+    state->mNextTone = state->mTones[0];
+    state->mTones.RemoveElementAt(0);
+    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)
+  JSErrorResult jrv;
+  RefPtr<RTCDTMFSender> dtmfSender = sender.GetDtmf(jrv);
+  if (jrv.Failed()) {
+    NS_WARNING("Failed to retrieve DTMF object on RTCRtpSender!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Attempt to locate state for the DTMFSender
+  DTMFState* state = nullptr;
+  for (uint32_t i = 0; i < mDTMFStates.Length(); ++i) {
+    if (mDTMFStates[i].mDTMFSender == dtmfSender) {
+      state = &mDTMFStates[i];
+      break;
+    }
+  }
+
+  if (state != nullptr && !state->mTones.IsEmpty()) {
+    for (uint32_t i = 0; i < state->mTones.Length(); ++i) {
+      if (state->mTones[i] == -1) {
+        outToneBuffer.Append(',');
+      } else {
+        outToneBuffer.Append(DTMF_TONECODES[state->mTones[i]]);
+      }
+    }
+  }
+#endif
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack,
                                  MediaStreamTrack& aWithTrack) {
   PC_AUTO_ENTER_API_CALL(true);
 
@@ -2838,16 +2949,20 @@ PeerConnectionImpl::CheckApiState(bool a
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::Close()
 {
   CSFLogDebug(logTag, "%s: for %s", __FUNCTION__, mHandle.c_str());
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
+  for (uint32_t i = 0; i < mDTMFStates.Length(); ++i) {
+    mDTMFStates[i].mSendTimer->Cancel();
+  }
+
   SetSignalingState_m(PCImplSignalingState::SignalingClosed);
 
   return NS_OK;
 }
 
 bool
 PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
                                 const nsAString& aPluginName)
@@ -3984,9 +4099,40 @@ PeerConnectionImpl::GetRemoteStreams(nsT
     result.AppendElement(info->GetMediaStream());
   }
   return NS_OK;
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
+
+void
+PeerConnectionImpl::DTMFSendTimerCallback_m(nsITimer* timer, void* closure)
+{
+  auto state = static_cast<DTMFState* >(closure);
+
+  RefPtr<AudioSessionConduit> conduit = state->mPeerConnectionImpl->mMedia->GetAudioConduit(state->mLevel);
+ if (conduit) {
+    //TODO: channel, inband, attenuation...
+    conduit->InsertDTMFTone(0, state->mNextTone, true, state->mDuration, 0);
+
+    NS_DispatchToMainThread(
+        WrapRunnableNM(&DTMFSentCallback_m, state),
+        NS_DISPATCH_NORMAL);
+  }
+}
+
+void
+PeerConnectionImpl::DTMFSentCallback_m(DTMFState *state)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!state->mTones.IsEmpty()) {
+    state->mNextTone = state->mTones[0];
+    state->mTones.RemoveElementAt(0);
+    state->mSendTimer->InitWithFuncCallback(DTMFSendTimerCallback_m, state,
+                                            state->mDuration + state->mInterToneGap,
+                                            nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
 }  // 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, double duration,
-                               double interToneGap) {
+                               dom::RTCRtpSender& sender,
+                               const nsAString& tones,
+                               double duration, double 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,38 @@ private:
   bool mNegotiationNeeded;
 
   bool mPrivateWindow;
 
   // storage for Telemetry data
   uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
   uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
 
+  // DTMF
+  struct DTMFState {
+    nsCOMPtr<nsITimer> mSendTimer;
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+    RefPtr<dom::RTCDTMFSender> mDTMFSender;
+#endif
+    PeerConnectionImpl *mPeerConnectionImpl;
+    uint16_t mLevel;
+    nsTArray<int> mTones;
+    int mNextTone;
+    double mDuration;
+    double mInterToneGap;
+  };
+
+  static void
+  DTMFSendTimerCallback_m(nsITimer* timer, void *);
+
+  static void
+  DTMFSentCallback_m(DTMFState* state);
+
+  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