Bug 1197045 - part2: Expose audio channels, layout, sample-rate, and native device information via DOMWindowUtils; r=padenot draft
authorChun-Min Chang <chun.m.chang@gmail.com>
Tue, 18 Jul 2017 16:09:10 +0800
changeset 610466 ede633f29146a7f83832278a6a77e5ccabb0e303
parent 610465 d1aa70e17ae7d3d25a253f7da98b6bf4e28f9e5e
child 610467 40c7d62e7ca0e9c818d8a0d32a682e0d5d1db1b7
child 612969 d81977b2f748f541ac8d5473a1feda4310477d79
child 614815 02c9752e8a9ff20dcff1155cacf38c11c186fa43
push id68896
push userbmo:cchang@mozilla.com
push dateTue, 18 Jul 2017 09:35:18 +0000
reviewerspadenot
bugs1197045
milestone56.0a1
Bug 1197045 - part2: Expose audio channels, layout, sample-rate, and native device information via DOMWindowUtils; r=padenot MozReview-Commit-ID: 9Kh2w0MioUQ
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -70,16 +70,17 @@
 #if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #endif
 
 #include "Layers.h"
 #include "gfxPrefs.h"
 
+#include "mozilla/dom/AudioDeviceInfo.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/IDBFactoryBinding.h"
 #include "mozilla/dom/IDBMutableFileBinding.h"
 #include "mozilla/dom/IDBMutableFile.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/quota/PersistenceType.h"
@@ -2446,16 +2447,63 @@ nsDOMWindowUtils::GetSupportsHardwareH26
 NS_IMETHODIMP
 nsDOMWindowUtils::GetCurrentAudioBackend(nsAString& aBackend)
 {
   CubebUtils::GetCurrentBackend(aBackend);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentMaxAudioChannels(uint32_t* aChannels)
+{
+  *aChannels = CubebUtils::MaxNumberOfChannels();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentPreferredChannelLayout(nsAString& aLayout)
+{
+  CubebUtils::GetPreferredChannelLayout(aLayout);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentPreferredSampleRate(uint32_t* aRate)
+{
+  *aRate = CubebUtils::PreferredSampleRate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AudioDevices(uint16_t aSide, nsIArray** aDevices)
+{
+  NS_ENSURE_ARG_POINTER(aDevices);
+  NS_ENSURE_ARG((aSide == AUDIO_INPUT) || (aSide == AUDIO_OUTPUT));
+  *aDevices = nullptr;
+
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIMutableArray> devices =
+    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsTArray<RefPtr<AudioDeviceInfo>> collection;
+  CubebUtils::GetDeviceCollection(collection,
+                                  aSide == AUDIO_INPUT
+                                    ? CubebUtils::Side::Input
+                                    : CubebUtils::Side::Output);
+  for (auto device: collection) {
+    devices->AppendElement(device, false);
+  }
+
+  devices.forget(aDevices);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::StartFrameTimeRecording(uint32_t *startIndex)
 {
   NS_ENSURE_ARG_POINTER(startIndex);
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return NS_ERROR_FAILURE;
 
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -24,16 +24,17 @@ class gfxContext;
 struct nsRect;
 %}
 
 [ref] native nsConstRect(const nsRect);
 native nscolor(nscolor);
 [ptr] native gfxContext(gfxContext);
 typedef unsigned long long nsViewID;
 
+interface nsIArray;
 interface nsICycleCollectorListener;
 interface nsIDOMNode;
 interface nsIDOMNodeList;
 interface nsIDOMElement;
 interface nsIDOMHTMLCanvasElement;
 interface nsIDOMEvent;
 interface nsIPreloadedStyleSheet;
 interface nsITransferable;
@@ -1422,16 +1423,38 @@ interface nsIDOMWindowUtils : nsISupport
   readonly attribute jsval supportsHardwareH264Decoding;
 
   /**
    * Returns the current audio backend as a free-form string.
    */
   readonly attribute AString currentAudioBackend;
 
   /**
+   * Returns the max channel counts of the current audio device.
+   */
+  readonly attribute unsigned long currentMaxAudioChannels;
+
+  /**
+   * Returns the preferred channel layout of the current audio device.
+   */
+  readonly attribute AString currentPreferredChannelLayout;
+
+  /**
+   * Returns the preferred sample rate of the current audio device.
+   */
+  readonly attribute unsigned long currentPreferredSampleRate;
+
+  /**
+   * Returns all the audio input/output devices.
+   */
+  const unsigned short AUDIO_INPUT   = 0;
+  const unsigned short AUDIO_OUTPUT  = 1;
+  nsIArray audioDevices(in unsigned short aSide);
+
+  /**
    * Record (and return) frame-intervals for frames which were presented
    *   between calling StartFrameTimeRecording and StopFrameTimeRecording.
    *
    * - Uses a cyclic buffer and serves concurrent consumers, so if Stop is called too late
    *     (elements were overwritten since Start), result is considered invalid and hence empty.
    * - Buffer is capable of holding 10 seconds @ 60fps (or more if frames were less frequent).
    *     Can be changed (up to 1 hour) via pref: toolkit.framesRecording.bufferSize.
    * - Note: the first frame-interval may be longer than expected because last frame
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -99,16 +99,43 @@ const char* AUDIOSTREAM_BACKEND_ID_STR[]
 /* Index for failures to create an audio stream the first time. */
 const int CUBEB_BACKEND_INIT_FAILURE_FIRST =
   ArrayLength(AUDIOSTREAM_BACKEND_ID_STR);
 /* Index for failures to create an audio stream after the first time */
 const int CUBEB_BACKEND_INIT_FAILURE_OTHER = CUBEB_BACKEND_INIT_FAILURE_FIRST + 1;
 /* Index for an unknown backend. */
 const int CUBEB_BACKEND_UNKNOWN = CUBEB_BACKEND_INIT_FAILURE_FIRST + 2;
 
+typedef struct {
+  const char* name;
+  const unsigned int channels;
+  const uint32_t mask;
+} layoutInfo;
+
+const layoutInfo kLayoutInfos[CUBEB_LAYOUT_MAX] = {
+  { "undefined",      0, 0 },               // CUBEB_LAYOUT_UNDEFINED
+  { "dual mono",      2, MASK_STEREO },     // CUBEB_LAYOUT_DUAL_MONO
+  { "dual mono lfe",  3, MASK_STEREO_LFE }, // CUBEB_LAYOUT_DUAL_MONO_LFE
+  { "mono",           1, MASK_MONO },       // CUBEB_LAYOUT_MONO
+  { "mono lfe",       2, MASK_MONO_LFE },   // CUBEB_LAYOUT_MONO_LFE
+  { "stereo",         2, MASK_STEREO },     // CUBEB_LAYOUT_STEREO
+  { "stereo lfe",     3, MASK_STEREO_LFE }, // CUBEB_LAYOUT_STEREO_LFE
+  { "3f",             3, MASK_3F },         // CUBEB_LAYOUT_3F
+  { "3f lfe",         4, MASK_3F_LFE },     // CUBEB_LAYOUT_3F_LFE
+  { "2f1",            3, MASK_2F1 },        // CUBEB_LAYOUT_2F1
+  { "2f1 lfe",        4, MASK_2F1_LFE },    // CUBEB_LAYOUT_2F1_LFE
+  { "3f1",            4, MASK_3F1 },        // CUBEB_LAYOUT_3F1
+  { "3f1 lfe",        5, MASK_3F1_LFE },    // CUBEB_LAYOUT_3F1_LFE
+  { "2f2",            4, MASK_2F2_LFE },    // CUBEB_LAYOUT_2F2
+  { "2f2 lfe",        5, MASK_2F2_LFE },    // CUBEB_LAYOUT_2F2_LFE
+  { "3f2",            5, MASK_3F2 },        // CUBEB_LAYOUT_3F2
+  { "3f2 lfe",        6, MASK_3F2_LFE },    // CUBEB_LAYOUT_3F2_LFE
+  { "3f3r lfe",       7, MASK_3F3R_LFE },   // CUBEB_LAYOUT_3F3R_LFE
+  { "3f4 lfe",        8, MASK_3F4_LFE }     // CUBEB_LAYOUT_3F4_LFE
+};
 
 // Prefered samplerate, in Hz (characteristic of the hardware, mixer, platform,
 // and API used).
 //
 // sMutex protects *initialization* of this, which must be performed from each
 // thread before fetching, after which it is safe to fetch without holding the
 // mutex because it is only written once per process execution (by the first
 // initialization to complete).  Since the init must have been called on a
@@ -259,52 +286,25 @@ bool InitPreferredChannelLayout()
 
   StaticMutexAutoLock lock(sMutex);
   sPreferredChannelLayout = layout;
   return true;
 }
 
 uint32_t PreferredChannelMap(uint32_t aChannels)
 {
-  // The first element of the following mapping table is channel counts,
-  // and the second one is its bit mask. It will be used in many times,
-  // so we shoule avoid to allocate it in stack, or it will be created
-  // and removed repeatedly. Use static to allocate this local variable
-  // in data space instead of stack.
-  static uint32_t layoutInfo[CUBEB_LAYOUT_MAX][2] = {
-    { 0, 0 },               // CUBEB_LAYOUT_UNDEFINED
-    { 2, MASK_STEREO },     // CUBEB_LAYOUT_DUAL_MONO
-    { 3, MASK_STEREO_LFE }, // CUBEB_LAYOUT_DUAL_MONO_LFE
-    { 1, MASK_MONO },       // CUBEB_LAYOUT_MONO
-    { 2, MASK_MONO_LFE },   // CUBEB_LAYOUT_MONO_LFE
-    { 2, MASK_STEREO },     // CUBEB_LAYOUT_STEREO
-    { 3, MASK_STEREO_LFE }, // CUBEB_LAYOUT_STEREO_LFE
-    { 3, MASK_3F },         // CUBEB_LAYOUT_3F
-    { 4, MASK_3F_LFE },     // CUBEB_LAYOUT_3F_LFE
-    { 3, MASK_2F1 },        // CUBEB_LAYOUT_2F1
-    { 4, MASK_2F1_LFE },    // CUBEB_LAYOUT_2F1_LFE
-    { 4, MASK_3F1 },        // CUBEB_LAYOUT_3F1
-    { 5, MASK_3F1_LFE },    // CUBEB_LAYOUT_3F1_LFE
-    { 4, MASK_2F2 },        // CUBEB_LAYOUT_2F2
-    { 5, MASK_2F2_LFE },    // CUBEB_LAYOUT_2F2_LFE
-    { 5, MASK_3F2 },        // CUBEB_LAYOUT_3F2
-    { 6, MASK_3F2_LFE },    // CUBEB_LAYOUT_3F2_LFE
-    { 7, MASK_3F3R_LFE },   // CUBEB_LAYOUT_3F3R_LFE
-    { 8, MASK_3F4_LFE },    // CUBEB_LAYOUT_3F4_LFE
-  };
-
   // Use SMPTE default channel map if we can't get preferred layout
   // or the channel counts of preferred layout is different from input's one
   if (!InitPreferredChannelLayout()
-      || layoutInfo[sPreferredChannelLayout][0] != aChannels) {
+      || kLayoutInfos[sPreferredChannelLayout].channels != aChannels) {
     AudioConfig::ChannelLayout smpteLayout(aChannels);
     return smpteLayout.Map();
   }
 
-  return layoutInfo[sPreferredChannelLayout][1];
+  return kLayoutInfos[sPreferredChannelLayout].mask;
 }
 
 void InitBrandName()
 {
   if (sBrandName) {
     return;
   }
   nsXPIDLString brandName;
@@ -522,10 +522,110 @@ void GetCurrentBackend(nsAString& aBacke
     if (backend) {
       aBackend.AssignASCII(backend);
       return;
     }
   }
   aBackend.AssignLiteral("unknown");
 }
 
+void GetPreferredChannelLayout(nsAString& aLayout)
+{
+  const char* layout = InitPreferredChannelLayout() ?
+    kLayoutInfos[sPreferredChannelLayout].name : "unknown";
+  aLayout.AssignASCII(layout);
+}
+
+uint16_t ConvertCubebType(cubeb_device_type aType)
+{
+  uint16_t map[] = {
+    nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
+    nsIAudioDeviceInfo::TYPE_INPUT,   // CUBEB_DEVICE_TYPE_INPUT,
+    nsIAudioDeviceInfo::TYPE_OUTPUT   // CUBEB_DEVICE_TYPE_OUTPUT
+  };
+  return map[aType];
+}
+
+uint16_t ConvertCubebState(cubeb_device_state aState)
+{
+  uint16_t map[] = {
+    nsIAudioDeviceInfo::STATE_DISABLED,   // CUBEB_DEVICE_STATE_DISABLED
+    nsIAudioDeviceInfo::STATE_UNPLUGGED,  // CUBEB_DEVICE_STATE_UNPLUGGED
+    nsIAudioDeviceInfo::STATE_ENABLED     // CUBEB_DEVICE_STATE_ENABLED
+  };
+  return map[aState];
+}
+
+uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred)
+{
+  if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
+    return nsIAudioDeviceInfo::PREF_NONE;
+  } else if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
+    return nsIAudioDeviceInfo::PREF_ALL;
+  }
+
+  uint16_t preferred = 0;
+  if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
+    preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
+  }
+  if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
+    preferred |= nsIAudioDeviceInfo::PREF_VOICE;
+  }
+  if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
+    preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
+  }
+  return preferred;
+}
+
+uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat)
+{
+  uint16_t format = 0;
+  if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
+    format |= nsIAudioDeviceInfo::FMT_S16LE;
+  }
+  if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
+    format |= nsIAudioDeviceInfo::FMT_S16BE;
+  }
+  if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
+    format |= nsIAudioDeviceInfo::FMT_F32LE;
+  }
+  if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
+    format |= nsIAudioDeviceInfo::FMT_F32BE;
+  }
+  return format;
+}
+
+void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
+                         Side aSide)
+{
+  cubeb* context = GetCubebContext();
+  if (context) {
+    cubeb_device_collection collection = { nullptr, 0 };
+    if (cubeb_enumerate_devices(context,
+                                aSide == Input ? CUBEB_DEVICE_TYPE_INPUT :
+                                                 CUBEB_DEVICE_TYPE_OUTPUT,
+                                &collection) == CUBEB_OK) {
+      for (unsigned int i = 0; i < collection.count; ++i) {
+        auto device = collection.device[i];
+        RefPtr<AudioDeviceInfo> info =
+          new AudioDeviceInfo(NS_ConvertASCIItoUTF16(device.friendly_name),
+                              NS_ConvertASCIItoUTF16(device.group_id),
+                              NS_ConvertASCIItoUTF16(device.vendor_name),
+                              ConvertCubebType(device.type),
+                              ConvertCubebState(device.state),
+                              ConvertCubebPreferred(device.preferred),
+                              ConvertCubebFormat(device.format),
+                              ConvertCubebFormat(device.default_format),
+                              device.max_channels,
+                              device.default_rate,
+                              device.max_rate,
+                              device.min_rate,
+                              device.latency_hi,
+                              device.latency_lo);
+        aDeviceInfos.AppendElement(info);
+      }
+    }
+    cubeb_device_collection_destroy(context, &collection);
+  }
+}
+
 } // namespace CubebUtils
 } // namespace mozilla
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #if !defined(CubebUtils_h_)
 #define CubebUtils_h_
 
 #include "cubeb/cubeb.h"
+#include "mozilla/dom/AudioDeviceInfo.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "mozilla/Maybe.h"
 
 namespace mozilla {
 namespace CubebUtils {
 
 typedef cubeb_devid AudioDeviceID;
 
@@ -28,28 +29,35 @@ void ShutdownLibrary();
 uint32_t MaxNumberOfChannels();
 
 // Get the sample rate the hardware/mixer runs at. Thread safe.
 uint32_t PreferredSampleRate();
 
 // Get the bit mask of the connected audio device's preferred layout.
 uint32_t PreferredChannelMap(uint32_t aChannels);
 
+enum Side {
+  Input,
+  Output
+};
+
 void PrefChanged(const char* aPref, void* aClosure);
 double GetVolumeScale();
 bool GetFirstStream();
 cubeb* GetCubebContext();
 cubeb* GetCubebContextUnlocked();
 void ReportCubebStreamInitFailure(bool aIsFirstStream);
 void ReportCubebBackendUsed();
 uint32_t GetCubebPlaybackLatencyInMilliseconds();
 Maybe<uint32_t> GetCubebMSGLatencyInFrames();
 bool CubebLatencyPrefSet();
 cubeb_channel_layout ConvertChannelMapToCubebLayout(uint32_t aChannelMap);
 #if defined(__ANDROID__) && defined(MOZ_B2G)
 cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel);
 #endif
 void GetCurrentBackend(nsAString& aBackend);
-
+void GetPreferredChannelLayout(nsAString& aLayout);
+void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
+                         Side aSide);
 } // namespace CubebUtils
 } // namespace mozilla
 
 #endif // CubebUtils_h_