Bug 1361336 - Part 4: Create AudioNotificationSender/Receiver to pass the device-changed notification. r=cpearce, a=lizzard
authorChun-Min Chang <chun.m.chang@gmail.com>
Wed, 26 Jul 2017 17:00:44 +0800
changeset 421657 2194d0f757a9db0b1189940cdebde4177e12c35b
parent 421656 de020e9792ff39cf66e6b5c43ce6b7c9cfbabf72
child 421658 18e55ae0fc5348e7ae7967fa46c547fdf6e032a2
push id7736
push userryanvm@gmail.com
push dateMon, 11 Sep 2017 15:47:51 +0000
treeherdermozilla-beta@94cf7f1ee06b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, lizzard
bugs1361336
milestone56.0
Bug 1361336 - Part 4: Create AudioNotificationSender/Receiver to pass the device-changed notification. r=cpearce, a=lizzard
dom/media/AudioNotificationReceiver.cpp
dom/media/AudioNotificationReceiver.h
dom/media/AudioNotificationSender.cpp
dom/media/AudioNotificationSender.h
dom/media/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioNotificationReceiver.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "AudioNotificationReceiver.h"
+#include "AudioStream.h"          // for AudioStream
+#include "mozilla/Logging.h"      // for LazyLogModule
+#include "mozilla/StaticMutex.h"  // for StaticMutex
+#include "mozilla/StaticPtr.h"    // for StaticAutoPtr
+#include "nsAppRunner.h"          // for XRE_IsContentProcess
+#include "nsTArray.h"             // for nsTArray
+
+static mozilla::LazyLogModule sLogger("AudioNotificationReceiver");
+
+#undef ANR_LOG
+#define ANR_LOG(...) MOZ_LOG(sLogger, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef ANR_LOGW
+#define ANR_LOGW(...) MOZ_LOG(sLogger, mozilla::LogLevel::Warning, (__VA_ARGS__))
+
+namespace mozilla {
+namespace audio {
+
+/*
+ * A list containing all clients subscribering the device-changed notifications.
+ */
+static StaticAutoPtr<nsTArray<AudioStream*>> sSubscribers;
+static StaticMutex sMutex;
+
+/*
+ * AudioNotificationReceiver Implementation
+ */
+/* static */ void
+AudioNotificationReceiver::Register(AudioStream* aAudioStream)
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+
+  StaticMutexAutoLock lock(sMutex);
+  if (!sSubscribers) {
+    sSubscribers = new nsTArray<AudioStream*>();
+  }
+  sSubscribers->AppendElement(aAudioStream);
+
+  ANR_LOG("The AudioStream: %p is registered successfully.", aAudioStream);
+}
+
+/* static */ void
+AudioNotificationReceiver::Unregister(AudioStream* aAudioStream)
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+
+  StaticMutexAutoLock lock(sMutex);
+  MOZ_ASSERT(!sSubscribers->IsEmpty(), "No subscriber.");
+
+  sSubscribers->RemoveElement(aAudioStream);
+
+  ANR_LOG("The AudioStream: %p is unregistered successfully.", aAudioStream);
+}
+
+/* static */ void
+AudioNotificationReceiver::NotifyDefaultDeviceChanged()
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+
+  StaticMutexAutoLock lock(sMutex);
+  for (AudioStream* stream : *sSubscribers) {
+    ANR_LOG("Notify the AudioStream: %p that the default device has been changed.", stream);
+    stream->ResetDefaultDevice();
+  }
+}
+
+} // namespace audio
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioNotificationReceiver.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef MOZILLA_AUDIONOTIFICATIONRECEIVER_H_
+#define MOZILLA_AUDIONOTIFICATIONRECEIVER_H_
+
+/*
+ * Architecture to send/receive default device-changed notification:
+ *
+ *  Chrome Process                       ContentProcess 1
+ *  ------------------                   ------------------
+ *
+ *       AudioNotification                       AudioStream 1    AudioStream N
+ *             |      ^                             |      ^         ^
+ *          (4)|      |(2)                          |(3)   |(8)      .
+ *             v      |                             v      |         v
+ *   AudioNotificationSender                  AudioNotificationReceiver
+ *     ^       |      ^                                ^
+ *     .    (5)|      |(1)                             |(7)
+ *     .       v      |                                |
+ *     .  (P)ContentParent 1                   (P)ContentChild 1
+ *     .          |                                    ^
+ *     .       (6)|                                    |
+ *     .          |                                    |
+ *     .          |                                    |
+ *     .          +------------------------------------+
+ *     .
+ *     .
+ *     .                                 Content Process M
+ *     .                                 ------------------
+ *     .                                          .
+ *     v                                          .
+ *   (P)ContentParent M  < . . . . . . . . .  > (P)ContentChild M
+ *                            PContent IPC
+ *
+ * Steps
+ * --------
+ *  1) Initailize the AudioNotificationSender when ContentParent is created.
+ *  2) Create an AudioNotification to get the device-changed signal
+ *     from the system.
+ *  3) Register the AudioStream to AudioNotificationReceiver when it's created.
+ *  4) When the default device is changed, AudioNotification get the signal and
+ *  5) Pass this message by AudioNotificationSender.
+ *  6) The AudioNotificationSender sends the device-changed notification via
+ *     the PContent.
+ *  7) The ContentChild will call AudioNotificationReceiver to
+ *  8) Notify all the registered audio streams to reconfigure the output devices.
+ *
+ * Notes
+ * --------
+ * a) There is only one AudioNotificationSender and AudioNotification
+ *    in a chrome process.
+ * b) There is only one AudioNotificationReceiver and might be many
+ *    AudioStreams in a content process.
+ * c) There might be many ContentParent in a chrome process.
+ * d) There is only one ContentChild in a content process.
+ * e) All the Audiostreams are registered in the AudioNotificationReceiver.
+ * f) All the ContentParents are registered in the AudioNotificationSender.
+ */
+
+namespace mozilla {
+
+class AudioStream;
+
+namespace audio {
+
+class AudioNotificationReceiver final
+{
+public:
+  // Add the AudioStream into the subscribers list.
+  static void Register(AudioStream* aAudioStream);
+
+  // Remove the AudioStream from the subscribers list.
+  static void Unregister(AudioStream* aAudioStream);
+
+  // Notify all the streams that the default device has been changed.
+  static void NotifyDefaultDeviceChanged();
+}; // AudioNotificationReceiver
+
+} // namespace audio
+} // namespace mozilla
+
+#endif // MOZILLA_AUDIONOTIFICATIONRECEIVER_H_
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioNotificationSender.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "AudioNotificationSender.h"
+#include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
+#include "mozilla/dom/ContentParent.h"  // for ContentParent
+#include "mozilla/Logging.h"            // for LazyLogModule
+#include "mozilla/RefPtr.h"             // for RefPtr
+#include "mozilla/StaticPtr.h"          // for StaticAutoPtr
+#include "nsAppRunner.h"                // for XRE_IsParentProcess
+#include "nsTArray.h"                   // for nsTArray
+#include <mmdeviceapi.h>                // for IMMNotificationClient interface
+
+static mozilla::LazyLogModule sLogger("AudioNotificationSender");
+
+#undef ANS_LOG
+#define ANS_LOG(...) MOZ_LOG(sLogger, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef ANS_LOGW
+#define ANS_LOGW(...) MOZ_LOG(sLogger, mozilla::LogLevel::Warning, (__VA_ARGS__))
+
+namespace mozilla {
+namespace audio {
+
+/*
+ * A runnable task to notify the audio device-changed event.
+ */
+class AudioDeviceChangedRunnable final : public Runnable
+{
+public:
+  explicit AudioDeviceChangedRunnable(): Runnable("AudioDeviceChangedRunnable")
+  {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsTArray<dom::ContentParent*> parents;
+    dom::ContentParent::GetAll(parents);
+    for (dom::ContentParent* p : parents) {
+      Unused << p->SendAudioDefaultDeviceChange();
+    }
+    return NS_OK;
+  }
+}; // class AudioDeviceChangedRunnable
+
+/*
+ * An observer for receiving audio device events from Windows.
+ */
+typedef void (* DefaultDeviceChangedCallback)();
+class AudioNotification final : public IMMNotificationClient
+{
+public:
+  explicit AudioNotification(DefaultDeviceChangedCallback aCallback)
+    : mCallback(aCallback)
+    , mRefCt(0)
+    , mIsRegistered(false)
+  {
+    MOZ_COUNT_CTOR(AudioNotification);
+    MOZ_ASSERT(mCallback);
+    const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
+    const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
+    HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator,
+                                  nullptr,
+                                  CLSCTX_INPROC_SERVER,
+                                  IID_IMMDeviceEnumerator,
+                                  getter_AddRefs(mDeviceEnumerator));
+
+    if (FAILED(hr)) {
+      ANS_LOGW("Cannot create an IMMDeviceEnumerator instance.");
+      return;
+    }
+
+    hr = mDeviceEnumerator->RegisterEndpointNotificationCallback(this);
+    if (FAILED(hr)) {
+      ANS_LOGW("Cannot register notification callback.");
+      return;
+    }
+
+    ANS_LOG("Register notification callback successfully.");
+    mIsRegistered = true;
+  }
+
+  ~AudioNotification()
+  {
+    MOZ_COUNT_DTOR(AudioNotification);
+    // Assert mIsRegistered is true when we have mDeviceEnumerator.
+    // Don't care mIsRegistered if there is no mDeviceEnumerator.
+    MOZ_ASSERT(!mDeviceEnumerator || mIsRegistered);
+    if (!mDeviceEnumerator) {
+      ANS_LOG("No device enumerator in use.");
+      return;
+    }
+
+    HRESULT hr = mDeviceEnumerator->UnregisterEndpointNotificationCallback(this);
+    if (FAILED(hr)) {
+      // We can't really do anything here, so we just add a log for debugging.
+      ANS_LOGW("Unregister notification failed.");
+    } else {
+      ANS_LOG("Unregister notification callback successfully.");
+    }
+
+    mIsRegistered = false;
+  }
+
+  // True whenever the notification server is set to report events to this object.
+  bool IsRegistered() const
+  {
+    return mIsRegistered;
+  }
+
+  // IMMNotificationClient Implementation
+  HRESULT STDMETHODCALLTYPE
+  OnDefaultDeviceChanged(EDataFlow aFlow, ERole aRole, LPCWSTR aDeviceId) override
+  {
+    ANS_LOG("Default device has changed: flow %d, role: %d\n", aFlow, aRole);
+    mCallback();
+    return S_OK;
+  }
+
+  // The remaining methods are not implemented. they simply log when called
+  // (if log is enabled), for debugging.
+  HRESULT STDMETHODCALLTYPE
+  OnDeviceAdded(LPCWSTR aDeviceId) override
+  {
+    ANS_LOG("Audio device added.");
+    return S_OK;
+  };
+
+  HRESULT STDMETHODCALLTYPE
+  OnDeviceRemoved(LPCWSTR aDeviceId) override
+  {
+    ANS_LOG("Audio device removed.");
+    return S_OK;
+  }
+
+  HRESULT STDMETHODCALLTYPE
+  OnDeviceStateChanged(LPCWSTR aDeviceId, DWORD aNewState) override
+  {
+    ANS_LOG("Audio device state changed.");
+    return S_OK;
+  }
+
+  HRESULT STDMETHODCALLTYPE
+  OnPropertyValueChanged(LPCWSTR aDeviceId, const PROPERTYKEY aKey) override
+  {
+    ANS_LOG("Audio device property value changed.");
+    return S_OK;
+  }
+
+  // IUnknown Implementation
+  ULONG STDMETHODCALLTYPE
+  AddRef() override
+  {
+    return InterlockedIncrement(&mRefCt);
+  }
+
+  ULONG STDMETHODCALLTYPE
+  Release() override
+  {
+    ULONG ulRef = InterlockedDecrement(&mRefCt);
+    if (0 == ulRef) {
+      delete this;
+    }
+    return ulRef;
+  }
+
+  HRESULT STDMETHODCALLTYPE
+  QueryInterface(REFIID riid, VOID **ppvInterface) override
+  {
+    if (__uuidof(IUnknown) == riid) {
+      AddRef();
+      *ppvInterface = static_cast<IUnknown*>(this);
+    } else if (__uuidof(IMMNotificationClient) == riid) {
+      AddRef();
+      *ppvInterface = static_cast<IMMNotificationClient*>(this);
+    } else {
+      *ppvInterface = NULL;
+      return E_NOINTERFACE;
+    }
+    return S_OK;
+  }
+
+private:
+  RefPtr<IMMDeviceEnumerator> mDeviceEnumerator;
+  DefaultDeviceChangedCallback mCallback;
+  LONG mRefCt;
+  bool mIsRegistered;
+}; // class AudioNotification
+
+/*
+ * A singleton observer for audio device changed events.
+ */
+static StaticAutoPtr<AudioNotification> sAudioNotification;
+
+/*
+ * AudioNotificationSender Implementation
+ */
+/* static */ nsresult
+AudioNotificationSender::Init()
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sAudioNotification) {
+    sAudioNotification = new AudioNotification(NotifyDefaultDeviceChanged);
+    ClearOnShutdown(&sAudioNotification);
+
+    if (!sAudioNotification->IsRegistered()) {
+      ANS_LOGW("The notification sender cannot be initialized.");
+      return NS_ERROR_FAILURE;
+    }
+    ANS_LOG("The notification sender is initailized successfully.");
+  }
+
+  return NS_OK;
+}
+
+/* static */ void
+AudioNotificationSender::NotifyDefaultDeviceChanged()
+{
+  // This is running on the callback thread (from OnDefaultDeviceChanged).
+  MOZ_ASSERT(XRE_IsParentProcess());
+  ANS_LOG("Notify the default device-changed event.");
+
+  RefPtr<AudioDeviceChangedRunnable> runnable = new AudioDeviceChangedRunnable();
+  NS_DispatchToMainThread(runnable);
+}
+
+} // namespace audio
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioNotificationSender.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef MOZILLA_AUDIONOTIFICATIONSENDER_H_
+#define MOZILLA_AUDIONOTIFICATIONSENDER_H_
+
+#include "nsError.h" // for nsresult
+
+namespace mozilla {
+namespace audio {
+
+// Please see the architecture figure in AudioNotificationReceiver.h.
+class AudioNotificationSender final
+{
+public:
+  // Register the AudioNotification to get the device-changed event.
+  static nsresult Init();
+
+private:
+  // Send the device-changed notification from the chrome processes
+  // to the content processes.
+  static void NotifyDefaultDeviceChanged();
+}; // AudioNotificationSender
+
+} // namespace audio
+} // namespace mozilla
+
+#endif // MOZILLA_AUDIONOTIFICATIONSENDER_H_
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -260,17 +260,25 @@ UNIFIED_SOURCES += [
     'VideoTrack.cpp',
     'VideoTrackList.cpp',
     'VideoUtils.cpp',
     'WebVTTListener.cpp',
     'XiphExtradata.cpp',
 ]
 
 if CONFIG['OS_TARGET'] == 'WINNT':
-  SOURCES += [ 'ThreadPoolCOMListener.cpp' ]
+  EXPORTS.mozilla.audio += [
+      'AudioNotificationReceiver.h',
+      'AudioNotificationSender.h',
+  ]
+  SOURCES += [
+      'AudioNotificationReceiver.cpp',
+      'AudioNotificationSender.cpp',
+      'ThreadPoolCOMListener.cpp',
+  ]
 
 # DecoderTraits.cpp needs to be built separately because of Mac OS X headers.
 SOURCES += [
     'DecoderTraits.cpp',
 ]
 
 # Some codec-related code uses multi-character constants, which GCC and clang
 # warn about. Suppress turning this warning into an error.