Bug 1291715 - Add DTMF support to PeerConnection and AudioConduit; r?jib
MozReview-Commit-ID: LNN1HNJicCj
--- 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