summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/CubebDeviceEnumerator.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/webrtc/CubebDeviceEnumerator.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/CubebDeviceEnumerator.cpp')
-rw-r--r--dom/media/webrtc/CubebDeviceEnumerator.cpp392
1 files changed, 392 insertions, 0 deletions
diff --git a/dom/media/webrtc/CubebDeviceEnumerator.cpp b/dom/media/webrtc/CubebDeviceEnumerator.cpp
new file mode 100644
index 0000000000..f7f8ec46d8
--- /dev/null
+++ b/dom/media/webrtc/CubebDeviceEnumerator.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "CubebDeviceEnumerator.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+#ifdef XP_WIN
+# include "mozilla/mscom/EnsureMTA.h"
+#endif
+
+namespace mozilla {
+
+using namespace CubebUtils;
+
+/* static */
+static StaticRefPtr<CubebDeviceEnumerator> sInstance;
+static StaticMutex sInstanceMutex;
+
+/* static */
+CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (!sInstance) {
+ sInstance = new CubebDeviceEnumerator();
+ static bool clearOnShutdownSetup = []() -> bool {
+ auto setClearOnShutdown = []() -> void {
+ ClearOnShutdown(&sInstance, ShutdownPhase::ShutdownThreads);
+ };
+ if (NS_IsMainThread()) {
+ setClearOnShutdown();
+ } else {
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("CubebDeviceEnumerator::::GetInstance()",
+ std::move(setClearOnShutdown)));
+ }
+ return true;
+ }();
+ Unused << clearOnShutdownSetup;
+ }
+ return sInstance.get();
+}
+
+CubebDeviceEnumerator::CubebDeviceEnumerator()
+ : mMutex("CubebDeviceListMutex"),
+ mManualInputInvalidation(false),
+ mManualOutputInvalidation(false) {
+#ifdef XP_WIN
+ // Ensure the MTA thread exists and gets instantiated before the
+ // CubebDeviceEnumerator so that this instance will always gets destructed
+ // before the MTA thread gets shutdown.
+ mozilla::mscom::EnsureMTA();
+ mozilla::mscom::EnsureMTA([&]() -> void {
+#endif
+ int rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT,
+ &OutputAudioDeviceListChanged_s, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not register the audio output"
+ " device collection changed callback.");
+ mManualOutputInvalidation = true;
+ }
+ rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT,
+ &InputAudioDeviceListChanged_s, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not register the audio input"
+ " device collection changed callback.");
+ mManualInputInvalidation = true;
+ }
+#ifdef XP_WIN
+ });
+#endif
+}
+
+/* static */
+void CubebDeviceEnumerator::Shutdown() {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (sInstance) {
+ sInstance = nullptr;
+ }
+}
+
+CubebDeviceEnumerator::~CubebDeviceEnumerator() {
+#ifdef XP_WIN
+ mozilla::mscom::EnsureMTA([&]() -> void {
+#endif
+ int rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT, nullptr, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not unregister the audio output"
+ " device collection changed callback.");
+ }
+ 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.");
+ }
+#ifdef XP_WIN
+ });
+#endif
+}
+
+void CubebDeviceEnumerator::EnumerateAudioInputDevices(
+ nsTArray<RefPtr<AudioDeviceInfo>>& aOutDevices) {
+ MutexAutoLock lock(mMutex);
+ aOutDevices.Clear();
+ EnumerateAudioDevices(Side::INPUT);
+ aOutDevices.AppendElements(mInputDevices);
+}
+
+void CubebDeviceEnumerator::EnumerateAudioOutputDevices(
+ nsTArray<RefPtr<AudioDeviceInfo>>& aOutDevices) {
+ MutexAutoLock lock(mMutex);
+ aOutDevices.Clear();
+ EnumerateAudioDevices(Side::OUTPUT);
+ aOutDevices.AppendElements(mOutputDevices);
+}
+
+#ifndef ANDROID
+static 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];
+}
+
+static 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];
+}
+
+static uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) {
+ if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
+ return nsIAudioDeviceInfo::PREF_NONE;
+ }
+ 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;
+}
+
+static 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;
+}
+
+static void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
+ Side aSide) {
+ cubeb* context = GetCubebContext();
+ if (context) {
+ cubeb_device_collection collection = {nullptr, 0};
+# ifdef XP_WIN
+ mozilla::mscom::EnsureMTA([&]() -> void {
+# endif
+ 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(
+ device.devid, NS_ConvertUTF8toUTF16(device.friendly_name),
+ NS_ConvertUTF8toUTF16(device.group_id),
+ NS_ConvertUTF8toUTF16(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);
+# ifdef XP_WIN
+ });
+# endif
+ }
+}
+#endif // non ANDROID
+
+void CubebDeviceEnumerator::EnumerateAudioDevices(
+ CubebDeviceEnumerator::Side aSide) {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(aSide == Side::INPUT || aSide == Side::OUTPUT);
+
+ nsTArray<RefPtr<AudioDeviceInfo>> devices;
+ bool manualInvalidation = true;
+
+ if (aSide == Side::INPUT) {
+ devices = std::move(mInputDevices);
+ manualInvalidation = mManualInputInvalidation;
+ } else {
+ MOZ_ASSERT(aSide == Side::OUTPUT);
+ devices = std::move(mOutputDevices);
+ manualInvalidation = mManualOutputInvalidation;
+ }
+
+ cubeb* context = GetCubebContext();
+ if (!context) {
+ return;
+ }
+
+#ifdef ANDROID
+ cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
+ uint32_t channels = 0;
+ nsAutoString name;
+ if (aSide == Side::INPUT) {
+ type = CUBEB_DEVICE_TYPE_INPUT;
+ channels = 1;
+ name = u"Default audio input device"_ns;
+ } else {
+ MOZ_ASSERT(aSide == Side::OUTPUT);
+ type = CUBEB_DEVICE_TYPE_OUTPUT;
+ channels = 2;
+ name = u"Default audio output device"_ns;
+ }
+
+ if (devices.IsEmpty()) {
+ // Bug 1473346: enumerating devices is not supported on Android in cubeb,
+ // simply state that there is a single sink, that it is the default, and has
+ // a single channel. All the other values are made up and are not to be
+ // used.
+ // Bug 1660391: we can't use fluent here yet to get localized strings, so
+ // those are hard-coded en_US strings for now.
+ RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
+ nullptr, name, u""_ns, u""_ns, type, CUBEB_DEVICE_STATE_ENABLED,
+ CUBEB_DEVICE_PREF_ALL, CUBEB_DEVICE_FMT_ALL, CUBEB_DEVICE_FMT_S16NE,
+ channels, 44100, 44100, 44100, 441, 128);
+ devices.AppendElement(info);
+ }
+#else
+ if (devices.IsEmpty() || manualInvalidation) {
+ devices.Clear();
+
+ MutexAutoUnlock unlock(mMutex);
+ GetDeviceCollection(devices, (aSide == Side::INPUT) ? CubebUtils::Input
+ : CubebUtils::Output);
+ }
+#endif
+
+ if (aSide == Side::INPUT) {
+ mInputDevices.AppendElements(devices);
+ } else {
+ mOutputDevices.AppendElements(devices);
+ }
+}
+
+already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromID(
+ CubebUtils::AudioDeviceID aID) {
+ MutexAutoLock lock(mMutex);
+
+ if (mInputDevices.IsEmpty() || mManualInputInvalidation) {
+ EnumerateAudioDevices(Side::INPUT);
+ }
+ for (RefPtr<AudioDeviceInfo>& device : mInputDevices) {
+ if (device->DeviceID() == aID) {
+ RefPtr<AudioDeviceInfo> other = device;
+ return other.forget();
+ }
+ }
+
+ if (mOutputDevices.IsEmpty() || mManualOutputInvalidation) {
+ EnumerateAudioDevices(Side::OUTPUT);
+ }
+ for (RefPtr<AudioDeviceInfo>& device : mOutputDevices) {
+ if (device->DeviceID() == aID) {
+ RefPtr<AudioDeviceInfo> other = device;
+ return other.forget();
+ }
+ }
+ return nullptr;
+}
+
+already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
+ const nsString& aName) {
+ RefPtr<AudioDeviceInfo> other = DeviceInfoFromName(aName, Side::INPUT);
+ if (other) {
+ return other.forget();
+ }
+ return DeviceInfoFromName(aName, Side::OUTPUT);
+}
+
+already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
+ const nsString& aName, Side aSide) {
+ MutexAutoLock lock(mMutex);
+
+ nsTArray<RefPtr<AudioDeviceInfo>>& devices =
+ (aSide == Side::INPUT) ? mInputDevices : mOutputDevices;
+ bool manualInvalidation = (aSide == Side::INPUT) ? mManualInputInvalidation
+ : mManualOutputInvalidation;
+
+ if (devices.IsEmpty() || manualInvalidation) {
+ EnumerateAudioDevices(aSide);
+ }
+ for (RefPtr<AudioDeviceInfo>& device : devices) {
+ if (device->Name().Equals(aName)) {
+ RefPtr<AudioDeviceInfo> other = device;
+ return other.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+RefPtr<AudioDeviceInfo> CubebDeviceEnumerator::DefaultDevice(Side aSide) {
+ MutexAutoLock lock(mMutex);
+
+ nsTArray<RefPtr<AudioDeviceInfo>>& devices =
+ (aSide == Side::INPUT) ? mInputDevices : mOutputDevices;
+ bool manualInvalidation = (aSide == Side::INPUT) ? mManualInputInvalidation
+ : mManualOutputInvalidation;
+
+ if (devices.IsEmpty() || manualInvalidation) {
+ EnumerateAudioDevices(aSide);
+ }
+ for (RefPtr<AudioDeviceInfo>& device : devices) {
+ if (device->Preferred()) {
+ RefPtr<AudioDeviceInfo> other = device;
+ return other.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb* aContext,
+ void* aUser) {
+ CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
+ self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::INPUT);
+}
+
+void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb* aContext,
+ void* aUser) {
+ CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
+ self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::OUTPUT);
+}
+
+void CubebDeviceEnumerator::AudioDeviceListChanged(Side aSide) {
+ MutexAutoLock lock(mMutex);
+ if (aSide == Side::INPUT) {
+ mInputDevices.Clear();
+ mOnInputDeviceListChange.Notify();
+ } else {
+ MOZ_ASSERT(aSide == Side::OUTPUT);
+ mOutputDevices.Clear();
+ mOnOutputDeviceListChange.Notify();
+ }
+}
+
+} // namespace mozilla