Bug 1404977 - Part 3 - Remove global statics, introduce and audio device enumerator r?pehrsons
MozReview-Commit-ID: Br0qaKsg6mj
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -25,106 +25,99 @@
#endif
static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
#undef LOG
#define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args)
namespace mozilla {
-// statics from AudioInputCubeb
-nsTArray<int>* AudioInputCubeb::mDeviceIndexes;
-int AudioInputCubeb::mDefaultDevice = -1;
-nsTArray<nsCString>* AudioInputCubeb::mDeviceNames;
-cubeb_device_collection AudioInputCubeb::mDevices = { nullptr, 0 };
-bool AudioInputCubeb::mAnyInUse = false;
-StaticMutex AudioInputCubeb::sMutex;
-uint32_t AudioInputCubeb::sUserChannelCount = 0;
-
-// AudioDeviceID is an annoying opaque value that's really a string
-// pointer, and is freed when the cubeb_device_collection is destroyed
-
-void AudioInputCubeb::UpdateDeviceList()
-{
- // We keep all the device names, but wipe the mappings and rebuild them.
- // Do this first so that if cubeb has failed we've unmapped our devices
- // before we early return. Otherwise we'd keep the old list.
- for (auto& device_index : (*mDeviceIndexes)) {
- device_index = -1; // unmapped
- }
-
- cubeb* cubebContext = CubebUtils::GetCubebContext();
- if (!cubebContext) {
- return;
- }
-
- cubeb_device_collection devices = { nullptr, 0 };
-
- if (CUBEB_OK != cubeb_enumerate_devices(cubebContext,
- CUBEB_DEVICE_TYPE_INPUT,
- &devices)) {
- return;
- }
-
- // Calculate translation from existing mDevices to new devices. Note we
- // never end up with less devices than before, since people have
- // stashed indexes.
- // For some reason the "fake" device for automation is marked as DISABLED,
- // so white-list it.
- mDefaultDevice = -1;
- for (uint32_t i = 0; i < devices.count; i++) {
- LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p",
- i, devices.device[i].type, devices.device[i].state,
- devices.device[i].friendly_name, devices.device[i].device_id));
- if (devices.device[i].type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
- devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED )
- {
- auto j = mDeviceNames->IndexOf(devices.device[i].device_id);
- if (j != nsTArray<nsCString>::NoIndex) {
- // match! update the mapping
- (*mDeviceIndexes)[j] = i;
- } else {
- // new device, add to the array
- mDeviceIndexes->AppendElement(i);
- mDeviceNames->AppendElement(devices.device[i].device_id);
- j = mDeviceIndexes->Length()-1;
- }
- if (devices.device[i].preferred & CUBEB_DEVICE_PREF_VOICE) {
- // There can be only one... we hope
- NS_ASSERTION(mDefaultDevice == -1, "multiple default cubeb input devices!");
- mDefaultDevice = j;
- }
- }
- }
- LOG(("Cubeb default input device %d", mDefaultDevice));
- StaticMutexAutoLock lock(sMutex);
- // swap state
- cubeb_device_collection_destroy(cubebContext, &mDevices);
- mDevices = devices;
-}
+using namespace CubebUtils;
MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
- : mMutex("MediaEngineWebRTC::mMutex"),
- mAudioInput(nullptr),
- mFullDuplex(aPrefs.mFullDuplex),
- mDelayAgnostic(aPrefs.mDelayAgnostic),
- mExtendedFilter(aPrefs.mExtendedFilter),
- mHasTabVideoSource(false)
+ : mMutex("mozilla::MediaEngineWebRTC")
+ , mEnumerator()
+ , mHasTabVideoSource(false)
{
nsCOMPtr<nsIComponentRegistrar> compMgr;
NS_GetComponentRegistrar(getter_AddRefs(compMgr));
if (compMgr) {
compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
}
camera::GetChildAndCall(
&camera::CamerasChild::AddDeviceChangeCallback,
this);
}
+CubebDeviceEnumerator::CubebDeviceEnumerator()
+ : mMutex("CubebDeviceListMutex")
+{
+ cubeb_register_device_collection_changed(GetCubebContext(),
+ CUBEB_DEVICE_TYPE_INPUT,
+ &mozilla::CubebDeviceEnumerator::AudioDeviceListChanged_s,
+ this);
+}
+
+CubebDeviceEnumerator::~CubebDeviceEnumerator()
+{
+ cubeb_register_device_collection_changed(GetCubebContext(),
+ CUBEB_DEVICE_TYPE_INPUT,
+ nullptr,
+ this);
+}
+
+void
+CubebDeviceEnumerator::EnumerateAudioInputDevice(nsTArray<RefPtr<AudioDeviceInfo>>& aDevices)
+{
+ cubeb* context = GetCubebContext();
+
+ if (!context) {
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+
+ if (mDevices.IsEmpty()) {
+ CubebUtils::GetDeviceCollection(mDevices, CubebUtils::Input);
+ }
+
+ aDevices.AppendElements(mDevices);
+}
+
+already_AddRefed<AudioDeviceInfo>
+CubebDeviceEnumerator::DeviceInfoFromID(CubebUtils::AudioDeviceID aID)
+{
+ MutexAutoLock lock(mMutex);
+
+ for (uint32_t i = 0; i < mDevices.Length(); i++) {
+ if (mDevices[i]->GetDeviceID().isSome() &&
+ mDevices[i]->GetDeviceID().ref() == aID) {
+ RefPtr<AudioDeviceInfo> other = mDevices[i];
+ return other.forget();
+ }
+ }
+ return nullptr;
+}
+
+void
+CubebDeviceEnumerator::AudioDeviceListChanged_s(cubeb* aContext, void* aUser)
+{
+ CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
+ self->AudioDeviceListChanged();
+}
+
+void
+CubebDeviceEnumerator::AudioDeviceListChanged()
+{
+ MutexAutoLock lock(mMutex);
+
+ mDevices.Clear();
+}
+
void
MediaEngineWebRTC::SetFakeDeviceChangeEvents()
{
camera::GetChildAndCall(
&camera::CamerasChild::SetFakeDeviceChangeEvents);
}
void
@@ -259,69 +252,55 @@ MediaEngineWebRTC::EnumerateVideoDevices
}
void
MediaEngineWebRTC::EnumerateMicrophoneDevices(uint64_t aWindowId,
nsTArray<RefPtr<MediaEngineSource> >* aSources)
{
mMutex.AssertCurrentThreadOwns();
- if (!mAudioInput) {
- if (!SupportsDuplex()) {
- return;
- }
- mAudioInput = new mozilla::AudioInputCubeb();
+ if (!mEnumerator) {
+ mEnumerator = new CubebDeviceEnumerator();
+ }
+
+ nsTArray<RefPtr<AudioDeviceInfo>> devices;
+ mEnumerator->EnumerateAudioInputDevice(devices);
+
+ // Handle enumeration error
+ if (devices.IsEmpty()) {
+ return;
}
- int nDevices = 0;
- mAudioInput->GetNumOfRecordingDevices(nDevices);
- int i;
-#if defined(MOZ_WIDGET_ANDROID)
- i = 0; // Bug 1037025 - let the OS handle defaulting for now on android/b2g
-#else
- // -1 is "default communications device" depending on OS in webrtc.org code
- i = -1;
-#endif
- for (; i < nDevices; i++) {
- // We use constants here because GetRecordingDeviceName takes char[128].
- char deviceName[128];
- char uniqueId[128];
- // paranoia; jingle doesn't bother with this
- deviceName[0] = '\0';
- uniqueId[0] = '\0';
-
- int error = mAudioInput->GetRecordingDeviceName(i, deviceName, uniqueId);
- if (error) {
- LOG((" AudioInput::GetRecordingDeviceName: Failed %d", error));
- continue;
- }
+ // For some reason the "fake" device for automation is marked as DISABLED,
+ // so white-list it.
+ for (uint32_t i = 0; i < devices.Length(); i++) {
+ MOZ_ASSERT(devices[i]->GetDeviceID().isSome());
+ LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p",
+ i,
+ devices[i]->Type(),
+ devices[i]->State(),
+ NS_ConvertUTF16toUTF8(devices[i]->FriendlyName()).get(),
+ devices[i]->GetDeviceID().ref()));
- if (uniqueId[0] == '\0') {
- // Mac and Linux don't set uniqueId!
- strcpy(uniqueId, deviceName); // safe given assert and initialization/error-check
- }
-
-
- RefPtr<MediaEngineSource> aSource;
- NS_ConvertUTF8toUTF16 uuid(uniqueId);
-
- nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
- devicesForThisWindow = mAudioSources.LookupOrAdd(aWindowId);
-
- if (devicesForThisWindow->Get(uuid, getter_AddRefs(aSource)) &&
- aSource->RequiresSharing()) {
- // We've already seen this device, just append.
- aSources->AppendElement(aSource.get());
- } else {
- aSource = new MediaEngineWebRTCMicrophoneSource(
- new mozilla::AudioInputCubeb(i),
- i, deviceName, uniqueId,
- mDelayAgnostic, mExtendedFilter);
- devicesForThisWindow->Put(uuid, aSource);
- aSources->AppendElement(aSource);
+ if ((devices[i]->State() == CUBEB_DEVICE_STATE_ENABLED ||
+ (devices[i]->State() == CUBEB_DEVICE_STATE_DISABLED &&
+ devices[i]->FriendlyName().Equals(NS_LITERAL_STRING("Sine source at 440 Hz"))))) {
+ MOZ_ASSERT(devices[i]->Type() == CUBEB_DEVICE_TYPE_INPUT);
+ // XXX do something for the default device.
+ RefPtr<MediaEngineSource> source =
+ new MediaEngineWebRTCMicrophoneSource(
+ mEnumerator,
+ devices[i]->GetDeviceID().ref(),
+ devices[i]->FriendlyName(),
+ // Lie and provide the name as UUID
+ NS_LossyConvertUTF16toASCII(devices[i]->FriendlyName()),
+ devices[i]->MaxChannels(),
+ mDelayAgnostic,
+ mExtendedFilter);
+ aSources->AppendElement(source);
}
}
}
void
MediaEngineWebRTC::EnumerateDevices(uint64_t aWindowId,
dom::MediaSourceEnum aMediaSource,
nsTArray<RefPtr<MediaEngineSource> >* aSources)
@@ -405,13 +384,14 @@ MediaEngineWebRTC::Shutdown()
}
LOG(("%s", __FUNCTION__));
// Shutdown all the sources, since we may have dangling references to the
// sources in nsDOMUserMediaStreams waiting for GC/CC
ShutdownSources(mVideoSources);
ShutdownSources(mAudioSources);
+ mEnumerator = nullptr;
+
mozilla::camera::Shutdown();
- AudioInputCubeb::CleanupGlobalData();
}
}
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -2,16 +2,17 @@
* 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/. */
#ifndef MEDIAENGINEWEBRTC_H_
#define MEDIAENGINEWEBRTC_H_
#include "AudioPacketizer.h"
#include "AudioSegment.h"
+#include "AudioDeviceInfo.h"
#include "CamerasChild.h"
#include "cubeb/cubeb.h"
#include "CubebUtils.h"
#include "DOMMediaStream.h"
#include "ipc/IPCMessageUtils.h"
#include "MediaEngine.h"
#include "MediaEnginePrefs.h"
#include "MediaEngineSource.h"
@@ -116,239 +117,51 @@ public:
uint32_t GetBestFitnessDistance(
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
const nsString& aDeviceId) const override;
protected:
virtual ~MediaEngineWebRTCAudioCaptureSource() = default;
};
-// Small subset of VoEHardware
-class AudioInput
+struct CubebDeviceCollectionDestroyFunctor
{
-public:
- AudioInput() = default;
- // Threadsafe because it's referenced from an MicrophoneSource, which can
- // had references to it on other threads.
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioInput)
-
- virtual int GetNumOfRecordingDevices(int& aDevices) = 0;
- virtual int GetRecordingDeviceName(int aIndex, char (&aStrNameUTF8)[128],
- char aStrGuidUTF8[128]) = 0;
- virtual int GetRecordingDeviceStatus(bool& aIsAvailable) = 0;
- virtual void GetChannelCount(uint32_t& aChannels) = 0;
- virtual int GetMaxAvailableChannels(uint32_t& aChannels) = 0;
- virtual void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener) = 0;
- virtual void StopRecording(SourceMediaStream *aStream) = 0;
- virtual int SetRecordingDevice(int aIndex) = 0;
- virtual void SetUserChannelCount(uint32_t aChannels) = 0;
-
-protected:
- // Protected destructor, to discourage deletion outside of Release():
- virtual ~AudioInput() = default;
+ void operator()(cubeb_device_collection* aCollection)
+ {
+ cubeb_device_collection_destroy(CubebUtils::GetCubebContext(), aCollection);
+ }
};
-class AudioInputCubeb final : public AudioInput
+// This class implement a cache for accessing the audio device list. It can be
+// access on any thread.
+class CubebDeviceEnumerator final
{
+NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CubebDeviceEnumerator)
public:
- explicit AudioInputCubeb(int aIndex = 0) :
- AudioInput(), mSelectedDevice(aIndex), mInUseCount(0)
- {
- if (!mDeviceIndexes) {
- mDeviceIndexes = new nsTArray<int>;
- mDeviceNames = new nsTArray<nsCString>;
- mDefaultDevice = -1;
- }
- }
-
- static void CleanupGlobalData()
- {
- if (mDevices.device) {
- cubeb_device_collection_destroy(CubebUtils::GetCubebContext(), &mDevices);
- }
- delete mDeviceIndexes;
- mDeviceIndexes = nullptr;
- delete mDeviceNames;
- mDeviceNames = nullptr;
- }
-
- int GetNumOfRecordingDevices(int& aDevices)
- {
-#ifdef MOZ_WIDGET_ANDROID
- // OpenSL ES does not support enumerate device.
- aDevices = 1;
-#else
- UpdateDeviceList();
- aDevices = mDeviceIndexes->Length();
-#endif
- return 0;
- }
-
- static int32_t DeviceIndex(int aIndex)
- {
- // -1 = system default if any
- if (aIndex == -1) {
- if (mDefaultDevice == -1) {
- aIndex = 0;
- } else {
- aIndex = mDefaultDevice;
- }
- }
- MOZ_ASSERT(mDeviceIndexes);
- if (aIndex < 0 || aIndex >= (int) mDeviceIndexes->Length()) {
- return -1;
- }
- // Note: if the device is gone, this will be -1
- return (*mDeviceIndexes)[aIndex]; // translate to mDevices index
- }
-
- static StaticMutex& Mutex()
- {
- return sMutex;
- }
-
- static bool GetDeviceID(int aDeviceIndex, CubebUtils::AudioDeviceID &aID)
- {
- // Assert sMutex is held
- sMutex.AssertCurrentThreadOwns();
-#ifdef MOZ_WIDGET_ANDROID
- aID = nullptr;
- return true;
-#else
- int dev_index = DeviceIndex(aDeviceIndex);
- if (dev_index != -1) {
- aID = mDevices.device[dev_index].devid;
- return true;
- }
- return false;
-#endif
- }
-
- int GetRecordingDeviceName(int aIndex, char (&aStrNameUTF8)[128],
- char aStrGuidUTF8[128])
- {
-#ifdef MOZ_WIDGET_ANDROID
- aStrNameUTF8[0] = '\0';
- aStrGuidUTF8[0] = '\0';
-#else
- int32_t devindex = DeviceIndex(aIndex);
- if (mDevices.count == 0 || devindex < 0) {
- return 1;
- }
- SprintfLiteral(aStrNameUTF8, "%s%s", aIndex == -1 ? "default: " : "",
- mDevices.device[devindex].friendly_name);
- aStrGuidUTF8[0] = '\0';
-#endif
- return 0;
- }
-
- int GetRecordingDeviceStatus(bool& aIsAvailable)
- {
- // With cubeb, we only expose devices of type CUBEB_DEVICE_TYPE_INPUT,
- // so unless it was removed, say it's available
- aIsAvailable = true;
- return 0;
- }
-
- void GetChannelCount(uint32_t& aChannels)
- {
- GetUserChannelCount(mSelectedDevice, aChannels);
- }
-
- static void GetUserChannelCount(int aDeviceIndex, uint32_t& aChannels)
- {
- aChannels = sUserChannelCount;
- }
-
- int GetMaxAvailableChannels(uint32_t& aChannels)
- {
- return GetDeviceMaxChannels(mSelectedDevice, aChannels);
- }
-
- static int GetDeviceMaxChannels(int aDeviceIndex, uint32_t& aChannels)
- {
-#ifdef MOZ_WIDGET_ANDROID
- aChannels = 1;
-#else
- int32_t devindex = DeviceIndex(aDeviceIndex);
- if (mDevices.count == 0 || devindex < 0) {
- return 1;
- }
- aChannels = mDevices.device[devindex].max_channels;
-#endif
- return 0;
- }
-
- void SetUserChannelCount(uint32_t aChannels)
- {
- if (GetDeviceMaxChannels(mSelectedDevice, sUserChannelCount)) {
- sUserChannelCount = 1; // error capture mono
- return;
- }
-
- if (aChannels && aChannels < sUserChannelCount) {
- sUserChannelCount = aChannels;
- }
- }
-
- void StartRecording(SourceMediaStream *aStream, AudioDataListener *aListener)
- {
-#ifdef MOZ_WIDGET_ANDROID
- // OpenSL ES does not support enumerating devices.
- MOZ_ASSERT(mDevices.count == 0);
-#else
- MOZ_ASSERT(mDevices.count > 0);
-#endif
-
- mAnyInUse = true;
- mInUseCount++;
- // Always tell the stream we're using it for input
- aStream->OpenAudioInput(mSelectedDevice, aListener);
- }
-
- void StopRecording(SourceMediaStream *aStream)
- {
- aStream->CloseAudioInput();
- if (--mInUseCount == 0) {
- mAnyInUse = false;
- }
- }
-
- int SetRecordingDevice(int aIndex)
- {
- mSelectedDevice = aIndex;
- return 0;
- }
+ CubebDeviceEnumerator();
+ void EnumerateAudioInputDevice(nsTArray<RefPtr<AudioDeviceInfo>>& aDevices);
+ // From a cubeb device id, maybe return the info for this device, if it's
+ // still a valid id.
+ already_AddRefed<AudioDeviceInfo>
+ DeviceInfoFromID(CubebUtils::AudioDeviceID aID);
protected:
- ~AudioInputCubeb() {
- MOZ_RELEASE_ASSERT(mInUseCount == 0);
- }
+ ~CubebDeviceEnumerator();
+
+ // Static function called by cubeb when the audio input device list changes
+ // (i.e. when a new device is made available, or non-available). This
+ // re-binds to this MediaEngineWebRTC, and simply calls
+ // `AudioDeviceListChanged` below.
+ static void AudioDeviceListChanged_s(cubeb* aContext, void* aUser);
+ // With the mutex taken, invalidates the cached audio input device list.
+ void AudioDeviceListChanged();
private:
- // It would be better to watch for device-change notifications
- void UpdateDeviceList();
-
- // We have an array, which consists of indexes to the current mDevices
- // list. This is updated on mDevices updates. Many devices in mDevices
- // won't be included in the array (wrong type, etc), or if a device is
- // removed it will map to -1 (and opens of this device will need to check
- // for this - and be careful of threading access. The mappings need to
- // updated on each re-enumeration.
- int mSelectedDevice;
- uint32_t mInUseCount;
-
- // pointers to avoid static constructors
- static nsTArray<int>* mDeviceIndexes;
- static int mDefaultDevice; // -1 == not set
- static nsTArray<nsCString>* mDeviceNames;
- static cubeb_device_collection mDevices;
- static bool mAnyInUse;
- static StaticMutex sMutex;
- static uint32_t sUserChannelCount;
+ Mutex mMutex;
+ nsTArray<RefPtr<AudioDeviceInfo>> mDevices;
};
class WebRTCAudioDataListener : public AudioDataListener
{
protected:
// Protected destructor, to discourage deletion outside of Release():
virtual ~WebRTCAudioDataListener() {}
@@ -644,17 +457,17 @@ private:
nsTArray<RefPtr<MediaEngineSource>>*);
void EnumerateMicrophoneDevices(uint64_t aWindowId,
nsTArray<RefPtr<MediaEngineSource>>*);
nsCOMPtr<nsIThread> mThread;
// gUM runnables can e.g. Enumerate from multiple threads
Mutex mMutex;
- RefPtr<mozilla::AudioInput> mAudioInput;
+ RefPtr<mozilla::CubebDeviceEnumerator> mEnumerator;
bool mFullDuplex;
bool mDelayAgnostic;
bool mExtendedFilter;
bool mHasTabVideoSource;
// Maps WindowID to a map of device uuid to their MediaEngineSource,
// separately for audio and video.
nsClassHashtable<nsUint64HashKey,