Bug 1404977 - Part 3 - Remove global statics, introduce an audio device enumerator r?pehrsons draft
authorPaul Adenot <paul@paul.cx>
Mon, 30 Apr 2018 15:18:22 +0200
changeset 798180 b19d0f2342ba1e3fa3d4c668289178ebafae13f2
parent 798179 cc2dc1608883e03bbfaa650bc4549245296ac2db
child 798181 7228c1667435807e974eab7a8f98c9219c23615d
push id110687
push userachronop@gmail.com
push dateTue, 22 May 2018 14:13:17 +0000
reviewerspehrsons
bugs1404977
milestone62.0a1
Bug 1404977 - Part 3 - Remove global statics, introduce an audio device enumerator r?pehrsons MozReview-Commit-ID: 55VlpGIAOxp
dom/media/webrtc/MediaEngineWebRTC.cpp
dom/media/webrtc/MediaEngineWebRTC.h
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -20,94 +20,22 @@
 #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")
+  , mEnumerator()
+  , mHasTabVideoSource(false)
 {
   nsCOMPtr<nsIComponentRegistrar> compMgr;
   NS_GetComponentRegistrar(getter_AddRefs(compMgr));
   if (compMgr) {
     compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
   }
 
   camera::GetChildAndCall(
@@ -243,69 +171,53 @@ 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->EnumerateAudioInputDevices(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) {
+      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)
@@ -389,13 +301,79 @@ 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")
+{
+  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::EnumerateAudioInputDevices(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();
 }
 
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -117,239 +117,44 @@ public:
   uint32_t GetBestFitnessDistance(
     const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId) const override;
 
 protected:
   virtual ~MediaEngineWebRTCAudioCaptureSource() = default;
 };
 
-// Small subset of VoEHardware
-class AudioInput
+// This class implements a cache for accessing the audio device list. It can be
+// accessed on any thread.
+class CubebDeviceEnumerator final
 {
+NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CubebDeviceEnumerator)
 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;
+  CubebDeviceEnumerator();
+  void EnumerateAudioInputDevices(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:
-  // Protected destructor, to discourage deletion outside of Release():
-  virtual ~AudioInput() = default;
-};
-
-class AudioInputCubeb final : public AudioInput
-{
-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
-  }
+  ~CubebDeviceEnumerator();
 
-  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;
-  }
-
-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 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;
+  // Synchronize access to mDevices
+  Mutex mMutex;
+  nsTArray<RefPtr<AudioDeviceInfo>> mDevices;
 };
 
 class WebRTCAudioDataListener : public AudioDataListener
 {
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~WebRTCAudioDataListener() {}
 
@@ -645,17 +450,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,