summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/MediaEngineWebRTCAudio.cpp')
-rw-r--r--dom/media/webrtc/MediaEngineWebRTCAudio.cpp937
1 files changed, 937 insertions, 0 deletions
diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
new file mode 100644
index 000000000..0b8796aa8
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -0,0 +1,937 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEngineWebRTC.h"
+#include <stdio.h>
+#include <algorithm>
+#include "mozilla/Assertions.h"
+#include "MediaTrackConstraints.h"
+#include "mtransport/runnable_utils.h"
+#include "nsAutoPtr.h"
+
+// scoped_ptr.h uses FF
+#ifdef FF
+#undef FF
+#endif
+#include "webrtc/modules/audio_device/opensl/single_rw_fifo.h"
+
+#define CHANNELS 1
+#define ENCODING "L16"
+#define DEFAULT_PORT 5555
+
+#define SAMPLE_RATE(freq) ((freq)*2*8) // bps, 16-bit samples
+#define SAMPLE_LENGTH(freq) (((freq)*10)/1000)
+
+// These are restrictions from the webrtc.org code
+#define MAX_CHANNELS 2
+#define MAX_SAMPLING_FREQ 48000 // Hz - multiple of 100
+
+#define MAX_AEC_FIFO_DEPTH 200 // ms - multiple of 10
+static_assert(!(MAX_AEC_FIFO_DEPTH % 10), "Invalid MAX_AEC_FIFO_DEPTH");
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetMediaManagerLog();
+#define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
+#define LOG_FRAMES(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg)
+
+/**
+ * Webrtc microphone source source.
+ */
+NS_IMPL_ISUPPORTS0(MediaEngineWebRTCMicrophoneSource)
+NS_IMPL_ISUPPORTS0(MediaEngineWebRTCAudioCaptureSource)
+
+// XXX temp until MSG supports registration
+StaticRefPtr<AudioOutputObserver> gFarendObserver;
+
+int MediaEngineWebRTCMicrophoneSource::sChannelsOpen = 0;
+ScopedCustomReleasePtr<webrtc::VoEBase> MediaEngineWebRTCMicrophoneSource::mVoEBase;
+ScopedCustomReleasePtr<webrtc::VoEExternalMedia> MediaEngineWebRTCMicrophoneSource::mVoERender;
+ScopedCustomReleasePtr<webrtc::VoENetwork> MediaEngineWebRTCMicrophoneSource::mVoENetwork;
+ScopedCustomReleasePtr<webrtc::VoEAudioProcessing> MediaEngineWebRTCMicrophoneSource::mVoEProcessing;
+
+AudioOutputObserver::AudioOutputObserver()
+ : mPlayoutFreq(0)
+ , mPlayoutChannels(0)
+ , mChunkSize(0)
+ , mSaved(nullptr)
+ , mSamplesSaved(0)
+{
+ // Buffers of 10ms chunks
+ mPlayoutFifo = new webrtc::SingleRwFifo(MAX_AEC_FIFO_DEPTH/10);
+}
+
+AudioOutputObserver::~AudioOutputObserver()
+{
+ Clear();
+ free(mSaved);
+ mSaved = nullptr;
+}
+
+void
+AudioOutputObserver::Clear()
+{
+ while (mPlayoutFifo->size() > 0) {
+ free(mPlayoutFifo->Pop());
+ }
+ // we'd like to touch mSaved here, but we can't if we might still be getting callbacks
+}
+
+FarEndAudioChunk *
+AudioOutputObserver::Pop()
+{
+ return (FarEndAudioChunk *) mPlayoutFifo->Pop();
+}
+
+uint32_t
+AudioOutputObserver::Size()
+{
+ return mPlayoutFifo->size();
+}
+
+void
+AudioOutputObserver::MixerCallback(AudioDataValue* aMixedBuffer,
+ AudioSampleFormat aFormat,
+ uint32_t aChannels,
+ uint32_t aFrames,
+ uint32_t aSampleRate)
+{
+ if (gFarendObserver) {
+ gFarendObserver->InsertFarEnd(aMixedBuffer, aFrames, false,
+ aSampleRate, aChannels, aFormat);
+ }
+}
+
+// static
+void
+AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aFrames, bool aOverran,
+ int aFreq, int aChannels, AudioSampleFormat aFormat)
+{
+ if (mPlayoutChannels != 0) {
+ if (mPlayoutChannels != static_cast<uint32_t>(aChannels)) {
+ MOZ_CRASH();
+ }
+ } else {
+ MOZ_ASSERT(aChannels <= MAX_CHANNELS);
+ mPlayoutChannels = static_cast<uint32_t>(aChannels);
+ }
+ if (mPlayoutFreq != 0) {
+ if (mPlayoutFreq != static_cast<uint32_t>(aFreq)) {
+ MOZ_CRASH();
+ }
+ } else {
+ MOZ_ASSERT(aFreq <= MAX_SAMPLING_FREQ);
+ MOZ_ASSERT(!(aFreq % 100), "Sampling rate for far end data should be multiple of 100.");
+ mPlayoutFreq = aFreq;
+ mChunkSize = aFreq/100; // 10ms
+ }
+
+#ifdef LOG_FAREND_INSERTION
+ static FILE *fp = fopen("insertfarend.pcm","wb");
+#endif
+
+ if (mSaved) {
+ // flag overrun as soon as possible, and only once
+ mSaved->mOverrun = aOverran;
+ aOverran = false;
+ }
+ // Rechunk to 10ms.
+ // The AnalyzeReverseStream() and WebRtcAec_BufferFarend() functions insist on 10ms
+ // samples per call. Annoying...
+ while (aFrames) {
+ if (!mSaved) {
+ mSaved = (FarEndAudioChunk *) moz_xmalloc(sizeof(FarEndAudioChunk) +
+ (mChunkSize * aChannels - 1)*sizeof(int16_t));
+ mSaved->mSamples = mChunkSize;
+ mSaved->mOverrun = aOverran;
+ aOverran = false;
+ }
+ uint32_t to_copy = mChunkSize - mSamplesSaved;
+ if (to_copy > aFrames) {
+ to_copy = aFrames;
+ }
+
+ int16_t *dest = &(mSaved->mData[mSamplesSaved * aChannels]);
+ ConvertAudioSamples(aBuffer, dest, to_copy * aChannels);
+
+#ifdef LOG_FAREND_INSERTION
+ if (fp) {
+ fwrite(&(mSaved->mData[mSamplesSaved * aChannels]), to_copy * aChannels, sizeof(int16_t), fp);
+ }
+#endif
+ aFrames -= to_copy;
+ mSamplesSaved += to_copy;
+ aBuffer += to_copy * aChannels;
+
+ if (mSamplesSaved >= mChunkSize) {
+ int free_slots = mPlayoutFifo->capacity() - mPlayoutFifo->size();
+ if (free_slots <= 0) {
+ // XXX We should flag an overrun for the reader. We can't drop data from it due to
+ // thread safety issues.
+ break;
+ } else {
+ mPlayoutFifo->Push((int8_t *) mSaved); // takes ownership
+ mSaved = nullptr;
+ mSamplesSaved = 0;
+ }
+ }
+ }
+}
+
+MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
+ webrtc::VoiceEngine* aVoiceEnginePtr,
+ mozilla::AudioInput* aAudioInput,
+ int aIndex,
+ const char* name,
+ const char* uuid)
+ : MediaEngineAudioSource(kReleased)
+ , mVoiceEngine(aVoiceEnginePtr)
+ , mAudioInput(aAudioInput)
+ , mMonitor("WebRTCMic.Monitor")
+ , mCapIndex(aIndex)
+ , mChannel(-1)
+ , mTrackID(TRACK_NONE)
+ , mStarted(false)
+ , mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
+ , mPlayoutDelay(0)
+ , mNullTransport(nullptr)
+ , mSkipProcessing(false)
+{
+ MOZ_ASSERT(aVoiceEnginePtr);
+ MOZ_ASSERT(aAudioInput);
+ mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
+ mDeviceUUID.Assign(uuid);
+ mListener = new mozilla::WebRTCAudioDataListener(this);
+ mSettings.mEchoCancellation.Construct(0);
+ mSettings.mMozAutoGainControl.Construct(0);
+ mSettings.mMozNoiseSuppression.Construct(0);
+ // We'll init lazily as needed
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::GetName(nsAString& aName) const
+{
+ aName.Assign(mDeviceName);
+ return;
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::GetUUID(nsACString& aUUID) const
+{
+ aUUID.Assign(mDeviceUUID);
+ return;
+}
+
+// GetBestFitnessDistance returns the best distance the capture device can offer
+// as a whole, given an accumulated number of ConstraintSets.
+// Ideal values are considered in the first ConstraintSet only.
+// Plain values are treated as Ideal in the first ConstraintSet.
+// Plain values are treated as Exact in subsequent ConstraintSets.
+// Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
+// A finite result may be used to calculate this device's ranking as a choice.
+
+uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+ const nsString& aDeviceId) const
+{
+ uint32_t distance = 0;
+
+ for (const auto* cs : aConstraintSets) {
+ distance = GetMinimumFitnessDistance(*cs, aDeviceId);
+ break; // distance is read from first entry only
+ }
+ return distance;
+}
+
+nsresult
+MediaEngineWebRTCMicrophoneSource::Restart(AllocationHandle* aHandle,
+ const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs &aPrefs,
+ const nsString& aDeviceId,
+ const char** aOutBadConstraint)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aHandle);
+ NormalizedConstraints constraints(aConstraints);
+ return ReevaluateAllocation(aHandle, &constraints, aPrefs, aDeviceId,
+ aOutBadConstraint);
+}
+
+bool operator == (const MediaEnginePrefs& a, const MediaEnginePrefs& b)
+{
+ return !memcmp(&a, &b, sizeof(MediaEnginePrefs));
+};
+
+nsresult
+MediaEngineWebRTCMicrophoneSource::UpdateSingleSource(
+ const AllocationHandle* aHandle,
+ const NormalizedConstraints& aNetConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const nsString& aDeviceId,
+ const char** aOutBadConstraint)
+{
+ FlattenedConstraints c(aNetConstraints);
+
+ MediaEnginePrefs prefs = aPrefs;
+ prefs.mAecOn = c.mEchoCancellation.Get(prefs.mAecOn);
+ prefs.mAgcOn = c.mMozAutoGainControl.Get(prefs.mAgcOn);
+ prefs.mNoiseOn = c.mMozNoiseSuppression.Get(prefs.mNoiseOn);
+
+ LOG(("Audio config: aec: %d, agc: %d, noise: %d, delay: %d",
+ prefs.mAecOn ? prefs.mAec : -1,
+ prefs.mAgcOn ? prefs.mAgc : -1,
+ prefs.mNoiseOn ? prefs.mNoise : -1,
+ prefs.mPlayoutDelay));
+
+ mPlayoutDelay = prefs.mPlayoutDelay;
+
+ switch (mState) {
+ case kReleased:
+ MOZ_ASSERT(aHandle);
+ if (sChannelsOpen == 0) {
+ if (!InitEngine()) {
+ LOG(("Audio engine is not initalized"));
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // Until we fix (or wallpaper) support for multiple mic input
+ // (Bug 1238038) fail allocation for a second device
+ return NS_ERROR_FAILURE;
+ }
+ if (!AllocChannel()) {
+ LOG(("Audio device is not initalized"));
+ return NS_ERROR_FAILURE;
+ }
+ if (mAudioInput->SetRecordingDevice(mCapIndex)) {
+ FreeChannel();
+ return NS_ERROR_FAILURE;
+ }
+ LOG(("Audio device %d allocated", mCapIndex));
+ break;
+
+ case kStarted:
+ if (prefs == mLastPrefs) {
+ return NS_OK;
+ }
+ if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
+ MonitorAutoLock lock(mMonitor);
+ if (mSources.IsEmpty()) {
+ LOG(("Audio device %d reallocated", mCapIndex));
+ } else {
+ LOG(("Audio device %d allocated shared", mCapIndex));
+ }
+ }
+ break;
+
+ default:
+ LOG(("Audio device %d %s in ignored state %d", mCapIndex,
+ (aHandle? aHandle->mOrigin.get() : ""), mState));
+ break;
+ }
+
+ if (sChannelsOpen > 0) {
+ int error;
+
+ error = mVoEProcessing->SetEcStatus(prefs.mAecOn, (webrtc::EcModes)prefs.mAec);
+ if (error) {
+ LOG(("%s Error setting Echo Status: %d ",__FUNCTION__, error));
+ // Overhead of capturing all the time is very low (<0.1% of an audio only call)
+ if (prefs.mAecOn) {
+ error = mVoEProcessing->SetEcMetricsStatus(true);
+ if (error) {
+ LOG(("%s Error setting Echo Metrics: %d ",__FUNCTION__, error));
+ }
+ }
+ }
+ error = mVoEProcessing->SetAgcStatus(prefs.mAgcOn, (webrtc::AgcModes)prefs.mAgc);
+ if (error) {
+ LOG(("%s Error setting AGC Status: %d ",__FUNCTION__, error));
+ }
+ error = mVoEProcessing->SetNsStatus(prefs.mNoiseOn, (webrtc::NsModes)prefs.mNoise);
+ if (error) {
+ LOG(("%s Error setting NoiseSuppression Status: %d ",__FUNCTION__, error));
+ }
+ }
+
+ mSkipProcessing = !(prefs.mAecOn || prefs.mAgcOn || prefs.mNoiseOn);
+ if (mSkipProcessing) {
+ mSampleFrequency = MediaEngine::USE_GRAPH_RATE;
+ }
+ SetLastPrefs(prefs);
+ return NS_OK;
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::SetLastPrefs(
+ const MediaEnginePrefs& aPrefs)
+{
+ mLastPrefs = aPrefs;
+
+ RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
+
+ NS_DispatchToMainThread(media::NewRunnableFrom([that, aPrefs]() mutable {
+ that->mSettings.mEchoCancellation.Value() = aPrefs.mAecOn;
+ that->mSettings.mMozAutoGainControl.Value() = aPrefs.mAgcOn;
+ that->mSettings.mMozNoiseSuppression.Value() = aPrefs.mNoiseOn;
+ return NS_OK;
+ }));
+}
+
+
+nsresult
+MediaEngineWebRTCMicrophoneSource::Deallocate(AllocationHandle* aHandle)
+{
+ AssertIsOnOwningThread();
+
+ Super::Deallocate(aHandle);
+
+ if (!mRegisteredHandles.Length()) {
+ // If empty, no callbacks to deliver data should be occuring
+ if (mState != kStopped && mState != kAllocated) {
+ return NS_ERROR_FAILURE;
+ }
+
+ FreeChannel();
+ LOG(("Audio device %d deallocated", mCapIndex));
+ } else {
+ LOG(("Audio device %d deallocated but still in use", mCapIndex));
+ }
+ return NS_OK;
+}
+
+nsresult
+MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream,
+ TrackID aID,
+ const PrincipalHandle& aPrincipalHandle)
+{
+ AssertIsOnOwningThread();
+ if (sChannelsOpen == 0 || !aStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MonitorAutoLock lock(mMonitor);
+ mSources.AppendElement(aStream);
+ mPrincipalHandles.AppendElement(aPrincipalHandle);
+ MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
+ }
+
+ AudioSegment* segment = new AudioSegment();
+ if (mSampleFrequency == MediaEngine::USE_GRAPH_RATE) {
+ mSampleFrequency = aStream->GraphRate();
+ }
+ aStream->AddAudioTrack(aID, mSampleFrequency, 0, segment, SourceMediaStream::ADDTRACK_QUEUED);
+
+ // XXX Make this based on the pref.
+ aStream->RegisterForAudioMixing();
+ LOG(("Start audio for stream %p", aStream));
+
+ if (!mListener) {
+ mListener = new mozilla::WebRTCAudioDataListener(this);
+ }
+ if (mState == kStarted) {
+ MOZ_ASSERT(aID == mTrackID);
+ // Make sure we're associated with this stream
+ mAudioInput->StartRecording(aStream, mListener);
+ return NS_OK;
+ }
+ mState = kStarted;
+ mTrackID = aID;
+
+ // Make sure logger starts before capture
+ AsyncLatencyLogger::Get(true);
+
+ // Register output observer
+ // XXX
+ MOZ_ASSERT(gFarendObserver);
+ gFarendObserver->Clear();
+
+ if (mVoEBase->StartReceive(mChannel)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Must be *before* StartSend() so it will notice we selected external input (full_duplex)
+ mAudioInput->StartRecording(aStream, mListener);
+
+ if (mVoEBase->StartSend(mChannel)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Attach external media processor, so this::Process will be called.
+ mVoERender->RegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel, *this);
+
+ return NS_OK;
+}
+
+nsresult
+MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
+{
+ AssertIsOnOwningThread();
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ size_t sourceIndex = mSources.IndexOf(aSource);
+ if (sourceIndex == mSources.NoIndex) {
+ // Already stopped - this is allowed
+ return NS_OK;
+ }
+ mSources.RemoveElementAt(sourceIndex);
+ mPrincipalHandles.RemoveElementAt(sourceIndex);
+ MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
+
+ aSource->EndTrack(aID);
+
+ if (!mSources.IsEmpty()) {
+ mAudioInput->StopRecording(aSource);
+ return NS_OK;
+ }
+ if (mState != kStarted) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!mVoEBase) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mState = kStopped;
+ }
+ if (mListener) {
+ // breaks a cycle, since the WebRTCAudioDataListener has a RefPtr to us
+ mListener->Shutdown();
+ mListener = nullptr;
+ }
+
+ mAudioInput->StopRecording(aSource);
+
+ mVoERender->DeRegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel);
+
+ if (mVoEBase->StopSend(mChannel)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (mVoEBase->StopReceive(mChannel)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph,
+ SourceMediaStream *aSource,
+ TrackID aID,
+ StreamTime aDesiredTime,
+ const PrincipalHandle& aPrincipalHandle)
+{
+ // Ignore - we push audio data
+ LOG_FRAMES(("NotifyPull, desired = %ld", (int64_t) aDesiredTime));
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::NotifyOutputData(MediaStreamGraph* aGraph,
+ AudioDataValue* aBuffer,
+ size_t aFrames,
+ TrackRate aRate,
+ uint32_t aChannels)
+{
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::PacketizeAndProcess(MediaStreamGraph* aGraph,
+ const AudioDataValue* aBuffer,
+ size_t aFrames,
+ TrackRate aRate,
+ uint32_t aChannels)
+{
+ // This will call Process() with data coming out of the AEC/NS/AGC/etc chain
+ if (!mPacketizer ||
+ mPacketizer->PacketSize() != aRate/100u ||
+ mPacketizer->Channels() != aChannels) {
+ // It's ok to drop the audio still in the packetizer here.
+ mPacketizer =
+ new AudioPacketizer<AudioDataValue, int16_t>(aRate/100, aChannels);
+ }
+
+ mPacketizer->Input(aBuffer, static_cast<uint32_t>(aFrames));
+
+ while (mPacketizer->PacketsAvailable()) {
+ uint32_t samplesPerPacket = mPacketizer->PacketSize() *
+ mPacketizer->Channels();
+ if (mInputBuffer.Length() < samplesPerPacket) {
+ mInputBuffer.SetLength(samplesPerPacket);
+ }
+ int16_t* packet = mInputBuffer.Elements();
+ mPacketizer->Output(packet);
+
+ mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
+ }
+}
+
+template<typename T>
+void
+MediaEngineWebRTCMicrophoneSource::InsertInGraph(const T* aBuffer,
+ size_t aFrames,
+ uint32_t aChannels)
+{
+ if (mState != kStarted) {
+ return;
+ }
+
+ size_t len = mSources.Length();
+ for (size_t i = 0; i < len; i++) {
+ if (!mSources[i]) {
+ continue;
+ }
+ RefPtr<SharedBuffer> buffer =
+ SharedBuffer::Create(aFrames * aChannels * sizeof(T));
+ PodCopy(static_cast<T*>(buffer->Data()),
+ aBuffer, aFrames * aChannels);
+
+ TimeStamp insertTime;
+ // Make sure we include the stream and the track.
+ // The 0:1 is a flag to note when we've done the final insert for a given input block.
+ LogTime(AsyncLatencyLogger::AudioTrackInsertion,
+ LATENCY_STREAM_ID(mSources[i].get(), mTrackID),
+ (i+1 < len) ? 0 : 1, insertTime);
+
+ nsAutoPtr<AudioSegment> segment(new AudioSegment());
+ AutoTArray<const T*, 1> channels;
+ // XXX Bug 971528 - Support stereo capture in gUM
+ MOZ_ASSERT(aChannels == 1,
+ "GraphDriver only supports us stereo audio for now");
+ channels.AppendElement(static_cast<T*>(buffer->Data()));
+ segment->AppendFrames(buffer.forget(), channels, aFrames,
+ mPrincipalHandles[i]);
+ segment->GetStartTime(insertTime);
+
+ mSources[i]->AppendToTrack(mTrackID, segment);
+ }
+}
+
+// Called back on GraphDriver thread!
+// Note this can be called back after ::Shutdown()
+void
+MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph,
+ const AudioDataValue* aBuffer,
+ size_t aFrames,
+ TrackRate aRate,
+ uint32_t aChannels)
+{
+ // If some processing is necessary, packetize and insert in the WebRTC.org
+ // code. Otherwise, directly insert the mic data in the MSG, bypassing all processing.
+ if (PassThrough()) {
+ InsertInGraph<AudioDataValue>(aBuffer, aFrames, aChannels);
+ } else {
+ PacketizeAndProcess(aGraph, aBuffer, aFrames, aRate, aChannels);
+ }
+}
+
+#define ResetProcessingIfNeeded(_processing) \
+do { \
+ webrtc::_processing##Modes mode; \
+ int rv = mVoEProcessing->Get##_processing##Status(enabled, mode); \
+ if (rv) { \
+ NS_WARNING("Could not get the status of the " \
+ #_processing " on device change."); \
+ return; \
+ } \
+ \
+ if (enabled) { \
+ rv = mVoEProcessing->Set##_processing##Status(!enabled); \
+ if (rv) { \
+ NS_WARNING("Could not reset the status of the " \
+ #_processing " on device change."); \
+ return; \
+ } \
+ \
+ rv = mVoEProcessing->Set##_processing##Status(enabled); \
+ if (rv) { \
+ NS_WARNING("Could not reset the status of the " \
+ #_processing " on device change."); \
+ return; \
+ } \
+ } \
+} while(0)
+
+void
+MediaEngineWebRTCMicrophoneSource::DeviceChanged() {
+ // Reset some processing
+ bool enabled;
+ ResetProcessingIfNeeded(Agc);
+ ResetProcessingIfNeeded(Ec);
+ ResetProcessingIfNeeded(Ns);
+}
+
+bool
+MediaEngineWebRTCMicrophoneSource::InitEngine()
+{
+ MOZ_ASSERT(!mVoEBase);
+ mVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
+
+ mVoEBase->Init();
+
+ mVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
+ if (mVoERender) {
+ mVoENetwork = webrtc::VoENetwork::GetInterface(mVoiceEngine);
+ if (mVoENetwork) {
+ mVoEProcessing = webrtc::VoEAudioProcessing::GetInterface(mVoiceEngine);
+ if (mVoEProcessing) {
+ mNullTransport = new NullTransport();
+ return true;
+ }
+ }
+ }
+ DeInitEngine();
+ return false;
+}
+
+// This shuts down the engine when no channel is open
+void
+MediaEngineWebRTCMicrophoneSource::DeInitEngine()
+{
+ if (mVoEBase) {
+ mVoEBase->Terminate();
+ delete mNullTransport;
+ mNullTransport = nullptr;
+
+ mVoEProcessing = nullptr;
+ mVoENetwork = nullptr;
+ mVoERender = nullptr;
+ mVoEBase = nullptr;
+ }
+}
+
+// This shuts down the engine when no channel is open.
+// mState records if a channel is allocated (slightly redundantly to mChannel)
+void
+MediaEngineWebRTCMicrophoneSource::FreeChannel()
+{
+ if (mState != kReleased) {
+ if (mChannel != -1) {
+ MOZ_ASSERT(mVoENetwork && mVoEBase);
+ if (mVoENetwork) {
+ mVoENetwork->DeRegisterExternalTransport(mChannel);
+ }
+ if (mVoEBase) {
+ mVoEBase->DeleteChannel(mChannel);
+ }
+ mChannel = -1;
+ }
+ mState = kReleased;
+
+ MOZ_ASSERT(sChannelsOpen > 0);
+ if (--sChannelsOpen == 0) {
+ DeInitEngine();
+ }
+ }
+}
+
+bool
+MediaEngineWebRTCMicrophoneSource::AllocChannel()
+{
+ MOZ_ASSERT(mVoEBase);
+
+ mChannel = mVoEBase->CreateChannel();
+ if (mChannel >= 0) {
+ if (!mVoENetwork->RegisterExternalTransport(mChannel, *mNullTransport)) {
+ mSampleFrequency = MediaEngine::DEFAULT_SAMPLE_RATE;
+ LOG(("%s: sampling rate %u", __FUNCTION__, mSampleFrequency));
+
+ // Check for availability.
+ if (!mAudioInput->SetRecordingDevice(mCapIndex)) {
+#ifndef MOZ_B2G
+ // Because of the permission mechanism of B2G, we need to skip the status
+ // check here.
+ bool avail = false;
+ mAudioInput->GetRecordingDeviceStatus(avail);
+ if (!avail) {
+ if (sChannelsOpen == 0) {
+ DeInitEngine();
+ }
+ return false;
+ }
+#endif // MOZ_B2G
+
+ // Set "codec" to PCM, 32kHz on 1 channel
+ ScopedCustomReleasePtr<webrtc::VoECodec> ptrVoECodec(webrtc::VoECodec::GetInterface(mVoiceEngine));
+ if (ptrVoECodec) {
+ webrtc::CodecInst codec;
+ strcpy(codec.plname, ENCODING);
+ codec.channels = CHANNELS;
+ MOZ_ASSERT(mSampleFrequency == 16000 || mSampleFrequency == 32000);
+ codec.rate = SAMPLE_RATE(mSampleFrequency);
+ codec.plfreq = mSampleFrequency;
+ codec.pacsize = SAMPLE_LENGTH(mSampleFrequency);
+ codec.pltype = 0; // Default payload type
+
+ if (!ptrVoECodec->SetSendCodec(mChannel, codec)) {
+ mState = kAllocated;
+ sChannelsOpen++;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ mVoEBase->DeleteChannel(mChannel);
+ mChannel = -1;
+ if (sChannelsOpen == 0) {
+ DeInitEngine();
+ }
+ return false;
+}
+
+void
+MediaEngineWebRTCMicrophoneSource::Shutdown()
+{
+ Super::Shutdown();
+ if (mListener) {
+ // breaks a cycle, since the WebRTCAudioDataListener has a RefPtr to us
+ mListener->Shutdown();
+ // Don't release the webrtc.org pointers yet until the Listener is (async) shutdown
+ mListener = nullptr;
+ }
+
+ if (mState == kStarted) {
+ SourceMediaStream *source;
+ bool empty;
+
+ while (1) {
+ {
+ MonitorAutoLock lock(mMonitor);
+ empty = mSources.IsEmpty();
+ if (empty) {
+ break;
+ }
+ source = mSources[0];
+ }
+ Stop(source, kAudioTrack); // XXX change to support multiple tracks
+ }
+ MOZ_ASSERT(mState == kStopped);
+ }
+
+ while (mRegisteredHandles.Length()) {
+ MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+ // on last Deallocate(), FreeChannel()s and DeInit()s if all channels are released
+ Deallocate(mRegisteredHandles[0].get());
+ }
+ MOZ_ASSERT(mState == kReleased);
+
+ mAudioInput = nullptr;
+}
+
+typedef int16_t sample;
+
+void
+MediaEngineWebRTCMicrophoneSource::Process(int channel,
+ webrtc::ProcessingTypes type,
+ sample *audio10ms, int length,
+ int samplingFreq, bool isStereo)
+{
+ MOZ_ASSERT(!PassThrough(), "This should be bypassed when in PassThrough mode.");
+ // On initial capture, throw away all far-end data except the most recent sample
+ // since it's already irrelevant and we want to keep avoid confusing the AEC far-end
+ // input code with "old" audio.
+ if (!mStarted) {
+ mStarted = true;
+ while (gFarendObserver->Size() > 1) {
+ free(gFarendObserver->Pop()); // only call if size() > 0
+ }
+ }
+
+ while (gFarendObserver->Size() > 0) {
+ FarEndAudioChunk *buffer = gFarendObserver->Pop(); // only call if size() > 0
+ if (buffer) {
+ int length = buffer->mSamples;
+ int res = mVoERender->ExternalPlayoutData(buffer->mData,
+ gFarendObserver->PlayoutFrequency(),
+ gFarendObserver->PlayoutChannels(),
+ mPlayoutDelay,
+ length);
+ free(buffer);
+ if (res == -1) {
+ return;
+ }
+ }
+ }
+
+ MonitorAutoLock lock(mMonitor);
+ if (mState != kStarted)
+ return;
+
+ MOZ_ASSERT(!isStereo);
+ InsertInGraph<int16_t>(audio10ms, length, 1);
+ return;
+}
+
+void
+MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName) const
+{
+ aName.AssignLiteral("AudioCapture");
+}
+
+void
+MediaEngineWebRTCAudioCaptureSource::GetUUID(nsACString &aUUID) const
+{
+ nsID uuid;
+ char uuidBuffer[NSID_LENGTH];
+ nsCString asciiString;
+ ErrorResult rv;
+
+ rv = nsContentUtils::GenerateUUIDInPlace(uuid);
+ if (rv.Failed()) {
+ aUUID.AssignLiteral("");
+ return;
+ }
+
+
+ uuid.ToProvidedString(uuidBuffer);
+ asciiString.AssignASCII(uuidBuffer);
+
+ // Remove {} and the null terminator
+ aUUID.Assign(Substring(asciiString, 1, NSID_LENGTH - 3));
+}
+
+nsresult
+MediaEngineWebRTCAudioCaptureSource::Start(SourceMediaStream *aMediaStream,
+ TrackID aId,
+ const PrincipalHandle& aPrincipalHandle)
+{
+ AssertIsOnOwningThread();
+ aMediaStream->AddTrack(aId, 0, new AudioSegment());
+ return NS_OK;
+}
+
+nsresult
+MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream,
+ TrackID aId)
+{
+ AssertIsOnOwningThread();
+ aMediaStream->EndAllTrackAndFinish();
+ return NS_OK;
+}
+
+nsresult
+MediaEngineWebRTCAudioCaptureSource::Restart(
+ AllocationHandle* aHandle,
+ const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs &aPrefs,
+ const nsString& aDeviceId,
+ const char** aOutBadConstraint)
+{
+ MOZ_ASSERT(!aHandle);
+ return NS_OK;
+}
+
+uint32_t
+MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
+ const nsString& aDeviceId) const
+{
+ // There is only one way of capturing audio for now, and it's always adequate.
+ return 0;
+}
+
+}