Bug 1404977 - Part 3 - Remove global statics, introduce an audio device enumerator r?pehrsons
MozReview-Commit-ID: 55VlpGIAOxp
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -21,94 +21,23 @@
#include "prenv.h"
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")
+ , mDelayAgnostic(aPrefs.mDelayAgnostic)
+ , mExtendedFilter(aPrefs.mExtendedFilter)
+ , mHasTabVideoSource(false)
{
nsCOMPtr<nsIComponentRegistrar> compMgr;
NS_GetComponentRegistrar(getter_AddRefs(compMgr));
if (compMgr) {
compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
}
camera::GetChildAndCall(
@@ -250,71 +179,48 @@ MediaEngineWebRTC::EnumerateVideoDevices
}
void
MediaEngineWebRTC::EnumerateMicrophoneDevices(uint64_t aWindowId,
nsTArray<RefPtr<MediaDevice> >* aDevices)
{
mMutex.AssertCurrentThreadOwns();
- if (!mAudioInput) {
- if (!SupportsDuplex()) {
- return;
- }
- mAudioInput = new mozilla::AudioInputCubeb();
+ if (!mEnumerator) {
+ mEnumerator.reset(new CubebDeviceEnumerator());
}
- 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';
+ nsTArray<RefPtr<AudioDeviceInfo>> devices;
+ mEnumerator->EnumerateAudioInputDevices(devices);
+
+ DebugOnly<bool> foundPreferredDevice = false;
- int error = mAudioInput->GetRecordingDeviceName(i, deviceName, uniqueId);
- if (error) {
- LOG((" AudioInput::GetRecordingDeviceName: Failed %d", error));
- continue;
- }
+ for (uint32_t i = 0; i < devices.Length(); i++) {
+ MOZ_ASSERT(devices[i]->DeviceID());
+ 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]->Name()).get(),
+ devices[i]->DeviceID()));
- if (uniqueId[0] == '\0') {
- // Mac and Linux don't set uniqueId!
- strcpy(uniqueId, deviceName); // safe given assert and initialization/error-check
+ if (devices[i]->State() == CUBEB_DEVICE_STATE_ENABLED) {
+ MOZ_ASSERT(devices[i]->Type() == CUBEB_DEVICE_TYPE_INPUT);
+ RefPtr<MediaEngineSource> source =
+ new MediaEngineWebRTCMicrophoneSource(
+ mEnumerator,
+ devices[i]->GetDeviceID().ref(),
+ devices[i]->Name(),
+ // Lie and provide the name as UUID
+ NS_LossyConvertUTF16toASCII(devices[i]->Name()),
+ devices[i]->MaxChannels(),
+ mDelayAgnostic,
+ mExtendedFilter);
+ aDevices->AppendElement(source);
}
-
-
- RefPtr<MediaEngineSource> micSource;
- NS_ConvertUTF8toUTF16 uuid(uniqueId);
-
- nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
- devicesForThisWindow = mAudioSources.LookupOrAdd(aWindowId);
-
- bool alreadySeenThisDeviceBefore = devicesForThisWindow->Get(uuid, getter_AddRefs(micSource)) &&
- micSource->RequiresSharing();
- if (!alreadySeenThisDeviceBefore) {
- micSource = new MediaEngineWebRTCMicrophoneSource(
- new mozilla::AudioInputCubeb(i),
- i, deviceName, uniqueId,
- mDelayAgnostic, mExtendedFilter);
- devicesForThisWindow->Put(uuid, micSource);
- }
- aDevices->AppendElement(MakeRefPtr<MediaDevice>(
- micSource,
- micSource->GetName(),
- NS_ConvertUTF8toUTF16(micSource->GetUUID())));
}
}
void
MediaEngineWebRTC::EnumerateSpeakerDevices(uint64_t aWindowId,
nsTArray<RefPtr<MediaDevice> >* aDevices)
{
nsTArray<RefPtr<AudioDeviceInfo>> devices;
@@ -331,21 +237,22 @@ MediaEngineWebRTC::EnumerateSpeakerDevic
aDevices->AppendElement(MakeRefPtr<MediaDevice>(
device->FriendlyName(),
dom::MediaDeviceKind::Audiooutput,
uuid));
}
}
}
+
void
MediaEngineWebRTC::EnumerateDevices(uint64_t aWindowId,
dom::MediaSourceEnum aMediaSource,
MediaSinkEnum aMediaSink,
- nsTArray<RefPtr<MediaDevice> >* aDevices)
+ nsTArray<RefPtr<MediaDevice>>* aDevices)
{
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other ||
aMediaSink != MediaSinkEnum::Other);
// We spawn threads to handle gUM runnables, so we must protect the member vars
MutexAutoLock lock(mMutex);
if (MediaEngineSource::IsVideo(aMediaSource)) {
EnumerateVideoDevices(aWindowId, aMediaSource, aDevices);
} else if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
@@ -360,22 +267,16 @@ MediaEngineWebRTC::EnumerateDevices(uint
EnumerateMicrophoneDevices(aWindowId, aDevices);
}
if (aMediaSink == MediaSinkEnum::Speaker) {
EnumerateSpeakerDevices(aWindowId, aDevices);
}
}
-bool
-MediaEngineWebRTC::SupportsDuplex()
-{
- return mFullDuplex;
-}
-
void
MediaEngineWebRTC::ReleaseResourcesForWindow(uint64_t aWindowId)
{
{
nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
audioDevicesForThisWindow = mAudioSources.Get(aWindowId);
if (audioDevicesForThisWindow) {
@@ -430,13 +331,89 @@ 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();
+}
+
+CubebDeviceEnumerator::CubebDeviceEnumerator()
+ : mMutex("CubebDeviceListMutex")
+ , mManualInvalidation(false)
+{
+ int rv = cubeb_register_device_collection_changed(GetCubebContext(),
+ CUBEB_DEVICE_TYPE_INPUT,
+ &mozilla::CubebDeviceEnumerator::AudioDeviceListChanged_s,
+ this);
+
+ if (rv != CUBEB_OK) {
+ NS_WARNING("Could not register the audio input"
+ " device collection changed callback.");
+ mManualInvalidation = true;
+ }
+}
+
+CubebDeviceEnumerator::~CubebDeviceEnumerator()
+{
+ int rv = cubeb_register_device_collection_changed(GetCubebContext(),
+ CUBEB_DEVICE_TYPE_INPUT,
+ nullptr,
+ this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING("Could not unregister the audio input"
+ " device collection changed callback.");
+ }
+}
+
+void
+CubebDeviceEnumerator::EnumerateAudioInputDevices(nsTArray<RefPtr<AudioDeviceInfo>>& aOutDevices)
+{
+ cubeb* context = GetCubebContext();
+
+ if (!context) {
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+
+ if (mDevices.IsEmpty() || mManualInvalidation) {
+ CubebUtils::GetDeviceCollection(mDevices, CubebUtils::Input);
+ }
+
+ aOutDevices.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]->DeviceID() == 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();
}
}
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -117,239 +117,52 @@ public:
uint32_t GetBestFitnessDistance(
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
const nsString& aDeviceId) const override;
protected:
virtual ~MediaEngineWebRTCAudioCaptureSource() = default;
};
-// Small subset of VoEHardware
-class AudioInput
-{
-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;
-};
-
-class AudioInputCubeb final : public AudioInput
+// This class implements a cache for accessing the audio device list. It can be
+// accessed on any thread.
+class CubebDeviceEnumerator final
{
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();
+ ~CubebDeviceEnumerator();
+ // This method returns a list of all the input and output audio devices
+ // available on this machine.
+ // This method is safe to call from all threads.
+ void EnumerateAudioInputDevices(nsTArray<RefPtr<AudioDeviceInfo>>& aOutDevices);
+ // From a cubeb device id, return the info for this device, if it's still a
+ // valid id, or nullptr otherwise.
+ // This method is safe to call from any thread.
+ already_AddRefed<AudioDeviceInfo>
+ DeviceInfoFromID(CubebUtils::AudioDeviceID aID);
protected:
- ~AudioInputCubeb() {
- MOZ_RELEASE_ASSERT(mInUseCount == 0);
- }
+
+ // 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 the MediaEngineWebRTC that instantiated this
+ // CubebDeviceEnumerator, and simply calls `AudioDeviceListChanged` below.
+ static void AudioDeviceListChanged_s(cubeb* aContext, void* aUser);
+ // Invalidates the cached audio input device list, can be called on any
+ // thread.
+ 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;
+ // Synchronize access to mDevices
+ Mutex mMutex;
+ nsTArray<RefPtr<AudioDeviceInfo>> mDevices;
+ // If mManualInvalidation is true, then it is necessary to query the device
+ // list each time instead of relying on automatic invalidation of the cache by
+ // cubeb itself. Set in the constructor and then can be access on any thread.
+ bool mManualInvalidation;
};
class WebRTCAudioDataListener : public AudioDataListener
{
protected:
// Protected destructor, to discourage deletion outside of Release():
virtual ~WebRTCAudioDataListener() {}
@@ -644,24 +457,23 @@ private:
void EnumerateVideoDevices(uint64_t aWindowId,
dom::MediaSourceEnum,
nsTArray<RefPtr<MediaDevice>>*);
void EnumerateMicrophoneDevices(uint64_t aWindowId,
nsTArray<RefPtr<MediaDevice>>*);
void EnumerateSpeakerDevices(uint64_t aWindowId,
nsTArray<RefPtr<MediaDevice> >*);
- nsCOMPtr<nsIThread> mThread;
-
// gUM runnables can e.g. Enumerate from multiple threads
Mutex mMutex;
- RefPtr<mozilla::AudioInput> mAudioInput;
- bool mFullDuplex;
- bool mDelayAgnostic;
- bool mExtendedFilter;
+ UniquePtr<mozilla::CubebDeviceEnumerator> mEnumerator;
+ const bool mDelayAgnostic;
+ const bool mExtendedFilter;
+ // This also is set in the ctor and then never changed, but we can't make it
+ // const because we pass it to a function that takes bool* in the ctor.
bool mHasTabVideoSource;
// Maps WindowID to a map of device uuid to their MediaEngineSource,
// separately for audio and video.
nsClassHashtable<nsUint64HashKey,
nsRefPtrHashtable<nsStringHashKey,
MediaEngineSource>> mVideoSources;
nsClassHashtable<nsUint64HashKey,