summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2021-08-12 14:14:18 +0000
committerMoonchild <moonchild@palemoon.org>2021-08-12 14:14:18 +0000
commitb78c3a06ac581682b30aef2cd3051400ba8f76d6 (patch)
tree84249949ea79a4db724097c4924e986484a9733b
parentb0d0dd92f1f653e25b85cbcc559830b249d905d8 (diff)
downloaduxp-b78c3a06ac581682b30aef2cd3051400ba8f76d6.tar.gz
Issue mcp-graveyard/UXP#1806 - Update libcubeb
This is a full lib update, changing callers where needed. This also moves GertCubeMSGLatencyInFrames to CubebUtils.{cpp|h} where it belongs.
-rw-r--r--dom/media/CubebUtils.cpp31
-rw-r--r--dom/media/CubebUtils.h2
-rw-r--r--dom/media/GraphDriver.cpp10
-rw-r--r--layout/media/symbols.def.in1
-rw-r--r--media/libcubeb/AUTHORS1
-rw-r--r--media/libcubeb/README.md5
-rw-r--r--media/libcubeb/README_MCP8
-rw-r--r--media/libcubeb/README_MOZILLA8
-rw-r--r--media/libcubeb/bug1292803_pulse_assert.patch46
-rw-r--r--media/libcubeb/bug1302231_emergency_bailout.patch140
-rw-r--r--media/libcubeb/fix-crashes.patch71
-rw-r--r--media/libcubeb/gtest/common.h145
-rw-r--r--media/libcubeb/gtest/test_audio.cpp244
-rw-r--r--media/libcubeb/gtest/test_devices.cpp255
-rw-r--r--media/libcubeb/gtest/test_duplex.cpp315
-rw-r--r--media/libcubeb/gtest/test_latency.cpp47
-rw-r--r--media/libcubeb/gtest/test_loopback.cpp578
-rw-r--r--media/libcubeb/gtest/test_overload_callback.cpp92
-rw-r--r--media/libcubeb/gtest/test_record.cpp116
-rw-r--r--media/libcubeb/gtest/test_resampler.cpp1081
-rw-r--r--media/libcubeb/gtest/test_ring_array.cpp73
-rw-r--r--media/libcubeb/gtest/test_sanity.cpp (renamed from media/libcubeb/tests/test_sanity.cpp)548
-rw-r--r--media/libcubeb/gtest/test_tone.cpp (renamed from media/libcubeb/tests/test_tone.cpp)96
-rw-r--r--media/libcubeb/gtest/test_utils.cpp72
-rw-r--r--media/libcubeb/include/cubeb.h518
-rw-r--r--media/libcubeb/osx-linearize-operations.patch968
-rw-r--r--media/libcubeb/prevent-double-free.patch46
-rw-r--r--media/libcubeb/src/android/audiotrack_definitions.h81
-rw-r--r--media/libcubeb/src/android/cubeb-output-latency.h76
-rw-r--r--media/libcubeb/src/android/cubeb_media_library.h64
-rw-r--r--media/libcubeb/src/android/sles_definitions.h69
-rw-r--r--media/libcubeb/src/audiotrack_definitions.h72
-rw-r--r--media/libcubeb/src/cubeb-internal.h86
-rw-r--r--media/libcubeb/src/cubeb-jni.cpp80
-rw-r--r--media/libcubeb/src/cubeb-jni.h13
-rw-r--r--media/libcubeb/src/cubeb-sles.h26
-rw-r--r--media/libcubeb/src/cubeb.c477
-rw-r--r--media/libcubeb/src/cubeb_aaudio.cpp1504
-rw-r--r--media/libcubeb/src/cubeb_alsa.c694
-rw-r--r--media/libcubeb/src/cubeb_android.h17
-rw-r--r--media/libcubeb/src/cubeb_array_queue.h99
-rw-r--r--media/libcubeb/src/cubeb_assert.h27
-rw-r--r--media/libcubeb/src/cubeb_audiotrack.c438
-rw-r--r--media/libcubeb/src/cubeb_audiounit.cpp3642
-rw-r--r--media/libcubeb/src/cubeb_jack.cpp693
-rw-r--r--media/libcubeb/src/cubeb_log.cpp132
-rw-r--r--media/libcubeb/src/cubeb_log.h39
-rw-r--r--media/libcubeb/src/cubeb_mixer.cpp625
-rw-r--r--media/libcubeb/src/cubeb_mixer.h36
-rw-r--r--media/libcubeb/src/cubeb_opensl.c1775
-rw-r--r--media/libcubeb/src/cubeb_oss.c1329
-rw-r--r--media/libcubeb/src/cubeb_osx_run_loop.h23
-rw-r--r--media/libcubeb/src/cubeb_pulse.c1385
-rw-r--r--media/libcubeb/src/cubeb_resampler.cpp349
-rw-r--r--media/libcubeb/src/cubeb_resampler.h43
-rw-r--r--media/libcubeb/src/cubeb_resampler_internal.h348
-rw-r--r--media/libcubeb/src/cubeb_ring_array.h40
-rw-r--r--media/libcubeb/src/cubeb_ringbuffer.h468
-rw-r--r--media/libcubeb/src/cubeb_sndio.c556
-rw-r--r--media/libcubeb/src/cubeb_strings.c154
-rw-r--r--media/libcubeb/src/cubeb_strings.h47
-rw-r--r--media/libcubeb/src/cubeb_sun.c991
-rw-r--r--media/libcubeb/src/cubeb_utils.cpp25
-rw-r--r--media/libcubeb/src/cubeb_utils.h226
-rw-r--r--media/libcubeb/src/cubeb_utils_unix.h21
-rw-r--r--media/libcubeb/src/cubeb_utils_win.h23
-rw-r--r--media/libcubeb/src/cubeb_wasapi.cpp3112
-rw-r--r--media/libcubeb/src/cubeb_winmm.c1067
-rw-r--r--media/libcubeb/src/moz.build47
-rw-r--r--media/libcubeb/tests/common.h61
-rw-r--r--media/libcubeb/tests/test_audio.cpp294
-rw-r--r--media/libcubeb/tests/test_devices.cpp162
-rw-r--r--media/libcubeb/tests/test_duplex.cpp151
-rw-r--r--media/libcubeb/tests/test_latency.cpp60
-rw-r--r--media/libcubeb/tests/test_record.cpp125
-rw-r--r--media/libcubeb/tests/test_resampler.cpp554
-rw-r--r--media/libcubeb/tests/test_utils.cpp80
-rw-r--r--media/libcubeb/unresampled-frames.patch36
-rwxr-xr-xmedia/libcubeb/update.sh80
-rw-r--r--media/libcubeb/uplift-part-of-f07ee6d-esr52.patch167
-rw-r--r--media/libcubeb/uplift-patch-7a4c711.patch69
-rw-r--r--media/libcubeb/uplift-system-listener-patch.patch402
-rw-r--r--media/libcubeb/uplift-wasapi-part-to-beta.patch118
83 files changed, 17190 insertions, 11705 deletions
diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp
index aa611973db..5ec8363355 100644
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -50,11 +50,11 @@ enum class CubebState {
Shutdown
} sCubebState = CubebState::Uninitialized;
cubeb* sCubebContext;
-double sVolumeScale;
-uint32_t sCubebPlaybackLatencyInMilliseconds;
-uint32_t sCubebMSGLatencyInFrames;
-bool sCubebPlaybackLatencyPrefSet;
-bool sCubebMSGLatencyPrefSet;
+double sVolumeScale = 1.0;
+uint32_t sCubebPlaybackLatencyInMilliseconds = 100;
+uint32_t sCubebMSGLatencyInFrames = 512;
+bool sCubebPlaybackLatencyPrefSet = false;
+bool sCubebMSGLatencyPrefSet = false;
bool sAudioStreamInitEverSucceeded = false;
StaticAutoPtr<char> sBrandName;
@@ -227,7 +227,7 @@ cubeb* GetCubebContextUnlocked()
sBrandName, "Did not initialize sbrandName, and not on the main thread?");
}
- int rv = cubeb_init(&sCubebContext, sBrandName);
+ int rv = cubeb_init(&sCubebContext, sBrandName, nullptr);
NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized;
@@ -258,14 +258,23 @@ bool CubebMSGLatencyPrefSet()
return sCubebMSGLatencyPrefSet;
}
-Maybe<uint32_t> GetCubebMSGLatencyInFrames()
+uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params)
{
StaticMutexAutoLock lock(sMutex);
- if (!sCubebMSGLatencyPrefSet) {
- return Maybe<uint32_t>();
+ if (sCubebMSGLatencyPrefSet) {
+ MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
+ return sCubebMSGLatencyInFrames;
}
- MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
- return Some(sCubebMSGLatencyInFrames);
+ cubeb* context = GetCubebContextUnlocked();
+ if (!context) {
+ return sCubebMSGLatencyInFrames; // default 512
+ }
+ uint32_t latency_frames = 0;
+ if (cubeb_get_min_latency(context, params, &latency_frames) != CUBEB_OK) {
+ NS_WARNING("Could not get minimal latency from cubeb.");
+ return sCubebMSGLatencyInFrames; // default 512
+ }
+ return latency_frames;
}
void InitLibrary()
diff --git a/dom/media/CubebUtils.h b/dom/media/CubebUtils.h
index f434923747..8ebd1af214 100644
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -36,7 +36,7 @@ bool GetFirstStream();
cubeb* GetCubebContext();
cubeb* GetCubebContextUnlocked();
uint32_t GetCubebPlaybackLatencyInMilliseconds();
-Maybe<uint32_t> GetCubebMSGLatencyInFrames();
+uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params);
bool CubebLatencyPrefSet();
void GetCurrentBackend(nsAString& aBackend);
diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp
index 90680d8c69..70b59c61f1 100644
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -593,7 +593,6 @@ AudioCallbackDriver::Init()
cubeb_stream_params output;
cubeb_stream_params input;
- uint32_t latency_frames;
MOZ_ASSERT(!NS_IsMainThread(),
"This is blocking and should never run on the main thread.");
@@ -609,14 +608,7 @@ AudioCallbackDriver::Init()
output.format = CUBEB_SAMPLE_FLOAT32NE;
}
- Maybe<uint32_t> latencyPref = CubebUtils::GetCubebMSGLatencyInFrames();
- if (latencyPref) {
- latency_frames = latencyPref.value();
- } else {
- if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
- NS_WARNING("Could not get minimal latency from cubeb.");
- }
- }
+ uint32_t latency_frames = CubebUtils::GetCubebMSGLatencyInFrames(&output);
input = output;
input.channels = mInputChannels; // change to support optional stereo capture
diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in
index 6e2364f51a..0880bedc7d 100644
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -155,7 +155,6 @@ cubeb_stream_start
cubeb_stream_stop
cubeb_stream_get_latency
cubeb_stream_set_volume
-cubeb_stream_set_panning
cubeb_stream_get_current_device
cubeb_stream_device_destroy
cubeb_stream_register_device_changed_callback
diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS
index 0fde65baad..f0f9595227 100644
--- a/media/libcubeb/AUTHORS
+++ b/media/libcubeb/AUTHORS
@@ -13,3 +13,4 @@ Landry Breuil <landry@openbsd.org>
Jacek Caban <jacek@codeweavers.com>
Paul Hancock <Paul.Hancock.17041993@live.com>
Ted Mielczarek <ted@mielczarek.org>
+Chun-Min Chang <chun.m.chang@gmail.com>
diff --git a/media/libcubeb/README.md b/media/libcubeb/README.md
index d26b3b645b..92df4f22c2 100644
--- a/media/libcubeb/README.md
+++ b/media/libcubeb/README.md
@@ -1,6 +1,7 @@
-[![Build Status](https://travis-ci.org/kinetiknz/cubeb.svg?branch=master)](https://travis-ci.org/kinetiknz/cubeb)
-[![Build status](https://ci.appveyor.com/api/projects/status/osv2r0m1j1nt9csr/branch/master?svg=true)](https://ci.appveyor.com/project/kinetiknz/cubeb/branch/master)
+[![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml)
See INSTALL.md for build instructions.
+See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
+
Licensed under an ISC-style license. See LICENSE for details.
diff --git a/media/libcubeb/README_MCP b/media/libcubeb/README_MCP
new file mode 100644
index 0000000000..8fdebb0457
--- /dev/null
+++ b/media/libcubeb/README_MCP
@@ -0,0 +1,8 @@
+The source from this directory was copied from the cubeb
+git repository using the update.sh script. The only changes
+made were those applied by update.sh and the addition of
+moz.build build files for the Mozilla build system.
+
+The cubeb git repository is: https://github.com/mozilla/cubeb.git
+
+The git commit ID used was 6ce95962c8bb1442a725cbacdc77d5d8cbce43ad.
diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA
deleted file mode 100644
index c7f5e9c638..0000000000
--- a/media/libcubeb/README_MOZILLA
+++ /dev/null
@@ -1,8 +0,0 @@
-The source from this directory was copied from the cubeb
-git repository using the update.sh script. The only changes
-made were those applied by update.sh and the addition of
-Makefile.in build files for the Mozilla build system.
-
-The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
-
-The git commit ID used was f8467510a8b36793b1b8b7e85461e2e189eb7015.
diff --git a/media/libcubeb/bug1292803_pulse_assert.patch b/media/libcubeb/bug1292803_pulse_assert.patch
deleted file mode 100644
index 8dee88777b..0000000000
--- a/media/libcubeb/bug1292803_pulse_assert.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-commit 2c7617f5ca20b764c605e19af490889c761e65e2
-Author: Matthew Gregan <kinetik@flim.org>
-Date: Thu Nov 10 19:07:07 2016 +1300
-
- pulse: Bail early from pulse_defer_event_cb when shutting down.
-
- When starting a stream, trigger_user_callback may be called from
- stream_write_callback and immediately enter a drain situation, creating
- a drain timer and setting shutdown to true. If pulse_defer_event_cb
- then runs without checking for shutdown, it can overwrite the current
- timer with a new timer, resulting in a leaked timer and a null pointer
- assertion.
-
-diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c
-index 5b61bda..86f2ba3 100644
---- a/src/cubeb_pulse.c
-+++ b/src/cubeb_pulse.c
-@@ -181,9 +181,9 @@ static void
- stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
- {
- (void)a;
-- (void)e;
- (void)tv;
- cubeb_stream * stm = u;
-+ assert(stm->drain_timer == e);
- stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
- /* there's no pa_rttime_free, so use this instead. */
- a->time_free(stm->drain_timer);
-@@ -267,6 +267,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub
- assert(r == 0 || r == -PA_ERR_NODATA);
- /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
- /* arbitrary safety margin: double the current latency. */
-+ assert(!stm->drain_timer);
- stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
- stm->shutdown = 1;
- return;
-@@ -851,6 +852,9 @@ pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
- {
- (void)a;
- cubeb_stream * stm = userdata;
-+ if (stm->shutdown) {
-+ return;
-+ }
- size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
- trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
- }
diff --git a/media/libcubeb/bug1302231_emergency_bailout.patch b/media/libcubeb/bug1302231_emergency_bailout.patch
deleted file mode 100644
index 82152c23f6..0000000000
--- a/media/libcubeb/bug1302231_emergency_bailout.patch
+++ /dev/null
@@ -1,140 +0,0 @@
-From 37ce70d4400a2ab6b59ee432b41d4ffcc9d136ff Mon Sep 17 00:00:00 2001
-From: Paul Adenot <paul@paul.cx>
-Date: Thu, 10 Nov 2016 21:45:14 +0100
-Subject: [PATCH] Bail out safely from the rendering loop if we could not join
- the rendering thread in time (#187)
-
-Bail out safely from the rendering loop if we could not join the rendering thread in time.
----
- src/cubeb_wasapi.cpp | 41 ++++++++++++++++++++++++++++++++++++-----
- 1 file changed, 36 insertions(+), 5 deletions(-)
-
-diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
-index 9e689b9..519d5ca 100644
---- a/src/cubeb_wasapi.cpp
-+++ b/src/cubeb_wasapi.cpp
-@@ -22,6 +22,7 @@
- #include <algorithm>
- #include <memory>
- #include <limits>
-+#include <atomic>
-
- #include "cubeb/cubeb.h"
- #include "cubeb-internal.h"
-@@ -220,9 +221,11 @@ struct cubeb_stream
- float volume;
- /* True if the stream is draining. */
- bool draining;
-+ /* True when we've destroyed the stream. This pointer is leaked on stream
-+ * destruction if we could not join the thread. */
-+ std::atomic<std::atomic<bool>*> emergency_bailout;
- };
-
--
- class wasapi_endpoint_notification_client : public IMMNotificationClient
- {
- public:
-@@ -781,6 +784,7 @@ static unsigned int __stdcall
- wasapi_stream_render_loop(LPVOID stream)
- {
- cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
-+ std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
-
- bool is_playing = true;
- HANDLE wait_array[4] = {
-@@ -820,6 +824,10 @@ wasapi_stream_render_loop(LPVOID stream)
- wait_array,
- FALSE,
- 1000);
-+ if (*emergency_bailout) {
-+ delete emergency_bailout;
-+ return 0;
-+ }
- if (waitResult != WAIT_TIMEOUT) {
- timeout_count = 0;
- }
-@@ -1134,12 +1142,13 @@ int wasapi_init(cubeb ** context, char const * context_name)
- }
-
- namespace {
--void stop_and_join_render_thread(cubeb_stream * stm)
-+bool stop_and_join_render_thread(cubeb_stream * stm)
- {
-+ bool rv = true;
- LOG("Stop and join render thread.");
- if (!stm->thread) {
- LOG("No thread present.");
-- return;
-+ return true;
- }
-
- BOOL ok = SetEvent(stm->shutdown_event);
-@@ -1153,11 +1162,15 @@ void stop_and_join_render_thread(cubeb_stream * stm)
- if (r == WAIT_TIMEOUT) {
- /* Something weird happened, leak the thread and continue the shutdown
- * process. */
-+ *(stm->emergency_bailout) = true;
- LOG("Destroy WaitForSingleObject on thread timed out,"
- " leaking the thread: %d", GetLastError());
-+ rv = false;
- }
- if (r == WAIT_FAILED) {
-+ *(stm->emergency_bailout) = true;
- LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
-+ rv = false;
- }
-
- LOG("Closing thread.");
-@@ -1167,6 +1180,8 @@ void stop_and_join_render_thread(cubeb_stream * stm)
-
- CloseHandle(stm->shutdown_event);
- stm->shutdown_event = 0;
-+
-+ return rv;
- }
-
- void wasapi_destroy(cubeb * context)
-@@ -1775,7 +1790,16 @@ void wasapi_stream_destroy(cubeb_stream * stm)
- {
- XASSERT(stm);
-
-- stop_and_join_render_thread(stm);
-+ // Only free stm->emergency_bailout if we could not join the thread.
-+ // If we could not join the thread, stm->emergency_bailout is true
-+ // and is still alive until the thread wakes up and exits cleanly.
-+ if (stop_and_join_render_thread(stm)) {
-+ delete stm->emergency_bailout.load();
-+ stm->emergency_bailout = nullptr;
-+ } else {
-+ // If we're leaking, it must be that this is true.
-+ assert(*(stm->emergency_bailout));
-+ }
-
- unregister_notification_client(stm);
-
-@@ -1844,6 +1868,8 @@ int wasapi_stream_start(cubeb_stream * stm)
-
- auto_lock lock(stm->stream_reset_lock);
-
-+ stm->emergency_bailout = new std::atomic<bool>(false);
-+
- if (stm->output_client) {
- int rv = stream_start_one_side(stm, OUTPUT);
- if (rv != CUBEB_OK) {
-@@ -1903,7 +1929,12 @@ int wasapi_stream_stop(cubeb_stream * stm)
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- }
-
-- stop_and_join_render_thread(stm);
-+ if (stop_and_join_render_thread(stm)) {
-+ if (stm->emergency_bailout.load()) {
-+ delete stm->emergency_bailout.load();
-+ stm->emergency_bailout = nullptr;
-+ }
-+ }
-
- return CUBEB_OK;
- }
---
-2.7.4
-
diff --git a/media/libcubeb/fix-crashes.patch b/media/libcubeb/fix-crashes.patch
deleted file mode 100644
index b23501fcfb..0000000000
--- a/media/libcubeb/fix-crashes.patch
+++ /dev/null
@@ -1,71 +0,0 @@
-This patch fixes three different crashes, one crash per chunk in this patch,
-in the same order.
-- Bug 1342389
-- Bug 1345147
-- Bug 1347453
-
-diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
---- a/media/libcubeb/src/cubeb_wasapi.cpp
-+++ b/media/libcubeb/src/cubeb_wasapi.cpp
-@@ -878,16 +878,23 @@ wasapi_stream_render_loop(LPVOID stream)
-
- /* WaitForMultipleObjects timeout can trigger in cases where we don't want to
- treat it as a timeout, such as across a system sleep/wake cycle. Trigger
- the timeout error handling only when the timeout_limit is reached, which is
- reset on each successful loop. */
- unsigned timeout_count = 0;
- const unsigned timeout_limit = 5;
- while (is_playing) {
-+ // We want to check the emergency bailout variable before a
-+ // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects
-+ // is going to wait on might have been closed already.
-+ if (*emergency_bailout) {
-+ delete emergency_bailout;
-+ return 0;
-+ }
- DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
- wait_array,
- FALSE,
- 1000);
- if (*emergency_bailout) {
- delete emergency_bailout;
- return 0;
- }
-@@ -1199,16 +1206,22 @@ bool stop_and_join_render_thread(cubeb_s
- {
- bool rv = true;
- LOG("Stop and join render thread.");
- if (!stm->thread) {
- LOG("No thread present.");
- return true;
- }
-
-+ // If we've already leaked the thread, just return,
-+ // there is not much we can do.
-+ if (!stm->emergency_bailout.load()) {
-+ return false;
-+ }
-+
- BOOL ok = SetEvent(stm->shutdown_event);
- if (!ok) {
- LOG("Destroy SetEvent failed: %lx", GetLastError());
- }
-
- /* Wait five seconds for the rendering thread to return. It's supposed to
- * check its event loop very often, five seconds is rather conservative. */
- DWORD r = WaitForSingleObject(stm->thread, 5000);
-diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh
---- a/media/libcubeb/update.sh
-+++ b/media/libcubeb/update.sh
-@@ -66,8 +66,11 @@ fi
- echo "Applying a patch on top of $version"
- patch -p1 < ./wasapi-drift-fix-passthrough-resampler.patch
-
- echo "Applying a patch on top of $version"
- patch -p1 < ./audiounit-drift-fix.patch
-
- echo "Applying a patch on top of $version"
- patch -p1 < ./uplift-wasapi-fixes-aurora.patch
-+
-+echo "Applying a patch on top of $version"
-+patch -p3 < ./fix-crashes.patch
diff --git a/media/libcubeb/gtest/common.h b/media/libcubeb/gtest/common.h
new file mode 100644
index 0000000000..f085d8115f
--- /dev/null
+++ b/media/libcubeb/gtest/common.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(TEST_COMMON)
+#define TEST_COMMON
+
+#if defined( _WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <objbase.h>
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <cstdarg>
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+
+template<typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T(&)[N])
+{
+ return N;
+}
+
+void delay(unsigned int ms)
+{
+#if defined(_WIN32)
+ Sleep(ms);
+#else
+ sleep(ms / 1000);
+ usleep(ms % 1000 * 1000);
+#endif
+}
+
+#if !defined(M_PI)
+#define M_PI 3.14159265358979323846
+#endif
+
+typedef struct {
+ char const * name;
+ unsigned int const channels;
+ uint32_t const layout;
+} layout_info;
+
+int has_available_input_device(cubeb * ctx)
+{
+ cubeb_device_collection devices;
+ int input_device_available = 0;
+ int r;
+ /* Bail out early if the host does not have input devices. */
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "error enumerating devices.");
+ return 0;
+ }
+
+ if (devices.count == 0) {
+ fprintf(stderr, "no input device available, skipping test.\n");
+ cubeb_device_collection_destroy(ctx, &devices);
+ return 0;
+ }
+
+ for (uint32_t i = 0; i < devices.count; i++) {
+ input_device_available |= (devices.device[i].state ==
+ CUBEB_DEVICE_STATE_ENABLED);
+ }
+
+ if (!input_device_available) {
+ fprintf(stderr, "there are input devices, but they are not "
+ "available, skipping\n");
+ }
+
+ cubeb_device_collection_destroy(ctx, &devices);
+ return !!input_device_available;
+}
+
+void print_log(const char * msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vprintf(msg, args);
+ va_end(args);
+}
+
+/** Initialize cubeb with backend override.
+ * Create call cubeb_init passing value for CUBEB_BACKEND env var as
+ * override. */
+int common_init(cubeb ** ctx, char const * ctx_name)
+{
+#ifdef ENABLE_NORMAL_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set normal log callback failed\n");
+ }
+#endif
+
+#ifdef ENABLE_VERBOSE_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set verbose log callback failed\n");
+ }
+#endif
+
+ int r;
+ char const * backend;
+ char const * ctx_backend;
+
+ backend = getenv("CUBEB_BACKEND");
+ r = cubeb_init(ctx, ctx_name, backend);
+ if (r == CUBEB_OK && backend) {
+ ctx_backend = cubeb_get_backend_id(*ctx);
+ if (strcmp(backend, ctx_backend) != 0) {
+ fprintf(stderr, "Requested backend `%s', got `%s'\n",
+ backend, ctx_backend);
+ }
+ }
+
+ return r;
+}
+
+#if defined( _WIN32)
+class TestEnvironment : public ::testing::Environment {
+public:
+ void SetUp() override {
+ hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ }
+
+ void TearDown() override {
+ if (SUCCEEDED(hr)) {
+ CoUninitialize();
+ }
+ }
+
+private:
+ HRESULT hr;
+};
+
+::testing::Environment* const foo_env = ::testing::AddGlobalTestEnvironment(new TestEnvironment);
+#endif
+
+#endif /* TEST_COMMON */
diff --git a/media/libcubeb/gtest/test_audio.cpp b/media/libcubeb/gtest/test_audio.cpp
new file mode 100644
index 0000000000..ea1b999c9b
--- /dev/null
+++ b/media/libcubeb/gtest/test_audio.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function exhaustive test. Plays a series of tones in different
+ * conditions. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <string.h>
+#include "cubeb/cubeb.h"
+#include <string>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+using namespace std;
+
+#define MAX_NUM_CHANNELS 32
+
+#if !defined(M_PI)
+#define M_PI 3.14159265358979323846
+#endif
+
+#define VOLUME 0.2
+
+float get_frequency(int channel_index)
+{
+ return 220.0f * (channel_index+1);
+}
+
+template<typename T> T ConvertSample(double input);
+template<> float ConvertSample(double input) { return input; }
+template<> short ConvertSample(double input) { return short(input * 32767.0f); }
+
+/* store the phase of the generated waveform */
+struct synth_state {
+ synth_state(int num_channels_, float sample_rate_)
+ : num_channels(num_channels_),
+ sample_rate(sample_rate_)
+ {
+ for(int i=0;i < MAX_NUM_CHANNELS;++i)
+ phase[i] = 0.0f;
+ }
+
+ template<typename T>
+ void run(T* audiobuffer, long nframes)
+ {
+ for(int c=0;c < num_channels;++c) {
+ float freq = get_frequency(c);
+ float phase_inc = 2.0 * M_PI * freq / sample_rate;
+ for(long n=0;n < nframes;++n) {
+ audiobuffer[n*num_channels+c] = ConvertSample<T>(sin(phase[c]) * VOLUME);
+ phase[c] += phase_inc;
+ }
+ }
+ }
+
+private:
+ int num_channels;
+ float phase[MAX_NUM_CHANNELS];
+ float sample_rate;
+};
+
+template<typename T>
+long data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
+{
+ synth_state *synth = (synth_state *)user;
+ synth->run((T*)outputbuffer, nframes);
+ return nframes;
+}
+
+void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
+{
+}
+
+/* Our android backends don't support float, only int16. */
+int supports_float32(string backend_id)
+{
+ return backend_id != "opensl"
+ && backend_id != "audiotrack";
+}
+
+/* Some backends don't have code to deal with more than mono or stereo. */
+int supports_channel_count(string backend_id, int nchannels)
+{
+ return nchannels <= 2 ||
+ (backend_id != "opensl" && backend_id != "audiotrack");
+}
+
+int run_test(int num_channels, int sampling_rate, int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb *ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test: channels");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id)) ||
+ !supports_channel_count(backend_id, num_channels)) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = sampling_rate;
+ params.channels = num_channels;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream *stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(200);
+ cubeb_stream_stop(stream);
+
+ return r;
+}
+
+int run_volume_test(int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb *ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id))) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = 44100;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream *stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>,
+ state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ fprintf(stderr, "Testing: volume\n");
+ for(int i=0;i <= 4; ++i)
+ {
+ fprintf(stderr, "Volume: %d%%\n", i*25);
+
+ cubeb_stream_set_volume(stream, i/4.0f);
+ cubeb_stream_start(stream);
+ delay(400);
+ cubeb_stream_stop(stream);
+ delay(100);
+ }
+
+ return r;
+}
+
+TEST(cubeb, run_volume_test_short)
+{
+ ASSERT_EQ(run_volume_test(0), CUBEB_OK);
+}
+
+TEST(cubeb, run_volume_test_float)
+{
+ ASSERT_EQ(run_volume_test(1), CUBEB_OK);
+}
+
+TEST(cubeb, run_channel_rate_test)
+{
+ unsigned int channel_values[] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 6,
+ };
+
+ int freq_values[] = {
+ 16000,
+ 24000,
+ 44100,
+ 48000,
+ };
+
+ for(auto channels : channel_values) {
+ for(auto freq : freq_values) {
+ ASSERT_TRUE(channels < MAX_NUM_CHANNELS);
+ fprintf(stderr, "--------------------------\n");
+ ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
+ ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
+ }
+ }
+}
diff --git a/media/libcubeb/gtest/test_devices.cpp b/media/libcubeb/gtest/test_devices.cpp
new file mode 100644
index 0000000000..e9b34b3245
--- /dev/null
+++ b/media/libcubeb/gtest/test_devices.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb enumerate device test/example.
+ * Prints out a list of devices enumerated. */
+#include "gtest/gtest.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ // noop, unused
+ return 0;
+}
+
+void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ // noop, unused
+}
+
+static void
+print_device_info(cubeb_device_info * info, FILE * f)
+{
+ char devfmts[64] = "";
+ const char * devtype, * devstate, * devdeffmt;
+
+ switch (info->type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ devtype = "input";
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ devtype = "output";
+ break;
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ default:
+ devtype = "unknown?";
+ break;
+ };
+
+ switch (info->state) {
+ case CUBEB_DEVICE_STATE_DISABLED:
+ devstate = "disabled";
+ break;
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ devstate = "unplugged";
+ break;
+ case CUBEB_DEVICE_STATE_ENABLED:
+ devstate = "enabled";
+ break;
+ default:
+ devstate = "unknown?";
+ break;
+ };
+
+ switch (info->default_format) {
+ case CUBEB_DEVICE_FMT_S16LE:
+ devdeffmt = "S16LE";
+ break;
+ case CUBEB_DEVICE_FMT_S16BE:
+ devdeffmt = "S16BE";
+ break;
+ case CUBEB_DEVICE_FMT_F32LE:
+ devdeffmt = "F32LE";
+ break;
+ case CUBEB_DEVICE_FMT_F32BE:
+ devdeffmt = "F32BE";
+ break;
+ default:
+ devdeffmt = "unknown?";
+ break;
+ };
+
+ if (info->format & CUBEB_DEVICE_FMT_S16LE)
+ strcat(devfmts, " S16LE");
+ if (info->format & CUBEB_DEVICE_FMT_S16BE)
+ strcat(devfmts, " S16BE");
+ if (info->format & CUBEB_DEVICE_FMT_F32LE)
+ strcat(devfmts, " F32LE");
+ if (info->format & CUBEB_DEVICE_FMT_F32BE)
+ strcat(devfmts, " F32BE");
+
+ fprintf(f,
+ "dev: \"%s\"%s\n"
+ "\tName: \"%s\"\n"
+ "\tGroup: \"%s\"\n"
+ "\tVendor: \"%s\"\n"
+ "\tType: %s\n"
+ "\tState: %s\n"
+ "\tCh: %u\n"
+ "\tFormat: %s (0x%x) (default: %s)\n"
+ "\tRate: %u - %u (default: %u)\n"
+ "\tLatency: lo %u frames, hi %u frames\n",
+ info->device_id, info->preferred ? " (PREFERRED)" : "",
+ info->friendly_name, info->group_id, info->vendor_name,
+ devtype, devstate, info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)info->format, devdeffmt,
+ info->min_rate, info->max_rate, info->default_rate,
+ info->latency_lo, info->latency_hi);
+}
+
+static void
+print_device_collection(cubeb_device_collection * collection, FILE * f)
+{
+ uint32_t i;
+
+ for (i = 0; i < collection->count; i++)
+ print_device_info(&collection->device[i], f);
+}
+
+TEST(cubeb, destroy_default_collection)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection{ nullptr, 0 };
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t) 0);
+
+ r = cubeb_device_collection_destroy(ctx, &collection);
+ if (r != CUBEB_ERROR_NOT_SUPPORTED) {
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t) 0);
+ }
+}
+
+TEST(cubeb, enumerate_devices)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ fprintf(stdout, "Enumerating input devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu input devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ fprintf(stdout, "Enumerating output devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu output devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ uint32_t count_before_creating_duplex_stream;
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ count_before_creating_duplex_stream = collection.count;
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ 1024, data_cb_duplex, state_cb_duplex, nullptr);
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ ASSERT_EQ(count_before_creating_duplex_stream, collection.count);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ cubeb_stream_destroy(stream);
+}
+
+TEST(cubeb, stream_get_current_device)
+{
+ cubeb * ctx = NULL;
+ int r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ fprintf(stdout, "Getting current devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ cubeb_stream * stream = NULL;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ 1024, data_cb_duplex, state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_device * device;
+ r = cubeb_stream_get_current_device(stream, &device);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Getting current device is not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error getting current devices";
+
+ fprintf(stdout, "Current output device: %s\n", device->output_name);
+ fprintf(stdout, "Current input device: %s\n", device->input_name);
+
+ r = cubeb_stream_device_destroy(stream, device);
+ ASSERT_EQ(r, CUBEB_OK) << "Error destroying current devices";
+} \ No newline at end of file
diff --git a/media/libcubeb/gtest/test_duplex.cpp b/media/libcubeb/gtest/test_duplex.cpp
new file mode 100644
index 0000000000..3620bf0666
--- /dev/null
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Loops input back to output and check audio
+ * is flowing. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
+
+struct user_state_duplex
+{
+ std::atomic<int> invalid_audio_value{ 0 };
+};
+
+long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
+ float *ib = (float *)inputbuffer;
+ float *ob = (float *)outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ // Loop back: upmix the single input channel to the two output channels,
+ // checking if there is noise in the process.
+ long output_index = 0;
+ for (long i = 0; i < nframes; i++) {
+ if (ib[i] <= -1.0 || ib[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ ob[output_index] = ob[output_index + 1] = ib[i];
+ output_index += 2;
+ }
+
+ return nframes;
+}
+
+void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, duplex)
+{
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!has_available_input_device(ctx)) {
+ return;
+ }
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+}
+
+void device_collection_changed_callback(cubeb * context, void * user)
+{
+ fprintf(stderr, "collection changed callback\n");
+ ASSERT_TRUE(false) << "Error: device collection changed callback"
+ " called when opening a stream";
+}
+
+TEST(cubeb, duplex_collection_change)
+{
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example with collection change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ r = cubeb_register_device_collection_changed(ctx,
+ static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
+ device_collection_changed_callback,
+ nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ cubeb_stream_destroy(stream);
+}
+
+long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ return nframes;
+}
+
+void state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream runs into error state\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+std::vector<cubeb_devid> get_devices(cubeb * ctx, cubeb_device_type type) {
+ std::vector<cubeb_devid> devices;
+
+ cubeb_device_collection collection;
+ int r = cubeb_enumerate_devices(ctx, type, &collection);
+
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Failed to enumerate devices\n");
+ return devices;
+ }
+
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) {
+ devices.emplace_back(collection.device[i].devid);
+ }
+ }
+
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ return devices;
+}
+
+TEST(cubeb, one_duplex_one_input)
+{
+ cubeb *ctx;
+ cubeb_stream *duplex_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex duplex_stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs at least two available input devices. */
+ std::vector<cubeb_devid> input_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ if (input_devices.size() < 2) {
+ return;
+ }
+
+ /* This test needs at least one available output device. */
+ std::vector<cubeb_devid> output_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_devices.size() < 1) {
+ return;
+ }
+
+ cubeb_devid duplex_input = input_devices.front();
+ cubeb_devid duplex_output = nullptr; // default device
+ cubeb_devid input_only = input_devices.back();
+
+ /* typical use-case: mono voice input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_VOICE;
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex",
+ duplex_input, &input_params, duplex_output, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, &duplex_stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream";
+ delay(500);
+
+ cubeb_stream *input_stream;
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb input",
+ input_only, &input_params, NULL, NULL,
+ latency_frames, data_cb_input, state_cb_input, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream";
+ delay(500);
+
+ r = cubeb_stream_stop(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream";
+
+ r = cubeb_stream_stop(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream";
+
+ ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load());
+}
diff --git a/media/libcubeb/gtest/test_latency.cpp b/media/libcubeb/gtest/test_latency.cpp
new file mode 100644
index 0000000000..522851044a
--- /dev/null
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -0,0 +1,47 @@
+#include "gtest/gtest.h"
+#include <stdlib.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+TEST(cubeb, latency)
+{
+ cubeb * ctx = NULL;
+ int r;
+ uint32_t max_channels;
+ uint32_t preferred_rate;
+ uint32_t latency_frames;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ r = cubeb_get_max_channel_count(ctx, &max_channels);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(max_channels, 0u);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(preferred_rate, 0u);
+ }
+
+ cubeb_stream_params params = {
+ CUBEB_SAMPLE_FLOAT32NE,
+ preferred_rate,
+ max_channels,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE
+ };
+ r = cubeb_get_min_latency(ctx, &params, &latency_frames);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(latency_frames, 0u);
+ }
+}
diff --git a/media/libcubeb/gtest/test_loopback.cpp b/media/libcubeb/gtest/test_loopback.cpp
new file mode 100644
index 0000000000..9977f6f934
--- /dev/null
+++ b/media/libcubeb/gtest/test_loopback.cpp
@@ -0,0 +1,578 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+ /* libcubeb api/function test. Requests a loopback device and checks that
+ output is being looped back to input. NOTE: Usage of output devices while
+ performing this test will cause flakey results! */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const uint32_t TONE_FREQUENCY = 440;
+const double OUTPUT_AMPLITUDE = 0.25;
+const int32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
+
+template<typename T> T ConvertSampleToOutput(double input);
+template<> float ConvertSampleToOutput(double input) { return float(input); }
+template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
+
+template<typename T> double ConvertSampleFromOutput(T sample);
+template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
+template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
+
+/* Simple cross correlation to help find phase shift. Not a performant impl */
+std::vector<double> cross_correlate(std::vector<double> & f,
+ std::vector<double> & g,
+ size_t signal_length)
+{
+ /* the length we sweep our window through to find the cross correlation */
+ size_t sweep_length = f.size() - signal_length + 1;
+ std::vector<double> correlation;
+ correlation.reserve(sweep_length);
+ for (size_t i = 0; i < sweep_length; i++) {
+ double accumulator = 0.0;
+ for (size_t j = 0; j < signal_length; j++) {
+ accumulator += f.at(j) * g.at(i + j);
+ }
+ correlation.push_back(accumulator);
+ }
+ return correlation;
+}
+
+/* best effort discovery of phase shift between output and (looped) input*/
+size_t find_phase(std::vector<double> & output_frames,
+ std::vector<double> & input_frames,
+ size_t signal_length)
+{
+ std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
+ size_t phase = 0;
+ double max_correlation = correlation.at(0);
+ for (size_t i = 1; i < correlation.size(); i++) {
+ if (correlation.at(i) > max_correlation) {
+ max_correlation = correlation.at(i);
+ phase = i;
+ }
+ }
+ return phase;
+}
+
+std::vector<double> normalize_frames(std::vector<double> & frames) {
+ double max = abs(*std::max_element(frames.begin(), frames.end(),
+ [](double a, double b) { return abs(a) < abs(b); }));
+ std::vector<double> normalized_frames;
+ normalized_frames.reserve(frames.size());
+ for (const double frame : frames) {
+ normalized_frames.push_back(frame / max);
+ }
+ return normalized_frames;
+}
+
+/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
+void compare_signals(std::vector<double> & output_frames,
+ std::vector<double> & input_frames)
+{
+ ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
+ size_t num_frames = output_frames.size();
+ std::vector<double> normalized_output_frames = normalize_frames(output_frames);
+ std::vector<double> normalized_input_frames = normalize_frames(input_frames);
+
+ /* calculate mean absolute errors */
+ /* mean absolute errors between output and input */
+ double io_mas = 0.0;
+ /* mean absolute errors between output and silence */
+ double output_silence_mas = 0.0;
+ /* mean absolute errors between input and silence */
+ double input_silence_mas = 0.0;
+ for (size_t i = 0; i < num_frames; i++) {
+ io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
+ output_silence_mas += abs(normalized_output_frames.at(i));
+ input_silence_mas += abs(normalized_input_frames.at(i));
+ }
+ io_mas /= num_frames;
+ output_silence_mas /= num_frames;
+ input_silence_mas /= num_frames;
+
+ ASSERT_LT(io_mas, output_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+ ASSERT_LT(io_mas, input_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+
+ /* make sure extrema are in (roughly) correct location */
+ /* number of maxima + minama expected in the frames*/
+ const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
+ /* expected index of first maxima */
+ const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
+ /* Threshold we expect all maxima and minima to be above or below. Ideally
+ the extrema would be 1 or -1, but particularly at the start of loopback
+ the values seen can be significantly lower. */
+ const double THRESHOLD = 0.5;
+
+ for (size_t i = 0; i < NUM_EXTREMA; i++) {
+ bool is_maximum = i % 2 == 0;
+ /* expected offset to current extreme: i * stide between extrema */
+ size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
+ if (is_maximum) {
+ ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Output frames have unexpected missing maximum!";
+ ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Input frames have unexpected missing maximum!";
+ } else {
+ ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Output frames have unexpected missing minimum!";
+ ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Input frames have unexpected missing minimum!";
+ }
+ }
+}
+
+struct user_state_loopback {
+ std::mutex user_state_mutex;
+ long position = 0;
+ /* track output */
+ std::vector<double> output_frames;
+ /* track input */
+ std::vector<double> input_frames;
+};
+
+template<typename T>
+long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ /* store any looped back output, may be silence */
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+
+ if (outputbuffer != NULL) {
+ // Can't assert as it needs to return, so expect to fail instead
+ EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
+ return CUBEB_ERROR;
+ }
+
+ if (stream == NULL || inputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ for (int i = 0; i < nframes; i++) {
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+void run_loopback_duplex_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: duplex stream");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup a duplex stream with loopback */
+ r = cubeb_stream_init(ctx, &stream, "Cubeb loopback",
+ NULL, &input_params, NULL, &output_params, latency_frames,
+ is_float ? data_cb_loop_duplex<float> : data_cb_loop_duplex<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(300);
+ cubeb_stream_stop(stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_EQ(output_frames.size(), input_frames.size())
+ << "#Output frames != #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_duplex)
+{
+ run_loopback_duplex_test(true);
+ run_loopback_duplex_test(false);
+}
+
+void run_loopback_separate_streams_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, NULL, &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_separate_streams)
+{
+ run_loopback_separate_streams_test(true);
+ run_loopback_separate_streams_test(false);
+}
+
+void run_loopback_silence_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream_params input_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: record silence");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ delay(300);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & input_frames = user_data->input_frames;
+
+ /* expect to have at least ~50ms of frames */
+ ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
+ double EPISILON = 0.0001;
+ /* frames should be 0.0, but use epsilon to avoid possible issues with impls
+ that may use ~0.0 silence values. */
+ for (double frame : input_frames) {
+ ASSERT_LT(abs(frame), EPISILON);
+ }
+}
+
+TEST(cubeb, loopback_silence)
+{
+ run_loopback_silence_test(true);
+ run_loopback_silence_test(false);
+}
+
+void run_loopback_device_selection_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_device_collection collection;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: device selection, separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ /* get first preferred output device id */
+ std::string device_id;
+ for (size_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].preferred) {
+ device_id = collection.device[i].device_id;
+ break;
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ if (device_id.empty()) {
+ fprintf(stderr, "Could not find preferred device, aborting test.\n");
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ device_id.c_str(), &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, device_id.c_str(), &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_device_selection)
+{
+ run_loopback_device_selection_test(true);
+ run_loopback_device_selection_test(false);
+}
diff --git a/media/libcubeb/gtest/test_overload_callback.cpp b/media/libcubeb/gtest/test_overload_callback.cpp
new file mode 100644
index 0000000000..4a19ce9f13
--- /dev/null
+++ b/media/libcubeb/gtest/test_overload_callback.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <atomic>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+std::atomic<bool> load_callback{ false };
+
+long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ if (load_callback) {
+ fprintf(stderr, "Sleeping...\n");
+ delay(100000);
+ fprintf(stderr, "Sleeping done\n");
+ }
+ return nframes;
+}
+
+void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ ASSERT_TRUE(!!stream);
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ FAIL() << "this test is not supposed to drain"; break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream error\n"); break;
+ default:
+ FAIL() << "this test is not supposed to have a weird state"; break;
+ }
+}
+
+TEST(cubeb, overload_callback)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb callback overload");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb",
+ NULL, NULL, NULL, &output_params,
+ latency_frames, data_cb, state_cb, NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ // This causes the callback to sleep for a large number of seconds.
+ load_callback = true;
+ delay(500);
+ cubeb_stream_stop(stream);
+}
diff --git a/media/libcubeb/gtest/test_record.cpp b/media/libcubeb/gtest/test_record.cpp
new file mode 100644
index 0000000000..ed40a2c27d
--- /dev/null
+++ b/media/libcubeb/gtest/test_record.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Record the mic and check there is sound. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+
+struct user_state_record
+{
+ std::atomic<int> invalid_audio_value{ 0 };
+};
+
+long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_record * u = reinterpret_cast<user_state_record*>(user);
+ float *b = (float *)inputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ for (long i = 0; i < nframes; i++) {
+ if (b[i] <= -1.0 || b[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ }
+
+ return nframes;
+}
+
+void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, record)
+{
+ if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != CUBEB_OK) {
+ fprintf(stderr, "Set log callback failed\n");
+ }
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params params;
+ int r;
+ user_state_record stream_state;
+
+ r = common_init(&ctx, "Cubeb record example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!has_available_input_device(ctx)) {
+ return;
+ }
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
+ 4096, data_cb_record, state_cb_record, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+#ifdef __linux__
+ // user callback does not arrive in Linux, silence the error
+ fprintf(stderr, "Check is disabled in Linux\n");
+#else
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+#endif
+}
diff --git a/media/libcubeb/gtest/test_resampler.cpp b/media/libcubeb/gtest/test_resampler.cpp
new file mode 100644
index 0000000000..8ac878fc3d
--- /dev/null
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -0,0 +1,1081 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+#include "gtest/gtest.h"
+#include "common.h"
+#include "cubeb_resampler_internal.h"
+#include <stdio.h>
+#include <algorithm>
+#include <iostream>
+
+/* Windows cmath USE_MATH_DEFINE thing... */
+const float PI = 3.14159265359f;
+
+/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
+ * only part of the test suite is ran. */
+#ifdef THOROUGH_TESTING
+/* Some standard sample rates we're testing with. */
+const uint32_t sample_rates[] = {
+ 8000,
+ 16000,
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 192000
+};
+/* The maximum number of channels we're resampling. */
+const uint32_t max_channels = 2;
+/* The minimum an maximum number of milliseconds we're resampling for. This is
+ * used to simulate the fact that the audio stream is resampled in chunks,
+ * because audio is delivered using callbacks. */
+const uint32_t min_chunks = 10; /* ms */
+const uint32_t max_chunks = 30; /* ms */
+const uint32_t chunk_increment = 1;
+
+#else
+
+const uint32_t sample_rates[] = {
+ 8000,
+ 44100,
+ 48000,
+};
+const uint32_t max_channels = 2;
+const uint32_t min_chunks = 10; /* ms */
+const uint32_t max_chunks = 30; /* ms */
+const uint32_t chunk_increment = 10;
+#endif
+
+#define DUMP_ARRAYS
+#ifdef DUMP_ARRAYS
+/**
+ * Files produced by dump(...) can be converted to .wave files using:
+ *
+ * sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav
+ *
+ * for floating-point audio, or:
+ *
+ * sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav
+ *
+ * for 16bit integer audio.
+ */
+
+/* Use the correct implementation of fopen, depending on the platform. */
+void fopen_portable(FILE ** f, const char * name, const char * mode)
+{
+#ifdef WIN32
+ fopen_s(f, name, mode);
+#else
+ *f = fopen(name, mode);
+#endif
+}
+
+template<typename T>
+void dump(const char * name, T * frames, size_t count)
+{
+ FILE * file;
+ fopen_portable(&file, name, "wb");
+
+ if (!file) {
+ fprintf(stderr, "error opening %s\n", name);
+ return;
+ }
+
+ if (count != fwrite(frames, sizeof(T), count, file)) {
+ fprintf(stderr, "error writing to %s\n", name);
+ }
+ fclose(file);
+}
+#else
+template<typename T>
+void dump(const char * name, T * frames, size_t count)
+{ }
+#endif
+
+// The more the ratio is far from 1, the more we accept a big error.
+float epsilon_tweak_ratio(float ratio)
+{
+ return ratio >= 1 ? ratio : 1 / ratio;
+}
+
+// Epsilon values for comparing resampled data to expected data.
+// The bigger the resampling ratio is, the more lax we are about errors.
+template<typename T>
+T epsilon(float ratio);
+
+template<>
+float epsilon(float ratio) {
+ return 0.08f * epsilon_tweak_ratio(ratio);
+}
+
+template<>
+int16_t epsilon(float ratio) {
+ return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
+}
+
+void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
+{
+ const size_t length_s = 2;
+ const size_t rate = 44100;
+ const size_t length_frames = rate * length_s;
+ delay_line<float> delay(delay_frames, channels, rate);
+ auto_array<float> input;
+ auto_array<float> output;
+ uint32_t chunk_length = channels * chunk_ms * rate / 1000;
+ uint32_t output_offset = 0;
+ uint32_t channel = 0;
+
+ /** Generate diracs every 100 frames, and check they are delayed. */
+ input.push_silence(length_frames * channels);
+ for (uint32_t i = 0; i < input.length() - 1; i+=100) {
+ input.data()[i + channel] = 0.5;
+ channel = (channel + 1) % channels;
+ }
+ dump("input.raw", input.data(), input.length());
+ while(input.length()) {
+ uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels);
+ float * in = delay.input_buffer(to_pop / channels);
+ input.pop(in, to_pop);
+ delay.written(to_pop / channels);
+ output.push_silence(to_pop);
+ delay.output(output.data() + output_offset, to_pop / channels);
+ output_offset += to_pop;
+ }
+
+ // Check the diracs have been shifted by `delay_frames` frames.
+ for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) {
+ ASSERT_EQ(output.data()[i + channel + delay_frames * channels], 0.5);
+ channel = (channel + 1) % channels;
+ }
+
+ dump("output.raw", output.data(), output.length());
+}
+/**
+ * This takes sine waves with a certain `channels` count, `source_rate`, and
+ * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
+ * Then a sample-wise comparison is performed against a sine wave generated at
+ * the correct rate.
+ */
+template<typename T>
+void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration)
+{
+ size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
+ float resampling_ratio = static_cast<float>(source_rate) / target_rate;
+ cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3);
+ auto_array<T> source(channels * source_rate * 10);
+ auto_array<T> destination(channels * target_rate * 10);
+ auto_array<T> expected(channels * target_rate * 10);
+ uint32_t phase_index = 0;
+ uint32_t offset = 0;
+ const uint32_t buf_len = 2; /* seconds */
+
+ // generate a sine wave in each channel, at the source sample rate
+ source.push_silence(channels * source_rate * buf_len);
+ while(offset != source.length()) {
+ float p = phase_index++ / static_cast<float>(source_rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+
+ dump("input.raw", source.data(), source.length());
+
+ expected.push_silence(channels * target_rate * buf_len);
+ // generate a sine wave in each channel, at the target sample rate.
+ // Insert silent samples at the beginning to account for the resampler latency.
+ offset = resampler.latency() * channels;
+ for (uint32_t i = 0; i < offset; i++) {
+ expected.data()[i] = 0.0f;
+ }
+ phase_index = 0;
+ while (offset != expected.length()) {
+ float p = phase_index++ / static_cast<float>(target_rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+
+ dump("expected.raw", expected.data(), expected.length());
+
+ // resample by chunk
+ uint32_t write_offset = 0;
+ destination.push_silence(channels * target_rate * buf_len);
+ while (write_offset < destination.length())
+ {
+ size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio));
+ uint32_t input_frames = resampler.input_needed_for_output(output_frames);
+ resampler.input(source.data(), input_frames);
+ source.pop(nullptr, input_frames * channels);
+ resampler.output(destination.data() + write_offset,
+ std::min(output_frames, (destination.length() - write_offset) / channels));
+ write_offset += output_frames * channels;
+ }
+
+ dump("output.raw", destination.data(), expected.length());
+
+ // compare, taking the latency into account
+ bool fuzzy_equal = true;
+ for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
+ float diff = fabs(expected.data()[i] - destination.data()[i]);
+ if (diff > epsilon<T>(resampling_ratio)) {
+ fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff);
+ fuzzy_equal = false;
+ }
+ }
+ ASSERT_TRUE(fuzzy_equal);
+}
+
+template<typename T>
+cubeb_sample_format cubeb_format();
+
+template<>
+cubeb_sample_format cubeb_format<float>()
+{
+ return CUBEB_SAMPLE_FLOAT32NE;
+}
+
+template<>
+cubeb_sample_format cubeb_format<short>()
+{
+ return CUBEB_SAMPLE_S16NE;
+}
+
+struct osc_state {
+ osc_state()
+ : input_phase_index(0)
+ , output_phase_index(0)
+ , output_offset(0)
+ , input_channels(0)
+ , output_channels(0)
+ {}
+ uint32_t input_phase_index;
+ uint32_t max_output_phase_index;
+ uint32_t output_phase_index;
+ uint32_t output_offset;
+ uint32_t input_channels;
+ uint32_t output_channels;
+ uint32_t output_rate;
+ uint32_t target_rate;
+ auto_array<float> input;
+ auto_array<float> output;
+};
+
+uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels,
+ uint32_t frames, uint32_t initial_phase)
+{
+ uint32_t offset = 0;
+ for (uint32_t i = 0; i < frames; i++) {
+ float p = initial_phase++ / static_cast<float>(rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+ return initial_phase;
+}
+
+long data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer, void * output_buffer, long frame_count)
+{
+ osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
+ const float * in = reinterpret_cast<const float*>(input_buffer);
+ float * out = reinterpret_cast<float*>(output_buffer);
+
+ state->input.push(in, frame_count * state->input_channels);
+
+ /* Check how much output frames we need to write */
+ uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
+ uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
+ state->output_phase_index = fill_with_sine(out,
+ state->target_rate,
+ state->output_channels,
+ to_write,
+ state->output_phase_index);
+
+ return to_write;
+}
+
+template<typename T>
+bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi)
+{
+ uint32_t len = std::min(lhs.length(), rhs.length());
+
+ for (uint32_t i = 0; i < len; i++) {
+ if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
+ std::cout << "not fuzzy equal at index: " << i
+ << " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
+ << " delta: " << fabs(lhs.at(i) - rhs.at(i))
+ << " epsilon: "<< epsi << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+template<typename T>
+void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
+ uint32_t input_rate, uint32_t output_rate,
+ uint32_t target_rate, float chunk_duration)
+{
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ osc_state state;
+
+ input_params.format = output_params.format = cubeb_format<T>();
+ state.input_channels = input_params.channels = input_channels;
+ state.output_channels = output_params.channels = output_channels;
+ input_params.rate = input_rate;
+ state.output_rate = output_params.rate = output_rate;
+ state.target_rate = target_rate;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+ long got;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
+ data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ long latency = cubeb_resampler_latency(resampler);
+
+ const uint32_t duration_s = 2;
+ int32_t duration_frames = duration_s * target_rate;
+ uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2;
+ uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
+ auto_array<float> input_buffer(input_channels * input_array_frame_count);
+ auto_array<float> output_buffer(output_channels * output_array_frame_count);
+ auto_array<float> expected_resampled_input(input_channels * duration_frames);
+ auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s);
+
+ state.max_output_phase_index = duration_s * target_rate;
+
+ expected_resampled_input.push_silence(input_channels * duration_frames);
+ expected_resampled_output.push_silence(output_channels * output_rate * duration_s);
+
+ /* expected output is a 440Hz sine wave at 16kHz */
+ fill_with_sine(expected_resampled_input.data() + latency,
+ target_rate, input_channels, duration_frames - latency, 0);
+ /* expected output is a 440Hz sine wave at 32kHz */
+ fill_with_sine(expected_resampled_output.data() + latency,
+ output_rate, output_channels, output_rate * duration_s - latency, 0);
+
+ while (state.output_phase_index != state.max_output_phase_index) {
+ uint32_t leftover_samples = input_buffer.length() * input_channels;
+ input_buffer.reserve(input_array_frame_count);
+ state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
+ input_rate,
+ input_channels,
+ input_array_frame_count - leftover_samples,
+ state.input_phase_index);
+ long input_consumed = input_array_frame_count;
+ input_buffer.set_length(input_array_frame_count);
+
+ got = cubeb_resampler_fill(resampler,
+ input_buffer.data(), &input_consumed,
+ output_buffer.data(), output_array_frame_count);
+
+ /* handle leftover input */
+ if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
+ input_buffer.pop(nullptr, input_consumed * input_channels);
+ } else {
+ input_buffer.clear();
+ }
+
+ state.output.push(output_buffer.data(), got * state.output_channels);
+ }
+
+ dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
+ dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
+ dump("input.raw", state.input.data(), state.input.length());
+ dump("output.raw", state.output.data(), state.output.length());
+
+ // This is disabled because the latency estimation in the resampler code is
+ // slightly off so we can generate expected vectors.
+ // See https://github.com/kinetiknz/cubeb/issues/93
+ // ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
+ // ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
+
+ cubeb_resampler_destroy(resampler);
+}
+
+#define array_size(x) (sizeof(x) / sizeof(x[0]))
+
+TEST(cubeb, resampler_one_way)
+{
+ /* Test one way resamplers */
+ for (uint32_t channels = 1; channels <= max_channels; channels++) {
+ for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
+ fprintf(stderr, "one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n",
+ channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration);
+ test_resampler_one_way<float>(channels, sample_rates[source_rate],
+ sample_rates[dest_rate], chunk_duration);
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, DISABLED_resampler_duplex)
+{
+ for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
+ for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
+ for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
+ for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
+ fprintf(stderr, "input channels:%d output_channels:%d input_rate:%d "
+ "output_rate:%d target_rate:%d chunk_ms:%d\n",
+ input_channels, output_channels,
+ sample_rates[source_rate_input],
+ sample_rates[source_rate_output],
+ sample_rates[dest_rate],
+ chunk_duration);
+ test_resampler_duplex<float>(input_channels, output_channels,
+ sample_rates[source_rate_input],
+ sample_rates[source_rate_output],
+ sample_rates[dest_rate],
+ chunk_duration);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, resampler_delay_line)
+{
+ for (uint32_t channel = 1; channel <= 2; channel++) {
+ for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) {
+ for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
+ fprintf(stderr, "channel: %d, delay_frames: %d, chunk_size: %d\n",
+ channel, delay_frames, chunk_size);
+ test_delay_lines(delay_frames, channel, chunk_size);
+ }
+ }
+ }
+}
+
+long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_output_only_noop)
+{
+ cubeb_stream_params output_params;
+ int target_rate;
+
+ output_params.rate = 44100;
+ output_params.channels = 1;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ target_rate = output_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
+ test_output_only_noop_data_cb, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr,
+ out_buffer, out_frames);
+
+ ASSERT_EQ(got, out_frames);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+long test_drain_data_cb(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ auto cb_count = static_cast<int *>(user_ptr);
+ (*cb_count)++;
+ return frame_count - 1;
+}
+
+TEST(cubeb, resampler_drain)
+{
+ cubeb_stream_params output_params;
+ int target_rate;
+
+ output_params.rate = 44100;
+ output_params.channels = 1;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ target_rate = 48000;
+ int cb_count = 0;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
+ test_drain_data_cb, &cb_count,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ do {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr,
+ out_buffer, out_frames);
+ } while (got == out_frames);
+
+ /* The callback should be called once but not again after returning <
+ * frame_count. */
+ ASSERT_EQ(cb_count, 1);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void check_output(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(input_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!output_buffer);
+}
+
+long cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ check_output(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_output_only)
+{
+ // Test that the passthrough resampler works when there is only an output stream.
+ cubeb_stream_params output_params;
+
+ const size_t output_channels = 2;
+ output_params.channels = output_channels;
+ output_params.rate = 44100;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = output_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
+ target_rate, cb_passthrough_resampler_output, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ float output_buffer[output_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void check_input(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(output_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!input_buffer);
+}
+
+long cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ check_input(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_input_only)
+{
+ // Test that the passthrough resampler works when there is only an output stream.
+ cubeb_stream_params input_params;
+
+ const size_t input_channels = 2;
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = input_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
+ target_rate, cb_passthrough_resampler_input, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ float input_buffer[input_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ long int frames = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+template<typename T>
+long seq(T* array, int stride, long start, long count)
+{
+ uint32_t output_idx = 0;
+ for(int i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ array[output_idx + j] = static_cast<T>(start + i);
+ }
+ output_idx += stride;
+ }
+ return start + count;
+}
+
+template<typename T>
+void is_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_EQ(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+template<typename T>
+void is_not_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_NE(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+struct closure {
+ int input_channel_count;
+};
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+template<typename T>
+void check_duplex(const T * input_buffer,
+ T * output_buffer, long frame_count,
+ int input_channel_count)
+{
+ ASSERT_EQ(frame_count, 256);
+ // Silence scan-build warning.
+ ASSERT_TRUE(!!output_buffer); assert(output_buffer);
+ ASSERT_TRUE(!!input_buffer); assert(input_buffer);
+
+ int output_index = 0;
+ int input_index = 0;
+ for (int i = 0; i < frame_count; i++) {
+ // output is two channels, input one or two channels.
+ if (input_channel_count == 1) {
+ output_buffer[output_index] = output_buffer[output_index + 1] = input_buffer[i];
+ } else if (input_channel_count == 2) {
+ output_buffer[output_index] = input_buffer[input_index];
+ output_buffer[output_index + 1] = input_buffer[input_index + 1];
+ }
+ output_index += 2;
+ input_index += input_channel_count;
+ }
+}
+
+long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ closure * c = reinterpret_cast<closure*>(user_ptr);
+ check_duplex<float>(static_cast<const float*>(input_buffer),
+ static_cast<float*>(output_buffer),
+ frame_count, c->input_channel_count);
+ return frame_count;
+}
+
+
+TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
+{
+ // Test that when pre-buffering on resampler creation, we can survive an input
+ // callback being delayed.
+
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int input_channels = 1;
+ const int output_channels = 2;
+
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = input_params.rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+ target_rate, cb_passthrough_resampler_duplex, &c,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long BUF_BASE_SIZE = 256;
+ float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_normal[input_channels * BUF_BASE_SIZE];
+ float output_buffer[output_channels * BUF_BASE_SIZE];
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
+ output_buffer, BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ // Simulate that sometimes, we don't have the input callback on time
+ if (i != 0 && (i % 100) == 0) {
+ long zero = 0;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
+ &zero, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else if (i != 0 && (i % 100) == 1) {
+ // if this is the case, the on the next iteration, we'll have twice the
+ // amount of input frames
+ seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
+ frames = 2 * BUF_BASE_SIZE;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else {
+ // normal case
+ seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// Artificially simulate output thread underruns,
+// by building up artificial delay in the input.
+// Check that the frame drop logic kicks in.
+TEST(cubeb, resampler_drift_drop_data)
+{
+ for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int output_channels = 2;
+ const int sample_rate = 44100;
+
+ input_params.channels = input_channels;
+ input_params.rate = sample_rate;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = sample_rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+ target_rate, cb_passthrough_resampler_duplex, &c,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long BUF_BASE_SIZE = 256;
+
+ // The factor by which the deadline is missed. This is intentionally
+ // kind of large to trigger the frame drop quickly. In real life, multiple
+ // smaller under-runs would accumulate.
+ const long UNDERRUN_FACTOR = 10;
+ // Number buffer used for pre-buffering, that some backends do.
+ const long PREBUFFER_FACTOR = 2;
+
+ std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE * PREBUFFER_FACTOR);
+ std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
+ std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames = input_buffer_prebuffer.size() / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(), &prebuffer_frames,
+ output_buffer.data(), BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ if (i != 0 && (i % 100) == 1) {
+ // Once in a while, the output thread misses its deadline.
+ // The input thread still produces data, so it ends up accumulating. Simulate this by providing a
+ // much bigger input buffer. Check that the sequence is now unaligned, meaning we've dropped data
+ // to keep everything in sync.
+ seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx, BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames, output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ else if (i != 0 && (i % 100) == 2) {
+ // On the next iteration, the sequence should be broken
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+ is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+ // Reclock so that we can use is_seq again.
+ output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
+ }
+ else {
+ // normal case
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+ }
+}
+
+static long
+passthrough_resampler_fill_eq_input(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_eq_input) {
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_eq_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 32;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+}
+
+static long
+passthrough_resampler_fill_short_input(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ // First part contains the input
+ for (int i = 0; i < 32; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ // missing part contains silence
+ for (int i = 32; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.0);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_short_input) {
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_short_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 16;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 16);
+}
+
+static long
+passthrough_resampler_fill_input_left(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ int iteration = *static_cast<int*>(user_ptr);
+ if (iteration == 1) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 2) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part contains the reamaining input samples from previous
+ // iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 64));
+ // next part contains the new buffer
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 3) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part (16 frames) contains the reamaining input samples
+ // from previous iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 32));
+ }
+ for (int i = 0; i < 16; ++i) {
+ // next part (8 frames) contains the new input buffer.
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ // last part (8 frames) contains silence.
+ ASSERT_FLOAT_EQ(input[i + 32 + 16], 0.0);
+ }
+ }();
+ }
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_input_left) {
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ int iteration = 0;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_input_left,
+ &iteration, channels, sample_rate);
+
+ long input_frame_count = 48; // 32 + 16
+ const long output_frame_count = 32;
+ float input[96] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+
+ // 1st iteration, add the extra input.
+ iteration = 1;
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 2st iteration, use the extra input from previous iteration,
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 32; // we need 16 input frames but we get more;
+ iteration = 2;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 3rd iteration, use the extra input from previous iteration.
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 16 - 8; // We need 16 more input frames but we only get 8.
+ iteration = 3;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 8);
+}
+
+TEST(cubeb, individual_methods) {
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ const uint32_t frames = 256;
+
+ delay_line<float> dl(10, channels, sample_rate);
+ uint32_t frames_needed1 = dl.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed1, 0u);
+
+ cubeb_resampler_speex_one_way<float> one_way(channels, sample_rate, sample_rate, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ float buffer[channels * frames] = {0.0};
+ // Add all frames in the resampler's internal buffer.
+ one_way.input(buffer, frames);
+ // Ask for less than the existing frames, this would create a uint overlflow without the fix.
+ uint32_t frames_needed2 = one_way.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed2, 0u);
+}
+
diff --git a/media/libcubeb/gtest/test_ring_array.cpp b/media/libcubeb/gtest/test_ring_array.cpp
new file mode 100644
index 0000000000..d258d50dbe
--- /dev/null
+++ b/media/libcubeb/gtest/test_ring_array.cpp
@@ -0,0 +1,73 @@
+#include "gtest/gtest.h"
+#ifdef __APPLE__
+#include <string.h>
+#include <iostream>
+#include <CoreAudio/CoreAudioTypes.h>
+#include "cubeb/cubeb.h"
+#include "cubeb_ring_array.h"
+
+TEST(cubeb, ring_array)
+{
+ ring_array ra;
+
+ ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER);
+ ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER);
+
+ unsigned int capacity = 8;
+ ring_array_init(&ra, capacity, sizeof(int), 1, 1);
+ int verify_data[capacity] ;// {1,2,3,4,5,6,7,8};
+ AudioBuffer * p_data = NULL;
+
+ for (unsigned int i = 0; i < capacity; ++i) {
+ verify_data[i] = i; // in case capacity change value
+ *(int*)ra.buffer_array[i].mData = i;
+ ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int));
+ ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u);
+ }
+
+ /* Get store buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
+ }
+ /*Now array is full extra store should give NULL*/
+ ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr);
+ /* Get fetch buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
+ }
+ /*Now array is empty extra fetch should give NULL*/
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr);
+
+ p_data = NULL;
+ /* Repeated store fetch should can go for ever*/
+ for (unsigned int i = 0; i < 2*capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data);
+ }
+
+ p_data = NULL;
+ /* Verify/modify buffer data*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int*)p_data->mData), verify_data[i]);
+ (*((int*)p_data->mData))++; // Modify data
+ }
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int*)p_data->mData), verify_data[i]+1); // Verify modified data
+ }
+
+ ring_array_destroy(&ra);
+}
+#else
+TEST(cubeb, DISABLED_ring_array)
+{
+}
+#endif
diff --git a/media/libcubeb/tests/test_sanity.cpp b/media/libcubeb/gtest/test_sanity.cpp
index 77973ff150..5fc72f5356 100644
--- a/media/libcubeb/tests/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -4,52 +4,63 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
+#endif
#include "cubeb/cubeb.h"
-#include <assert.h>
+#include <atomic>
#include <stdio.h>
#include <string.h>
#include <math.h>
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__)
-#define END_TEST fprintf(stderr, "END %s\n", __func__)
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
#define STREAM_RATE 44100
#define STREAM_LATENCY 100 * STREAM_RATE / 1000
#define STREAM_CHANNELS 1
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#else
+#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#endif
-template<typename T, size_t N>
-constexpr size_t
-ARRAY_LENGTH(T(&)[N])
+int is_windows_7()
{
- return N;
+#ifdef __MINGW32__
+ fprintf(stderr, "Warning: this test was built with MinGW.\n"
+ "MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n");
+ return 1;
+#endif
+#if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__))
+ OSVERSIONINFOEX osvi;
+ DWORDLONG condition_mask = 0;
+
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+ // NT 6.1 is Windows 7
+ osvi.dwMajorVersion = 6;
+ osvi.dwMinorVersion = 1;
+
+ VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL);
+ VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+
+ return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask);
+#else
+ return 0;
+#endif
}
static int dummy;
-static uint64_t total_frames_written;
+static std::atomic<uint64_t> total_frames_written;
static int delay_callback;
static long
test_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
- assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
-#if (defined(_WIN32) || defined(__WIN32__))
- memset(outputbuffer, 0, nframes * sizeof(float));
-#else
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
memset(outputbuffer, 0, nframes * sizeof(short));
-#endif
total_frames_written += nframes;
if (delay_callback) {
@@ -63,121 +74,103 @@ test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state /*s
{
}
-static void
-test_init_destroy_context(void)
+TEST(cubeb, init_destroy_context)
{
int r;
cubeb * ctx;
char const* backend_id;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
-
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
backend_id = cubeb_get_backend_id(ctx);
- assert(backend_id);
+ ASSERT_TRUE(backend_id);
fprintf(stderr, "Backend: %s\n", backend_id);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_init_destroy_multiple_contexts(void)
+TEST(cubeb, init_destroy_multiple_contexts)
{
size_t i;
int r;
cubeb * ctx[4];
int order[4] = {2, 0, 3, 1};
- assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order));
-
- BEGIN_TEST;
+ ASSERT_EQ(ARRAY_LENGTH(ctx), ARRAY_LENGTH(order));
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
- r = cubeb_init(&ctx[i], NULL);
- assert(r == 0 && ctx[i]);
+ r = common_init(&ctx[i], NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
}
/* destroy in a different order */
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
cubeb_destroy(ctx[order[i]]);
}
-
- END_TEST;
}
-static void
-test_context_variables(void)
+TEST(cubeb, context_variables)
{
int r;
cubeb * ctx;
uint32_t value;
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_context_variables");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_context_variables");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.channels = STREAM_CHANNELS;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
- r = cubeb_get_min_latency(ctx, params, &value);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &params, &value);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
- assert(value > 0);
+ ASSERT_TRUE(value > 0);
}
r = cubeb_get_preferred_sample_rate(ctx, &value);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
- assert(value > 0);
+ ASSERT_TRUE(value > 0);
}
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_init_destroy_stream(void)
+TEST(cubeb, init_destroy_stream)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_init_destroy_multiple_streams(void)
+TEST(cubeb, init_destroy_multiple_streams)
{
size_t i;
int r;
@@ -185,23 +178,21 @@ test_init_destroy_multiple_streams(void)
cubeb_stream * stream[8];
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0);
- assert(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
}
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
@@ -209,43 +200,72 @@ test_init_destroy_multiple_streams(void)
}
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_configure_stream(void)
+TEST(cubeb, configure_stream)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
- params.channels = 2; // panning
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
r = cubeb_stream_set_volume(stream, 1.0f);
- assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
- r = cubeb_stream_set_panning(stream, 0.0f);
- assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+ r = cubeb_stream_set_name(stream, "test 2");
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, configure_stream_undefined_layout)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ delay(100);
+
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
- END_TEST;
}
static void
@@ -257,34 +277,31 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
cubeb_stream * stream[8];
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0);
- assert(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
if (early) {
r = cubeb_stream_start(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
}
-
if (!early) {
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_start(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
}
@@ -295,25 +312,43 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
if (!early) {
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_stop(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
}
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
if (early) {
r = cubeb_stream_stop(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
cubeb_stream_destroy(stream[i]);
}
cubeb_destroy(ctx);
+}
- END_TEST;
+TEST(cubeb, init_start_stop_destroy_multiple_streams)
+{
+ /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
+ * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
+ * the HRESULT value for "Cannot create a file when that file already exists",
+ * and is not documented as a possible return value for this call. Hence, we
+ * try to limit the number of streams we create in this test. */
+ if (!is_windows_7()) {
+ delay_callback = 0;
+ test_init_start_stop_destroy_multiple_streams(0, 0);
+ test_init_start_stop_destroy_multiple_streams(1, 0);
+ test_init_start_stop_destroy_multiple_streams(0, 150);
+ test_init_start_stop_destroy_multiple_streams(1, 150);
+ delay_callback = 1;
+ test_init_start_stop_destroy_multiple_streams(0, 0);
+ test_init_start_stop_destroy_multiple_streams(1, 0);
+ test_init_start_stop_destroy_multiple_streams(0, 150);
+ test_init_start_stop_destroy_multiple_streams(1, 150);
+ }
}
-static void
-test_init_destroy_multiple_contexts_and_streams(void)
+TEST(cubeb, init_destroy_multiple_contexts_and_streams)
{
size_t i, j;
int r;
@@ -321,26 +356,32 @@ test_init_destroy_multiple_contexts_and_streams(void)
cubeb_stream * stream[8];
cubeb_stream_params params;
size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx);
- assert(ARRAY_LENGTH(ctx) * streams_per_ctx == ARRAY_LENGTH(stream));
+ ASSERT_EQ(ARRAY_LENGTH(ctx) * streams_per_ctx, ARRAY_LENGTH(stream));
- BEGIN_TEST;
+ /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
+ * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
+ * the HRESULT value for "Cannot create a file when that file already exists",
+ * and is not documented as a possible return value for this call. Hence, we
+ * try to limit the number of streams we create in this test. */
+ if (is_windows_7())
+ return;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
- r = cubeb_init(&ctx[i], "test_sanity");
- assert(r == 0 && ctx[i]);
+ r = common_init(&ctx[i], "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
for (j = 0; j < streams_per_ctx; ++j) {
r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0);
- assert(stream[i * streams_per_ctx + j]);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i * streams_per_ctx + j], nullptr);
}
}
@@ -350,61 +391,65 @@ test_init_destroy_multiple_contexts_and_streams(void)
}
cubeb_destroy(ctx[i]);
}
-
- END_TEST;
}
-static void
-test_basic_stream_operations(void)
+TEST(cubeb, basic_stream_operations)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position;
+ uint32_t latency;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
- /* position and volume before stream has started */
+ /* position and latency before stream has started */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0 && position == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
- /* position and volume after while stream running */
+ /* position and latency after while stream running */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_stop(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
- /* position and volume after stream has stopped */
+ /* position and latency after stream has stopped */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_stream_position(void)
+TEST(cubeb, stream_position)
{
size_t i;
int r;
@@ -413,60 +458,61 @@ test_stream_position(void)
cubeb_stream_params params;
uint64_t position, last_position;
- BEGIN_TEST;
-
total_frames_written = 0;
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
/* stream position should not advance before starting playback */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0 && position == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0 && position == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
/* stream position should advance during playback */
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
/* XXX let start happen */
delay(500);
/* stream should have prefilled */
- assert(total_frames_written > 0);
+ ASSERT_TRUE(total_frames_written.load() > 0);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
last_position = position;
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(position >= last_position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
last_position = position;
/* stream position should not exceed total frames written */
for (i = 0; i < 5; ++i) {
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(position >= last_position);
- assert(position <= total_frames_written);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
+ ASSERT_LE(position, total_frames_written.load());
last_position = position;
delay(500);
}
@@ -475,60 +521,55 @@ test_stream_position(void)
* stopping the stream. */
for (i = 0; i < 5; ++i) {
r = cubeb_stream_stop(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(last_position < position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(last_position < position);
last_position = position;
delay(500);
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
delay(500);
}
- assert(last_position != 0);
+ ASSERT_NE(last_position, 0u);
/* stream position should not advance after stopping playback */
r = cubeb_stream_stop(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
/* XXX allow stream to settle */
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
last_position = position;
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(position == last_position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, last_position);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static int do_drain;
-static int got_drain;
+static std::atomic<int> do_drain;
+static std::atomic<int> got_drain;
static long
test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
- assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
if (do_drain == 1) {
do_drain = 2;
return 0;
}
/* once drain has started, callback must never be called again */
- assert(do_drain != 2);
-#if (defined(_WIN32) || defined(__WIN32__))
- memset(outputbuffer, 0, nframes * sizeof(float));
-#else
+ EXPECT_TRUE(do_drain != 2);
memset(outputbuffer, 0, nframes * sizeof(short));
-#endif
total_frames_written += nframes;
return nframes;
}
@@ -537,13 +578,12 @@ void
test_drain_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state state)
{
if (state == CUBEB_STATE_DRAINED) {
- assert(!got_drain);
+ ASSERT_TRUE(!got_drain);
got_drain = 1;
}
}
-static void
-test_drain(void)
+TEST(cubeb, drain)
{
int r;
cubeb * ctx;
@@ -551,126 +591,102 @@ test_drain(void)
cubeb_stream_params params;
uint64_t position;
- BEGIN_TEST;
-
+ delay_callback = 0;
total_frames_written = 0;
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_drain_data_callback, test_drain_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
- delay(500);
+ delay(5000);
do_drain = 1;
for (;;) {
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
if (got_drain) {
break;
} else {
- assert(position <= total_frames_written);
+ ASSERT_LE(position, total_frames_written.load());
}
delay(500);
}
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(got_drain);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(got_drain);
// Really, we should be able to rely on position reaching our final written frame, but
// for now let's make sure it doesn't continue beyond that point.
- //assert(position <= total_frames_written);
+ //ASSERT_LE(position, total_frames_written.load());
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
- END_TEST;
+ got_drain = 0;
+ do_drain = 0;
}
-int is_windows_7()
+TEST(cubeb, DISABLED_eos_during_prefill)
{
-#ifdef __MINGW32__
- printf("Warning: this test was built with MinGW.\n"
- "MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n");
- return 1;
-#endif
-#if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__))
- OSVERSIONINFOEX osvi;
- DWORDLONG condition_mask = 0;
+ // This test needs to be implemented.
+}
- ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+TEST(cubeb, DISABLED_stream_destroy_pending_drain)
+{
+ // This test needs to be implemented.
+}
- // NT 6.1 is Windows 7
- osvi.dwMajorVersion = 6;
- osvi.dwMinorVersion = 1;
+TEST(cubeb, stable_devid)
+{
+ /* Test that the devid field of cubeb_device_info is stable
+ * (ie. compares equal) over two invocations of
+ * cubeb_enumerate_devices(). */
- VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL);
- VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ int r;
+ cubeb * ctx;
+ cubeb_device_collection first;
+ cubeb_device_collection second;
+ cubeb_device_type all_devices =
+ (cubeb_device_type) (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT);
+ size_t n;
- return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask);
-#else
- return 0;
-#endif
-}
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
-int
-main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_sanity");
-#endif
+ r = cubeb_enumerate_devices(ctx, all_devices, &first);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED)
+ return;
- test_init_destroy_context();
- test_init_destroy_multiple_contexts();
- test_context_variables();
- test_init_destroy_stream();
- test_init_destroy_multiple_streams();
- test_configure_stream();
- test_basic_stream_operations();
- test_stream_position();
+ ASSERT_EQ(r, CUBEB_OK);
- /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
- * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
- * the HRESULT value for "Cannot create a file when that file already exists",
- * and is not documented as a possible return value for this call. Hence, we
- * try to limit the number of streams we create in this test. */
- if (!is_windows_7()) {
- test_init_destroy_multiple_contexts_and_streams();
+ r = cubeb_enumerate_devices(ctx, all_devices, &second);
+ ASSERT_EQ(r, CUBEB_OK);
- delay_callback = 0;
- test_init_start_stop_destroy_multiple_streams(0, 0);
- test_init_start_stop_destroy_multiple_streams(1, 0);
- test_init_start_stop_destroy_multiple_streams(0, 150);
- test_init_start_stop_destroy_multiple_streams(1, 150);
- delay_callback = 1;
- test_init_start_stop_destroy_multiple_streams(0, 0);
- test_init_start_stop_destroy_multiple_streams(1, 0);
- test_init_start_stop_destroy_multiple_streams(0, 150);
- test_init_start_stop_destroy_multiple_streams(1, 150);
+ ASSERT_EQ(first.count, second.count);
+ for (n = 0; n < first.count; n++) {
+ ASSERT_EQ(first.device[n].devid, second.device[n].devid);
}
- delay_callback = 0;
- test_drain();
-/*
- to implement:
- test_eos_during_prefill();
- test_stream_destroy_pending_drain();
-*/
- printf("\n");
- return 0;
+ r = cubeb_device_collection_destroy(ctx, &first);
+ ASSERT_EQ(r, CUBEB_OK);
+ r = cubeb_device_collection_destroy(ctx, &second);
+ ASSERT_EQ(r, CUBEB_OK);
+ cubeb_destroy(ctx);
}
diff --git a/media/libcubeb/tests/test_tone.cpp b/media/libcubeb/gtest/test_tone.cpp
index 3c6e0ec548..70a71885e6 100644
--- a/media/libcubeb/tests/test_tone.cpp
+++ b/media/libcubeb/gtest/test_tone.cpp
@@ -6,42 +6,35 @@
*/
/* libcubeb api/function test. Plays a simple tone. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
+#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
-#include <assert.h>
+#include <memory>
#include <limits.h>
-
#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
+
#define SAMPLE_FREQUENCY 48000
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#else
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#endif
/* store the phase of the generated waveform */
struct cb_user_data {
- long position;
+ std::atomic<long> position;
};
-long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
+long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
{
struct cb_user_data *u = (struct cb_user_data *)user;
-#if (defined(_WIN32) || defined(__WIN32__))
- float *b = (float *)outputbuffer;
-#else
short *b = (short *)outputbuffer;
-#endif
float t1, t2;
int i;
@@ -53,21 +46,12 @@ long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void
/* North American dial tone */
t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY);
t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY);
-#if (defined(_WIN32) || defined(__WIN32__))
- b[i] = 0.5 * t1;
- b[i] += 0.5 * t2;
-#else
b[i] = (SHRT_MAX / 2) * t1;
b[i] += (SHRT_MAX / 2) * t2;
-#endif
/* European dial tone */
/*
t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
-#if (defined(_WIN32) || defined(__WIN32__))
- b[i] = t1;
-#else
b[i] = SHRT_MAX * t1;
-#endif
*/
}
/* remember our phase to avoid clicking on buffer transitions */
@@ -77,7 +61,7 @@ long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void
return nframes;
}
-void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
+void state_cb_tone(cubeb_stream *stream, void *user, cubeb_state state)
{
struct cb_user_data *u = (struct cb_user_data *)user;
@@ -86,64 +70,52 @@ void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
switch (state) {
case CUBEB_STATE_STARTED:
- printf("stream started\n"); break;
+ fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
- printf("stream stopped\n"); break;
+ fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
- printf("stream drained\n"); break;
+ fprintf(stderr, "stream drained\n"); break;
default:
- printf("unknown stream state %d\n", state);
+ fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
-int main(int /*argc*/, char * /*argv*/[])
+TEST(cubeb, tone)
{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_tone");
-#endif
-
cubeb *ctx;
cubeb_stream *stream;
cubeb_stream_params params;
- struct cb_user_data *user_data;
int r;
- r = cubeb_init(&ctx, "Cubeb tone example");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
+ r = common_init(&ctx, "Cubeb tone example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
+ params.layout = CUBEB_LAYOUT_MONO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<cb_user_data> user_data(new cb_user_data());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
- user_data = (struct cb_user_data *) malloc(sizeof(*user_data));
- if (user_data == NULL) {
- fprintf(stderr, "Error allocating user data\n");
- return CUBEB_ERROR;
- }
user_data->position = 0;
r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, &params,
- 4096, data_cb, state_cb, user_data);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream\n");
- return r;
- }
+ 4096, data_cb_tone, state_cb_tone, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
cubeb_stream_start(stream);
- delay(500);
+ delay(5000);
cubeb_stream_stop(stream);
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
-
- assert(user_data->position);
-
- free(user_data);
-
- return CUBEB_OK;
+ ASSERT_TRUE(user_data->position.load());
}
diff --git a/media/libcubeb/gtest/test_utils.cpp b/media/libcubeb/gtest/test_utils.cpp
new file mode 100644
index 0000000000..cbdb960984
--- /dev/null
+++ b/media/libcubeb/gtest/test_utils.cpp
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+#include "cubeb_utils.h"
+
+TEST(cubeb, auto_array)
+{
+ auto_array<uint32_t> array;
+ auto_array<uint32_t> array2(10);
+ uint32_t a[10];
+
+ ASSERT_EQ(array2.length(), 0u);
+ ASSERT_EQ(array2.capacity(), 10u);
+
+
+ for (uint32_t i = 0; i < 10; i++) {
+ a[i] = i;
+ }
+
+ ASSERT_EQ(array.capacity(), 0u);
+ ASSERT_EQ(array.length(), 0u);
+
+ array.push(a, 10);
+
+ ASSERT_TRUE(!array.reserve(9));
+
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[i], i);
+ }
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 10u);
+
+ uint32_t b[10];
+
+ array.pop(b, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 5u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(b[i], i);
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+ uint32_t* bb = b + 5;
+ array.pop(bb, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 0u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(bb[i], 5 + i);
+ }
+
+ ASSERT_TRUE(!array.pop(nullptr, 1));
+
+ array.push(a, 10);
+ array.push(a, 10);
+
+ for (uint32_t j = 0; j < 2; j++) {
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[10 * j + i], i);
+ }
+ }
+ ASSERT_EQ(array.length(), 20u);
+ ASSERT_EQ(array.capacity(), 20u);
+ array.pop(nullptr, 5);
+
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+
+ ASSERT_EQ(array.length(), 15u);
+ ASSERT_EQ(array.capacity(), 20u);
+}
+
diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h
index 449b39c55e..f653f5b7d1 100644
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -7,8 +7,9 @@
#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
-#include <stdint.h>
#include "cubeb_export.h"
+#include <stdint.h>
+#include <stdlib.h>
#if defined(__cplusplus)
extern "C" {
@@ -30,19 +31,13 @@ extern "C" {
@code
cubeb * app_ctx;
- cubeb_init(&app_ctx, "Example Application");
+ cubeb_init(&app_ctx, "Example Application", NULL);
int rv;
- int rate;
- int latency_frames;
+ uint32_t rate;
+ uint32_t latency_frames;
uint64_t ts;
- rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames);
- if (rv != CUBEB_OK) {
- fprintf(stderr, "Could not get minimum latency");
- return rv;
- }
-
- rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate);
+ rv = cubeb_get_preferred_sample_rate(app_ctx, &rate);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not get preferred sample-rate");
return rv;
@@ -52,16 +47,26 @@ extern "C" {
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
output_params.rate = rate;
output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get minimum latency");
+ return rv;
+ }
cubeb_stream_params input_params;
- output_params.format = CUBEB_SAMPLE_FLOAT32NE;
- output_params.rate = rate;
- output_params.channels = 1;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = rate;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
cubeb_stream * stm;
rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
- NULL, input_params,
- NULL, output_params,
+ NULL, &input_params,
+ NULL, &output_params,
latency_frames,
data_cb, state_cb,
NULL);
@@ -92,14 +97,14 @@ extern "C" {
@code
long data_cb(cubeb_stream * stm, void * user,
- void * input_buffer, void * output_buffer, long nframes)
+ const void * input_buffer, void * output_buffer, long nframes)
{
- float * in = input_buffer;
+ const float * in = input_buffer;
float * out = output_buffer;
- for (i = 0; i < nframes; ++i) {
- for (c = 0; c < 2; ++c) {
- buf[i][c] = in[i];
+ for (int i = 0; i < nframes; ++i) {
+ for (int c = 0; c < 2; ++c) {
+ out[2 * i + c] = in[i];
}
}
return nframes;
@@ -117,8 +122,10 @@ extern "C" {
/** @file
The <tt>libcubeb</tt> C API. */
-typedef struct cubeb cubeb; /**< Opaque handle referencing the application state. */
-typedef struct cubeb_stream cubeb_stream; /**< Opaque handle referencing the stream state. */
+typedef struct cubeb
+ cubeb; /**< Opaque handle referencing the application state. */
+typedef struct cubeb_stream
+ cubeb_stream; /**< Opaque handle referencing the stream state. */
/** Sample format enumeration. */
typedef enum {
@@ -143,54 +150,126 @@ typedef enum {
#endif
} cubeb_sample_format;
-#if defined(__ANDROID__)
-/**
- * This maps to the underlying stream types on supported platforms, e.g.
- * Android.
- */
-typedef enum {
- CUBEB_STREAM_TYPE_VOICE_CALL = 0,
- CUBEB_STREAM_TYPE_SYSTEM = 1,
- CUBEB_STREAM_TYPE_RING = 2,
- CUBEB_STREAM_TYPE_MUSIC = 3,
- CUBEB_STREAM_TYPE_ALARM = 4,
- CUBEB_STREAM_TYPE_NOTIFICATION = 5,
- CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6,
- CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7,
- CUBEB_STREAM_TYPE_DTMF = 8,
- CUBEB_STREAM_TYPE_TTS = 9,
- CUBEB_STREAM_TYPE_FM = 10,
-
- CUBEB_STREAM_TYPE_MAX
-} cubeb_stream_type;
-#endif
-
/** An opaque handle used to refer a particular input or output device
* across calls. */
-typedef void * cubeb_devid;
+typedef void const * cubeb_devid;
/** Level (verbosity) of logging for a particular cubeb context. */
typedef enum {
CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
- CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */
- CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
+ CUBEB_LOG_NORMAL =
+ 1, /**< Logging lifetime operation (creation/destruction). */
+ CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance
+ implications. */
} cubeb_log_level;
+typedef enum {
+ CHANNEL_UNKNOWN = 0,
+ CHANNEL_FRONT_LEFT = 1 << 0,
+ CHANNEL_FRONT_RIGHT = 1 << 1,
+ CHANNEL_FRONT_CENTER = 1 << 2,
+ CHANNEL_LOW_FREQUENCY = 1 << 3,
+ CHANNEL_BACK_LEFT = 1 << 4,
+ CHANNEL_BACK_RIGHT = 1 << 5,
+ CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
+ CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
+ CHANNEL_BACK_CENTER = 1 << 8,
+ CHANNEL_SIDE_LEFT = 1 << 9,
+ CHANNEL_SIDE_RIGHT = 1 << 10,
+ CHANNEL_TOP_CENTER = 1 << 11,
+ CHANNEL_TOP_FRONT_LEFT = 1 << 12,
+ CHANNEL_TOP_FRONT_CENTER = 1 << 13,
+ CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
+ CHANNEL_TOP_BACK_LEFT = 1 << 15,
+ CHANNEL_TOP_BACK_CENTER = 1 << 16,
+ CHANNEL_TOP_BACK_RIGHT = 1 << 17
+} cubeb_channel;
+
+typedef uint32_t cubeb_channel_layout;
+// Some common layout definitions.
+enum {
+ CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
+ CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
+ CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F1 =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
+ CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+};
+
+/** Miscellaneous stream preferences. */
+typedef enum {
+ CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
+ CUBEB_STREAM_PREF_LOOPBACK =
+ 0x01, /**< Request a loopback stream. Should be
+ specified on the input params and an
+ output device to loopback from should
+ be passed in place of an input device. */
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
+ default device on OS
+ changes. */
+ CUBEB_STREAM_PREF_VOICE =
+ 0x04, /**< This stream is going to transport voice data.
+ Depending on the backend and platform, this can
+ change the audio input or output devices
+ selected, as well as the quality of the stream,
+ for example to accomodate bluetooth SCO modes on
+ bluetooth devices. */
+ CUBEB_STREAM_PREF_RAW =
+ 0x08, /**< Windows only. Bypass all signal processing
+ except for always on APO, driver and hardware. */
+ CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute
+ settings should persist across restarts
+ of the stream and/or application. This is
+ obsolete and ignored by all backends. */
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to
+ connect ports. Only affects
+ the jack backend. */
+} cubeb_stream_prefs;
+
/** Stream format initialization parameters. */
typedef struct {
cubeb_sample_format format; /**< Requested sample format. One of
#cubeb_sample_format. */
- unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
- unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
-#if defined(__ANDROID__)
- cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
-#endif
+ uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
+ uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
+ cubeb_channel_layout
+ layout; /**< Requested channel layout. This must be consistent with the
+ provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */
+ cubeb_stream_prefs prefs; /**< Requested preferences. */
} cubeb_stream_params;
/** Audio device description */
typedef struct {
char * output_name; /**< The name of the output device */
- char * input_name; /**< The name of the input device */
+ char * input_name; /**< The name of the input device */
} cubeb_device;
/** Stream states signaled via state_callback. */
@@ -203,12 +282,15 @@ typedef enum {
/** Result code enumeration. */
enum {
- CUBEB_OK = 0, /**< Success. */
- CUBEB_ERROR = -1, /**< Unclassified error. */
- CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */
+ CUBEB_OK = 0, /**< Success. */
+ CUBEB_ERROR = -1, /**< Unclassified error. */
+ CUBEB_ERROR_INVALID_FORMAT =
+ -2, /**< Unsupported #cubeb_stream_params requested. */
CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
- CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */
- CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */
+ CUBEB_ERROR_NOT_SUPPORTED =
+ -4, /**< Optional function not implemented in current backend. */
+ CUBEB_ERROR_DEVICE_UNAVAILABLE =
+ -5 /**< Device specified by #cubeb_devid not available. */
};
/**
@@ -224,82 +306,96 @@ typedef enum {
* The state of a device.
*/
typedef enum {
- CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */
- CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */
- CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
+ CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system
+ level. */
+ CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is
+ plugged into it. */
+ CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
} cubeb_device_state;
/**
* Architecture specific sample type.
*/
typedef enum {
- CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
- CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
- CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
- CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
+ CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
+ CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
+ CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
+ CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
} cubeb_device_fmt;
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
/** 16-bit integers, native endianess, when on a Big Endian environment. */
-#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
-/** 32-bit floating points, native endianess, when on a Big Endian environment. */
-#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
+#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
+/** 32-bit floating points, native endianess, when on a Big Endian environment.
+ */
+#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
#else
/** 16-bit integers, native endianess, when on a Little Endian environment. */
-#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
+#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
/** 32-bit floating points, native endianess, when on a Little Endian
* environment. */
-#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
+#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
#endif
/** All the 16-bit integers types. */
-#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
+#define CUBEB_DEVICE_FMT_S16_MASK \
+ (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
/** All the 32-bit floating points types. */
-#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
+#define CUBEB_DEVICE_FMT_F32_MASK \
+ (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
/** All the device formats types. */
-#define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
+#define CUBEB_DEVICE_FMT_ALL \
+ (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
/** Channel type for a `cubeb_stream`. Depending on the backend and platform
* used, this can control inter-stream interruption, ducking, and volume
* control.
*/
typedef enum {
- CUBEB_DEVICE_PREF_NONE = 0x00,
- CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
- CUBEB_DEVICE_PREF_VOICE = 0x02,
- CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
- CUBEB_DEVICE_PREF_ALL = 0x0F
+ CUBEB_DEVICE_PREF_NONE = 0x00,
+ CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
+ CUBEB_DEVICE_PREF_VOICE = 0x02,
+ CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
+ CUBEB_DEVICE_PREF_ALL = 0x0F
} cubeb_device_pref;
/** This structure holds the characteristics
- * of an input or output audio device. It can be obtained using
- * `cubeb_enumerate_devices`, and must be destroyed using
- * `cubeb_device_info_destroy`. */
+ * of an input or output audio device. It is obtained using
+ * `cubeb_enumerate_devices`, which returns these structures via
+ * `cubeb_device_collection` and must be destroyed via
+ * `cubeb_device_collection_destroy`. */
typedef struct {
- cubeb_devid devid; /**< Device identifier handle. */
- char * device_id; /**< Device identifier which might be presented in a UI. */
- char * friendly_name; /**< Friendly device name which might be presented in a UI. */
- char * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
- char * vendor_name; /**< Optional vendor name, may be NULL. */
-
- cubeb_device_type type; /**< Type of device (Input/Output). */
- cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
- cubeb_device_pref preferred;/**< Preferred device. */
-
- cubeb_device_fmt format; /**< Sample format supported. */
- cubeb_device_fmt default_format; /**< The default sample format for this device. */
- unsigned int max_channels; /**< Channels. */
- unsigned int default_rate; /**< Default/Preferred sample rate. */
- unsigned int max_rate; /**< Maximum sample rate supported. */
- unsigned int min_rate; /**< Minimum sample rate supported. */
-
- unsigned int latency_lo; /**< Lowest possible latency in frames. */
- unsigned int latency_hi; /**< Higest possible latency in frames. */
+ cubeb_devid devid; /**< Device identifier handle. */
+ char const *
+ device_id; /**< Device identifier which might be presented in a UI. */
+ char const * friendly_name; /**< Friendly device name which might be presented
+ in a UI. */
+ char const * group_id; /**< Two devices have the same group identifier if they
+ belong to the same physical device; for example a
+ headset and microphone. */
+ char const * vendor_name; /**< Optional vendor name, may be NULL. */
+
+ cubeb_device_type type; /**< Type of device (Input/Output). */
+ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
+ cubeb_device_pref preferred; /**< Preferred device. */
+
+ cubeb_device_fmt format; /**< Sample format supported. */
+ cubeb_device_fmt
+ default_format; /**< The default sample format for this device. */
+ uint32_t max_channels; /**< Channels. */
+ uint32_t default_rate; /**< Default/Preferred sample rate. */
+ uint32_t max_rate; /**< Maximum sample rate supported. */
+ uint32_t min_rate; /**< Minimum sample rate supported. */
+
+ uint32_t latency_lo; /**< Lowest possible latency in frames. */
+ uint32_t latency_hi; /**< Higest possible latency in frames. */
} cubeb_device_info;
-/** Device collection. */
+/** Device collection.
+ * Returned by `cubeb_enumerate_devices` and destroyed by
+ * `cubeb_device_collection_destroy`. */
typedef struct {
- uint32_t count; /**< Device count in collection. */
- cubeb_device_info * device[1]; /**< Array of pointers to device info. */
+ cubeb_device_info * device; /**< Array of pointers to device info. */
+ size_t count; /**< Device count in collection. */
} cubeb_device_collection;
/** User supplied data callback.
@@ -316,54 +412,68 @@ typedef struct {
@param output_buffer A pointer to a buffer to be filled with audio samples,
or nullptr if this is an input-only stream.
@param nframes The number of frames of the two buffer.
- @retval Number of frames written to the output buffer. If this number is
- less than nframes, then the stream will start to drain.
+ @retval If the stream has output, this is the number of frames written to
+ the output buffer. In this case, if this number is less than
+ nframes then the stream will start to drain. If the stream is
+ input only, then returning nframes indicates data has been read.
+ In this case, a value less than nframes will result in the stream
+ being stopped.
@retval CUBEB_ERROR on error, in which case the data callback will stop
and the stream will enter a shutdown state. */
-typedef long (* cubeb_data_callback)(cubeb_stream * stream,
- void * user_ptr,
- const void * input_buffer,
- void * output_buffer,
- long nframes);
+typedef long (*cubeb_data_callback)(cubeb_stream * stream, void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer, long nframes);
/** User supplied state callback.
@param stream The stream for this this callback fired.
@param user_ptr The pointer passed to cubeb_stream_init.
@param state The new state of the stream. */
-typedef void (* cubeb_state_callback)(cubeb_stream * stream,
- void * user_ptr,
- cubeb_state state);
+typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
+ cubeb_state state);
/**
* User supplied callback called when the underlying device changed.
* @param user The pointer passed to cubeb_stream_init. */
-typedef void (* cubeb_device_changed_callback)(void * user_ptr);
+typedef void (*cubeb_device_changed_callback)(void * user_ptr);
/**
* User supplied callback called when the underlying device collection changed.
* @param context A pointer to the cubeb context.
- * @param user_ptr The pointer passed to cubeb_stream_init. */
-typedef void (* cubeb_device_collection_changed_callback)(cubeb * context,
- void * user_ptr);
+ * @param user_ptr The pointer passed to
+ * cubeb_register_device_collection_changed. */
+typedef void (*cubeb_device_collection_changed_callback)(cubeb * context,
+ void * user_ptr);
/** User supplied callback called when a message needs logging. */
-typedef void (* cubeb_log_callback)(const char * fmt, ...);
+typedef void (*cubeb_log_callback)(char const * fmt, ...);
/** Initialize an application context. This will perform any library or
application scoped initialization.
+
+ Note: On Windows platforms, COM must be initialized in MTA mode on
+ any thread that will call the cubeb API.
+
@param context A out param where an opaque pointer to the application
context will be returned.
@param context_name A name for the context. Depending on the platform this
can appear in different locations.
+ @param backend_name The name of the cubeb backend user desires to select.
+ Accepted values self-documented in cubeb.c: init_oneshot
+ If NULL, a default ordering is used for backend choice.
+ A valid choice overrides all other possible backends,
+ so long as the backend was included at compile time.
@retval CUBEB_OK in case of success.
@retval CUBEB_ERROR in case of error, for example because the host
has no audio hardware. */
-CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name);
+CUBEB_EXPORT int
+cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name);
/** Get a read-only string identifying this context's current backend.
@param context A pointer to the cubeb context.
@retval Read-only string identifying current backend. */
-CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
+CUBEB_EXPORT char const *
+cubeb_get_backend_id(cubeb * context);
/** Get the maximum possible number of channels.
@param context A pointer to the cubeb context.
@@ -372,11 +482,12 @@ CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
+CUBEB_EXPORT int
+cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
/** Get the minimal latency value, in frames, that is guaranteed to work
when creating a stream for the specified sample rate. This is platform,
- hardware and backend dependant.
+ hardware and backend dependent.
@param context A pointer to the cubeb context.
@param params On some backends, the minimum achievable latency depends on
the characteristics of the stream.
@@ -385,23 +496,25 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
- cubeb_stream_params params,
- uint32_t * latency_frames);
+CUBEB_EXPORT int
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
+ uint32_t * latency_frames);
/** Get the preferred sample rate for this backend: this is hardware and
- platform dependant, and can avoid resampling, and/or trigger fastpaths.
+ platform dependent, and can avoid resampling, and/or trigger fastpaths.
@param context A pointer to the cubeb context.
@param rate The samplerate (in Hz) the current configuration prefers.
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
+CUBEB_EXPORT int
+cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
/** Destroy an application context. This must be called after all stream have
* been destroyed.
@param context A pointer to the cubeb context.*/
-CUBEB_EXPORT void cubeb_destroy(cubeb * context);
+CUBEB_EXPORT void
+cubeb_destroy(cubeb * context);
/** Initialize a stream associated with the supplied application context.
@param context A pointer to the cubeb context.
@@ -409,13 +522,21 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
cubeb stream.
@param stream_name A name for this stream.
@param input_device Device for the input side of the stream. If NULL the
- default input device is used.
+ default input device is used. Passing a valid
+ cubeb_devid means the stream only ever uses that device. Passing a NULL
+ cubeb_devid allows the stream to follow that device
+ type's OS default.
@param input_stream_params Parameters for the input side of the stream, or
NULL if this stream is output only.
@param output_device Device for the output side of the stream. If NULL the
- default output device is used.
+ default output device is used. Passing a valid
+ cubeb_devid means the stream only ever uses that device. Passing a NULL
+ cubeb_devid allows the stream to follow that device
+ type's OS default.
@param output_stream_params Parameters for the output side of the stream, or
- NULL if this stream is input only.
+ NULL if this stream is input only. When input
+ and output stream parameters are supplied, their
+ rate has to be the same.
@param latency_frames Stream latency in frames. Valid range
is [1, 96000].
@param data_callback Will be called to preroll data before playback is
@@ -427,41 +548,42 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
@retval CUBEB_ERROR
@retval CUBEB_ERROR_INVALID_FORMAT
@retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
-CUBEB_EXPORT int cubeb_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr);
+CUBEB_EXPORT int
+cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ uint32_t latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
stream.
@param stream The stream to destroy. */
-CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream);
+CUBEB_EXPORT void
+cubeb_stream_destroy(cubeb_stream * stream);
/** Start playback.
@param stream
@retval CUBEB_OK
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream);
+CUBEB_EXPORT int
+cubeb_stream_start(cubeb_stream * stream);
/** Stop playback.
@param stream
@retval CUBEB_OK
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream);
+CUBEB_EXPORT int
+cubeb_stream_stop(cubeb_stream * stream);
/** Get the current stream playback position.
@param stream
@param position Playback position in frames.
@retval CUBEB_OK
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
+CUBEB_EXPORT int
+cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
/** Get the latency for this stream, in frames. This is the number of frames
between the time cubeb acquires the data in the callback and the listener
@@ -471,8 +593,20 @@ CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * pos
@retval CUBEB_OK
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
+CUBEB_EXPORT int
+cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
+/** Get the input latency for this stream, in frames. This is the number of
+ frames between the time the audio input devices records the data, and they
+ are available in the data callback.
+ This returns CUBEB_ERROR when the stream is output-only.
+ @param stream
+ @param latency Current approximate stream latency in frames.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
/** Set the volume for a stream.
@param stream the stream for which to adjust the volume.
@param volume a float between 0.0 (muted) and 1.0 (maximum volume)
@@ -480,21 +614,17 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late
@retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or
stream is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
-
-/** If the stream is stereo, set the left/right panning. If the stream is mono,
- this has no effect.
- @param stream the stream for which to change the panning
- @param panning a number from -1.0 to 1.0. -1.0 means that the stream is
- fully mixed in the left channel, 1.0 means the stream is fully
- mixed in the right channel. 0.0 is equal power in the right and
- left channel (default).
+CUBEB_EXPORT int
+cubeb_stream_set_volume(cubeb_stream * stream, float volume);
+
+/** Change a stream's name.
+ @param stream the stream for which to set the name.
+ @param stream_name the new name for the stream
@retval CUBEB_OK
- @retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is
- outside the [-1.0, 1.0] range.
- @retval CUBEB_ERROR_NOT_SUPPORTED
- @retval CUBEB_ERROR stream is not mono nor stereo */
-CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
+ @retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
/** Get the current output device for this stream.
@param stm the stream for which to query the current output device
@@ -503,8 +633,9 @@ CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
@retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are
invalid pointers
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
- cubeb_device ** const device);
+CUBEB_EXPORT int
+cubeb_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device);
/** Destroy a cubeb_device structure.
@param stream the stream passed in cubeb_stream_get_current_device
@@ -512,8 +643,8 @@ CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
@retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * devices);
+CUBEB_EXPORT int
+cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * devices);
/** Set a callback to be notified when the output device changes.
@param stream the stream for which to set the callback.
@@ -523,45 +654,57 @@ CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
@retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
device_changed_callback are invalid pointers.
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback);
+CUBEB_EXPORT int
+cubeb_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback);
+
+/** Return the user data pointer registered with the stream with
+ cubeb_stream_init.
+ @param stream the stream for which to retrieve user data pointer.
+ @retval user data pointer */
+CUBEB_EXPORT void *
+cubeb_stream_user_ptr(cubeb_stream * stream);
/** Returns enumerated devices.
@param context
@param devtype device type to include
- @param collection output collection. Must be destroyed with cubeb_device_collection_destroy
+ @param collection output collection. Must be destroyed with
+ cubeb_device_collection_destroy
@retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection ** collection);
+CUBEB_EXPORT int
+cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection * collection);
/** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
+ @param context
@param collection collection to destroy
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
-CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb_device_collection * collection);
-
-/** Destroy a cubeb_device_info structure.
- @param info pointer to device info structure
- @retval CUBEB_OK
- @retval CUBEB_ERROR_INVALID_PARAMETER if info is an invalid pointer */
-CUBEB_EXPORT int cubeb_device_info_destroy(cubeb_device_info * info);
+CUBEB_EXPORT int
+cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
/** Registers a callback which is called when the system detects
a new device or a device is removed.
@param context
- @param devtype device type to include
+ @param devtype device type to include. Different callbacks and user pointers
+ can be registered for each devtype. The hybrid devtype
+ `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
+ and will register the provided callback and user pointer in both
+ sides.
@param callback a function called whenever the system device list changes.
- Passing NULL allow to unregister a function
+ Passing NULL allow to unregister a function. You have to unregister
+ first before you register a new callback.
@param user_ptr pointer to user specified data which will be present in
subsequent callbacks.
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback callback,
- void * user_ptr);
+CUBEB_EXPORT int
+cubeb_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr);
/** Set a callback to be called with a message.
@param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
@@ -571,8 +714,9 @@ CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
@retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are
invalid pointers, or if level is not
in cubeb_log_level. */
-CUBEB_EXPORT int cubeb_set_log_callback(cubeb_log_level log_level,
- cubeb_log_callback log_callback);
+CUBEB_EXPORT int
+cubeb_set_log_callback(cubeb_log_level log_level,
+ cubeb_log_callback log_callback);
#if defined(__cplusplus)
}
diff --git a/media/libcubeb/osx-linearize-operations.patch b/media/libcubeb/osx-linearize-operations.patch
deleted file mode 100644
index 9f4f31bcaf..0000000000
--- a/media/libcubeb/osx-linearize-operations.patch
+++ /dev/null
@@ -1,968 +0,0 @@
-From: Paul Adenot <paul@paul.cx>
-Subject: Linearize operations on AudioUnits to sidestep a deadlock.
-
----
-
-diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
---- a/src/cubeb_audiounit.cpp
-+++ b/src/cubeb_audiounit.cpp
-@@ -53,40 +53,45 @@ typedef UInt32 AudioFormatFlags;
-
- #define AU_OUT_BUS 0
- #define AU_IN_BUS 1
-
- #define PRINT_ERROR_CODE(str, r) do { \
- LOG("System call failed: %s (rv: %d)", str, r); \
- } while(0)
-
-+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
-+
- /* Testing empirically, some headsets report a minimal latency that is very
- * low, but this does not work in practice. Lie and say the minimum is 256
- * frames. */
- const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
- const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
-
- void audiounit_stream_stop_internal(cubeb_stream * stm);
- void audiounit_stream_start_internal(cubeb_stream * stm);
--static void close_audiounit_stream(cubeb_stream * stm);
--static int setup_audiounit_stream(cubeb_stream * stm);
-+static void audiounit_close_stream(cubeb_stream *stm);
-+static int audiounit_setup_stream(cubeb_stream *stm);
-
- extern cubeb_ops const audiounit_ops;
-
- struct cubeb {
- cubeb_ops const * ops;
- owned_critical_section mutex;
- std::atomic<int> active_streams;
-+ uint32_t global_latency_frames = 0;
- int limit_streams;
- cubeb_device_collection_changed_callback collection_changed_callback;
- void * collection_changed_user_ptr;
- /* Differentiate input from output devices. */
- cubeb_device_type collection_changed_devtype;
- uint32_t devtype_device_count;
- AudioObjectID * devtype_device_array;
-+ // The queue is asynchronously deallocated once all references to it are released
-+ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
- };
-
- class auto_array_wrapper
- {
- public:
- explicit auto_array_wrapper(auto_array<float> * ar)
- : float_ar(ar)
- , short_ar(nullptr)
-@@ -205,16 +210,17 @@ struct cubeb_stream {
- cubeb_resampler * resampler;
- /* This is the number of output callback we got in a row. This is usually one,
- * but can be two when the input and output rate are different, and more when
- * a device has been plugged or unplugged, as there can be some time before
- * the device is ready. */
- std::atomic<int> output_callback_in_a_row;
- /* This is true if a device change callback is currently running. */
- std::atomic<bool> switching_device;
-+ std::atomic<bool> buffer_size_change_state{ false };
- };
-
- bool has_input(cubeb_stream * stm)
- {
- return stm->input_stream_params.rate != 0;
- }
-
- bool has_output(cubeb_stream * stm)
-@@ -256,16 +262,24 @@ audiotimestamp_to_latency(AudioTimeStamp
-
- uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
- uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
-
- return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
- }
-
- static void
-+audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
-+{
-+ stm->mutex.assert_current_thread_owns();
-+ assert(stm->context->active_streams == 1);
-+ stm->context->global_latency_frames = latency_frames;
-+}
-+
-+static void
- audiounit_make_silent(AudioBuffer * ioData)
- {
- assert(ioData);
- assert(ioData->mData);
- memset(ioData->mData, 0, ioData->mDataByteSize);
- }
-
- static OSStatus
-@@ -576,29 +590,54 @@ audiounit_get_input_device_id(AudioDevic
- device_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
- }
-
-+static int
-+audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
-+{
-+ if (is_started) {
-+ audiounit_stream_stop_internal(stm);
-+ }
-+
-+ {
-+ auto_lock lock(stm->mutex);
-+
-+ audiounit_close_stream(stm);
-+
-+ if (audiounit_setup_stream(stm) != CUBEB_OK) {
-+ LOG("(%p) Stream reinit failed.", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ // Reset input frames to force new stream pre-buffer
-+ // silence if needed, check `is_extra_input_needed()`
-+ stm->frames_read = 0;
-+
-+ // If the stream was running, start it again.
-+ if (is_started) {
-+ audiounit_stream_start_internal(stm);
-+ }
-+ }
-+ return CUBEB_OK;
-+}
-+
- static OSStatus
- audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
- const AudioObjectPropertyAddress * addresses,
- void * user)
- {
- cubeb_stream * stm = (cubeb_stream*) user;
-- int rv;
-- bool was_running = false;
--
- stm->switching_device = true;
--
- // Note if the stream was running or not
-- was_running = !stm->shutdown;
-+ bool was_running = !stm->shutdown;
-
- LOG("(%p) Audio device changed, %d events.", stm, address_count);
- for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
- // Allow restart to choose the new default
- stm->output_device = nullptr;
-@@ -639,38 +678,25 @@ audiounit_property_listener_callback(Aud
- if (stm->device_changed_callback) {
- stm->device_changed_callback(stm->user_ptr);
- }
- break;
- }
- }
- }
-
-- // This means the callback won't be called again.
-- audiounit_stream_stop_internal(stm);
--
-- {
-- auto_lock lock(stm->mutex);
-- close_audiounit_stream(stm);
-- rv = setup_audiounit_stream(stm);
-- if (rv != CUBEB_OK) {
-- LOG("(%p) Could not reopen a stream after switching.", stm);
-+ // Use a new thread, through the queue, to avoid deadlock when calling
-+ // Get/SetProperties method from inside notify callback
-+ dispatch_async(stm->context->serial_queue, ^() {
-+ if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-- return noErr;
-+ LOG("(%p) Could not reopen the stream after switching.", stm);
- }
--
-- stm->frames_read = 0;
--
-- // If the stream was running, start it again.
-- if (was_running) {
-- audiounit_stream_start_internal(stm);
-- }
-- }
--
-- stm->switching_device = false;
-+ stm->switching_device = false;
-+ });
-
- return noErr;
- }
-
- OSStatus
- audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
- AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
- {
-@@ -1155,18 +1181,17 @@ audiounit_init_input_linear_buffer(cubeb
-
- static void
- audiounit_destroy_input_linear_buffer(cubeb_stream * stream)
- {
- delete stream->input_linear_buffer;
- }
-
- static uint32_t
--audiounit_clamp_latency(cubeb_stream * stm,
-- uint32_t latency_frames)
-+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
- {
- // For the 1st stream set anything within safe min-max
- assert(stm->context->active_streams > 0);
- if (stm->context->active_streams == 1) {
- return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
- }
-
-@@ -1219,26 +1244,374 @@ audiounit_clamp_latency(cubeb_stream * s
- } else {
- upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
- }
-
- return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
- SAFE_MIN_LATENCY_FRAMES);
- }
-
-+/*
-+ * Change buffer size is prone to deadlock thus we change it
-+ * following the steps:
-+ * - register a listener for the buffer size property
-+ * - change the property
-+ * - wait until the listener is executed
-+ * - property has changed, remove the listener
-+ * */
-+static void
-+buffer_size_changed_callback(void * inClientData,
-+ AudioUnit inUnit,
-+ AudioUnitPropertyID inPropertyID,
-+ AudioUnitScope inScope,
-+ AudioUnitElement inElement)
-+{
-+ cubeb_stream * stm = (cubeb_stream *)inClientData;
-+
-+ AudioUnit au = inUnit;
-+ AudioUnitScope au_scope = kAudioUnitScope_Input;
-+ AudioUnitElement au_element = inElement;
-+ const char * au_type = "output";
-+
-+ if (au == stm->input_unit) {
-+ au_scope = kAudioUnitScope_Output;
-+ au_type = "input";
-+ }
-+
-+ switch (inPropertyID) {
-+
-+ case kAudioDevicePropertyBufferFrameSize: {
-+ if (inScope != au_scope) {
-+ break;
-+ }
-+ UInt32 new_buffer_size;
-+ UInt32 outSize = sizeof(UInt32);
-+ OSStatus r = AudioUnitGetProperty(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ au_scope,
-+ au_element,
-+ &new_buffer_size,
-+ &outSize);
-+ if (r != noErr) {
-+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
-+ } else {
-+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
-+ au_type, new_buffer_size, inScope);
-+ }
-+ stm->buffer_size_change_state = true;
-+ break;
-+ }
-+ }
-+}
-+
-+enum set_buffer_size_side {
-+ INPUT,
-+ OUTPUT,
-+};
-+
- static int
--setup_audiounit_stream(cubeb_stream * stm)
-+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
-+{
-+ AudioUnit au = stm->output_unit;
-+ AudioUnitScope au_scope = kAudioUnitScope_Input;
-+ AudioUnitElement au_element = AU_OUT_BUS;
-+ const char * au_type = "output";
-+
-+ if (set_side == INPUT) {
-+ au = stm->input_unit;
-+ au_scope = kAudioUnitScope_Output;
-+ au_element = AU_IN_BUS;
-+ au_type = "input";
-+ }
-+
-+ uint32_t buffer_frames = 0;
-+ UInt32 size = sizeof(buffer_frames);
-+ int r = AudioUnitGetProperty(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ au_scope,
-+ au_element,
-+ &buffer_frames,
-+ &size);
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ return CUBEB_ERROR;
-+ }
-+
-+ if (new_size_frames == buffer_frames) {
-+ LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
-+ return CUBEB_OK;
-+ }
-+
-+ r = AudioUnitAddPropertyListener(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ buffer_size_changed_callback,
-+ stm);
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ return CUBEB_ERROR;
-+ }
-+
-+ stm->buffer_size_change_state = false;
-+
-+ r = AudioUnitSetProperty(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ au_scope,
-+ au_element,
-+ &new_size_frames,
-+ sizeof(new_size_frames));
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+
-+ r = AudioUnitRemovePropertyListenerWithUserData(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ buffer_size_changed_callback,
-+ stm);
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ }
-+
-+ return CUBEB_ERROR;
-+ }
-+
-+ int count = 0;
-+ while (!stm->buffer_size_change_state && count++ < 30) {
-+ struct timespec req, rem;
-+ req.tv_sec = 0;
-+ req.tv_nsec = 100000000L; // 0.1 sec
-+ if (nanosleep(&req , &rem) < 0 ) {
-+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
-+ }
-+ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
-+ }
-+
-+ r = AudioUnitRemovePropertyListenerWithUserData(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ buffer_size_changed_callback,
-+ stm);
-+ if (r != noErr) {
-+ return CUBEB_ERROR;
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ }
-+
-+ if (!stm->buffer_size_change_state && count >= 30) {
-+ LOG("(%p) Error, did not get buffer size change callback ...", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_configure_input(cubeb_stream * stm)
-+{
-+ int r = 0;
-+ UInt32 size;
-+ AURenderCallbackStruct aurcbs_in;
-+
-+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
-+ stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
-+ stm->input_stream_params.format, stm->latency_frames);
-+
-+ /* Get input device sample rate. */
-+ AudioStreamBasicDescription input_hw_desc;
-+ size = sizeof(AudioStreamBasicDescription);
-+ r = AudioUnitGetProperty(stm->input_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Input,
-+ AU_IN_BUS,
-+ &input_hw_desc,
-+ &size);
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+ stm->input_hw_rate = input_hw_desc.mSampleRate;
-+ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
-+
-+ /* Set format description according to the input params. */
-+ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Setting format description for input failed.", stm);
-+ return r;
-+ }
-+
-+ // Use latency to set buffer size
-+ stm->input_buffer_frames = stm->latency_frames;
-+ r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Error in change input buffer size.", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ AudioStreamBasicDescription src_desc = stm->input_desc;
-+ /* Input AudioUnit must be configured with device's sample rate.
-+ we will resample inside input callback. */
-+ src_desc.mSampleRate = stm->input_hw_rate;
-+
-+ r = AudioUnitSetProperty(stm->input_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Output,
-+ AU_IN_BUS,
-+ &src_desc,
-+ sizeof(AudioStreamBasicDescription));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ /* Frames per buffer in the input callback. */
-+ r = AudioUnitSetProperty(stm->input_unit,
-+ kAudioUnitProperty_MaximumFramesPerSlice,
-+ kAudioUnitScope_Global,
-+ AU_IN_BUS,
-+ &stm->input_buffer_frames,
-+ sizeof(UInt32));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ // Input only capacity
-+ unsigned int array_capacity = 1;
-+ if (has_output(stm)) {
-+ // Full-duplex increase capacity
-+ array_capacity = 8;
-+ }
-+ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
-+ return CUBEB_ERROR;
-+ }
-+
-+ assert(stm->input_unit != NULL);
-+ aurcbs_in.inputProc = audiounit_input_callback;
-+ aurcbs_in.inputProcRefCon = stm;
-+
-+ r = AudioUnitSetProperty(stm->input_unit,
-+ kAudioOutputUnitProperty_SetInputCallback,
-+ kAudioUnitScope_Global,
-+ AU_OUT_BUS,
-+ &aurcbs_in,
-+ sizeof(aurcbs_in));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
-+ return CUBEB_ERROR;
-+ }
-+ LOG("(%p) Input audiounit init successfully.", stm);
-+
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_configure_output(cubeb_stream * stm)
-+{
-+ int r;
-+ AURenderCallbackStruct aurcbs_out;
-+ UInt32 size;
-+
-+
-+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
-+ stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
-+ stm->output_stream_params.format, stm->latency_frames);
-+
-+ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not initialize the audio stream description.", stm);
-+ return r;
-+ }
-+
-+ /* Get output device sample rate. */
-+ AudioStreamBasicDescription output_hw_desc;
-+ size = sizeof(AudioStreamBasicDescription);
-+ memset(&output_hw_desc, 0, size);
-+ r = AudioUnitGetProperty(stm->output_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Output,
-+ AU_OUT_BUS,
-+ &output_hw_desc,
-+ &size);
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+ stm->output_hw_rate = output_hw_desc.mSampleRate;
-+ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
-+
-+ r = AudioUnitSetProperty(stm->output_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Input,
-+ AU_OUT_BUS,
-+ &stm->output_desc,
-+ sizeof(AudioStreamBasicDescription));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Error in change output buffer size.", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ /* Frames per buffer in the input callback. */
-+ r = AudioUnitSetProperty(stm->output_unit,
-+ kAudioUnitProperty_MaximumFramesPerSlice,
-+ kAudioUnitScope_Global,
-+ AU_OUT_BUS,
-+ &stm->latency_frames,
-+ sizeof(UInt32));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ assert(stm->output_unit != NULL);
-+ aurcbs_out.inputProc = audiounit_output_callback;
-+ aurcbs_out.inputProcRefCon = stm;
-+ r = AudioUnitSetProperty(stm->output_unit,
-+ kAudioUnitProperty_SetRenderCallback,
-+ kAudioUnitScope_Global,
-+ AU_OUT_BUS,
-+ &aurcbs_out,
-+ sizeof(aurcbs_out));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ LOG("(%p) Output audiounit init successfully.", stm);
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_setup_stream(cubeb_stream * stm)
- {
- stm->mutex.assert_current_thread_owns();
-
-- int r;
-- AURenderCallbackStruct aurcbs_in;
-- AURenderCallbackStruct aurcbs_out;
-- UInt32 size;
--
-+ int r = 0;
- if (has_input(stm)) {
- r = audiounit_create_unit(&stm->input_unit, true,
- &stm->input_stream_params,
- stm->input_device);
- if (r != CUBEB_OK) {
- LOG("(%p) AudioUnit creation for input failed.", stm);
- return r;
- }
-@@ -1249,180 +1622,46 @@ setup_audiounit_stream(cubeb_stream * st
- &stm->output_stream_params,
- stm->output_device);
- if (r != CUBEB_OK) {
- LOG("(%p) AudioUnit creation for output failed.", stm);
- return r;
- }
- }
-
-+ /* Latency cannot change if another stream is operating in parallel. In this case
-+ * latecy is set to the other stream value. */
-+ if (stm->context->active_streams > 1) {
-+ LOG("(%p) More than one active stream, use global latency.", stm);
-+ stm->latency_frames = stm->context->global_latency_frames;
-+ } else {
-+ /* Silently clamp the latency down to the platform default, because we
-+ * synthetize the clock from the callbacks, and we want the clock to update
-+ * often. */
-+ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
-+ assert(stm->latency_frames); // Ungly error check
-+ audiounit_set_global_latency(stm, stm->latency_frames);
-+ }
-+
- /* Setup Input Stream! */
- if (has_input(stm)) {
-- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
-- stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
-- stm->input_stream_params.format, stm->latency_frames);
-- /* Get input device sample rate. */
-- AudioStreamBasicDescription input_hw_desc;
-- size = sizeof(AudioStreamBasicDescription);
-- r = AudioUnitGetProperty(stm->input_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Input,
-- AU_IN_BUS,
-- &input_hw_desc,
-- &size);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
-- stm->input_hw_rate = input_hw_desc.mSampleRate;
-- LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
--
-- /* Set format description according to the input params. */
-- r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
-+ r = audiounit_configure_input(stm);
- if (r != CUBEB_OK) {
-- LOG("(%p) Setting format description for input failed.", stm);
-+ LOG("(%p) Configure audiounit input failed.", stm);
- return r;
- }
--
-- // Use latency to set buffer size
-- stm->input_buffer_frames = stm->latency_frames;
-- LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames));
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioDevicePropertyBufferFrameSize,
-- kAudioUnitScope_Output,
-- AU_IN_BUS,
-- &stm->input_buffer_frames,
-- sizeof(UInt32));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-- return CUBEB_ERROR;
-- }
--
-- AudioStreamBasicDescription src_desc = stm->input_desc;
-- /* Input AudioUnit must be configured with device's sample rate.
-- we will resample inside input callback. */
-- src_desc.mSampleRate = stm->input_hw_rate;
--
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Output,
-- AU_IN_BUS,
-- &src_desc,
-- sizeof(AudioStreamBasicDescription));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
--
-- /* Frames per buffer in the input callback. */
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioUnitProperty_MaximumFramesPerSlice,
-- kAudioUnitScope_Output,
-- AU_IN_BUS,
-- &stm->input_buffer_frames,
-- sizeof(UInt32));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
-- return CUBEB_ERROR;
-- }
--
-- // Input only capacity
-- unsigned int array_capacity = 1;
-- if (has_output(stm)) {
-- // Full-duplex increase capacity
-- array_capacity = 8;
-- }
-- if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
-- return CUBEB_ERROR;
-- }
--
-- assert(stm->input_unit != NULL);
-- aurcbs_in.inputProc = audiounit_input_callback;
-- aurcbs_in.inputProcRefCon = stm;
--
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioOutputUnitProperty_SetInputCallback,
-- kAudioUnitScope_Global,
-- AU_OUT_BUS,
-- &aurcbs_in,
-- sizeof(aurcbs_in));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
-- return CUBEB_ERROR;
-- }
-- LOG("(%p) Input audiounit init successfully.", stm);
- }
-
- /* Setup Output Stream! */
- if (has_output(stm)) {
-- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
-- stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
-- stm->output_stream_params.format, stm->latency_frames);
-- r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
-+ r = audiounit_configure_output(stm);
- if (r != CUBEB_OK) {
-- LOG("(%p) Could not initialize the audio stream description.", stm);
-+ LOG("(%p) Configure audiounit output failed.", stm);
- return r;
- }
--
-- /* Get output device sample rate. */
-- AudioStreamBasicDescription output_hw_desc;
-- size = sizeof(AudioStreamBasicDescription);
-- memset(&output_hw_desc, 0, size);
-- r = AudioUnitGetProperty(stm->output_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Output,
-- AU_OUT_BUS,
-- &output_hw_desc,
-- &size);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
-- stm->output_hw_rate = output_hw_desc.mSampleRate;
-- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
--
-- r = AudioUnitSetProperty(stm->output_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Input,
-- AU_OUT_BUS,
-- &stm->output_desc,
-- sizeof(AudioStreamBasicDescription));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
--
-- // Use latency to calculate buffer size
-- uint32_t output_buffer_frames = stm->latency_frames;
-- LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames);
-- r = AudioUnitSetProperty(stm->output_unit,
-- kAudioDevicePropertyBufferFrameSize,
-- kAudioUnitScope_Input,
-- AU_OUT_BUS,
-- &output_buffer_frames,
-- sizeof(output_buffer_frames));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-- return CUBEB_ERROR;
-- }
--
-- assert(stm->output_unit != NULL);
-- aurcbs_out.inputProc = audiounit_output_callback;
-- aurcbs_out.inputProcRefCon = stm;
-- r = AudioUnitSetProperty(stm->output_unit,
-- kAudioUnitProperty_SetRenderCallback,
-- kAudioUnitScope_Global,
-- AU_OUT_BUS,
-- &aurcbs_out,
-- sizeof(aurcbs_out));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
-- return CUBEB_ERROR;
-- }
-- LOG("(%p) Output audiounit init successfully.", stm);
- }
-
- // Setting the latency doesn't work well for USB headsets (eg. plantronics).
- // Keep the default latency for now.
- #if 0
- buffer_size = latency;
-
- /* Get the range of latency this particular device can work with, and clamp
-@@ -1535,63 +1774,60 @@ audiounit_stream_init(cubeb * context,
- void * user_ptr)
- {
- cubeb_stream * stm;
- int r;
-
- assert(context);
- *stream = NULL;
-
-+ assert(latency_frames > 0);
- if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
- LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX);
- return CUBEB_ERROR;
- }
-- context->active_streams += 1;
-
- stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream));
- assert(stm);
- // Placement new to call the ctors of cubeb_stream members.
- new (stm) cubeb_stream();
-
- /* These could be different in the future if we have both
- * full-duplex stream and different devices for input vs output. */
- stm->context = context;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
-+ stm->latency_frames = latency_frames;
- stm->device_changed_callback = NULL;
- if (input_stream_params) {
- stm->input_stream_params = *input_stream_params;
- stm->input_device = input_device;
- stm->is_default_input = input_device == nullptr ||
- (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) ==
- reinterpret_cast<intptr_t>(input_device));
- }
- if (output_stream_params) {
- stm->output_stream_params = *output_stream_params;
- stm->output_device = output_device;
- }
-
- /* Init data members where necessary */
- stm->hw_latency_frames = UINT64_MAX;
-
-- /* Silently clamp the latency down to the platform default, because we
-- * synthetize the clock from the callbacks, and we want the clock to update
-- * often. */
-- stm->latency_frames = audiounit_clamp_latency(stm, latency_frames);
-- assert(latency_frames > 0);
--
- stm->switching_device = false;
-
-+ auto_lock context_lock(context->mutex);
- {
- // It's not critical to lock here, because no other thread has been started
- // yet, but it allows to assert that the lock has been taken in
-- // `setup_audiounit_stream`.
-+ // `audiounit_setup_stream`.
-+ context->active_streams += 1;
- auto_lock lock(stm->mutex);
-- r = setup_audiounit_stream(stm);
-+ r = audiounit_setup_stream(stm);
- }
-
- if (r != CUBEB_OK) {
- LOG("(%p) Could not setup the audiounit stream.", stm);
- audiounit_stream_destroy(stm);
- return r;
- }
-
-@@ -1602,17 +1838,17 @@ audiounit_stream_init(cubeb * context,
- }
-
- *stream = stm;
- LOG("Cubeb stream (%p) init successful.", stm);
- return CUBEB_OK;
- }
-
- static void
--close_audiounit_stream(cubeb_stream * stm)
-+audiounit_close_stream(cubeb_stream *stm)
- {
- stm->mutex.assert_current_thread_owns();
- if (stm->input_unit) {
- AudioUnitUninitialize(stm->input_unit);
- AudioComponentInstanceDispose(stm->input_unit);
- }
-
- audiounit_destroy_input_linear_buffer(stm);
-@@ -1625,33 +1861,36 @@ close_audiounit_stream(cubeb_stream * st
- cubeb_resampler_destroy(stm->resampler);
- }
-
- static void
- audiounit_stream_destroy(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-+ auto_lock context_locl(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- {
- auto_lock lock(stm->mutex);
-- close_audiounit_stream(stm);
-+ audiounit_close_stream(stm);
- }
-
- #if !TARGET_OS_IPHONE
- int r = audiounit_uninstall_device_changed_callback(stm);
- if (r != CUBEB_OK) {
- LOG("(%p) Could not uninstall the device changed callback", stm);
- }
- #endif
-
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
-
-+ LOG("Cubeb stream (%p) destroyed successful.", stm);
-+
- stm->~cubeb_stream();
- free(stm);
- }
-
- void
- audiounit_stream_start_internal(cubeb_stream * stm)
- {
- OSStatus r;
-@@ -1666,16 +1905,17 @@ audiounit_stream_start_internal(cubeb_st
- }
-
- static int
- audiounit_stream_start(cubeb_stream * stm)
- {
- stm->shutdown = false;
- stm->draining = false;
-
-+ auto_lock context_locl(stm->context->mutex);
- audiounit_stream_start_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- LOG("Cubeb stream (%p) started successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -1693,16 +1933,17 @@ audiounit_stream_stop_internal(cubeb_str
- }
- }
-
- static int
- audiounit_stream_stop(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-+ auto_lock context_locl(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- LOG("Cubeb stream (%p) stopped successfully.", stm);
- return CUBEB_OK;
- }
-
diff --git a/media/libcubeb/prevent-double-free.patch b/media/libcubeb/prevent-double-free.patch
deleted file mode 100644
index aa5356d7f2..0000000000
--- a/media/libcubeb/prevent-double-free.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From f82f15635e09aac4f07d2ddac3d53c84b593d911 Mon Sep 17 00:00:00 2001
-From: Paul Adenot <paul@paul.cx>
-Date: Mon, 16 Jan 2017 04:49:41 -0800
-Subject: [PATCH 1/1] Prevent double-free when doing an emergency bailout from
- the rendering thread.
-
-This caused gecko bug 1326176.
-
-This was caused by the fact that we would null out `stm->thread` when in
-fact it was still running, so we would delete `stm->emergency_bailout`
-twice, because we would return true from `stop_and_join_thread`.
----
- src/cubeb_wasapi.cpp | 15 ++++++++++-----
- 1 file changed, 10 insertions(+), 5 deletions(-)
-
-diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
-index 63c12ac..2920b5d 100644
---- a/src/cubeb_wasapi.cpp
-+++ b/src/cubeb_wasapi.cpp
-@@ -1230,13 +1230,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
- rv = false;
- }
-
-- LOG("Closing thread.");
-
-- CloseHandle(stm->thread);
-- stm->thread = NULL;
-+ // Only attempts to close and null out the thread and event if the
-+ // WaitForSingleObject above succeeded, so that calling this function again
-+ // attemps to clean up the thread and event each time.
-+ if (rv) {
-+ LOG("Closing thread.");
-+ CloseHandle(stm->thread);
-+ stm->thread = NULL;
-
-- CloseHandle(stm->shutdown_event);
-- stm->shutdown_event = 0;
-+ CloseHandle(stm->shutdown_event);
-+ stm->shutdown_event = 0;
-+ }
-
- return rv;
- }
---
-2.7.4
-
diff --git a/media/libcubeb/src/android/audiotrack_definitions.h b/media/libcubeb/src/android/audiotrack_definitions.h
deleted file mode 100644
index cd501533d9..0000000000
--- a/media/libcubeb/src/android/audiotrack_definitions.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdint.h>
-
-/*
- * The following definitions are copied from the android sources. Only the
- * relevant enum member and values needed are copied.
- */
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
- */
-typedef int32_t status_t;
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
- */
-struct Buffer {
- uint32_t flags;
- int channelCount;
- int format;
- size_t frameCount;
- size_t size;
- union {
- void* raw;
- short* i16;
- int8_t* i8;
- };
-};
-
-enum event_type {
- EVENT_MORE_DATA = 0,
- EVENT_UNDERRUN = 1,
- EVENT_LOOP_END = 2,
- EVENT_MARKER = 3,
- EVENT_NEW_POS = 4,
- EVENT_BUFFER_END = 5
-};
-
-/**
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
- * and
- * https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
- */
-
-#define AUDIO_STREAM_TYPE_MUSIC 3
-
-enum {
- AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
- AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
- AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
- AUDIO_CHANNEL_OUT_STEREO_ICS = (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
-} AudioTrack_ChannelMapping_ICS;
-
-enum {
- AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
- AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
- AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy,
- AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy | AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
-} AudioTrack_ChannelMapping_Legacy;
-
-typedef enum {
- AUDIO_FORMAT_PCM = 0x00000000,
- AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
- AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
-} AudioTrack_SampleType;
-
diff --git a/media/libcubeb/src/android/cubeb-output-latency.h b/media/libcubeb/src/android/cubeb-output-latency.h
new file mode 100644
index 0000000000..870a884a3c
--- /dev/null
+++ b/media/libcubeb/src/android/cubeb-output-latency.h
@@ -0,0 +1,76 @@
+#ifndef _CUBEB_OUTPUT_LATENCY_H_
+#define _CUBEB_OUTPUT_LATENCY_H_
+
+#include "../cubeb-jni.h"
+#include "cubeb_media_library.h"
+#include <stdbool.h>
+
+struct output_latency_function {
+ media_lib * from_lib;
+ cubeb_jni * from_jni;
+ int version;
+};
+
+typedef struct output_latency_function output_latency_function;
+
+const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
+
+output_latency_function *
+cubeb_output_latency_load_method(int version)
+{
+ output_latency_function * ol = NULL;
+ ol = calloc(1, sizeof(output_latency_function));
+
+ ol->version = version;
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ ol->from_jni = cubeb_jni_init();
+ return ol;
+ }
+
+ ol->from_lib = cubeb_load_media_library();
+ return ol;
+}
+
+bool
+cubeb_output_latency_method_is_loaded(output_latency_function * ol)
+{
+ assert(ol);
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ return !!ol->from_jni;
+ }
+
+ return !!ol->from_lib;
+}
+
+void
+cubeb_output_latency_unload_method(output_latency_function * ol)
+{
+ if (!ol) {
+ return;
+ }
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
+ cubeb_jni_destroy(ol->from_jni);
+ }
+
+ if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
+ cubeb_close_media_library(ol->from_lib);
+ }
+
+ free(ol);
+}
+
+uint32_t
+cubeb_get_output_latency(output_latency_function * ol)
+{
+ assert(cubeb_output_latency_method_is_loaded(ol));
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ return cubeb_get_output_latency_from_jni(ol->from_jni);
+ }
+
+ return cubeb_get_output_latency_from_media_library(ol->from_lib);
+}
+
+#endif // _CUBEB_OUTPUT_LATENCY_H_
diff --git a/media/libcubeb/src/android/cubeb_media_library.h b/media/libcubeb/src/android/cubeb_media_library.h
new file mode 100644
index 0000000000..27fbc86ec2
--- /dev/null
+++ b/media/libcubeb/src/android/cubeb_media_library.h
@@ -0,0 +1,64 @@
+#ifndef _CUBEB_MEDIA_LIBRARY_H_
+#define _CUBEB_MEDIA_LIBRARY_H_
+
+struct media_lib {
+ void * libmedia;
+ int32_t (*get_output_latency)(uint32_t * latency, int stream_type);
+};
+
+typedef struct media_lib media_lib;
+
+media_lib *
+cubeb_load_media_library()
+{
+ media_lib ml = {0};
+ ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
+ if (!ml.libmedia) {
+ return NULL;
+ }
+
+ // Get the latency, in ms, from AudioFlinger. First, try the most recent
+ // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
+ // audio_stream_type_t streamType)
+ ml.get_output_latency = dlsym(
+ ml.libmedia,
+ "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
+ if (!ml.get_output_latency) {
+ // In case of failure, try the signature from legacy version.
+ // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
+ ml.get_output_latency =
+ dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
+ if (!ml.get_output_latency) {
+ return NULL;
+ }
+ }
+
+ media_lib * rv = NULL;
+ rv = calloc(1, sizeof(media_lib));
+ assert(rv);
+ *rv = ml;
+ return rv;
+}
+
+void
+cubeb_close_media_library(media_lib * ml)
+{
+ dlclose(ml->libmedia);
+ ml->libmedia = NULL;
+ ml->get_output_latency = NULL;
+ free(ml);
+}
+
+uint32_t
+cubeb_get_output_latency_from_media_library(media_lib * ml)
+{
+ uint32_t latency = 0;
+ const int audio_stream_type_music = 3;
+ int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
+ if (r) {
+ return 0;
+ }
+ return latency;
+}
+
+#endif // _CUBEB_MEDIA_LIBRARY_H_
diff --git a/media/libcubeb/src/android/sles_definitions.h b/media/libcubeb/src/android/sles_definitions.h
index 1b1ace567e..b107003d1b 100644
--- a/media/libcubeb/src/android/sles_definitions.h
+++ b/media/libcubeb/src/android/sles_definitions.h
@@ -29,24 +29,23 @@
/** Audio recording preset */
/** Audio recording preset key */
-#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset")
+#define SL_ANDROID_KEY_RECORDING_PRESET \
+ ((const SLchar *)"androidRecordingPreset")
/** Audio recording preset values */
/** preset "none" cannot be set, it is used to indicate the current settings
* do not match any of the presets. */
-#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000)
+#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000)
/** generic recording configuration on the platform */
-#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001)
+#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001)
/** uses the microphone audio source with the same orientation as the camera
* if available, the main device microphone otherwise */
-#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002)
+#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002)
/** uses the main microphone tuned for voice recognition */
-#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003)
+#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003)
/** uses the main microphone tuned for audio communications */
-#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004)
-
-/** Audio recording get session ID (read only) */
-/** Audio recording get session ID key */
-#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId")
+#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004)
+/** uses the main microphone unprocessed */
+#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
/*---------------------------------------------------------------------------*/
/* Android AudioPlayer configuration */
@@ -54,24 +53,52 @@
/** Audio playback stream type */
/** Audio playback stream type key */
-#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType")
+#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType")
/** Audio playback stream type values */
/* same as android.media.AudioManager.STREAM_VOICE_CALL */
-#define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000)
+#define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000)
/* same as android.media.AudioManager.STREAM_SYSTEM */
-#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001)
+#define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001)
/* same as android.media.AudioManager.STREAM_RING */
-#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002)
+#define SL_ANDROID_STREAM_RING ((SLint32)0x00000002)
/* same as android.media.AudioManager.STREAM_MUSIC */
-#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003)
+#define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003)
/* same as android.media.AudioManager.STREAM_ALARM */
-#define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004)
+#define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004)
/* same as android.media.AudioManager.STREAM_NOTIFICATION */
-#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
-/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */
-#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006)
-/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */
-#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007)
+#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005)
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer and AudioRecorder configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio Performance mode.
+ * Performance mode tells the framework how to configure the audio path
+ * for a player or recorder according to application performance and
+ * functional requirements.
+ * It affects the output or input latency based on acceptable tradeoffs on
+ * battery drain and use of pre or post processing effects.
+ * Performance mode should be set before realizing the object and should be
+ * read after realizing the object to check if the requested mode could be
+ * granted or not.
+ */
+/** Audio Performance mode key */
+#define SL_ANDROID_KEY_PERFORMANCE_MODE \
+ ((const SLchar *)"androidPerformanceMode")
+
+/** Audio performance values */
+/* No specific performance requirement. Allows HW and SW pre/post
+ * processing. */
+#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
+/* Priority given to latency. No HW or software pre/post processing.
+ * This is the default if no performance mode is specified. */
+#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
+/* Priority given to latency while still allowing HW pre and post
+ * processing. */
+#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
+/* Priority given to power saving if latency is not a concern.
+ * Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
diff --git a/media/libcubeb/src/audiotrack_definitions.h b/media/libcubeb/src/audiotrack_definitions.h
deleted file mode 100644
index 2beeca2de7..0000000000
--- a/media/libcubeb/src/audiotrack_definitions.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cubeb/cubeb-stdint.h>
-
-/*
- * The following definitions are copied from the android sources. Only the
- * relevant enum member and values needed are copied.
- */
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
- */
-typedef int32_t status_t;
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
- */
-struct Buffer {
- uint32_t flags;
- int channelCount;
- int format;
- size_t frameCount;
- size_t size;
- union {
- void* raw;
- short* i16;
- int8_t* i8;
- };
-};
-
-enum event_type {
- EVENT_MORE_DATA = 0,
- EVENT_UNDERRUN = 1,
- EVENT_LOOP_END = 2,
- EVENT_MARKER = 3,
- EVENT_NEW_POS = 4,
- EVENT_BUFFER_END = 5
-};
-
-/**
- * From https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
- */
-
-#define AUDIO_STREAM_TYPE_MUSIC 3
-
-enum {
- AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
- AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
- AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
- AUDIO_CHANNEL_OUT_STEREO_ICS = (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
-} AudioTrack_ChannelMapping_ICS;
-
-typedef enum {
- AUDIO_FORMAT_PCM = 0x00000000,
- AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
- AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
-} AudioTrack_SampleType;
-
diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h
index dfcc186c57..79326e1ebf 100644
--- a/media/libcubeb/src/cubeb-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -8,6 +8,7 @@
#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
#include "cubeb/cubeb.h"
+#include "cubeb_assert.h"
#include "cubeb_log.h"
#include <stdio.h>
#include <string.h>
@@ -20,7 +21,7 @@
#define CLANG_ANALYZER_NORETURN
#endif // ifndef CLANG_ANALYZER_NORETURN
#endif // __has_feature(attribute_analyzer_noreturn)
-#else // __clang__
+#else // __clang__
#define CLANG_ANALYZER_NORETURN
#endif
@@ -28,59 +29,46 @@
extern "C" {
#endif
-/* Crash the caller. */
-void cubeb_crash() CLANG_ANALYZER_NORETURN;
-
#if defined(__cplusplus)
}
#endif
struct cubeb_ops {
- int (* init)(cubeb ** context, char const * context_name);
- char const * (* get_backend_id)(cubeb * context);
- int (* get_max_channel_count)(cubeb * context, uint32_t * max_channels);
- int (* get_min_latency)(cubeb * context,
- cubeb_stream_params params,
- uint32_t * latency_ms);
- int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
- int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection);
- void (* destroy)(cubeb * context);
- int (* stream_init)(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr);
- void (* stream_destroy)(cubeb_stream * stream);
- int (* stream_start)(cubeb_stream * stream);
- int (* stream_stop)(cubeb_stream * stream);
- int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
- int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
- int (* stream_set_volume)(cubeb_stream * stream, float volumes);
- int (* stream_set_panning)(cubeb_stream * stream, float panning);
- int (* stream_get_current_device)(cubeb_stream * stream,
- cubeb_device ** const device);
- int (* stream_device_destroy)(cubeb_stream * stream,
- cubeb_device * device);
- int (* stream_register_device_changed_callback)(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback);
- int (* register_device_collection_changed)(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback callback,
- void * user_ptr);
+ int (*init)(cubeb ** context, char const * context_name);
+ char const * (*get_backend_id)(cubeb * context);
+ int (*get_max_channel_count)(cubeb * context, uint32_t * max_channels);
+ int (*get_min_latency)(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_ms);
+ int (*get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
+ int (*enumerate_devices)(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection);
+ int (*device_collection_destroy)(cubeb * context,
+ cubeb_device_collection * collection);
+ void (*destroy)(cubeb * context);
+ int (*stream_init)(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+ void (*stream_destroy)(cubeb_stream * stream);
+ int (*stream_start)(cubeb_stream * stream);
+ int (*stream_stop)(cubeb_stream * stream);
+ int (*stream_get_position)(cubeb_stream * stream, uint64_t * position);
+ int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (*stream_set_volume)(cubeb_stream * stream, float volumes);
+ int (*stream_set_name)(cubeb_stream * stream, char const * stream_name);
+ int (*stream_get_current_device)(cubeb_stream * stream,
+ cubeb_device ** const device);
+ int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device);
+ int (*stream_register_device_changed_callback)(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback);
+ int (*register_device_collection_changed)(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr);
};
-#define XASSERT(expr) do { \
- if (!(expr)) { \
- fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
- cubeb_crash(); \
- } \
- } while (0)
-
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
diff --git a/media/libcubeb/src/cubeb-jni.cpp b/media/libcubeb/src/cubeb-jni.cpp
new file mode 100644
index 0000000000..d50326606b
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni.cpp
@@ -0,0 +1,80 @@
+#include "cubeb-jni-instances.h"
+#include "jni.h"
+#include <assert.h>
+
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+struct cubeb_jni {
+ jobject s_audio_manager_obj = nullptr;
+ jclass s_audio_manager_class = nullptr;
+ jmethodID s_get_output_latency_id = nullptr;
+};
+
+extern "C" cubeb_jni *
+cubeb_jni_init()
+{
+ jobject ctx_obj = cubeb_jni_get_context_instance();
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ if (!jni_env || !ctx_obj) {
+ return nullptr;
+ }
+
+ cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
+ assert(cubeb_jni_ptr);
+
+ // Find the audio manager object and make it global to call it from another
+ // method
+ jclass context_class = jni_env->FindClass("android/content/Context");
+ jfieldID audio_service_field = jni_env->GetStaticFieldID(
+ context_class, "AUDIO_SERVICE", "Ljava/lang/String;");
+ jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class,
+ audio_service_field);
+ jmethodID get_system_service_id =
+ jni_env->GetMethodID(context_class, "getSystemService",
+ "(Ljava/lang/String;)Ljava/lang/Object;");
+ jobject audio_manager_obj =
+ jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr);
+ cubeb_jni_ptr->s_audio_manager_obj =
+ reinterpret_cast<jobject>(jni_env->NewGlobalRef(audio_manager_obj));
+
+ // Make the audio manager class a global reference in order to preserve method
+ // id
+ jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager");
+ cubeb_jni_ptr->s_audio_manager_class =
+ reinterpret_cast<jclass>(jni_env->NewGlobalRef(audio_manager_class));
+ cubeb_jni_ptr->s_get_output_latency_id =
+ jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I");
+
+ jni_env->DeleteLocalRef(ctx_obj);
+ jni_env->DeleteLocalRef(context_class);
+ jni_env->DeleteLocalRef(jstr);
+ jni_env->DeleteLocalRef(audio_manager_obj);
+ jni_env->DeleteLocalRef(audio_manager_class);
+
+ return cubeb_jni_ptr;
+}
+
+extern "C" int
+cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ return jni_env->CallIntMethod(
+ cubeb_jni_ptr->s_audio_manager_obj,
+ cubeb_jni_ptr->s_get_output_latency_id,
+ AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC
+}
+
+extern "C" void
+cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ assert(jni_env);
+
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj);
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class);
+
+ delete cubeb_jni_ptr;
+}
diff --git a/media/libcubeb/src/cubeb-jni.h b/media/libcubeb/src/cubeb-jni.h
new file mode 100644
index 0000000000..c4a712a062
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni.h
@@ -0,0 +1,13 @@
+#ifndef _CUBEB_JNI_H_
+#define _CUBEB_JNI_H_
+
+typedef struct cubeb_jni cubeb_jni;
+
+cubeb_jni *
+cubeb_jni_init();
+int
+cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
+void
+cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
+
+#endif // _CUBEB_JNI_H_
diff --git a/media/libcubeb/src/cubeb-sles.h b/media/libcubeb/src/cubeb-sles.h
deleted file mode 100644
index 0725523528..0000000000
--- a/media/libcubeb/src/cubeb-sles.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef _CUBEB_SLES_H_
-#define _CUBEB_SLES_H_
-#include <OpenSLESProvider.h>
-#include <SLES/OpenSLES.h>
-
-static SLresult cubeb_get_sles_engine(
- SLObjectItf *pEngine,
- SLuint32 numOptions,
- const SLEngineOption *pEngineOptions,
- SLuint32 numInterfaces,
- const SLInterfaceID *pInterfaceIds,
- const SLboolean * pInterfaceRequired) {
- return mozilla_get_sles_engine(pEngine, numOptions, pEngineOptions);
-}
-
-static void cubeb_destroy_sles_engine(SLObjectItf *self) {
- mozilla_destroy_sles_engine(self);
-}
-
-/* Only synchronous operation is supported, as if the second
- parameter was FALSE. */
-static SLresult cubeb_realize_sles_engine(SLObjectItf self) {
- return mozilla_realize_sles_engine(self);
-}
-
-#endif
diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c
index a239319a46..b3d32eea3d 100644
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -5,88 +5,121 @@
* accompanying file LICENSE for details.
*/
#undef NDEBUG
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
+#include <string.h>
-#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
-
-cubeb_log_level g_log_level;
-cubeb_log_callback g_log_callback;
+#define NELEMS(x) ((int)(sizeof(x) / sizeof(x[0])))
struct cubeb {
struct cubeb_ops * ops;
};
struct cubeb_stream {
+ /*
+ * Note: All implementations of cubeb_stream must keep the following
+ * layout.
+ */
struct cubeb * context;
+ void * user_ptr;
};
#if defined(USE_PULSE)
-int pulse_init(cubeb ** context, char const * context_name);
+int
+pulse_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_PULSE_RUST)
+int
+pulse_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_JACK)
-int jack_init (cubeb ** context, char const * context_name);
+int
+jack_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_ALSA)
-int alsa_init(cubeb ** context, char const * context_name);
+int
+alsa_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AUDIOUNIT)
-int audiounit_init(cubeb ** context, char const * context_name);
+int
+audiounit_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+int
+audiounit_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_WINMM)
-int winmm_init(cubeb ** context, char const * context_name);
+int
+winmm_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_WASAPI)
-int wasapi_init(cubeb ** context, char const * context_name);
+int
+wasapi_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_SNDIO)
-int sndio_init(cubeb ** context, char const * context_name);
+int
+sndio_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_SUN)
+int
+sun_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_OPENSL)
-int opensl_init(cubeb ** context, char const * context_name);
+int
+opensl_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_OSS)
+int
+oss_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AAUDIO)
+int
+aaudio_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AUDIOTRACK)
-int audiotrack_init(cubeb ** context, char const * context_name);
+int
+audiotrack_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_KAI)
-int kai_init(cubeb ** context, char const * context_name);
-#endif
-#if defined(USE_SUN)
-int sunaudio_init(cubeb ** context, char const * context_name);
+int
+kai_init(cubeb ** context, char const * context_name);
#endif
-
static int
validate_stream_params(cubeb_stream_params * input_stream_params,
cubeb_stream_params * output_stream_params)
{
XASSERT(input_stream_params || output_stream_params);
if (output_stream_params) {
- if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
- output_stream_params->channels < 1 || output_stream_params->channels > 8) {
+ if (output_stream_params->rate < 1000 ||
+ output_stream_params->rate > 192000 ||
+ output_stream_params->channels < 1 ||
+ output_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
if (input_stream_params) {
- if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
- input_stream_params->channels < 1 || input_stream_params->channels > 8) {
+ if (input_stream_params->rate < 1000 ||
+ input_stream_params->rate > 192000 ||
+ input_stream_params->channels < 1 ||
+ input_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
// Rate and sample format must be the same for input and output, if using a
// duplex stream
if (input_stream_params && output_stream_params) {
- if (input_stream_params->rate != output_stream_params->rate ||
+ if (input_stream_params->rate != output_stream_params->rate ||
input_stream_params->format != output_stream_params->format) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
- cubeb_stream_params * params = input_stream_params ?
- input_stream_params : output_stream_params;
+ cubeb_stream_params * params =
+ input_stream_params ? input_stream_params : output_stream_params;
switch (params->format) {
case CUBEB_SAMPLE_S16LE:
@@ -99,8 +132,6 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
return CUBEB_ERROR_INVALID_FORMAT;
}
-
-
static int
validate_latency(int latency)
{
@@ -111,18 +142,104 @@ validate_latency(int latency)
}
int
-cubeb_init(cubeb ** context, char const * context_name)
+cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name)
{
- int (* init[])(cubeb **, char const *) = {
+ int (*init_oneshot)(cubeb **, char const *) = NULL;
+
+ if (backend_name != NULL) {
+ if (!strcmp(backend_name, "pulse")) {
+#if defined(USE_PULSE)
+ init_oneshot = pulse_init;
+#endif
+ } else if (!strcmp(backend_name, "pulse-rust")) {
+#if defined(USE_PULSE_RUST)
+ init_oneshot = pulse_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "jack")) {
#if defined(USE_JACK)
- jack_init,
+ init_oneshot = jack_init;
+#endif
+ } else if (!strcmp(backend_name, "alsa")) {
+#if defined(USE_ALSA)
+ init_oneshot = alsa_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit")) {
+#if defined(USE_AUDIOUNIT)
+ init_oneshot = audiounit_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit-rust")) {
+#if defined(USE_AUDIOUNIT_RUST)
+ init_oneshot = audiounit_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "wasapi")) {
+#if defined(USE_WASAPI)
+ init_oneshot = wasapi_init;
+#endif
+ } else if (!strcmp(backend_name, "winmm")) {
+#if defined(USE_WINMM)
+ init_oneshot = winmm_init;
+#endif
+ } else if (!strcmp(backend_name, "sndio")) {
+#if defined(USE_SNDIO)
+ init_oneshot = sndio_init;
+#endif
+ } else if (!strcmp(backend_name, "sun")) {
+#if defined(USE_SUN)
+ init_oneshot = sun_init;
+#endif
+ } else if (!strcmp(backend_name, "opensl")) {
+#if defined(USE_OPENSL)
+ init_oneshot = opensl_init;
+#endif
+ } else if (!strcmp(backend_name, "oss")) {
+#if defined(USE_OSS)
+ init_oneshot = oss_init;
+#endif
+ } else if (!strcmp(backend_name, "aaudio")) {
+#if defined(USE_AAUDIO)
+ init_oneshot = aaudio_init;
+#endif
+ } else if (!strcmp(backend_name, "audiotrack")) {
+#if defined(USE_AUDIOTRACK)
+ init_oneshot = audiotrack_init;
+#endif
+ } else if (!strcmp(backend_name, "kai")) {
+#if defined(USE_KAI)
+ init_oneshot = kai_init;
+#endif
+ } else {
+ /* Already set */
+ }
+ }
+
+ int (*default_init[])(cubeb **, char const *) = {
+ /*
+ * init_oneshot must be at the top to allow user
+ * to override all other choices
+ */
+ init_oneshot,
+#if defined(USE_PULSE_RUST)
+ pulse_rust_init,
#endif
#if defined(USE_PULSE)
pulse_init,
#endif
+#if defined(USE_JACK)
+ jack_init,
+#endif
+#if defined(USE_SNDIO)
+ sndio_init,
+#endif
#if defined(USE_ALSA)
alsa_init,
#endif
+#if defined(USE_OSS)
+ oss_init,
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+ audiounit_rust_init,
+#endif
#if defined(USE_AUDIOUNIT)
audiounit_init,
#endif
@@ -132,21 +249,23 @@ cubeb_init(cubeb ** context, char const * context_name)
#if defined(USE_WINMM)
winmm_init,
#endif
-#if defined(USE_SNDIO)
- sndio_init,
+#if defined(USE_SUN)
+ sun_init,
#endif
#if defined(USE_OPENSL)
opensl_init,
#endif
+ // TODO: should probably be preferred over OpenSLES when available.
+ // Initialization will fail on old android devices.
+#if defined(USE_AAUDIO)
+ aaudio_init,
+#endif
#if defined(USE_AUDIOTRACK)
audiotrack_init,
#endif
#if defined(USE_KAI)
kai_init,
#endif
-#if defined(USE_SUN)
- sunaudio_init,
-#endif
};
int i;
@@ -154,10 +273,10 @@ cubeb_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR_INVALID_PARAMETER;
}
- for (i = 0; i < NELEMS(init); ++i) {
- if (init[i](context, context_name) == CUBEB_OK) {
+#define OK(fn) assert((*context)->ops->fn)
+ for (i = 0; i < NELEMS(default_init); ++i) {
+ if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
/* Assert that the minimal API is implemented. */
-#define OK(fn) assert((* context)->ops->fn)
OK(get_backend_id);
OK(destroy);
OK(stream_init);
@@ -168,7 +287,6 @@ cubeb_init(cubeb ** context, char const * context_name)
return CUBEB_OK;
}
}
-
return CUBEB_ERROR;
}
@@ -197,9 +315,10 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
}
int
-cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
+ uint32_t * latency_ms)
{
- if (!context || !latency_ms) {
+ if (!context || !params || !latency_ms) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
@@ -207,7 +326,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * la
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return context->ops->get_min_latency(context, params, latency_ms);
+ return context->ops->get_min_latency(context, *params, latency_ms);
}
int
@@ -235,36 +354,39 @@ cubeb_destroy(cubeb * context)
}
int
-cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
+cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
{
int r;
- if (!context || !stream) {
+ if (!context || !stream || !data_callback || !state_callback) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
- if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK ||
+ if ((r = validate_stream_params(input_stream_params, output_stream_params)) !=
+ CUBEB_OK ||
(r = validate_latency(latency)) != CUBEB_OK) {
return r;
}
- return context->ops->stream_init(context, stream, stream_name,
- input_device,
- input_stream_params,
- output_device,
- output_stream_params,
- latency,
- data_callback,
- state_callback,
- user_ptr);
+ r = context->ops->stream_init(context, stream, stream_name, input_device,
+ input_stream_params, output_device,
+ output_stream_params, latency, data_callback,
+ state_callback, user_ptr);
+
+ if (r == CUBEB_ERROR_INVALID_FORMAT) {
+ LOG("Invalid format, %p %p %d %d", output_stream_params,
+ input_stream_params,
+ output_stream_params && output_stream_params->format,
+ input_stream_params && input_stream_params->format);
+ }
+
+ return r;
}
void
@@ -322,6 +444,20 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
}
int
+cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ if (!stream || !latency) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_input_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_input_latency(stream, latency);
+}
+
+int
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
{
if (!stream || volume > 1.0 || volume < 0.0) {
@@ -335,21 +471,23 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume)
return stream->context->ops->stream_set_volume(stream, volume);
}
-int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
+int
+cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name)
{
- if (!stream || panning < -1.0 || panning > 1.0) {
+ if (!stream || !stream_name) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
- if (!stream->context->ops->stream_set_panning) {
+ if (!stream->context->ops->stream_set_name) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return stream->context->ops->stream_set_panning(stream, panning);
+ return stream->context->ops->stream_set_name(stream, stream_name);
}
-int cubeb_stream_get_current_device(cubeb_stream * stream,
- cubeb_device ** const device)
+int
+cubeb_stream_get_current_device(cubeb_stream * stream,
+ cubeb_device ** const device)
{
if (!stream || !device) {
return CUBEB_ERROR_INVALID_PARAMETER;
@@ -362,8 +500,8 @@ int cubeb_stream_get_current_device(cubeb_stream * stream,
return stream->context->ops->stream_get_current_device(stream, device);
}
-int cubeb_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * device)
+int
+cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
{
if (!stream || !device) {
return CUBEB_ERROR_INVALID_PARAMETER;
@@ -376,8 +514,10 @@ int cubeb_stream_device_destroy(cubeb_stream * stream,
return stream->context->ops->stream_device_destroy(stream, device);
}
-int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback)
+int
+cubeb_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback)
{
if (!stream) {
return CUBEB_ERROR_INVALID_PARAMETER;
@@ -387,59 +527,70 @@ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
+ return stream->context->ops->stream_register_device_changed_callback(
+ stream, device_changed_callback);
}
-static
-void log_device(cubeb_device_info * device_info)
+void *
+cubeb_stream_user_ptr(cubeb_stream * stream)
+{
+ if (!stream) {
+ return NULL;
+ }
+
+ return stream->user_ptr;
+}
+
+static void
+log_device(cubeb_device_info * device_info)
{
char devfmts[128] = "";
- const char * devtype, * devstate, * devdeffmt;
+ const char *devtype, *devstate, *devdeffmt;
switch (device_info->type) {
- case CUBEB_DEVICE_TYPE_INPUT:
- devtype = "input";
- break;
- case CUBEB_DEVICE_TYPE_OUTPUT:
- devtype = "output";
- break;
- case CUBEB_DEVICE_TYPE_UNKNOWN:
- default:
- devtype = "unknown?";
- break;
+ case CUBEB_DEVICE_TYPE_INPUT:
+ devtype = "input";
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ devtype = "output";
+ break;
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ default:
+ devtype = "unknown?";
+ break;
};
switch (device_info->state) {
- case CUBEB_DEVICE_STATE_DISABLED:
- devstate = "disabled";
- break;
- case CUBEB_DEVICE_STATE_UNPLUGGED:
- devstate = "unplugged";
- break;
- case CUBEB_DEVICE_STATE_ENABLED:
- devstate = "enabled";
- break;
- default:
- devstate = "unknown?";
- break;
+ case CUBEB_DEVICE_STATE_DISABLED:
+ devstate = "disabled";
+ break;
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ devstate = "unplugged";
+ break;
+ case CUBEB_DEVICE_STATE_ENABLED:
+ devstate = "enabled";
+ break;
+ default:
+ devstate = "unknown?";
+ break;
};
switch (device_info->default_format) {
- case CUBEB_DEVICE_FMT_S16LE:
- devdeffmt = "S16LE";
- break;
- case CUBEB_DEVICE_FMT_S16BE:
- devdeffmt = "S16BE";
- break;
- case CUBEB_DEVICE_FMT_F32LE:
- devdeffmt = "F32LE";
- break;
- case CUBEB_DEVICE_FMT_F32BE:
- devdeffmt = "F32BE";
- break;
- default:
- devdeffmt = "unknown?";
- break;
+ case CUBEB_DEVICE_FMT_S16LE:
+ devdeffmt = "S16LE";
+ break;
+ case CUBEB_DEVICE_FMT_S16BE:
+ devdeffmt = "S16BE";
+ break;
+ case CUBEB_DEVICE_FMT_F32LE:
+ devdeffmt = "F32LE";
+ break;
+ case CUBEB_DEVICE_FMT_F32BE:
+ devdeffmt = "F32BE";
+ break;
+ default:
+ devdeffmt = "unknown?";
+ break;
};
if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
@@ -466,20 +617,17 @@ void log_device(cubeb_device_info * device_info)
"\tRate:\t[%u, %u] (default: %u)\n"
"\tLatency: lo %u frames, hi %u frames",
device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
- device_info->friendly_name,
- device_info->group_id,
- device_info->vendor_name,
- devtype,
- devstate,
- device_info->max_channels,
- (devfmts[0] == '\0') ? devfmts : devfmts + 1, (unsigned int)device_info->format, devdeffmt,
- device_info->min_rate, device_info->max_rate, device_info->default_rate,
- device_info->latency_lo, device_info->latency_hi);
+ device_info->friendly_name, device_info->group_id,
+ device_info->vendor_name, devtype, devstate, device_info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)device_info->format, devdeffmt, device_info->min_rate,
+ device_info->max_rate, device_info->default_rate, device_info->latency_lo,
+ device_info->latency_hi);
}
-int cubeb_enumerate_devices(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection ** collection)
+int
+cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection * collection)
{
int rv;
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
@@ -491,61 +639,59 @@ int cubeb_enumerate_devices(cubeb * context,
rv = context->ops->enumerate_devices(context, devtype, collection);
- if (g_log_callback) {
- for (uint32_t i = 0; i < (*collection)->count; i++) {
- log_device((*collection)->device[i]);
+ if (g_cubeb_log_callback) {
+ for (size_t i = 0; i < collection->count; i++) {
+ log_device(&collection->device[i]);
}
}
return rv;
}
-int cubeb_device_collection_destroy(cubeb_device_collection * collection)
+int
+cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
{
- uint32_t i;
+ int r;
- if (collection == NULL)
+ if (context == NULL || collection == NULL)
return CUBEB_ERROR_INVALID_PARAMETER;
- for (i = 0; i < collection->count; i++)
- cubeb_device_info_destroy(collection->device[i]);
+ if (!context->ops->device_collection_destroy)
+ return CUBEB_ERROR_NOT_SUPPORTED;
- free(collection);
- return CUBEB_OK;
-}
+ if (!collection->device)
+ return CUBEB_OK;
-int cubeb_device_info_destroy(cubeb_device_info * info)
-{
- if (info == NULL) {
- return CUBEB_ERROR_INVALID_PARAMETER;
+ r = context->ops->device_collection_destroy(context, collection);
+ if (r == CUBEB_OK) {
+ collection->device = NULL;
+ collection->count = 0;
}
- free(info->device_id);
- free(info->friendly_name);
- free(info->group_id);
- free(info->vendor_name);
-
- free(info);
- return CUBEB_OK;
+ return r;
}
-int cubeb_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback callback,
- void * user_ptr)
+int
+cubeb_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr)
{
- if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
+ if (context == NULL ||
+ (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
return CUBEB_ERROR_INVALID_PARAMETER;
if (!context->ops->register_device_collection_changed) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr);
+ return context->ops->register_device_collection_changed(context, devtype,
+ callback, user_ptr);
}
-int cubeb_set_log_callback(cubeb_log_level log_level,
- cubeb_log_callback log_callback)
+int
+cubeb_set_log_callback(cubeb_log_level log_level,
+ cubeb_log_callback log_callback)
{
if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
return CUBEB_ERROR_INVALID_FORMAT;
@@ -555,20 +701,21 @@ int cubeb_set_log_callback(cubeb_log_level log_level,
return CUBEB_ERROR_INVALID_PARAMETER;
}
- if (g_log_callback && log_callback) {
+ if (g_cubeb_log_callback && log_callback) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
- g_log_callback = log_callback;
- g_log_level = log_level;
+ g_cubeb_log_callback = log_callback;
+ g_cubeb_log_level = log_level;
- return CUBEB_OK;
-}
+ // Logging a message here allows to initialize the asynchronous logger from a
+ // thread that is not the audio rendering thread, and especially to not
+ // initialize it the first time we find a verbose log, which is often in the
+ // audio rendering callback, that runs from the audio rendering thread, and
+ // that is high priority, and that we don't want to block.
+ if (log_level >= CUBEB_LOG_VERBOSE) {
+ ALOGV("Starting cubeb log");
+ }
-void
-cubeb_crash()
-{
- *((volatile int *) NULL) = 0;
- abort();
+ return CUBEB_OK;
}
-
diff --git a/media/libcubeb/src/cubeb_aaudio.cpp b/media/libcubeb/src/cubeb_aaudio.cpp
new file mode 100644
index 0000000000..448ea918e0
--- /dev/null
+++ b/media/libcubeb/src/cubeb_aaudio.cpp
@@ -0,0 +1,1504 @@
+/* ex: set tabstop=2 shiftwidth=2 expandtab:
+ * Copyright © 2019 Jan Kelling
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_android.h"
+#include "cubeb_log.h"
+#include "cubeb_resampler.h"
+#include <aaudio/AAudio.h>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <condition_variable>
+#include <cstring>
+#include <dlfcn.h>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <time.h>
+
+#ifdef DISABLE_LIBAAUDIO_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBAAUDIO_API_VISIT(X) \
+ X(AAudio_convertResultToText) \
+ X(AAudio_convertStreamStateToText) \
+ X(AAudio_createStreamBuilder) \
+ X(AAudioStreamBuilder_openStream) \
+ X(AAudioStreamBuilder_setChannelCount) \
+ X(AAudioStreamBuilder_setBufferCapacityInFrames) \
+ X(AAudioStreamBuilder_setDirection) \
+ X(AAudioStreamBuilder_setFormat) \
+ X(AAudioStreamBuilder_setSharingMode) \
+ X(AAudioStreamBuilder_setPerformanceMode) \
+ X(AAudioStreamBuilder_setSampleRate) \
+ X(AAudioStreamBuilder_delete) \
+ X(AAudioStreamBuilder_setDataCallback) \
+ X(AAudioStreamBuilder_setErrorCallback) \
+ X(AAudioStream_close) \
+ X(AAudioStream_read) \
+ X(AAudioStream_requestStart) \
+ X(AAudioStream_requestPause) \
+ X(AAudioStream_setBufferSizeInFrames) \
+ X(AAudioStream_getTimestamp) \
+ X(AAudioStream_requestFlush) \
+ X(AAudioStream_requestStop) \
+ X(AAudioStream_getPerformanceMode) \
+ X(AAudioStream_getSharingMode) \
+ X(AAudioStream_getBufferSizeInFrames) \
+ X(AAudioStream_getBufferCapacityInFrames) \
+ X(AAudioStream_getSampleRate) \
+ X(AAudioStream_waitForStateChange) \
+ X(AAudioStream_getFramesRead) \
+ X(AAudioStream_getState) \
+ X(AAudioStream_getFramesWritten) \
+ X(AAudioStream_getFramesPerBurst) \
+ X(AAudioStreamBuilder_setInputPreset) \
+ X(AAudioStreamBuilder_setUsage)
+
+// not needed or added later on
+// X(AAudioStreamBuilder_setFramesPerDataCallback) \
+ // X(AAudioStreamBuilder_setDeviceId) \
+ // X(AAudioStreamBuilder_setSamplesPerFrame) \
+ // X(AAudioStream_getSamplesPerFrame) \
+ // X(AAudioStream_getDeviceId) \
+ // X(AAudioStream_write) \
+ // X(AAudioStream_getChannelCount) \
+ // X(AAudioStream_getFormat) \
+ // X(AAudioStream_getXRunCount) \
+ // X(AAudioStream_isMMapUsed) \
+ // X(AAudioStreamBuilder_setContentType) \
+ // X(AAudioStreamBuilder_setSessionId) \
+ // X(AAudioStream_getUsage) \
+ // X(AAudioStream_getContentType) \
+ // X(AAudioStream_getInputPreset) \
+ // X(AAudioStream_getSessionId) \
+
+#define MAKE_TYPEDEF(x) static decltype(x) * cubeb_##x;
+LIBAAUDIO_API_VISIT(MAKE_TYPEDEF)
+#undef MAKE_TYPEDEF
+#endif
+
+const uint8_t MAX_STREAMS = 16;
+
+using unique_lock = std::unique_lock<std::mutex>;
+using lock_guard = std::lock_guard<std::mutex>;
+
+enum class stream_state {
+ INIT = 0,
+ STOPPED,
+ STOPPING,
+ STARTED,
+ STARTING,
+ DRAINING,
+ ERROR,
+ SHUTDOWN,
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context{};
+ void * user_ptr{};
+
+ std::atomic<bool> in_use{false};
+ std::atomic<stream_state> state{stream_state::INIT};
+
+ AAudioStream * ostream{};
+ AAudioStream * istream{};
+ cubeb_data_callback data_callback{};
+ cubeb_state_callback state_callback{};
+ cubeb_resampler * resampler{};
+
+ // mutex synchronizes access to the stream from the state thread
+ // and user-called functions. Everything that is accessed in the
+ // aaudio data (or error) callback is synchronized only via atomics.
+ std::mutex mutex;
+
+ std::unique_ptr<char[]> in_buf;
+ unsigned in_frame_size{}; // size of one input frame
+
+ cubeb_sample_format out_format{};
+ std::atomic<float> volume{1.f};
+ unsigned out_channels{};
+ unsigned out_frame_size{};
+ int64_t latest_output_latency = 0;
+ int64_t latest_input_latency = 0;
+ bool voice_input;
+ bool voice_output;
+ uint64_t previous_clock;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops{};
+ void * libaaudio{};
+
+ struct {
+ // The state thread: it waits for state changes and stops
+ // drained streams.
+ std::thread thread;
+ std::thread notifier;
+ std::mutex mutex;
+ std::condition_variable cond;
+ std::atomic<bool> join{false};
+ std::atomic<bool> waiting{false};
+ } state;
+
+ // streams[i].in_use signals whether a stream is used
+ struct cubeb_stream streams[MAX_STREAMS];
+};
+
+// Only allowed from state thread, while mutex on stm is locked
+static void
+shutdown(cubeb_stream * stm)
+{
+ if (stm->istream) {
+ WRAP(AAudioStream_requestStop)(stm->istream);
+ }
+ if (stm->ostream) {
+ WRAP(AAudioStream_requestStop)(stm->ostream);
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ stm->state.store(stream_state::SHUTDOWN);
+}
+
+// Returns whether the given state is one in which we wait for
+// an asynchronous change
+static bool
+waiting_state(stream_state state)
+{
+ switch (state) {
+ case stream_state::DRAINING:
+ case stream_state::STARTING:
+ case stream_state::STOPPING:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void
+update_state(cubeb_stream * stm)
+{
+ // Fast path for streams that don't wait for state change or are invalid
+ enum stream_state old_state = stm->state.load();
+ if (old_state == stream_state::INIT || old_state == stream_state::STARTED ||
+ old_state == stream_state::STOPPED ||
+ old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ // If the main thread currently operates on this thread, we don't
+ // have to wait for it
+ unique_lock lock(stm->mutex, std::try_to_lock);
+ if (!lock.owns_lock()) {
+ return;
+ }
+
+ // check again: if this is true now, the stream was destroyed or
+ // changed between our fast path check and locking the mutex
+ old_state = stm->state.load();
+ if (old_state == stream_state::INIT || old_state == stream_state::STARTED ||
+ old_state == stream_state::STOPPED ||
+ old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ // We compute the new state the stream has and then compare_exchange it
+ // if it has changed. This way we will never just overwrite state
+ // changes that were set from the audio thread in the meantime,
+ // such as a DRAINING or error state.
+ enum stream_state new_state;
+ do {
+ if (old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ if (old_state == stream_state::ERROR) {
+ shutdown(stm);
+ return;
+ }
+
+ new_state = old_state;
+
+ aaudio_stream_state_t istate = 0;
+ aaudio_stream_state_t ostate = 0;
+
+ // We use waitForStateChange (with zero timeout) instead of just
+ // getState since only the former internally updates the state.
+ // See the docs of aaudio getState/waitForStateChange for details,
+ // why we are passing STATE_UNKNOWN.
+ aaudio_result_t res;
+ if (stm->istream) {
+ res = WRAP(AAudioStream_waitForStateChange)(
+ stm->istream, AAUDIO_STREAM_STATE_UNKNOWN, &istate, 0);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ assert(istate);
+ }
+
+ if (stm->ostream) {
+ res = WRAP(AAudioStream_waitForStateChange)(
+ stm->ostream, AAUDIO_STREAM_STATE_UNKNOWN, &ostate, 0);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ assert(ostate);
+ }
+
+ // handle invalid stream states
+ if (istate == AAUDIO_STREAM_STATE_PAUSING ||
+ istate == AAUDIO_STREAM_STATE_PAUSED ||
+ istate == AAUDIO_STREAM_STATE_FLUSHING ||
+ istate == AAUDIO_STREAM_STATE_FLUSHED ||
+ istate == AAUDIO_STREAM_STATE_UNKNOWN ||
+ istate == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ const char * name = WRAP(AAudio_convertStreamStateToText)(istate);
+ LOG("Unexpected android input stream state %s", name);
+ shutdown(stm);
+ return;
+ }
+
+ if (ostate == AAUDIO_STREAM_STATE_PAUSING ||
+ ostate == AAUDIO_STREAM_STATE_PAUSED ||
+ ostate == AAUDIO_STREAM_STATE_FLUSHING ||
+ ostate == AAUDIO_STREAM_STATE_FLUSHED ||
+ ostate == AAUDIO_STREAM_STATE_UNKNOWN ||
+ ostate == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ const char * name = WRAP(AAudio_convertStreamStateToText)(istate);
+ LOG("Unexpected android output stream state %s", name);
+ shutdown(stm);
+ return;
+ }
+
+ switch (old_state) {
+ case stream_state::STARTING:
+ if ((!istate || istate == AAUDIO_STREAM_STATE_STARTED) &&
+ (!ostate || ostate == AAUDIO_STREAM_STATE_STARTED)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ new_state = stream_state::STARTED;
+ }
+ break;
+ case stream_state::DRAINING:
+ // The DRAINING state means that we want to stop the streams but
+ // may not have done so yet.
+ // The aaudio docs state that returning STOP from the callback isn't
+ // enough, the stream has to be stopped from another thread
+ // afterwards.
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ // Therefor it is important to close ostream first.
+ if (ostate && ostate != AAUDIO_STREAM_STATE_STOPPING &&
+ ostate != AAUDIO_STREAM_STATE_STOPPED) {
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ }
+ if (istate && istate != AAUDIO_STREAM_STATE_STOPPING &&
+ istate != AAUDIO_STREAM_STATE_STOPPED) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ }
+
+ // we always wait until both streams are stopped until we
+ // send CUBEB_STATE_DRAINED. Then we can directly transition
+ // our logical state to STOPPED, not triggering
+ // an additional CUBEB_STATE_STOPPED callback (which might
+ // be unexpected for the user).
+ if ((!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED) &&
+ (!istate || istate == AAUDIO_STREAM_STATE_STOPPED)) {
+ new_state = stream_state::STOPPED;
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ break;
+ case stream_state::STOPPING:
+ assert(!istate || istate == AAUDIO_STREAM_STATE_STOPPING ||
+ istate == AAUDIO_STREAM_STATE_STOPPED);
+ assert(!ostate || ostate == AAUDIO_STREAM_STATE_STOPPING ||
+ ostate == AAUDIO_STREAM_STATE_STOPPED);
+ if ((!istate || istate == AAUDIO_STREAM_STATE_STOPPED) &&
+ (!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ new_state = stream_state::STOPPED;
+ }
+ break;
+ default:
+ assert(false && "Unreachable: invalid state");
+ }
+ } while (old_state != new_state &&
+ !stm->state.compare_exchange_strong(old_state, new_state));
+}
+
+// See https://nyorain.github.io/lock-free-wakeup.html for a note
+// why this is needed. The audio thread notifies the state thread about
+// state changes and must not block. The state thread on the other hand should
+// sleep until there is work to be done. So we need a lockfree producer
+// and blocking producer. This can only be achieved safely with a new thread
+// that only serves as notifier backup (in case the notification happens
+// right between the state thread checking and going to sleep in which case
+// this thread will kick in and signal it right again).
+static void
+notifier_thread(cubeb * ctx)
+{
+ unique_lock lock(ctx->state.mutex);
+
+ while (!ctx->state.join.load()) {
+ ctx->state.cond.wait(lock);
+ if (ctx->state.waiting.load()) {
+ // This must signal our state thread since there is no other
+ // thread currently waiting on the condition variable.
+ // The state change thread is guaranteed to be waiting since
+ // we hold the mutex it locks when awake.
+ ctx->state.cond.notify_one();
+ }
+ }
+
+ // make sure other thread joins as well
+ ctx->state.cond.notify_one();
+ LOG("Exiting notifier thread");
+}
+
+static void
+state_thread(cubeb * ctx)
+{
+ unique_lock lock(ctx->state.mutex);
+
+ bool waiting = false;
+ while (!ctx->state.join.load()) {
+ waiting |= ctx->state.waiting.load();
+ if (waiting) {
+ ctx->state.waiting.store(false);
+ waiting = false;
+ for (unsigned i = 0u; i < MAX_STREAMS; ++i) {
+ cubeb_stream * stm = &ctx->streams[i];
+ update_state(stm);
+ waiting |= waiting_state(atomic_load(&stm->state));
+ }
+
+ // state changed from another thread, update again immediately
+ if (ctx->state.waiting.load()) {
+ waiting = true;
+ continue;
+ }
+
+ // Not waiting for any change anymore: we can wait on the
+ // condition variable without timeout
+ if (!waiting) {
+ continue;
+ }
+
+ // while any stream is waiting for state change we sleep with regular
+ // timeouts. But we wake up immediately if signaled.
+ // This might seem like a poor man's implementation of state change
+ // waiting but (as of october 2020), the implementation of
+ // AAudioStream_waitForStateChange is just sleeping with regular
+ // timeouts as well:
+ // https://android.googlesource.com/platform/frameworks/av/+/refs/heads/master/media/libaaudio/src/core/AudioStream.cpp
+ auto dur = std::chrono::milliseconds(5);
+ ctx->state.cond.wait_for(lock, dur);
+ } else {
+ ctx->state.cond.wait(lock);
+ }
+ }
+
+ // make sure other thread joins as well
+ ctx->state.cond.notify_one();
+ LOG("Exiting state thread");
+}
+
+static char const *
+aaudio_get_backend_id(cubeb * /* ctx */)
+{
+ return "aaudio";
+}
+
+static int
+aaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+ // NOTE: we might get more, AAudio docs don't specify anything.
+ *max_channels = 2;
+ return CUBEB_OK;
+}
+
+static void
+aaudio_destroy(cubeb * ctx)
+{
+ assert(ctx);
+
+#ifndef NDEBUG
+ // make sure all streams were destroyed
+ for (unsigned i = 0u; i < MAX_STREAMS; ++i) {
+ assert(!ctx->streams[i].in_use.load());
+ }
+#endif
+
+ // broadcast joining to both threads
+ // they will additionally signal each other before joining
+ ctx->state.join.store(true);
+ ctx->state.cond.notify_all();
+
+ if (ctx->state.thread.joinable()) {
+ ctx->state.thread.join();
+ }
+ if (ctx->state.notifier.joinable()) {
+ ctx->state.notifier.join();
+ }
+
+ if (ctx->libaaudio) {
+ dlclose(ctx->libaaudio);
+ }
+ delete ctx;
+}
+
+static void
+apply_volume(cubeb_stream * stm, void * audio_data, uint32_t num_frames)
+{
+ float volume = stm->volume.load();
+ // optimization: we don't have to change anything in this case
+ if (volume == 1.f) {
+ return;
+ }
+
+ switch (stm->out_format) {
+ case CUBEB_SAMPLE_S16NE:
+ for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) {
+ (static_cast<int16_t *>(audio_data))[i] *= volume;
+ }
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) {
+ (static_cast<float *>(audio_data))[i] *= volume;
+ }
+ break;
+ default:
+ assert(false && "Unreachable: invalid stream out_format");
+ }
+}
+
+// Returning AAUDIO_CALLBACK_RESULT_STOP seems to put the stream in
+// an invalid state. Seems like an AAudio bug/bad documentation.
+// We therefore only return it on error.
+
+static aaudio_data_callback_result_t
+aaudio_duplex_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ assert(stm->ostream == astream);
+ assert(stm->istream);
+ assert(num_frames >= 0);
+
+ stream_state state = atomic_load(&stm->state);
+ // int istate = WRAP(AAudioStream_getState)(stm->istream);
+ // int ostate = WRAP(AAudioStream_getState)(stm->ostream);
+ // ALOGV("aaudio duplex data cb on stream %p: state %ld (in: %d, out: %d),
+ // num_frames: %ld",
+ // (void*) stm, state, istate, ostate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // stopped the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ std::memset(audio_data, 0x0, num_frames * stm->out_frame_size);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ // The aaudio docs state that AAudioStream_read must not be called on
+ // the stream associated with a callback. But we call it on the input stream
+ // while this callback is for the output stream so this is ok.
+ // We also pass timeout 0, giving us strong non-blocking guarantees.
+ // This is exactly how it's done in the aaudio duplex example code snippet.
+ long in_num_frames =
+ WRAP(AAudioStream_read)(stm->istream, stm->in_buf.get(), num_frames, 0);
+ if (in_num_frames < 0) { // error
+ stm->state.store(stream_state::ERROR);
+ LOG("AAudioStream_read: %s",
+ WRAP(AAudio_convertResultToText)(in_num_frames));
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ // This can happen shortly after starting the stream. AAudio might immediately
+ // begin to buffer output but not have any input ready yet. We could
+ // block AAudioStream_read (passing a timeout > 0) but that leads to issues
+ // since blocking in this callback is a bad idea in general and it might break
+ // the stream when it is stopped by another thread shortly after being
+ // started. We therefore simply send silent input to the application, as shown
+ // in the AAudio duplex stream code example.
+ if (in_num_frames < num_frames) {
+ // LOG("AAudioStream_read returned not enough frames: %ld instead of %d",
+ // in_num_frames, num_frames);
+ unsigned left = num_frames - in_num_frames;
+ char * buf = stm->in_buf.get() + in_num_frames * stm->in_frame_size;
+ std::memset(buf, 0x0, left * stm->in_frame_size);
+ in_num_frames = num_frames;
+ }
+
+ long done_frames =
+ cubeb_resampler_fill(stm->resampler, stm->in_buf.get(), &in_num_frames,
+ audio_data, num_frames);
+
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ } else if (done_frames < num_frames) {
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+
+ char * begin =
+ static_cast<char *>(audio_data) + done_frames * stm->out_frame_size;
+ std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size);
+ }
+
+ apply_volume(stm, audio_data, done_frames);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static aaudio_data_callback_result_t
+aaudio_output_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ assert(stm->ostream == astream);
+ assert(!stm->istream);
+ assert(num_frames >= 0);
+
+ stream_state state = stm->state.load();
+ // int ostate = WRAP(AAudioStream_getState)(stm->ostream);
+ // ALOGV("aaudio output data cb on stream %p: state %ld (%d), num_frames:
+ // %ld",
+ // (void*) stm, state, ostate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // stopped the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ std::memset(audio_data, 0x0, num_frames * stm->out_frame_size);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ long done_frames =
+ cubeb_resampler_fill(stm->resampler, NULL, NULL, audio_data, num_frames);
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ } else if (done_frames < num_frames) {
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+
+ char * begin =
+ static_cast<char *>(audio_data) + done_frames * stm->out_frame_size;
+ std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size);
+ }
+
+ apply_volume(stm, audio_data, done_frames);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static aaudio_data_callback_result_t
+aaudio_input_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ assert(stm->istream == astream);
+ assert(!stm->ostream);
+ assert(num_frames >= 0);
+
+ stream_state state = stm->state.load();
+ // int istate = WRAP(AAudioStream_getState)(stm->istream);
+ // ALOGV("aaudio input data cb on stream %p: state %ld (%d), num_frames: %ld",
+ // (void*) stm, state, istate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // STOPPED the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ long input_frame_count = num_frames;
+ long done_frames = cubeb_resampler_fill(stm->resampler, audio_data,
+ &input_frame_count, NULL, 0);
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ } else if (done_frames < input_frame_count) {
+ // we don't really drain an input stream, just have to
+ // stop it from the state thread. That is signaled via the
+ // DRAINING state.
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static void
+aaudio_error_cb(AAudioStream * astream, void * user_data, aaudio_result_t error)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_data);
+ assert(stm->ostream == astream || stm->istream == astream);
+ LOG("AAudio error callback: %s", WRAP(AAudio_convertResultToText)(error));
+ stm->state.store(stream_state::ERROR);
+}
+
+static int
+realize_stream(AAudioStreamBuilder * sb, const cubeb_stream_params * params,
+ AAudioStream ** stream, unsigned * frame_size)
+{
+ aaudio_result_t res;
+ assert(params->rate);
+ assert(params->channels);
+
+ WRAP(AAudioStreamBuilder_setSampleRate)(sb, params->rate);
+ WRAP(AAudioStreamBuilder_setChannelCount)(sb, params->channels);
+
+ aaudio_format_t fmt;
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ fmt = AAUDIO_FORMAT_PCM_I16;
+ *frame_size = sizeof(int16_t) * params->channels;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ fmt = AAUDIO_FORMAT_PCM_FLOAT;
+ *frame_size = sizeof(float) * params->channels;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ WRAP(AAudioStreamBuilder_setFormat)(sb, fmt);
+ res = WRAP(AAudioStreamBuilder_openStream)(sb, stream);
+ if (res == AAUDIO_ERROR_INVALID_FORMAT) {
+ LOG("AAudio device doesn't support output format %d", fmt);
+ return CUBEB_ERROR_INVALID_FORMAT;
+ } else if (params->rate && res == AAUDIO_ERROR_INVALID_RATE) {
+ // The requested rate is not supported.
+ // Just try again with default rate, we create a resampler anyways
+ WRAP(AAudioStreamBuilder_setSampleRate)(sb, AAUDIO_UNSPECIFIED);
+ res = WRAP(AAudioStreamBuilder_openStream)(sb, stream);
+ LOG("Requested rate of %u is not supported, inserting resampler",
+ params->rate);
+ }
+
+ // When the app has no permission to record audio
+ // (android.permission.RECORD_AUDIO) but requested and input stream, this will
+ // return INVALID_ARGUMENT.
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_openStream: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static void
+aaudio_stream_destroy(cubeb_stream * stm)
+{
+ lock_guard lock(stm->mutex);
+ assert(stm->state == stream_state::STOPPED ||
+ stm->state == stream_state::STOPPING ||
+ stm->state == stream_state::INIT ||
+ stm->state == stream_state::DRAINING ||
+ stm->state == stream_state::ERROR ||
+ stm->state == stream_state::SHUTDOWN);
+
+ aaudio_result_t res;
+
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ if (stm->ostream) {
+ if (stm->state != stream_state::STOPPED &&
+ stm->state != stream_state::STOPPING &&
+ stm->state != stream_state::SHUTDOWN) {
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ }
+ }
+
+ WRAP(AAudioStream_close)(stm->ostream);
+ stm->ostream = NULL;
+ }
+
+ if (stm->istream) {
+ if (stm->state != stream_state::STOPPED &&
+ stm->state != stream_state::STOPPING &&
+ stm->state != stream_state::SHUTDOWN) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ }
+ }
+
+ WRAP(AAudioStream_close)(stm->istream);
+ stm->istream = NULL;
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ stm->resampler = NULL;
+ }
+
+ stm->in_buf = {};
+ stm->in_frame_size = {};
+ stm->out_format = {};
+ stm->out_channels = {};
+ stm->out_frame_size = {};
+
+ stm->state.store(stream_state::INIT);
+ stm->in_use.store(false);
+}
+
+static int
+aaudio_stream_init_impl(cubeb_stream * stm, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames)
+{
+ assert(stm->state.load() == stream_state::INIT);
+ stm->in_use.store(true);
+
+ aaudio_result_t res;
+ AAudioStreamBuilder * sb;
+ res = WRAP(AAudio_createStreamBuilder)(&sb);
+ if (res != AAUDIO_OK) {
+ LOG("AAudio_createStreamBuilder: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ // make sure the builder is always destroyed
+ struct StreamBuilderDestructor {
+ void operator()(AAudioStreamBuilder * sb)
+ {
+ WRAP(AAudioStreamBuilder_delete)(sb);
+ }
+ };
+
+ std::unique_ptr<AAudioStreamBuilder, StreamBuilderDestructor> sbPtr(sb);
+
+ WRAP(AAudioStreamBuilder_setErrorCallback)(sb, aaudio_error_cb, stm);
+ WRAP(AAudioStreamBuilder_setBufferCapacityInFrames)(sb, latency_frames);
+
+ AAudioStream_dataCallback in_data_callback{};
+ AAudioStream_dataCallback out_data_callback{};
+ if (output_stream_params && input_stream_params) {
+ out_data_callback = aaudio_duplex_data_cb;
+ in_data_callback = NULL;
+ } else if (input_stream_params) {
+ in_data_callback = aaudio_input_data_cb;
+ } else if (output_stream_params) {
+ out_data_callback = aaudio_output_data_cb;
+ } else {
+ LOG("Tried to open stream without input or output parameters");
+ return CUBEB_ERROR;
+ }
+
+#ifdef CUBEB_AAUDIO_EXCLUSIVE_STREAM
+ LOG("AAudio setting exclusive share mode for stream");
+ WRAP(AAudioStreamBuilder_setSharingMode)(sb, AAUDIO_SHARING_MODE_EXCLUSIVE);
+#endif
+
+ if (latency_frames <= POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ LOG("AAudio setting low latency mode for stream");
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (sb, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ } else {
+ LOG("AAudio setting power saving mode for stream");
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (sb, AAUDIO_PERFORMANCE_MODE_POWER_SAVING);
+ }
+
+ unsigned frame_size;
+
+ // initialize streams
+ // output
+ uint32_t target_sample_rate = 0;
+ cubeb_stream_params out_params;
+ if (output_stream_params) {
+ int output_preset = stm->voice_output ? AAUDIO_USAGE_VOICE_COMMUNICATION
+ : AAUDIO_USAGE_MEDIA;
+ WRAP(AAudioStreamBuilder_setUsage)(sb, output_preset);
+ WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_OUTPUT);
+ WRAP(AAudioStreamBuilder_setDataCallback)(sb, out_data_callback, stm);
+ int res_err =
+ realize_stream(sb, output_stream_params, &stm->ostream, &frame_size);
+ if (res_err) {
+ return res_err;
+ }
+
+ // output debug information
+ aaudio_sharing_mode_t sm = WRAP(AAudioStream_getSharingMode)(stm->ostream);
+ aaudio_performance_mode_t pm =
+ WRAP(AAudioStream_getPerformanceMode)(stm->ostream);
+ int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->ostream);
+ int bsize = WRAP(AAudioStream_getBufferSizeInFrames)(stm->ostream);
+ int rate = WRAP(AAudioStream_getSampleRate)(stm->ostream);
+ LOG("AAudio output stream sharing mode: %d", sm);
+ LOG("AAudio output stream performance mode: %d", pm);
+ LOG("AAudio output stream buffer capacity: %d", bcap);
+ LOG("AAudio output stream buffer size: %d", bsize);
+ LOG("AAudio output stream buffer rate: %d", rate);
+
+ target_sample_rate = output_stream_params->rate;
+ out_params = *output_stream_params;
+ out_params.rate = rate;
+
+ stm->out_channels = output_stream_params->channels;
+ stm->out_format = output_stream_params->format;
+ stm->out_frame_size = frame_size;
+ stm->volume.store(1.f);
+ }
+
+ // input
+ cubeb_stream_params in_params;
+ if (input_stream_params) {
+ // Match what the OpenSL backend does for now, we could use UNPROCESSED and
+ // VOICE_COMMUNICATION here, but we'd need to make it clear that
+ // application-level AEC and other voice processing should be disabled
+ // there.
+ int input_preset = stm->voice_input ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+ : AAUDIO_INPUT_PRESET_CAMCORDER;
+ WRAP(AAudioStreamBuilder_setInputPreset)(sb, input_preset);
+ WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_INPUT);
+ WRAP(AAudioStreamBuilder_setDataCallback)(sb, in_data_callback, stm);
+ int res_err =
+ realize_stream(sb, input_stream_params, &stm->istream, &frame_size);
+ if (res_err) {
+ return res_err;
+ }
+
+ // output debug information
+ aaudio_sharing_mode_t sm = WRAP(AAudioStream_getSharingMode)(stm->istream);
+ aaudio_performance_mode_t pm =
+ WRAP(AAudioStream_getPerformanceMode)(stm->istream);
+ int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->istream);
+ int bsize = WRAP(AAudioStream_getBufferSizeInFrames)(stm->istream);
+ int rate = WRAP(AAudioStream_getSampleRate)(stm->istream);
+ LOG("AAudio input stream sharing mode: %d", sm);
+ LOG("AAudio input stream performance mode: %d", pm);
+ LOG("AAudio input stream buffer capacity: %d", bcap);
+ LOG("AAudio input stream buffer size: %d", bsize);
+ LOG("AAudio input stream buffer rate: %d", rate);
+
+ stm->in_buf.reset(new char[bcap * frame_size]());
+ assert(!target_sample_rate ||
+ target_sample_rate == input_stream_params->rate);
+
+ target_sample_rate = input_stream_params->rate;
+ in_params = *input_stream_params;
+ in_params.rate = rate;
+ stm->in_frame_size = frame_size;
+ }
+
+ // initialize resampler
+ stm->resampler = cubeb_resampler_create(
+ stm, input_stream_params ? &in_params : NULL,
+ output_stream_params ? &out_params : NULL, target_sample_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ return CUBEB_ERROR;
+ }
+
+ // the stream isn't started initially. We don't need to differentiate
+ // between a stream that was just initialized and one that played
+ // already but was stopped.
+ stm->state.store(stream_state::STOPPED);
+ LOG("Cubeb stream (%p) INIT success", (void *)stm);
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * /* stream_name */, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ assert(!input_device);
+ assert(!output_device);
+
+ // atomically find a free stream.
+ cubeb_stream * stm = NULL;
+ unique_lock lock;
+ for (unsigned i = 0u; i < MAX_STREAMS; ++i) {
+ // This check is only an optimization, we don't strictly need it
+ // since we check again after locking the mutex.
+ if (ctx->streams[i].in_use.load()) {
+ continue;
+ }
+
+ // if this fails, another thread initialized this stream
+ // between our check of in_use and this.
+ lock = unique_lock(ctx->streams[i].mutex, std::try_to_lock);
+ if (!lock.owns_lock()) {
+ continue;
+ }
+
+ if (ctx->streams[i].in_use.load()) {
+ lock = {};
+ continue;
+ }
+
+ stm = &ctx->streams[i];
+ break;
+ }
+
+ if (!stm) {
+ LOG("Error: maximum number of streams reached");
+ return CUBEB_ERROR;
+ }
+
+ stm->context = ctx;
+ stm->user_ptr = user_ptr;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->voice_input = input_stream_params &&
+ !!(input_stream_params->prefs & CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output = output_stream_params &&
+ !!(output_stream_params->prefs & CUBEB_STREAM_PREF_VOICE);
+ stm->previous_clock = 0;
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
+ stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+ int err = aaudio_stream_init_impl(stm, input_device, input_stream_params,
+ output_device, output_stream_params,
+ latency_frames);
+ if (err != CUBEB_OK) {
+ // This is needed since aaudio_stream_destroy will lock the mutex again.
+ // It's no problem that there is a gap in between as the stream isn't
+ // actually in u se.
+ lock.unlock();
+ aaudio_stream_destroy(stm);
+ return err;
+ }
+
+ *stream = stm;
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_start(cubeb_stream * stm)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0;
+ int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0;
+ LOGV("STARTING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate);
+
+ switch (state) {
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ LOG("cubeb stream %p already STARTING/STARTED", (void *)stm);
+ return CUBEB_OK;
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ case stream_state::DRAINING:
+ break;
+ }
+
+ aaudio_result_t res;
+
+ // Important to start istream before ostream.
+ // As soon as we start ostream, the callbacks might be triggered an we
+ // might read from istream (on duplex). If istream wasn't started yet
+ // this is a problem.
+ if (stm->istream) {
+ res = WRAP(AAudioStream_requestStart)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStart (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->ostream) {
+ res = WRAP(AAudioStream_requestStart)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStart (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ int ret = CUBEB_OK;
+ bool success;
+
+ while (!(success = stm->state.compare_exchange_strong(
+ state, stream_state::STARTING))) {
+ // we land here only if the state has changed in the meantime
+ switch (state) {
+ // If an error ocurred in the meantime, we can't change that.
+ // The stream will be stopped when shut down.
+ case stream_state::ERROR:
+ ret = CUBEB_ERROR;
+ break;
+ // The only situation in which the state could have switched to draining
+ // is if the callback was already fired and requested draining. Don't
+ // overwrite that. It's not an error either though.
+ case stream_state::DRAINING:
+ break;
+
+ // If the state switched [DRAINING -> STOPPING] or [DRAINING/STOPPING ->
+ // STOPPED] in the meantime, we can simply overwrite that since we restarted
+ // the stream.
+ case stream_state::STOPPING:
+ case stream_state::STOPPED:
+ continue;
+
+ // There is no situation in which the state could have been valid before
+ // but now in shutdown mode, since we hold the streams mutex.
+ // There is also no way that it switched *into* STARTING or
+ // STARTED mode.
+ default:
+ assert(false && "Invalid state change");
+ ret = CUBEB_ERROR;
+ break;
+ }
+
+ break;
+ }
+
+ if (success) {
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return ret;
+}
+
+static int
+aaudio_stream_stop(cubeb_stream * stm)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0;
+ int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0;
+ LOGV("STOPPING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate);
+
+ switch (state) {
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ case stream_state::DRAINING:
+ LOG("cubeb stream %p already STOPPING/STOPPED", (void *)stm);
+ return CUBEB_OK;
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ break;
+ }
+
+ aaudio_result_t res;
+
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ // Therefor it is important to close ostream first.
+ if (stm->ostream) {
+ // Could use pause + flush here as well, the public cubeb interface
+ // doesn't state behavior.
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->istream) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ int ret = CUBEB_OK;
+ bool success;
+ while (!(success = atomic_compare_exchange_strong(&stm->state, &state,
+ stream_state::STOPPING))) {
+ // we land here only if the state has changed in the meantime
+ switch (state) {
+ // If an error ocurred in the meantime, we can't change that.
+ // The stream will be STOPPED when shut down.
+ case stream_state::ERROR:
+ ret = CUBEB_ERROR;
+ break;
+ // If it was switched to DRAINING in the meantime, it was or
+ // will be STOPPED soon anyways. We don't interfere with
+ // the DRAINING process, no matter in which state.
+ // Not an error
+ case stream_state::DRAINING:
+ case stream_state::STOPPING:
+ case stream_state::STOPPED:
+ break;
+
+ // If the state switched from STARTING to STARTED in the meantime
+ // we can simply overwrite that since we just STOPPED it.
+ case stream_state::STARTED:
+ continue;
+
+ // There is no situation in which the state could have been valid before
+ // but now in shutdown mode, since we hold the streams mutex.
+ // There is also no way that it switched *into* STARTING mode.
+ default:
+ assert(false && "Invalid state change");
+ ret = CUBEB_ERROR;
+ break;
+ }
+
+ break;
+ }
+
+ if (success) {
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return ret;
+}
+
+static int
+aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ AAudioStream * stream = stm->ostream ? stm->ostream : stm->istream;
+ switch (state) {
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::DRAINING:
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ // getTimestamp is only valid when the stream is playing.
+ // Simply return the number of frames passed to aaudio
+ *position = WRAP(AAudioStream_getFramesRead)(stream);
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ break;
+ }
+
+ int64_t pos;
+ int64_t ns;
+ aaudio_result_t res;
+ res = WRAP(AAudioStream_getTimestamp)(stream, CLOCK_MONOTONIC, &pos, &ns);
+ if (res != AAUDIO_OK) {
+ // When the audio stream is not running, invalid_state is returned and we
+ // simply fall back to the method we use for non-playing streams.
+ if (res == AAUDIO_ERROR_INVALID_STATE) {
+ *position = WRAP(AAudioStream_getFramesRead)(stream);
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+ }
+
+ LOG("AAudioStream_getTimestamp: %s", WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ *position = pos;
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ int64_t pos;
+ int64_t ns;
+ aaudio_result_t res;
+
+ if (!stm->ostream) {
+ LOG("error: aaudio_stream_get_latency on input-only stream");
+ return CUBEB_ERROR;
+ }
+
+ res =
+ WRAP(AAudioStream_getTimestamp)(stm->ostream, CLOCK_MONOTONIC, &pos, &ns);
+ if (res != AAUDIO_OK) {
+ LOG("aaudio_stream_get_latency, AAudioStream_getTimestamp: %s, returning "
+ "memoized value",
+ WRAP(AAudio_convertResultToText)(res));
+ // Expected when the stream is paused.
+ *latency = stm->latest_output_latency;
+ return CUBEB_OK;
+ }
+
+ int64_t read = WRAP(AAudioStream_getFramesRead)(stm->ostream);
+
+ *latency = stm->latest_output_latency = read - pos;
+ LOG("aaudio_stream_get_latency, %u", *latency);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ int64_t pos;
+ int64_t ns;
+ aaudio_result_t res;
+
+ if (!stm->istream) {
+ LOG("error: aaudio_stream_get_input_latency on an ouput-only stream");
+ return CUBEB_ERROR;
+ }
+
+ res =
+ WRAP(AAudioStream_getTimestamp)(stm->istream, CLOCK_MONOTONIC, &pos, &ns);
+ if (res != AAUDIO_OK) {
+ // Expected when the stream is paused.
+ LOG("aaudio_stream_get_input_latency, AAudioStream_getTimestamp: %s, "
+ "returning memoized value",
+ WRAP(AAudio_convertResultToText)(res));
+ *latency = stm->latest_input_latency;
+ return CUBEB_OK;
+ }
+
+ int64_t written = WRAP(AAudioStream_getFramesWritten)(stm->istream);
+
+ *latency = stm->latest_input_latency = written - pos;
+ LOG("aaudio_stream_get_input_latency, %u", *latency);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ assert(stm && stm->in_use.load() && stm->ostream);
+ stm->volume.store(volume);
+ return CUBEB_OK;
+}
+
+aaudio_data_callback_result_t
+dummy_callback(AAudioStream * stream, void * userData, void * audioData,
+ int32_t numFrames)
+{
+ return AAUDIO_CALLBACK_RESULT_STOP;
+}
+
+// Returns a dummy stream with all default settings
+static AAudioStream *
+init_dummy_stream()
+{
+ AAudioStreamBuilder * streamBuilder;
+ aaudio_result_t res;
+ res = WRAP(AAudio_createStreamBuilder)(&streamBuilder);
+ if (res != AAUDIO_OK) {
+ LOG("init_dummy_stream: AAudio_createStreamBuilder: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return nullptr;
+ }
+ WRAP(AAudioStreamBuilder_setDataCallback)
+ (streamBuilder, dummy_callback, nullptr);
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+
+ AAudioStream * stream;
+ res = WRAP(AAudioStreamBuilder_openStream)(streamBuilder, &stream);
+ if (res != AAUDIO_OK) {
+ LOG("init_dummy_stream: AAudioStreamBuilder_openStream %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return nullptr;
+ }
+ WRAP(AAudioStreamBuilder_delete)(streamBuilder);
+
+ return stream;
+}
+
+static void
+destroy_dummy_stream(AAudioStream * stream)
+{
+ WRAP(AAudioStream_close)(stream);
+}
+
+static int
+aaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ AAudioStream * stream = init_dummy_stream();
+
+ if (!stream) {
+ return CUBEB_ERROR;
+ }
+
+ // https://android.googlesource.com/platform/compatibility/cdd/+/refs/heads/master/5_multimedia/5_6_audio-latency.md
+ *latency_frames = WRAP(AAudioStream_getFramesPerBurst)(stream);
+
+ LOG("aaudio_get_min_latency: %u frames", *latency_frames);
+
+ destroy_dummy_stream(stream);
+
+ return CUBEB_OK;
+}
+
+int
+aaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ AAudioStream * stream = init_dummy_stream();
+
+ if (!stream) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = WRAP(AAudioStream_getSampleRate)(stream);
+
+ LOG("aaudio_get_preferred_sample_rate %uHz", *rate);
+
+ destroy_dummy_stream(stream);
+
+ return CUBEB_OK;
+}
+
+extern "C" int
+aaudio_init(cubeb ** context, char const * context_name);
+
+const static struct cubeb_ops aaudio_ops = {
+ /*.init =*/aaudio_init,
+ /*.get_backend_id =*/aaudio_get_backend_id,
+ /*.get_max_channel_count =*/aaudio_get_max_channel_count,
+ /* .get_min_latency =*/aaudio_get_min_latency,
+ /*.get_preferred_sample_rate =*/aaudio_get_preferred_sample_rate,
+ /*.enumerate_devices =*/NULL,
+ /*.device_collection_destroy =*/NULL,
+ /*.destroy =*/aaudio_destroy,
+ /*.stream_init =*/aaudio_stream_init,
+ /*.stream_destroy =*/aaudio_stream_destroy,
+ /*.stream_start =*/aaudio_stream_start,
+ /*.stream_stop =*/aaudio_stream_stop,
+ /*.stream_get_position =*/aaudio_stream_get_position,
+ /*.stream_get_latency =*/aaudio_stream_get_latency,
+ /*.stream_get_input_latency =*/aaudio_stream_get_input_latency,
+ /*.stream_set_volume =*/aaudio_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback =*/NULL,
+ /*.register_device_collection_changed =*/NULL};
+
+extern "C" /*static*/ int
+aaudio_init(cubeb ** context, char const * /* context_name */)
+{
+ // load api
+ void * libaaudio = NULL;
+#ifndef DISABLE_LIBAAUDIO_DLOPEN
+ libaaudio = dlopen("libaaudio.so", RTLD_NOW);
+ if (!libaaudio) {
+ return CUBEB_ERROR;
+ }
+
+#define LOAD(x) \
+ { \
+ WRAP(x) = (decltype(WRAP(x)))(dlsym(libaaudio, #x)); \
+ if (!WRAP(x)) { \
+ LOG("AAudio: Failed to load %s", #x); \
+ dlclose(libaaudio); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBAAUDIO_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ cubeb * ctx = new cubeb;
+ ctx->ops = &aaudio_ops;
+ ctx->libaaudio = libaaudio;
+
+ ctx->state.thread = std::thread(state_thread, ctx);
+
+ // NOTE: using platform-specific APIs we could set the priority of the
+ // notifier thread lower than the priority of the state thread.
+ // This way, it's more likely that the state thread will be woken up
+ // by the condition variable signal when both are currently waiting
+ ctx->state.notifier = std::thread(notifier_thread, ctx);
+
+ *context = ctx;
+ return CUBEB_OK;
+}
diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c
index 72a6acfb1c..4a55b02317 100644
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -8,15 +8,63 @@
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _XOPEN_SOURCE 500
-#include <pthread.h>
-#include <sys/time.h>
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <alsa/asoundlib.h>
#include <assert.h>
+#include <dlfcn.h>
#include <limits.h>
#include <poll.h>
+#include <pthread.h>
+#include <sys/time.h>
#include <unistd.h>
-#include <alsa/asoundlib.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
+
+#ifdef DISABLE_LIBASOUND_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBASOUND_API_VISIT(X) \
+ X(snd_config) \
+ X(snd_config_add) \
+ X(snd_config_copy) \
+ X(snd_config_delete) \
+ X(snd_config_get_id) \
+ X(snd_config_get_string) \
+ X(snd_config_imake_integer) \
+ X(snd_config_search) \
+ X(snd_config_search_definition) \
+ X(snd_lib_error_set_handler) \
+ X(snd_pcm_avail_update) \
+ X(snd_pcm_close) \
+ X(snd_pcm_delay) \
+ X(snd_pcm_drain) \
+ X(snd_pcm_frames_to_bytes) \
+ X(snd_pcm_get_params) \
+ X(snd_pcm_hw_params_any) \
+ X(snd_pcm_hw_params_get_channels_max) \
+ X(snd_pcm_hw_params_get_rate) \
+ X(snd_pcm_hw_params_set_rate_near) \
+ X(snd_pcm_hw_params_sizeof) \
+ X(snd_pcm_nonblock) \
+ X(snd_pcm_open) \
+ X(snd_pcm_open_lconf) \
+ X(snd_pcm_pause) \
+ X(snd_pcm_poll_descriptors) \
+ X(snd_pcm_poll_descriptors_count) \
+ X(snd_pcm_poll_descriptors_revents) \
+ X(snd_pcm_readi) \
+ X(snd_pcm_recover) \
+ X(snd_pcm_set_params) \
+ X(snd_pcm_start) \
+ X(snd_pcm_state) \
+ X(snd_pcm_writei)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBASOUND_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+/* snd_pcm_hw_params_alloca is actually a macro */
+#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof
+#endif
#define CUBEB_STREAM_MAX 16
#define CUBEB_WATCHDOG_MS 10000
@@ -36,6 +84,7 @@ static struct cubeb_ops const alsa_ops;
struct cubeb {
struct cubeb_ops const * ops;
+ void * libasound;
pthread_t thread;
@@ -52,7 +101,8 @@ struct cubeb {
int shutdown;
- /* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */
+ /* Control pipe for forcing poll to wake and rebuild fds or recalculate the
+ * timeout. */
int control_fd_read;
int control_fd_write;
@@ -67,22 +117,18 @@ struct cubeb {
int is_pa;
};
-enum stream_state {
- INACTIVE,
- RUNNING,
- DRAINING,
- PROCESSING,
- ERROR
-};
+enum stream_state { INACTIVE, RUNNING, DRAINING, PROCESSING, ERROR };
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
+ void * user_ptr;
+ /**/
pthread_mutex_t mutex;
snd_pcm_t * pcm;
cubeb_data_callback data_callback;
cubeb_state_callback state_callback;
- void * user_ptr;
- snd_pcm_uframes_t write_position;
+ snd_pcm_uframes_t stream_position;
snd_pcm_uframes_t last_position;
snd_pcm_uframes_t buffer_size;
cubeb_stream_params params;
@@ -95,7 +141,8 @@ struct cubeb_stream {
enum stream_state state;
struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */
- struct pollfd * fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
+ struct pollfd *
+ fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
nfds_t nfds;
struct timeval drain_timeout;
@@ -107,6 +154,12 @@ struct cubeb_stream {
being logically active and playing. */
struct timeval last_activity;
float volume;
+
+ char * buffer;
+ snd_pcm_uframes_t bufframes;
+ snd_pcm_stream_t stream_type;
+
+ struct cubeb_stream * other_stream;
};
static int
@@ -235,6 +288,16 @@ set_timeout(struct timeval * timeout, unsigned int ms)
}
static void
+stream_buffer_decrement(cubeb_stream * stm, long count)
+{
+ char * bufremains =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count);
+ memmove(stm->buffer, bufremains,
+ WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count));
+ stm->bufframes -= count;
+}
+
+static void
alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
{
cubeb * ctx;
@@ -249,97 +312,185 @@ alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
}
static enum stream_state
-alsa_refill_stream(cubeb_stream * stm)
+alsa_process_stream(cubeb_stream * stm)
{
+ unsigned short revents;
snd_pcm_sframes_t avail;
- long got;
- void * p;
int draining;
draining = 0;
pthread_mutex_lock(&stm->mutex);
- avail = snd_pcm_avail_update(stm->pcm);
- if (avail < 0) {
- snd_pcm_recover(stm->pcm, avail, 1);
- avail = snd_pcm_avail_update(stm->pcm);
- }
+ /* Call _poll_descriptors_revents() even if we don't use it
+ to let underlying plugins clear null events. Otherwise poll()
+ may wake up again and again, producing unnecessary CPU usage. */
+ WRAP(snd_pcm_poll_descriptors_revents)
+ (stm->pcm, stm->fds, stm->nfds, &revents);
- /* Failed to recover from an xrun, this stream must be broken. */
- if (avail < 0) {
+ avail = WRAP(snd_pcm_avail_update)(stm->pcm);
+
+ /* Got null event? Bail and wait for another wakeup. */
+ if (avail == 0) {
pthread_mutex_unlock(&stm->mutex);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return ERROR;
+ return RUNNING;
}
- /* This should never happen. */
- if ((unsigned int) avail > stm->buffer_size) {
+ /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time.
+ */
+ if ((unsigned int)avail > stm->buffer_size) {
avail = stm->buffer_size;
}
- /* poll(2) claims this stream is active, so there should be some space
- available to write. If avail is still zero here, the stream must be in
- a funky state, bail and wait for another wakeup. */
- if (avail == 0) {
+ /* Capture: Read available frames */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) {
+ snd_pcm_sframes_t got;
+
+ if (avail + stm->bufframes > stm->buffer_size) {
+ /* Buffer overflow. Skip and overwrite with new data. */
+ stm->bufframes = 0;
+ // TODO: should it be marked as DRAINING?
+ }
+
+ got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer + stm->bufframes, avail);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+ stm->stream_position += got;
+
+ gettimeofday(&stm->last_activity, NULL);
+ }
+ }
+
+ /* Capture: Pass read frames to callback function */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 &&
+ (!stm->other_stream ||
+ stm->other_stream->bufframes < stm->other_stream->buffer_size)) {
+ snd_pcm_sframes_t wrote = stm->bufframes;
+ struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer +
+ stm->other_stream->bufframes
+ : NULL;
+
+ /* Correct write size to the other stream available space */
+ if (stm->other_stream &&
+ wrote > (snd_pcm_sframes_t)(stm->other_stream->buffer_size -
+ stm->other_stream->bufframes)) {
+ wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes;
+ }
+
pthread_mutex_unlock(&stm->mutex);
- return RUNNING;
+ wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer,
+ other_buffer, wrote);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (wrote < 0) {
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ if (stm->other_stream) {
+ stm->other_stream->bufframes += wrote;
+ }
+ }
}
- p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail));
- assert(p);
+ /* Playback: Don't have enough data? Let's ask for more. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
+ avail > (snd_pcm_sframes_t)stm->bufframes &&
+ (!stm->other_stream || stm->other_stream->bufframes > 0)) {
+ long got = avail - stm->bufframes;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
+ char * buftail =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+
+ /* Correct read size to the other stream available frames */
+ if (stm->other_stream &&
+ got > (snd_pcm_sframes_t)stm->other_stream->bufframes) {
+ got = stm->other_stream->bufframes;
+ }
- pthread_mutex_unlock(&stm->mutex);
- got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail);
- pthread_mutex_lock(&stm->mutex);
- if (got < 0) {
pthread_mutex_unlock(&stm->mutex);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- free(p);
- return ERROR;
+ got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+
+ if (stm->other_stream) {
+ stream_buffer_decrement(stm->other_stream, got);
+ }
+ }
+ }
+
+ /* Playback: Still don't have enough data? Add some silence. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
+ avail > (snd_pcm_sframes_t)stm->bufframes) {
+ long drain_frames = avail - stm->bufframes;
+ double drain_time = (double)drain_frames / stm->params.rate;
+
+ char * buftail =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+ memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames));
+ stm->bufframes = avail;
+
+ /* Mark as draining, unless we're waiting for capture */
+ if (!stm->other_stream || stm->other_stream->bufframes > 0) {
+ set_timeout(&stm->drain_timeout, drain_time * 1000);
+
+ draining = 1;
+ }
}
- if (got > 0) {
+
+ /* Playback: Have enough data and no errors. Let's write it out. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) {
snd_pcm_sframes_t wrote;
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
- float * b = (float *) p;
- for (uint32_t i = 0; i < got * stm->params.channels; i++) {
+ float * b = (float *)stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
b[i] *= stm->volume;
}
} else {
- short * b = (short *) p;
- for (uint32_t i = 0; i < got * stm->params.channels; i++) {
+ short * b = (short *)stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
b[i] *= stm->volume;
}
}
- wrote = snd_pcm_writei(stm->pcm, p, got);
+
+ wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail);
if (wrote < 0) {
- snd_pcm_recover(stm->pcm, wrote, 1);
- wrote = snd_pcm_writei(stm->pcm, p, got);
- }
- if (wrote < 0 || wrote != got) {
- /* Recovery failed, somehow. */
- pthread_mutex_unlock(&stm->mutex);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return ERROR;
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ stm->stream_position += wrote;
+ gettimeofday(&stm->last_activity, NULL);
}
- stm->write_position += wrote;
- gettimeofday(&stm->last_activity, NULL);
}
- if (got != avail) {
- long buffer_fill = stm->buffer_size - (avail - got);
- double buffer_time = (double) buffer_fill / stm->params.rate;
- /* Fill the remaining buffer with silence to guarantee one full period
- has been written. */
- snd_pcm_writei(stm->pcm, (char *) p + got, avail - got);
+ /* Got some error? Let's try to recover the stream. */
+ if (avail < 0) {
+ avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0);
- set_timeout(&stm->drain_timeout, buffer_time * 1000);
+ /* Capture pcm must be started after initial setup/recover */
+ if (avail >= 0 && stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ avail = WRAP(snd_pcm_start)(stm->pcm);
+ }
+ }
- draining = 1;
+ /* Failed to recover, this stream must be broken. */
+ if (avail < 0) {
+ pthread_mutex_unlock(&stm->mutex);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return ERROR;
}
- free(p);
pthread_mutex_unlock(&stm->mutex);
return draining ? DRAINING : RUNNING;
}
@@ -392,10 +543,11 @@ alsa_run(cubeb * ctx)
stm = ctx->streams[i];
/* We can't use snd_pcm_poll_descriptors_revents here because of
https://github.com/kinetiknz/cubeb/issues/135. */
- if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) {
+ if (stm && stm->state == RUNNING && stm->fds &&
+ any_revents(stm->fds, stm->nfds)) {
alsa_set_stream_state(stm, PROCESSING);
pthread_mutex_unlock(&ctx->mutex);
- state = alsa_refill_stream(stm);
+ state = alsa_process_stream(stm);
pthread_mutex_lock(&ctx->mutex);
alsa_set_stream_state(stm, state);
}
@@ -407,7 +559,8 @@ alsa_run(cubeb * ctx)
if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) {
alsa_set_stream_state(stm, INACTIVE);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- } else if (stm->state == RUNNING && ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
+ } else if (stm->state == RUNNING &&
+ ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
alsa_set_stream_state(stm, ERROR);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
@@ -445,35 +598,36 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
slave_def = NULL;
- r = snd_config_search(root_pcm, "slave", &slave_pcm);
+ r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm);
if (r < 0) {
return NULL;
}
- r = snd_config_get_string(slave_pcm, &string);
+ r = WRAP(snd_config_get_string)(slave_pcm, &string);
if (r >= 0) {
- r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def);
+ r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string,
+ &slave_def);
if (r < 0) {
return NULL;
}
}
do {
- r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
+ r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
if (r < 0) {
break;
}
- r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string);
+ r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string);
if (r < 0) {
break;
}
r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
- if (r < 0 || r > (int) sizeof(node_name)) {
+ if (r < 0 || r > (int)sizeof(node_name)) {
break;
}
- r = snd_config_search(lconf, node_name, &pcm);
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm);
if (r < 0) {
break;
}
@@ -482,7 +636,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
} while (0);
if (slave_def) {
- snd_config_delete(slave_def);
+ WRAP(snd_config_delete)(slave_def);
}
return NULL;
@@ -492,7 +646,8 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
higher than requested latency, but the plugin does not update its (and
ALSA's) internal state to reflect that, leading to an immediate underrun
situation. Inspired by WINE's make_handle_underrun_config.
- Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */
+ Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05
+ */
static snd_config_t *
init_local_config_with_workaround(char const * pcm_name)
{
@@ -505,47 +660,49 @@ init_local_config_with_workaround(char const * pcm_name)
lconf = NULL;
- if (snd_config == NULL) {
+ if (*WRAP(snd_config) == NULL) {
return NULL;
}
- r = snd_config_copy(&lconf, snd_config);
+ r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config));
if (r < 0) {
return NULL;
}
do {
- r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node);
+ r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node);
if (r < 0) {
break;
}
- r = snd_config_get_id(pcm_node, &string);
+ r = WRAP(snd_config_get_id)(pcm_node, &string);
if (r < 0) {
break;
}
r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
- if (r < 0 || r > (int) sizeof(node_name)) {
+ if (r < 0 || r > (int)sizeof(node_name)) {
break;
}
- r = snd_config_search(lconf, node_name, &pcm_node);
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm_node);
if (r < 0) {
break;
}
- /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */
+ /* If this PCM has a slave, walk the slave configurations until we reach the
+ * bottom. */
while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) {
pcm_node = node;
}
- /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */
- r = snd_config_search(pcm_node, "type", &node);
+ /* Fetch the PCM node's type, and bail out if it's not the PulseAudio
+ * plugin. */
+ r = WRAP(snd_config_search)(pcm_node, "type", &node);
if (r < 0) {
break;
}
- r = snd_config_get_string(node, &string);
+ r = WRAP(snd_config_get_string)(node, &string);
if (r < 0) {
break;
}
@@ -556,18 +713,18 @@ init_local_config_with_workaround(char const * pcm_name)
/* Don't clobber an explicit existing handle_underrun value, set it only
if it doesn't already exist. */
- r = snd_config_search(pcm_node, "handle_underrun", &node);
+ r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node);
if (r != -ENOENT) {
break;
}
/* Disable pcm_pulse's asynchronous underrun handling. */
- r = snd_config_imake_integer(&node, "handle_underrun", 0);
+ r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0);
if (r < 0) {
break;
}
- r = snd_config_add(pcm_node, node);
+ r = WRAP(snd_config_add)(pcm_node, node);
if (r < 0) {
break;
}
@@ -575,21 +732,23 @@ init_local_config_with_workaround(char const * pcm_name)
return lconf;
} while (0);
- snd_config_delete(lconf);
+ WRAP(snd_config_delete)(lconf);
return NULL;
}
static int
-alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config)
+alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name,
+ snd_pcm_stream_t stream, snd_config_t * local_config)
{
int r;
pthread_mutex_lock(&cubeb_alsa_mutex);
if (local_config) {
- r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config);
+ r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK,
+ local_config);
} else {
- r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK);
+ r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
}
pthread_mutex_unlock(&cubeb_alsa_mutex);
@@ -602,7 +761,7 @@ alsa_locked_pcm_close(snd_pcm_t * pcm)
int r;
pthread_mutex_lock(&cubeb_alsa_mutex);
- r = snd_pcm_close(pcm);
+ r = WRAP(snd_pcm_close)(pcm);
pthread_mutex_unlock(&cubeb_alsa_mutex);
return r;
@@ -658,6 +817,7 @@ silent_error_handler(char const * file, int line, char const * function,
alsa_init(cubeb ** context, char const * context_name)
{
(void)context_name;
+ void * libasound = NULL;
cubeb * ctx;
int r;
int i;
@@ -668,9 +828,31 @@ alsa_init(cubeb ** context, char const * context_name)
assert(context);
*context = NULL;
+#ifndef DISABLE_LIBASOUND_DLOPEN
+ libasound = dlopen("libasound.so.2", RTLD_LAZY);
+ if (!libasound) {
+ libasound = dlopen("libasound.so", RTLD_LAZY);
+ if (!libasound) {
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libasound, #x); \
+ if (!cubeb_##x) { \
+ dlclose(libasound); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBASOUND_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
pthread_mutex_lock(&cubeb_alsa_mutex);
if (!cubeb_alsa_error_handler_set) {
- snd_lib_error_set_handler(silent_error_handler);
+ WRAP(snd_lib_error_set_handler)(silent_error_handler);
cubeb_alsa_error_handler_set = 1;
}
pthread_mutex_unlock(&cubeb_alsa_mutex);
@@ -679,6 +861,7 @@ alsa_init(cubeb ** context, char const * context_name)
assert(ctx);
ctx->ops = &alsa_ops;
+ ctx->libasound = libasound;
r = pthread_mutex_init(&ctx->mutex, NULL);
assert(r == 0);
@@ -712,7 +895,8 @@ alsa_init(cubeb ** context, char const * context_name)
/* Open a dummy PCM to force the configuration space to be evaluated so that
init_local_config_with_workaround can find and modify the default node. */
- r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL);
+ r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
+ NULL);
if (r >= 0) {
alsa_locked_pcm_close(dummy);
}
@@ -722,12 +906,13 @@ alsa_init(cubeb ** context, char const * context_name)
pthread_mutex_unlock(&cubeb_alsa_mutex);
if (ctx->local_config) {
ctx->is_pa = 1;
- r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME,
+ SND_PCM_STREAM_PLAYBACK, ctx->local_config);
/* If we got a local_config, we found a PA PCM. If opening a PCM with that
config fails with EINVAL, the PA PCM is too old for this workaround. */
if (r == -EINVAL) {
pthread_mutex_lock(&cubeb_alsa_mutex);
- snd_config_delete(ctx->local_config);
+ WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex);
ctx->local_config = NULL;
} else if (r >= 0) {
@@ -769,24 +954,28 @@ alsa_destroy(cubeb * ctx)
if (ctx->local_config) {
pthread_mutex_lock(&cubeb_alsa_mutex);
- snd_config_delete(ctx->local_config);
+ WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex);
}
+ if (ctx->libasound) {
+ dlclose(ctx->libasound);
+ }
+
free(ctx);
}
-static void alsa_stream_destroy(cubeb_stream * stm);
+static void
+alsa_stream_destroy(cubeb_stream * stm);
static int
-alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback, cubeb_state_callback state_callback,
- void * user_ptr)
+alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, snd_pcm_stream_t stream_type,
+ cubeb_devid deviceid,
+ cubeb_stream_params * stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
{
(void)stream_name;
cubeb_stream * stm;
@@ -794,23 +983,18 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
snd_pcm_format_t format;
snd_pcm_uframes_t period_size;
int latency_us = 0;
-
+ char const * pcm_name =
+ deviceid ? (char const *)deviceid : CUBEB_ALSA_PCM_NAME;
assert(ctx && stream);
- if (input_stream_params) {
- /* Capture support not yet implemented. */
- return CUBEB_ERROR_NOT_SUPPORTED;
- }
+ *stream = NULL;
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
}
- *stream = NULL;
-
- switch (output_stream_params->format) {
+ switch (stream_params->format) {
case CUBEB_SAMPLE_S16LE:
format = SND_PCM_FORMAT_S16_LE;
break;
@@ -842,20 +1026,28 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
- stm->params = *output_stream_params;
+ stm->params = *stream_params;
stm->state = INACTIVE;
stm->volume = 1.0;
+ stm->buffer = NULL;
+ stm->bufframes = 0;
+ stm->stream_type = stream_type;
+ stm->other_stream = NULL;
r = pthread_mutex_init(&stm->mutex, NULL);
assert(r == 0);
- r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ r = pthread_cond_init(&stm->cond, NULL);
+ assert(r == 0);
+
+ r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type,
+ ctx->local_config);
if (r < 0) {
alsa_stream_destroy(stm);
return CUBEB_ERROR;
}
- r = snd_pcm_nonblock(stm->pcm, 1);
+ r = WRAP(snd_pcm_nonblock)(stm->pcm, 1);
assert(r == 0);
latency_us = latency_frames * 1e6 / stm->params.rate;
@@ -865,30 +1057,34 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
Only resort to this hack if the handle_underrun workaround failed. */
if (!ctx->local_config && ctx->is_pa) {
const int min_latency = 5e5;
- latency_us = latency_us < min_latency ? min_latency: latency_us;
+ latency_us = latency_us < min_latency ? min_latency : latency_us;
}
- r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
- stm->params.channels, stm->params.rate, 1,
- latency_us);
+ r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
+ stm->params.channels, stm->params.rate, 1,
+ latency_us);
if (r < 0) {
alsa_stream_destroy(stm);
return CUBEB_ERROR_INVALID_FORMAT;
}
- r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size);
+ r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size);
assert(r == 0);
- stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm);
+ /* Double internal buffer size to have enough space when waiting for the other
+ * side of duplex connection */
+ stm->buffer_size *= 2;
+ stm->buffer =
+ calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size));
+ assert(stm->buffer);
+
+ stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm);
assert(stm->nfds > 0);
stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
assert(stm->saved_fds);
- r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds);
- assert((nfds_t) r == stm->nfds);
-
- r = pthread_cond_init(&stm->cond, NULL);
- assert(r == 0);
+ r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds);
+ assert((nfds_t)r == stm->nfds);
if (alsa_register_stream(ctx, stm) != 0) {
alsa_stream_destroy(stm);
@@ -900,22 +1096,66 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
return CUBEB_OK;
}
+static int
+alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int result = CUBEB_OK;
+ cubeb_stream *instm = NULL, *outstm = NULL;
+
+ if (result == CUBEB_OK && input_stream_params) {
+ result = alsa_stream_init_single(ctx, &instm, stream_name,
+ SND_PCM_STREAM_CAPTURE, input_device,
+ input_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && output_stream_params) {
+ result = alsa_stream_init_single(ctx, &outstm, stream_name,
+ SND_PCM_STREAM_PLAYBACK, output_device,
+ output_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && input_stream_params && output_stream_params) {
+ instm->other_stream = outstm;
+ outstm->other_stream = instm;
+ }
+
+ if (result != CUBEB_OK && instm) {
+ alsa_stream_destroy(instm);
+ }
+
+ *stream = outstm ? outstm : instm;
+
+ return result;
+}
+
static void
alsa_stream_destroy(cubeb_stream * stm)
{
int r;
cubeb * ctx;
- assert(stm && (stm->state == INACTIVE ||
- stm->state == ERROR ||
+ assert(stm && (stm->state == INACTIVE || stm->state == ERROR ||
stm->state == DRAINING));
ctx = stm->context;
+ if (stm->other_stream) {
+ stm->other_stream->other_stream = NULL; // to stop infinite recursion
+ alsa_stream_destroy(stm->other_stream);
+ }
+
pthread_mutex_lock(&stm->mutex);
if (stm->pcm) {
if (stm->state == DRAINING) {
- snd_pcm_drain(stm->pcm);
+ WRAP(snd_pcm_drain)(stm->pcm);
}
alsa_locked_pcm_close(stm->pcm);
stm->pcm = NULL;
@@ -934,6 +1174,8 @@ alsa_stream_destroy(cubeb_stream * stm)
ctx->active_streams -= 1;
pthread_mutex_unlock(&ctx->mutex);
+ free(stm->buffer);
+
free(stm);
}
@@ -942,7 +1184,7 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
int r;
cubeb_stream * stm;
- snd_pcm_hw_params_t* hw_params;
+ snd_pcm_hw_params_t * hw_params;
cubeb_stream_params params;
params.rate = 44100;
params.format = CUBEB_SAMPLE_FLOAT32NE;
@@ -952,17 +1194,20 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
assert(ctx);
- r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL, NULL, NULL);
+ r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL,
+ NULL, NULL);
if (r != CUBEB_OK) {
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_any(stm->pcm, hw_params);
+ assert(stm);
+
+ r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params);
if (r < 0) {
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels);
+ r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels);
if (r < 0) {
return CUBEB_ERROR;
}
@@ -973,7 +1218,8 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
}
static int
-alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
+alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
(void)ctx;
int r, dir;
snd_pcm_t * pcm;
@@ -983,40 +1229,42 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
/* get a pcm, disabling resampling, so we get a rate the
* hardware/dmix/pulse/etc. supports. */
- r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE);
+ r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NO_AUTO_RESAMPLE);
if (r < 0) {
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_any(pcm, hw_params);
+ r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params);
if (r < 0) {
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir);
+ r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir);
if (r >= 0) {
/* There is a default rate: use it. */
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_OK;
}
/* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */
*rate = 44100;
- r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL);
+ r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL);
if (r < 0) {
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_ERROR;
}
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_OK;
}
static int
-alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
{
(void)ctx;
/* 40ms is found to be an acceptable minimum, even on a super low-end
@@ -1034,8 +1282,19 @@ alsa_stream_start(cubeb_stream * stm)
assert(stm);
ctx = stm->context;
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_start(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
pthread_mutex_lock(&stm->mutex);
- snd_pcm_pause(stm->pcm, 0);
+ /* Capture pcm must be started after initial setup/recover */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ WRAP(snd_pcm_start)(stm->pcm);
+ }
+ WRAP(snd_pcm_pause)(stm->pcm, 0);
gettimeofday(&stm->last_activity, NULL);
pthread_mutex_unlock(&stm->mutex);
@@ -1059,6 +1318,12 @@ alsa_stream_stop(cubeb_stream * stm)
assert(stm);
ctx = stm->context;
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_stop(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
pthread_mutex_lock(&ctx->mutex);
while (stm->state == PROCESSING) {
r = pthread_cond_wait(&stm->cond, &ctx->mutex);
@@ -1069,7 +1334,7 @@ alsa_stream_stop(cubeb_stream * stm)
pthread_mutex_unlock(&ctx->mutex);
pthread_mutex_lock(&stm->mutex);
- snd_pcm_pause(stm->pcm, 1);
+ WRAP(snd_pcm_pause)(stm->pcm, 1);
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
@@ -1085,8 +1350,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
pthread_mutex_lock(&stm->mutex);
delay = -1;
- if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING ||
- snd_pcm_delay(stm->pcm, &delay) != 0) {
+ if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING ||
+ WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) {
*position = stm->last_position;
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
@@ -1095,8 +1360,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
assert(delay >= 0);
*position = 0;
- if (stm->write_position >= (snd_pcm_uframes_t) delay) {
- *position = stm->write_position - delay;
+ if (stm->stream_position >= (snd_pcm_uframes_t)delay) {
+ *position = stm->stream_position - delay;
}
stm->last_position = *position;
@@ -1110,8 +1375,9 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
snd_pcm_sframes_t delay;
/* This function returns the delay in frames until a frame written using
- snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */
- if (snd_pcm_delay(stm->pcm, &delay)) {
+ snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways.
+ */
+ if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) {
return CUBEB_ERROR;
}
@@ -1131,24 +1397,86 @@ alsa_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK;
}
+static int
+alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info * device = NULL;
+
+ if (!context)
+ return CUBEB_ERROR;
+
+ uint32_t rate, max_channels;
+ int r;
+
+ r = alsa_get_preferred_sample_rate(context, &rate);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ r = alsa_get_max_channel_count(context, &max_channels);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ char const * a_name = "default";
+ device = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
+ assert(device);
+ if (!device)
+ return CUBEB_ERROR;
+
+ device->device_id = a_name;
+ device->devid = (cubeb_devid)device->device_id;
+ device->friendly_name = a_name;
+ device->group_id = a_name;
+ device->vendor_name = a_name;
+ device->type = type;
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = max_channels;
+ device->min_rate = rate;
+ device->max_rate = rate;
+ device->default_rate = rate;
+ device->latency_lo = 0;
+ device->latency_hi = 0;
+
+ collection->device = device;
+ collection->count = 1;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ assert(collection->count == 1);
+ (void)context;
+ free(collection->device);
+ return CUBEB_OK;
+}
+
static struct cubeb_ops const alsa_ops = {
- .init = alsa_init,
- .get_backend_id = alsa_get_backend_id,
- .get_max_channel_count = alsa_get_max_channel_count,
- .get_min_latency = alsa_get_min_latency,
- .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = alsa_destroy,
- .stream_init = alsa_stream_init,
- .stream_destroy = alsa_stream_destroy,
- .stream_start = alsa_stream_start,
- .stream_stop = alsa_stream_stop,
- .stream_get_position = alsa_stream_get_position,
- .stream_get_latency = alsa_stream_get_latency,
- .stream_set_volume = alsa_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = alsa_init,
+ .get_backend_id = alsa_get_backend_id,
+ .get_max_channel_count = alsa_get_max_channel_count,
+ .get_min_latency = alsa_get_min_latency,
+ .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
+ .enumerate_devices = alsa_enumerate_devices,
+ .device_collection_destroy = alsa_device_collection_destroy,
+ .destroy = alsa_destroy,
+ .stream_init = alsa_stream_init,
+ .stream_destroy = alsa_stream_destroy,
+ .stream_start = alsa_stream_start,
+ .stream_stop = alsa_stream_stop,
+ .stream_get_position = alsa_stream_get_position,
+ .stream_get_latency = alsa_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = alsa_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_android.h b/media/libcubeb/src/cubeb_android.h
new file mode 100644
index 0000000000..c21a941ab5
--- /dev/null
+++ b/media/libcubeb/src/cubeb_android.h
@@ -0,0 +1,17 @@
+#ifndef CUBEB_ANDROID_H
+#define CUBEB_ANDROID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// If the latency requested is above this threshold, this stream is considered
+// intended for playback (vs. real-time). Tell Android it should favor saving
+// power over performance or latency.
+// This is around 100ms at 44100 or 48000
+const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // CUBEB_ANDROID_H
diff --git a/media/libcubeb/src/cubeb_array_queue.h b/media/libcubeb/src/cubeb_array_queue.h
new file mode 100644
index 0000000000..d6d9581325
--- /dev/null
+++ b/media/libcubeb/src/cubeb_array_queue.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ARRAY_QUEUE_H
+#define CUBEB_ARRAY_QUEUE_H
+
+#include <assert.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ void ** buf;
+ size_t num;
+ size_t writePos;
+ size_t readPos;
+ pthread_mutex_t mutex;
+} array_queue;
+
+array_queue *
+array_queue_create(size_t num)
+{
+ assert(num != 0);
+ array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue));
+ new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
+ new_queue->readPos = 0;
+ new_queue->writePos = 0;
+ new_queue->num = num;
+
+ pthread_mutex_init(&new_queue->mutex, NULL);
+
+ return new_queue;
+}
+
+void
+array_queue_destroy(array_queue * aq)
+{
+ assert(aq);
+
+ free(aq->buf);
+ pthread_mutex_destroy(&aq->mutex);
+ free(aq);
+}
+
+int
+array_queue_push(array_queue * aq, void * item)
+{
+ assert(item);
+
+ pthread_mutex_lock(&aq->mutex);
+ int ret = -1;
+ if (aq->buf[aq->writePos % aq->num] == NULL) {
+ aq->buf[aq->writePos % aq->num] = item;
+ aq->writePos = (aq->writePos + 1) % aq->num;
+ ret = 0;
+ }
+ // else queue is full
+ pthread_mutex_unlock(&aq->mutex);
+ return ret;
+}
+
+void *
+array_queue_pop(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ void * value = aq->buf[aq->readPos % aq->num];
+ if (value) {
+ aq->buf[aq->readPos % aq->num] = NULL;
+ aq->readPos = (aq->readPos + 1) % aq->num;
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return value;
+}
+
+size_t
+array_queue_get_size(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ ssize_t r = aq->writePos - aq->readPos;
+ if (r < 0) {
+ r = aq->num + r;
+ assert(r >= 0);
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return (size_t)r;
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBE_ARRAY_QUEUE_H
diff --git a/media/libcubeb/src/cubeb_assert.h b/media/libcubeb/src/cubeb_assert.h
new file mode 100644
index 0000000000..d81a1eacaf
--- /dev/null
+++ b/media/libcubeb/src/cubeb_assert.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ASSERT
+#define CUBEB_ASSERT
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/**
+ * This allow using an external release assert method. This file should only
+ * export a function or macro called XASSERT that aborts the program.
+ */
+
+#define XASSERT(expr) \
+ do { \
+ if (!(expr)) { \
+ fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
+ abort(); \
+ } \
+ } while (0)
+
+#endif
diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c
deleted file mode 100644
index fe2603405e..0000000000
--- a/media/libcubeb/src/cubeb_audiotrack.c
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright © 2013 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-#if !defined(NDEBUG)
-#define NDEBUG
-#endif
-#include <assert.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <time.h>
-#include <dlfcn.h>
-#include "android/log.h"
-
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include "android/audiotrack_definitions.h"
-
-#ifndef ALOG
-#if defined(DEBUG) || defined(FORCE_ALOG)
-#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args)
-#else
-#define ALOG(args...)
-#endif
-#endif
-
-/**
- * A lot of bytes for safety. It should be possible to bring this down a bit. */
-#define SIZE_AUDIOTRACK_INSTANCE 256
-
-/**
- * call dlsym to get the symbol |mangled_name|, handle the error and store the
- * pointer in |pointer|. Because depending on Android version, we want different
- * symbols, not finding a symbol is not an error. */
-#define DLSYM_DLERROR(mangled_name, pointer, lib) \
- do { \
- pointer = dlsym(lib, mangled_name); \
- if (!pointer) { \
- ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
- } else { \
- ALOG("%stm: OK", mangled_name); \
- } \
- } while(0);
-
-static struct cubeb_ops const audiotrack_ops;
-void audiotrack_destroy(cubeb * context);
-void audiotrack_stream_destroy(cubeb_stream * stream);
-
-struct AudioTrack {
- /* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */
- /* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate);
- /* if we have a recent ctor, but can't find the above symbol, we
- * can get the minimum frame count with this signature, and we are
- * running gingerbread. */
- /* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate);
- void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int);
- void* (*dtor)(void* instance);
- void (*start)(void* instance);
- void (*pause)(void* instance);
- uint32_t (*latency)(void* instance);
- status_t (*check)(void* instance);
- status_t (*get_position)(void* instance, uint32_t* position);
- /* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
- status_t (*set_marker_position)(void* instance, unsigned int);
- status_t (*set_volume)(void* instance, float left, float right);
-};
-
-struct cubeb {
- struct cubeb_ops const * ops;
- void * library;
- struct AudioTrack klass;
-};
-
-struct cubeb_stream {
- cubeb * context;
- cubeb_stream_params params;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * instance;
- void * user_ptr;
- /* Number of frames that have been passed to the AudioTrack callback */
- long unsigned written;
- int draining;
-};
-
-static void
-audiotrack_refill(int event, void* user, void* info)
-{
- cubeb_stream * stream = user;
- switch (event) {
- case EVENT_MORE_DATA: {
- long got = 0;
- struct Buffer * b = (struct Buffer*)info;
-
- if (stream->draining) {
- return;
- }
-
- got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount);
-
- stream->written += got;
-
- if (got != (long)b->frameCount) {
- stream->draining = 1;
- /* set a marker so we are notified when the are done draining, that is,
- * when every frame has been played by android. */
- stream->context->klass.set_marker_position(stream->instance, stream->written);
- }
-
- break;
- }
- case EVENT_UNDERRUN:
- ALOG("underrun in cubeb backend.");
- break;
- case EVENT_LOOP_END:
- assert(0 && "We don't support the loop feature of audiotrack.");
- break;
- case EVENT_MARKER:
- assert(stream->draining);
- stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
- break;
- case EVENT_NEW_POS:
- assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack.");
- break;
- case EVENT_BUFFER_END:
- assert(0 && "Should not happen.");
- break;
- }
-}
-
-/* We are running on gingerbread if we found the gingerbread signature for
- * getMinFrameCount */
-static int
-audiotrack_version_is_gingerbread(cubeb * ctx)
-{
- return ctx->klass.get_min_frame_count_gingerbread != NULL;
-}
-
-int
-audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count)
-{
- status_t status;
- /* Recent Android have a getMinFrameCount method. */
- if (!audiotrack_version_is_gingerbread(ctx)) {
- status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate);
- } else {
- status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate);
- }
- if (status != 0) {
- ALOG("error getting the min frame count");
- return CUBEB_ERROR;
- }
- return CUBEB_OK;
-}
-
-int
-audiotrack_init(cubeb ** context, char const * context_name)
-{
- cubeb * ctx;
- struct AudioTrack* c;
-
- assert(context);
- *context = NULL;
-
- ctx = calloc(1, sizeof(*ctx));
- assert(ctx);
-
- /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
- * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
- * the first call to a dlsym'ed function. Somehow this does not happen when
- * using only the name of the library. */
- ctx->library = dlopen("libmedia.so", RTLD_LAZY);
- if (!ctx->library) {
- ALOG("dlopen error: %s.", dlerror());
- free(ctx);
- return CUBEB_ERROR;
- }
-
- /* Recent Android first, then Gingerbread. */
- DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
-
- DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
- DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
-
- DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
-
- /* |getMinFrameCount| is available on gingerbread and ICS with different signatures. */
- DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library);
- if (!ctx->klass.get_min_frame_count) {
- DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library);
- }
-
- DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, ctx->library);
-
- /* check that we have a combination of symbol that makes sense */
- c = &ctx->klass;
- if(!(c->ctor &&
- c->dtor && c->latency && c->check &&
- /* at least one way to get the minimum frame count to request. */
- (c->get_min_frame_count ||
- c->get_min_frame_count_gingerbread) &&
- c->start && c->pause && c->get_position && c->set_marker_position)) {
- ALOG("Could not find all the symbols we need.");
- audiotrack_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- ctx->ops = &audiotrack_ops;
-
- *context = ctx;
-
- return CUBEB_OK;
-}
-
-char const *
-audiotrack_get_backend_id(cubeb * context)
-{
- return "audiotrack";
-}
-
-static int
-audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
-{
- assert(ctx && max_channels);
-
- /* The android mixer handles up to two channels, see
- http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
- *max_channels = 2;
-
- return CUBEB_OK;
-}
-
-static int
-audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
-{
- /* We always use the lowest latency possible when using this backend (see
- * audiotrack_stream_init), so this value is not going to be used. */
- int r;
-
- r = audiotrack_get_min_frame_count(ctx, &params, (int *)latency_ms);
- if (r != CUBEB_OK) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
-}
-
-static int
-audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- status_t r;
-
- r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
-
- return r == 0 ? CUBEB_OK : CUBEB_ERROR;
-}
-
-void
-audiotrack_destroy(cubeb * context)
-{
- assert(context);
-
- dlclose(context->library);
-
- free(context);
-}
-
-int
-audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
-{
- cubeb_stream * stm;
- int32_t channels;
- uint32_t min_frame_count;
-
- assert(ctx && stream);
-
- assert(!input_stream_params && "not supported");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
-
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
- output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
- return CUBEB_ERROR_INVALID_FORMAT;
- }
-
- if (audiotrack_get_min_frame_count(ctx, output_stream_params, (int *)&min_frame_count)) {
- return CUBEB_ERROR;
- }
-
- stm = calloc(1, sizeof(*stm));
- assert(stm);
-
- stm->context = ctx;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->params = *output_stream_params;
-
- stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
- (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad;
- assert(stm->instance && "cubeb: EOM");
-
- /* gingerbread uses old channel layout enum */
- if (audiotrack_version_is_gingerbread(ctx)) {
- channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy;
- } else {
- channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS;
- }
-
- ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate,
- AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
- audiotrack_refill, stm, 0, 0);
-
- assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
-
- if (ctx->klass.check(stm->instance)) {
- ALOG("stream not initialized properly.");
- audiotrack_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- *stream = stm;
-
- return CUBEB_OK;
-}
-
-void
-audiotrack_stream_destroy(cubeb_stream * stream)
-{
- assert(stream->context);
-
- stream->context->klass.dtor(stream->instance);
-
- free(stream->instance);
- stream->instance = NULL;
- free(stream);
-}
-
-int
-audiotrack_stream_start(cubeb_stream * stream)
-{
- assert(stream->instance);
-
- stream->context->klass.start(stream->instance);
- stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
-
- return CUBEB_OK;
-}
-
-int
-audiotrack_stream_stop(cubeb_stream * stream)
-{
- assert(stream->instance);
-
- stream->context->klass.pause(stream->instance);
- stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
-
- return CUBEB_OK;
-}
-
-int
-audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
-{
- uint32_t p;
-
- assert(stream->instance && position);
- stream->context->klass.get_position(stream->instance, &p);
- *position = p;
-
- return CUBEB_OK;
-}
-
-int
-audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
-{
- assert(stream->instance && latency);
-
- /* Android returns the latency in ms, we want it in frames. */
- *latency = stream->context->klass.latency(stream->instance);
- /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
- *latency = (*latency * stream->params.rate) / 1000;
-
- return 0;
-}
-
-int
-audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
-{
- status_t status;
-
- status = stream->context->klass.set_volume(stream->instance, volume, volume);
-
- if (status) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
-}
-
-static struct cubeb_ops const audiotrack_ops = {
- .init = audiotrack_init,
- .get_backend_id = audiotrack_get_backend_id,
- .get_max_channel_count = audiotrack_get_max_channel_count,
- .get_min_latency = audiotrack_get_min_latency,
- .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = audiotrack_destroy,
- .stream_init = audiotrack_stream_init,
- .stream_destroy = audiotrack_stream_destroy,
- .stream_start = audiotrack_stream_start,
- .stream_stop = audiotrack_stream_stop,
- .stream_get_position = audiotrack_stream_get_position,
- .stream_get_latency = audiotrack_stream_get_latency,
- .stream_set_volume = audiotrack_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
index 9483c2795e..02cd134697 100644
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -6,236 +6,387 @@
*/
#undef NDEBUG
+#include <AudioUnit/AudioUnit.h>
#include <TargetConditionals.h>
#include <assert.h>
#include <mach/mach_time.h>
#include <pthread.h>
#include <stdlib.h>
-#include <AudioUnit/AudioUnit.h>
#if !TARGET_OS_IPHONE
#include <AvailabilityMacros.h>
#include <CoreAudio/AudioHardware.h>
#include <CoreAudio/HostTime.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
-#include <CoreAudio/CoreAudioTypes.h>
-#include <AudioToolbox/AudioToolbox.h>
-#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
-#include "cubeb_panner.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include <AudioToolbox/AudioToolbox.h>
+#include <CoreAudio/CoreAudioTypes.h>
#if !TARGET_OS_IPHONE
#include "cubeb_osx_run_loop.h"
#endif
#include "cubeb_resampler.h"
#include "cubeb_ring_array.h"
-#include "cubeb_utils.h"
#include <algorithm>
#include <atomic>
+#include <set>
+#include <string>
+#include <sys/time.h>
+#include <vector>
-#if !defined(kCFCoreFoundationVersionNumber10_7)
-/* From CoreFoundation CFBase.h */
-#define kCFCoreFoundationVersionNumber10_7 635.00
-#endif
-
-#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060
-#define AudioComponent Component
-#define AudioComponentDescription ComponentDescription
-#define AudioComponentFindNext FindNextComponent
-#define AudioComponentInstanceNew OpenAComponent
-#define AudioComponentInstanceDispose CloseComponent
-#endif
+using namespace std;
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
-typedef UInt32 AudioFormatFlags;
+typedef UInt32 AudioFormatFlags;
#endif
-#define CUBEB_STREAM_MAX 8
-
-#define AU_OUT_BUS 0
-#define AU_IN_BUS 1
-
-#define PRINT_ERROR_CODE(str, r) do { \
- LOG("System call failed: %s (rv: %d)", str, r); \
-} while(0)
+#define AU_OUT_BUS 0
+#define AU_IN_BUS 1
const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
+const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice";
+
+#ifdef ALOGV
+#undef ALOGV
+#endif
+#define ALOGV(msg, ...) \
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
+ ^{ \
+ LOGV(msg, ##__VA_ARGS__); \
+ })
+
+#ifdef ALOG
+#undef ALOG
+#endif
+#define ALOG(msg, ...) \
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
+ ^{ \
+ LOG(msg, ##__VA_ARGS__); \
+ })
/* Testing empirically, some headsets report a minimal latency that is very
* low, but this does not work in practice. Lie and say the minimum is 256
* frames. */
-const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
+const uint32_t SAFE_MIN_LATENCY_FRAMES = 128;
const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
-void audiounit_stream_stop_internal(cubeb_stream * stm);
-void audiounit_stream_start_internal(cubeb_stream * stm);
-static void audiounit_close_stream(cubeb_stream *stm);
-static int audiounit_setup_stream(cubeb_stream *stm);
+const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
+
+typedef uint32_t device_flags_value;
+
+enum device_flags {
+ DEV_UNKNOWN = 0x00, /* Unknown */
+ DEV_INPUT = 0x01, /* Record device like mic */
+ DEV_OUTPUT = 0x02, /* Playback device like speakers */
+ DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
+ DEV_SELECTED_DEFAULT =
+ 0x08, /* User selected to use the system default device */
+};
+
+void
+audiounit_stream_stop_internal(cubeb_stream * stm);
+static int
+audiounit_stream_start_internal(cubeb_stream * stm);
+static void
+audiounit_close_stream(cubeb_stream * stm);
+static int
+audiounit_setup_stream(cubeb_stream * stm);
+static vector<AudioObjectID>
+audiounit_get_devices_of_type(cubeb_device_type devtype);
+static UInt32
+audiounit_get_device_presentation_latency(AudioObjectID devid,
+ AudioObjectPropertyScope scope);
+
+#if !TARGET_OS_IPHONE
+static AudioObjectID
+audiounit_get_default_device_id(cubeb_device_type type);
+static int
+audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
+static int
+audiounit_uninstall_system_changed_callback(cubeb_stream * stm);
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags);
+#endif
extern cubeb_ops const audiounit_ops;
struct cubeb {
- cubeb_ops const * ops;
+ cubeb_ops const * ops = &audiounit_ops;
owned_critical_section mutex;
- std::atomic<int> active_streams;
+ int active_streams = 0;
uint32_t global_latency_frames = 0;
- int limit_streams;
- cubeb_device_collection_changed_callback collection_changed_callback;
- void * collection_changed_user_ptr;
- /* Differentiate input from output devices. */
- cubeb_device_type collection_changed_devtype;
- uint32_t devtype_device_count;
- AudioObjectID * devtype_device_array;
- // The queue is asynchronously deallocated once all references to it are released
- dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ cubeb_device_collection_changed_callback input_collection_changed_callback =
+ nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ cubeb_device_collection_changed_callback output_collection_changed_callback =
+ nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ // Store list of devices to detect changes
+ vector<AudioObjectID> input_device_array;
+ vector<AudioObjectID> output_device_array;
+ // The queue should be released when it’s no longer needed.
+ dispatch_queue_t serial_queue =
+ dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ // Current used channel layout
+ atomic<cubeb_channel_layout> layout{CUBEB_LAYOUT_UNDEFINED};
+ uint32_t channels = 0;
};
-class auto_array_wrapper
+static unique_ptr<AudioChannelLayout, decltype(&free)>
+make_sized_audio_channel_layout(size_t sz)
{
-public:
- explicit auto_array_wrapper(auto_array<float> * ar)
- : float_ar(ar)
- , short_ar(nullptr)
- {assert((float_ar && !short_ar) || (!float_ar && short_ar));}
-
- explicit auto_array_wrapper(auto_array<short> * ar)
- : float_ar(nullptr)
- , short_ar(ar)
- {assert((float_ar && !short_ar) || (!float_ar && short_ar));}
-
- ~auto_array_wrapper() {
- auto_lock l(lock);
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- delete float_ar;
- delete short_ar;
- }
-
- void push(void * elements, size_t length){
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->push(static_cast<float*>(elements), length);
- return short_ar->push(static_cast<short*>(elements), length);
- }
+ assert(sz >= sizeof(AudioChannelLayout));
+ AudioChannelLayout * acl =
+ reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
+ assert(acl); // Assert the allocation works.
+ return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
+}
- size_t length() {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->length();
- return short_ar->length();
- }
+enum class io_side {
+ INPUT,
+ OUTPUT,
+};
- void push_silence(size_t length) {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->push_silence(length);
- return short_ar->push_silence(length);
+static char const *
+to_string(io_side side)
+{
+ switch (side) {
+ case io_side::INPUT:
+ return "input";
+ case io_side::OUTPUT:
+ return "output";
}
+}
- bool pop(void * elements, size_t length) {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->pop(static_cast<float*>(elements), length);
- return short_ar->pop(static_cast<short*>(elements), length);
- }
+struct device_info {
+ AudioDeviceID id = kAudioObjectUnknown;
+ device_flags_value flags = DEV_UNKNOWN;
+};
- void * data() {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->data();
- return short_ar->data();
- }
+struct property_listener {
+ AudioDeviceID device_id;
+ const AudioObjectPropertyAddress * property_address;
+ AudioObjectPropertyListenerProc callback;
+ cubeb_stream * stream;
- void clear() {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar) {
- float_ar->clear();
- } else {
- short_ar->clear();
- }
+ property_listener(AudioDeviceID id,
+ const AudioObjectPropertyAddress * address,
+ AudioObjectPropertyListenerProc proc, cubeb_stream * stm)
+ : device_id(id), property_address(address), callback(proc), stream(stm)
+ {
}
-
-private:
- auto_array<float> * float_ar;
- auto_array<short> * short_ar;
- owned_critical_section lock;
};
struct cubeb_stream {
+ explicit cubeb_stream(cubeb * context);
+
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- cubeb_device_changed_callback device_changed_callback;
+ void * user_ptr = nullptr;
+ /**/
+
+ cubeb_data_callback data_callback = nullptr;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_device_changed_callback device_changed_callback = nullptr;
+ owned_critical_section device_changed_callback_lock;
/* Stream creation parameters */
- cubeb_stream_params input_stream_params;
- cubeb_stream_params output_stream_params;
- cubeb_devid input_device;
- bool is_default_input;
- cubeb_devid output_device;
- /* User pointer of data_callback */
- void * user_ptr;
+ cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ device_info input_device;
+ device_info output_device;
/* Format descriptions */
AudioStreamBasicDescription input_desc;
AudioStreamBasicDescription output_desc;
/* I/O AudioUnits */
- AudioUnit input_unit;
- AudioUnit output_unit;
+ AudioUnit input_unit = nullptr;
+ AudioUnit output_unit = nullptr;
/* I/O device sample rate */
- Float64 input_hw_rate;
- Float64 output_hw_rate;
+ Float64 input_hw_rate = 0;
+ Float64 output_hw_rate = 0;
/* Expected I/O thread interleave,
* calculated from I/O hw rate. */
- int expected_output_callbacks_in_a_row;
+ int expected_output_callbacks_in_a_row = 0;
owned_critical_section mutex;
- /* Hold the input samples in every
- * input callback iteration */
- auto_array_wrapper * input_linear_buffer;
- /* Frames on input buffer */
- std::atomic<uint32_t> input_buffer_frames;
+ // Hold the input samples in every input callback iteration.
+ // Only accessed on input/output callback thread and during initial configure.
+ unique_ptr<auto_array_wrapper> input_linear_buffer;
/* Frame counters */
- uint64_t frames_played;
- uint64_t frames_queued;
- std::atomic<int64_t> frames_read;
- std::atomic<bool> shutdown;
- std::atomic<bool> draining;
+ atomic<uint64_t> frames_played{0};
+ uint64_t frames_queued = 0;
+ // How many frames got read from the input since the stream started (includes
+ // padded silence)
+ atomic<int64_t> frames_read{0};
+ // How many frames got written to the output device since the stream started
+ atomic<int64_t> frames_written{0};
+ atomic<bool> shutdown{true};
+ atomic<bool> draining{false};
+ atomic<bool> reinit_pending{false};
+ atomic<bool> destroy_pending{false};
/* Latency requested by the user. */
- uint32_t latency_frames;
- std::atomic<uint64_t> current_latency_frames;
- uint64_t hw_latency_frames;
- std::atomic<float> panning;
- cubeb_resampler * resampler;
- /* This is the number of output callback we got in a row. This is usually one,
- * but can be two when the input and output rate are different, and more when
- * a device has been plugged or unplugged, as there can be some time before
- * the device is ready. */
- std::atomic<int> output_callback_in_a_row;
+ uint32_t latency_frames = 0;
+ atomic<uint32_t> current_latency_frames{0};
+ atomic<uint32_t> total_output_latency_frames{0};
+ unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
/* This is true if a device change callback is currently running. */
- std::atomic<bool> switching_device;
- std::atomic<bool> buffer_size_change_state{ false };
+ atomic<bool> switching_device{false};
+ atomic<bool> buffer_size_change_state{false};
+ AudioDeviceID aggregate_device_id =
+ kAudioObjectUnknown; // the aggregate device id
+ AudioObjectID plugin_id =
+ kAudioObjectUnknown; // used to create aggregate device
+ /* Mixer interface */
+ unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;
+ /* Buffer where remixing/resampling will occur when upmixing is required */
+ /* Only accessed from callback thread */
+ unique_ptr<uint8_t[]> temp_buffer;
+ size_t temp_buffer_size = 0; // size in bytes.
+ /* Listeners indicating what system events are monitored. */
+ unique_ptr<property_listener> default_input_listener;
+ unique_ptr<property_listener> default_output_listener;
+ unique_ptr<property_listener> input_alive_listener;
+ unique_ptr<property_listener> input_source_listener;
+ unique_ptr<property_listener> output_source_listener;
};
-bool has_input(cubeb_stream * stm)
+bool
+has_input(cubeb_stream * stm)
{
return stm->input_stream_params.rate != 0;
}
-bool has_output(cubeb_stream * stm)
+bool
+has_output(cubeb_stream * stm)
{
return stm->output_stream_params.rate != 0;
}
+cubeb_channel
+channel_label_to_cubeb_channel(UInt32 label)
+{
+ switch (label) {
+ case kAudioChannelLabel_Left:
+ return CHANNEL_FRONT_LEFT;
+ case kAudioChannelLabel_Right:
+ return CHANNEL_FRONT_RIGHT;
+ case kAudioChannelLabel_Center:
+ return CHANNEL_FRONT_CENTER;
+ case kAudioChannelLabel_LFEScreen:
+ return CHANNEL_LOW_FREQUENCY;
+ case kAudioChannelLabel_LeftSurround:
+ return CHANNEL_BACK_LEFT;
+ case kAudioChannelLabel_RightSurround:
+ return CHANNEL_BACK_RIGHT;
+ case kAudioChannelLabel_LeftCenter:
+ return CHANNEL_FRONT_LEFT_OF_CENTER;
+ case kAudioChannelLabel_RightCenter:
+ return CHANNEL_FRONT_RIGHT_OF_CENTER;
+ case kAudioChannelLabel_CenterSurround:
+ return CHANNEL_BACK_CENTER;
+ case kAudioChannelLabel_LeftSurroundDirect:
+ return CHANNEL_SIDE_LEFT;
+ case kAudioChannelLabel_RightSurroundDirect:
+ return CHANNEL_SIDE_RIGHT;
+ case kAudioChannelLabel_TopCenterSurround:
+ return CHANNEL_TOP_CENTER;
+ case kAudioChannelLabel_VerticalHeightLeft:
+ return CHANNEL_TOP_FRONT_LEFT;
+ case kAudioChannelLabel_VerticalHeightCenter:
+ return CHANNEL_TOP_FRONT_CENTER;
+ case kAudioChannelLabel_VerticalHeightRight:
+ return CHANNEL_TOP_FRONT_RIGHT;
+ case kAudioChannelLabel_TopBackLeft:
+ return CHANNEL_TOP_BACK_LEFT;
+ case kAudioChannelLabel_TopBackCenter:
+ return CHANNEL_TOP_BACK_CENTER;
+ case kAudioChannelLabel_TopBackRight:
+ return CHANNEL_TOP_BACK_RIGHT;
+ default:
+ return CHANNEL_UNKNOWN;
+ }
+}
+
+AudioChannelLabel
+cubeb_channel_to_channel_label(cubeb_channel channel)
+{
+ switch (channel) {
+ case CHANNEL_FRONT_LEFT:
+ return kAudioChannelLabel_Left;
+ case CHANNEL_FRONT_RIGHT:
+ return kAudioChannelLabel_Right;
+ case CHANNEL_FRONT_CENTER:
+ return kAudioChannelLabel_Center;
+ case CHANNEL_LOW_FREQUENCY:
+ return kAudioChannelLabel_LFEScreen;
+ case CHANNEL_BACK_LEFT:
+ return kAudioChannelLabel_LeftSurround;
+ case CHANNEL_BACK_RIGHT:
+ return kAudioChannelLabel_RightSurround;
+ case CHANNEL_FRONT_LEFT_OF_CENTER:
+ return kAudioChannelLabel_LeftCenter;
+ case CHANNEL_FRONT_RIGHT_OF_CENTER:
+ return kAudioChannelLabel_RightCenter;
+ case CHANNEL_BACK_CENTER:
+ return kAudioChannelLabel_CenterSurround;
+ case CHANNEL_SIDE_LEFT:
+ return kAudioChannelLabel_LeftSurroundDirect;
+ case CHANNEL_SIDE_RIGHT:
+ return kAudioChannelLabel_RightSurroundDirect;
+ case CHANNEL_TOP_CENTER:
+ return kAudioChannelLabel_TopCenterSurround;
+ case CHANNEL_TOP_FRONT_LEFT:
+ return kAudioChannelLabel_VerticalHeightLeft;
+ case CHANNEL_TOP_FRONT_CENTER:
+ return kAudioChannelLabel_VerticalHeightCenter;
+ case CHANNEL_TOP_FRONT_RIGHT:
+ return kAudioChannelLabel_VerticalHeightRight;
+ case CHANNEL_TOP_BACK_LEFT:
+ return kAudioChannelLabel_TopBackLeft;
+ case CHANNEL_TOP_BACK_CENTER:
+ return kAudioChannelLabel_TopBackCenter;
+ case CHANNEL_TOP_BACK_RIGHT:
+ return kAudioChannelLabel_TopBackRight;
+ default:
+ return kAudioChannelLabel_Unknown;
+ }
+}
+
#if TARGET_OS_IPHONE
typedef UInt32 AudioDeviceID;
typedef UInt32 AudioObjectID;
#define AudioGetCurrentHostTime mach_absolute_time
+#endif
+
uint64_t
-AudioConvertHostTimeToNanos(uint64_t host_time)
+ConvertHostTimeToNanos(uint64_t host_time)
{
static struct mach_timebase_info timebase_info;
static bool initialized = false;
@@ -251,27 +402,34 @@ AudioConvertHostTimeToNanos(uint64_t host_time)
}
return (uint64_t)answer;
}
-#endif
-static int64_t
-audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream)
+static void
+audiounit_increment_active_streams(cubeb * ctx)
{
- if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) {
- return 0;
- }
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams += 1;
+}
- uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
- uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+static void
+audiounit_decrement_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams -= 1;
+}
- return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
+static int
+audiounit_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ return ctx->active_streams;
}
static void
-audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
+audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames)
{
- stm->mutex.assert_current_thread_owns();
- assert(stm->context->active_streams == 1);
- stm->context->global_latency_frames = latency_frames;
+ ctx->mutex.assert_current_thread_owns();
+ assert(audiounit_active_streams(ctx) == 1);
+ ctx->global_latency_frames = latency_frames;
}
static void
@@ -283,10 +441,8 @@ audiounit_make_silent(AudioBuffer * ioData)
}
static OSStatus
-audiounit_render_input(cubeb_stream * stm,
- AudioUnitRenderActionFlags * flags,
- AudioTimeStamp const * tstamp,
- UInt32 bus,
+audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp, UInt32 bus,
UInt32 input_frames)
{
/* Create the AudioBufferList to store input. */
@@ -294,66 +450,76 @@ audiounit_render_input(cubeb_stream * stm,
input_buffer_list.mBuffers[0].mDataByteSize =
stm->input_desc.mBytesPerFrame * input_frames;
input_buffer_list.mBuffers[0].mData = nullptr;
- input_buffer_list.mBuffers[0].mNumberChannels = stm->input_desc.mChannelsPerFrame;
+ input_buffer_list.mBuffers[0].mNumberChannels =
+ stm->input_desc.mChannelsPerFrame;
input_buffer_list.mNumberBuffers = 1;
/* Render input samples */
- OSStatus r = AudioUnitRender(stm->input_unit,
- flags,
- tstamp,
- bus,
- input_frames,
- &input_buffer_list);
+ OSStatus r = AudioUnitRender(stm->input_unit, flags, tstamp, bus,
+ input_frames, &input_buffer_list);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitRender", r);
- return r;
+ LOG("AudioUnitRender rv=%d", r);
+ if (r != kAudioUnitErr_CannotDoInCurrentContext) {
+ return r;
+ }
+ if (stm->output_unit) {
+ // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT
+ // headset and the profile is changed from A2DP to HFP/HSP. The previous
+ // output device is no longer valid and must be reset.
+ audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);
+ }
+ // For now state that no error occurred and feed silence, stream will be
+ // resumed once reinit has completed.
+ ALOGV("(%p) input: reinit pending feeding silence instead", stm);
+ stm->input_linear_buffer->push_silence(input_frames *
+ stm->input_desc.mChannelsPerFrame);
+ } else {
+ /* Copy input data in linear buffer. */
+ stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
+ input_frames *
+ stm->input_desc.mChannelsPerFrame);
}
- /* Copy input data in linear buffer. */
- stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
- input_frames * stm->input_desc.mChannelsPerFrame);
-
- LOGV("(%p) input: buffers %d, size %d, channels %d, frames %d.",
- stm, input_buffer_list.mNumberBuffers,
- input_buffer_list.mBuffers[0].mDataByteSize,
- input_buffer_list.mBuffers[0].mNumberChannels,
- input_frames);
-
/* Advance input frame counter. */
assert(input_frames > 0);
stm->frames_read += input_frames;
+ ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, "
+ "total frames %lu.",
+ stm, (unsigned int)input_buffer_list.mNumberBuffers,
+ (unsigned int)input_buffer_list.mBuffers[0].mDataByteSize,
+ (unsigned int)input_buffer_list.mBuffers[0].mNumberChannels,
+ (unsigned int)input_frames,
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
+
return noErr;
}
static OSStatus
-audiounit_input_callback(void * user_ptr,
- AudioUnitRenderActionFlags * flags,
- AudioTimeStamp const * tstamp,
- UInt32 bus,
- UInt32 input_frames,
- AudioBufferList * /* bufs */)
+audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 input_frames, AudioBufferList * /* bufs */)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
- long outframes;
assert(stm->input_unit != NULL);
assert(AU_IN_BUS == bus);
if (stm->shutdown) {
- LOG("(%p) input shutdown", stm);
+ ALOG("(%p) input shutdown", stm);
return noErr;
}
- // This happens when we're finally getting a new input callback after having
- // switched device, we can clear the input buffer now, only keeping the data
- // we just got.
- if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) {
- stm->input_linear_buffer->pop(
- nullptr,
- stm->input_linear_buffer->length() -
- input_frames * stm->input_stream_params.channels);
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ // Only fire state callback in input-only stream. For duplex stream,
+ // the state callback will be fired in output callback.
+ if (stm->output_unit == NULL) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ return noErr;
}
OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
@@ -363,147 +529,199 @@ audiounit_input_callback(void * user_ptr,
// Full Duplex. We'll call data_callback in the AudioUnit output callback.
if (stm->output_unit != NULL) {
- stm->output_callback_in_a_row = 0;
return noErr;
}
/* Input only. Call the user callback through resampler.
Resampler will deliver input buffer in the correct rate. */
- assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
- long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
- outframes = cubeb_resampler_fill(stm->resampler,
- stm->input_linear_buffer->data(),
- &total_input_frames,
- NULL,
- 0);
+ assert(input_frames <= stm->input_linear_buffer->length() /
+ stm->input_desc.mChannelsPerFrame);
+ long total_input_frames =
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ long outframes = cubeb_resampler_fill(stm->resampler.get(),
+ stm->input_linear_buffer->data(),
+ &total_input_frames, NULL, 0);
+ stm->draining = outframes < total_input_frames;
+
// Reset input buffer
stm->input_linear_buffer->clear();
- if (outframes < 0 || outframes != input_frames) {
- stm->shutdown = true;
- return noErr;
- }
-
return noErr;
}
-static bool
-is_extra_input_needed(cubeb_stream * stm)
+static void
+audiounit_mix_output_buffer(cubeb_stream * stm, size_t output_frames,
+ void * input_buffer, size_t input_buffer_size,
+ void * output_buffer, size_t output_buffer_size)
{
- /* If the output callback came first and this is a duplex stream, we need to
- * fill in some additional silence in the resampler.
- * Otherwise, if we had more than expected callbacks in a row, or we're currently
- * switching, we add some silence as well to compensate for the fact that
- * we're lacking some input data. */
+ assert(input_buffer_size >=
+ cubeb_sample_size(stm->output_stream_params.format) *
+ stm->output_stream_params.channels * output_frames);
+ assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames);
- /* If resampling is taking place after every output callback
- * the input buffer expected to be empty. Any frame left over
- * from resampling is stored inside the resampler available to
- * be used in next iteration as needed.
- * BUT when noop_resampler is operating we have left over
- * frames since it does not store anything internally. */
- return stm->frames_read == 0 ||
- (stm->input_linear_buffer->length() == 0 &&
- (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row ||
- stm->switching_device));
+ int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer,
+ input_buffer_size, output_buffer, output_buffer_size);
+ if (r != 0) {
+ LOG("Remix error = %d", r);
+ }
+}
+
+// Return how many input frames (sampled at input_hw_rate) are needed to provide
+// output_frames (sampled at output_stream_params.rate)
+static int64_t
+minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)
+{
+ if (stm->input_hw_rate == stm->output_stream_params.rate) {
+ // Fast path.
+ return output_frames;
+ }
+ return ceil(stm->input_hw_rate * output_frames /
+ stm->output_stream_params.rate);
}
static OSStatus
audiounit_output_callback(void * user_ptr,
AudioUnitRenderActionFlags * /* flags */,
- AudioTimeStamp const * tstamp,
- UInt32 bus,
- UInt32 output_frames,
- AudioBufferList * outBufferList)
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 output_frames, AudioBufferList * outBufferList)
{
assert(AU_OUT_BUS == bus);
assert(outBufferList->mNumberBuffers == 1);
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
- stm->output_callback_in_a_row++;
-
- LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.",
- stm, outBufferList->mNumberBuffers,
- outBufferList->mBuffers[0].mDataByteSize,
- outBufferList->mBuffers[0].mNumberChannels, output_frames);
-
- long outframes = 0, input_frames = 0;
- void * output_buffer = NULL, * input_buffer = NULL;
+ uint64_t now = ConvertHostTimeToNanos(mach_absolute_time());
+ uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime);
+ uint64_t output_latency_ns = audio_output_time - now;
+
+ const int ns2s = 1e9;
+ // The total output latency is the timestamp difference + the stream latency +
+ // the hardware latency.
+ stm->total_output_latency_frames =
+ output_latency_ns * stm->output_hw_rate / ns2s +
+ stm->current_latency_frames;
+
+ ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input "
+ "frames %lu.",
+ stm, (unsigned int)outBufferList->mNumberBuffers,
+ (unsigned int)outBufferList->mBuffers[0].mDataByteSize,
+ (unsigned int)outBufferList->mBuffers[0].mNumberChannels,
+ (unsigned int)output_frames,
+ has_input(stm) ? stm->input_linear_buffer->length() /
+ stm->input_desc.mChannelsPerFrame
+ : 0);
+
+ long input_frames = 0;
+ void *output_buffer = NULL, *input_buffer = NULL;
if (stm->shutdown) {
- LOG("(%p) output shutdown.", stm);
+ ALOG("(%p) output shutdown.", stm);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
- stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
- if (stm->input_unit) {
- r = AudioOutputUnitStop(stm->input_unit);
- assert(r == 0);
- }
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
+
/* Get output buffer. */
- output_buffer = outBufferList->mBuffers[0].mData;
+ if (stm->mixer) {
+ // If remixing needs to occur, we can't directly work in our final
+ // destination buffer as data may be overwritten or too small to start with.
+ size_t size_needed = output_frames * stm->output_stream_params.channels *
+ cubeb_sample_size(stm->output_stream_params.format);
+ if (stm->temp_buffer_size < size_needed) {
+ stm->temp_buffer.reset(new uint8_t[size_needed]);
+ stm->temp_buffer_size = size_needed;
+ }
+ output_buffer = stm->temp_buffer.get();
+ } else {
+ output_buffer = outBufferList->mBuffers[0].mData;
+ }
+
+ stm->frames_written += output_frames;
+
/* If Full duplex get also input buffer */
if (stm->input_unit != NULL) {
- if (is_extra_input_needed(stm)) {
- uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate *
- stm->input_buffer_frames);
- stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame);
- LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
- stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required);
- }
- // The input buffer
+ /* If the output callback came first and this is a duplex stream, we need to
+ * fill in some additional silence in the resampler.
+ * Otherwise, if we had more than expected callbacks in a row, or we're
+ * currently switching, we add some silence as well to compensate for the
+ * fact that we're lacking some input data. */
+ uint32_t input_frames_needed =
+ minimum_resampling_input_frames(stm, stm->frames_written);
+ long missing_frames = input_frames_needed - stm->frames_read;
+ if (missing_frames > 0) {
+ stm->input_linear_buffer->push_silence(missing_frames *
+ stm->input_desc.mChannelsPerFrame);
+ stm->frames_read = input_frames_needed;
+
+ ALOG("(%p) %s pushed %ld frames of input silence.", stm,
+ stm->frames_read == 0 ? "Input hasn't started,"
+ : stm->switching_device ? "Device switching,"
+ : "Drop out,",
+ missing_frames);
+ }
input_buffer = stm->input_linear_buffer->data();
- // Number of input frames in the buffer
- input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ // Number of input frames in the buffer. It will change to actually used
+ // frames inside fill
+ input_frames =
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
}
/* Call user callback through resampler. */
- outframes = cubeb_resampler_fill(stm->resampler,
- input_buffer,
- input_buffer ? &input_frames : NULL,
- output_buffer,
- output_frames);
+ long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer,
+ input_buffer ? &input_frames : NULL,
+ output_buffer, output_frames);
if (input_buffer) {
- stm->input_linear_buffer->pop(nullptr, input_frames * stm->input_desc.mChannelsPerFrame);
+ // Pop from the buffer the frames used by the the resampler.
+ stm->input_linear_buffer->pop(input_frames *
+ stm->input_desc.mChannelsPerFrame);
}
- if (outframes < 0) {
+ if (outframes < 0 || outframes > output_frames) {
stm->shutdown = true;
+ OSStatus r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ if (stm->input_unit) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
- size_t outbpf = stm->output_desc.mBytesPerFrame;
- stm->draining = outframes < output_frames;
+ stm->draining = (UInt32)outframes < output_frames;
stm->frames_played = stm->frames_queued;
stm->frames_queued += outframes;
- AudioFormatFlags outaff = stm->output_desc.mFormatFlags;
- float panning = (stm->output_desc.mChannelsPerFrame == 2) ?
- stm->panning.load(std::memory_order_relaxed) : 0.0f;
-
/* Post process output samples. */
if (stm->draining) {
/* Clear missing frames (silence) */
- memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf);
+ size_t channels = stm->output_stream_params.channels;
+ size_t missing_samples = (output_frames - outframes) * channels;
+ size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);
+ /* number of bytes that have been filled with valid audio by the callback.
+ */
+ size_t audio_byte_count = outframes * channels * size_sample;
+ PodZero((uint8_t *)output_buffer + audio_byte_count,
+ missing_samples * size_sample);
}
- /* Pan stereo. */
- if (panning != 0.0f) {
- if (outaff & kAudioFormatFlagIsFloat) {
- cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning);
- } else if (outaff & kAudioFormatFlagIsSignedInteger) {
- cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning);
- }
+
+ /* Mixing */
+ if (stm->mixer) {
+ audiounit_mix_output_buffer(stm, output_frames, output_buffer,
+ stm->temp_buffer_size,
+ outBufferList->mBuffers[0].mData,
+ outBufferList->mBuffers[0].mDataByteSize);
}
+
return noErr;
}
@@ -511,25 +729,11 @@ extern "C" {
int
audiounit_init(cubeb ** context, char const * /* context_name */)
{
- cubeb * ctx;
-
- *context = NULL;
-
- ctx = (cubeb *)calloc(1, sizeof(cubeb));
- assert(ctx);
- // Placement new to call the ctors of cubeb members.
- new (ctx) cubeb();
-
- ctx->ops = &audiounit_ops;
-
- ctx->active_streams = 0;
-
- ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7;
#if !TARGET_OS_IPHONE
cubeb_set_coreaudio_notification_runloop();
#endif
- *context = ctx;
+ *context = new cubeb;
return CUBEB_OK;
}
@@ -542,255 +746,338 @@ audiounit_get_backend_id(cubeb * /* ctx */)
}
#if !TARGET_OS_IPHONE
+
+static int
+audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
static int
-audiounit_get_output_device_id(AudioDeviceID * device_id)
+audiounit_stream_set_volume(cubeb_stream * stm, float volume);
+
+static int
+audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
{
- UInt32 size;
- OSStatus r;
- AudioObjectPropertyAddress output_device_address = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
+ assert(stm);
- size = sizeof(*device_id);
+ device_info * info = nullptr;
+ cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
- r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &output_device_address,
- 0,
- NULL,
- &size,
- device_id);
- if (r != noErr) {
- PRINT_ERROR_CODE("output_device_id", r);
- return CUBEB_ERROR;
+ if (side == io_side::INPUT) {
+ info = &stm->input_device;
+ type = CUBEB_DEVICE_TYPE_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info = &stm->output_device;
+ type = CUBEB_DEVICE_TYPE_OUTPUT;
}
+ memset(info, 0, sizeof(device_info));
+ info->id = id;
- return CUBEB_OK;
-}
-
-static int
-audiounit_get_input_device_id(AudioDeviceID * device_id)
-{
- UInt32 size;
- OSStatus r;
- AudioObjectPropertyAddress input_device_address = {
- kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
-
- size = sizeof(*device_id);
+ if (side == io_side::INPUT) {
+ info->flags |= DEV_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info->flags |= DEV_OUTPUT;
+ }
- r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &input_device_address,
- 0,
- NULL,
- &size,
- device_id);
- if (r != noErr) {
+ AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
+ if (default_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
+ if (id == kAudioObjectUnknown) {
+ info->id = default_device_id;
+ info->flags |= DEV_SELECTED_DEFAULT;
+ }
+
+ if (info->id == default_device_id) {
+ info->flags |= DEV_SYSTEM_DEFAULT;
+ }
+
+ assert(info->id);
+ assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
+ !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
return CUBEB_OK;
}
-static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
-static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
-
static int
-audiounit_reinit_stream(cubeb_stream * stm)
+audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
{
auto_lock context_lock(stm->context->mutex);
+ assert((flags & DEV_INPUT && stm->input_unit) ||
+ (flags & DEV_OUTPUT && stm->output_unit));
if (!stm->shutdown) {
audiounit_stream_stop_internal(stm);
}
int r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
- LOG("(%p) Could not uninstall the device changed callback", stm);
+ LOG("(%p) Could not uninstall all device change listeners.", stm);
}
{
auto_lock lock(stm->mutex);
float volume = 0.0;
- int vol_rv = audiounit_stream_get_volume(stm, &volume);
+ int vol_rv = CUBEB_ERROR;
+ if (stm->output_unit) {
+ vol_rv = audiounit_stream_get_volume(stm, &volume);
+ }
audiounit_close_stream(stm);
+ /* Reinit occurs in one of the following case:
+ * - When the device is not alive any more
+ * - When the default system device change.
+ * - The bluetooth device changed from A2DP to/from HFP/HSP profile
+ * We first attempt to re-use the same device id, should that fail we will
+ * default to the (potentially new) default device. */
+ AudioDeviceID input_device =
+ flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown;
+ if (flags & DEV_INPUT) {
+ r = audiounit_set_device_info(stm, input_device, io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set input device info failed. This can happen when last "
+ "media device is unplugged",
+ stm);
+ return CUBEB_ERROR;
+ }
+ }
+
+ /* Always use the default output on reinit. This is not correct in every
+ * case but it is sufficient for Firefox and prevent reinit from reporting
+ * failures. It will change soon when reinit mechanism will be updated. */
+ r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set output device info failed. This can happen when last media "
+ "device is unplugged",
+ stm);
+ return CUBEB_ERROR;
+ }
+
if (audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Stream reinit failed.", stm);
- return CUBEB_ERROR;
+ if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) {
+ // Attempt to re-use the same device-id failed, so attempt again with
+ // default input device.
+ audiounit_close_stream(stm);
+ if (audiounit_set_device_info(stm, kAudioObjectUnknown,
+ io_side::INPUT) != CUBEB_OK ||
+ audiounit_setup_stream(stm) != CUBEB_OK) {
+ LOG("(%p) Second stream reinit failed.", stm);
+ return CUBEB_ERROR;
+ }
+ }
}
if (vol_rv == CUBEB_OK) {
audiounit_stream_set_volume(stm, volume);
}
- // Reset input frames to force new stream pre-buffer
- // silence if needed, check `is_extra_input_needed()`
- stm->frames_read = 0;
-
// If the stream was running, start it again.
if (!stm->shutdown) {
- audiounit_stream_start_internal(stm);
+ r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
}
}
return CUBEB_OK;
}
-static OSStatus
-audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
- const AudioObjectPropertyAddress * addresses,
- void * user)
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)
{
- cubeb_stream * stm = (cubeb_stream*) user;
+ if (std::atomic_exchange(&stm->reinit_pending, true)) {
+ // A reinit task is already pending, nothing more to do.
+ ALOG("(%p) re-init stream task already pending, cancelling request", stm);
+ return;
+ }
+
+ // Use a new thread, through the queue, to avoid deadlock when calling
+ // Get/SetProperties method from inside notify callback
+ dispatch_async(stm->context->serial_queue, ^() {
+ if (stm->destroy_pending) {
+ ALOG("(%p) stream pending destroy, cancelling reinit task", stm);
+ return;
+ }
+
+ if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) {
+ if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
+ LOG("(%p) Could not uninstall system changed callback", stm);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ LOG("(%p) Could not reopen the stream after switching.", stm);
+ }
+ stm->switching_device = false;
+ stm->reinit_pending = false;
+ });
+}
+
+static char const *
+event_addr_to_string(AudioObjectPropertySelector selector)
+{
+ switch (selector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ return "kAudioHardwarePropertyDefaultOutputDevice";
+ case kAudioHardwarePropertyDefaultInputDevice:
+ return "kAudioHardwarePropertyDefaultInputDevice";
+ case kAudioDevicePropertyDeviceIsAlive:
+ return "kAudioDevicePropertyDeviceIsAlive";
+ case kAudioDevicePropertyDataSource:
+ return "kAudioDevicePropertyDataSource";
+ default:
+ return "Unknown";
+ }
+}
+
+static OSStatus
+audiounit_property_listener_callback(
+ AudioObjectID id, UInt32 address_count,
+ const AudioObjectPropertyAddress * addresses, void * user)
+{
+ cubeb_stream * stm = (cubeb_stream *)user;
+ if (stm->switching_device) {
+ LOG("Switching is already taking place. Skip Event %s for id=%d",
+ event_addr_to_string(addresses[0].mSelector), id);
+ return noErr;
+ }
stm->switching_device = true;
- LOG("(%p) Audio device changed, %d events.", stm, address_count);
+ LOG("(%p) Audio device changed, %u events.", stm,
+ (unsigned int)address_count);
for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
- // Allow restart to choose the new default
- stm->output_device = nullptr;
- }
- break;
- case kAudioHardwarePropertyDefaultInputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i);
- // Allow restart to choose the new default
- stm->input_device = nullptr;
- }
- break;
- case kAudioDevicePropertyDeviceIsAlive: {
- LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i);
- // If this is the default input device ignore the event,
- // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
- if (stm->is_default_input) {
- LOG("It's the default input device, ignore the event");
- return noErr;
- }
- // Allow restart to choose the new default. Event register only for input.
- stm->input_device = nullptr;
- }
- break;
- case kAudioDevicePropertyDataSource: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
- return noErr;
- }
+ switch (addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice "
+ "for id=%d",
+ (unsigned int)i, id);
+ } break;
+ case kAudioHardwarePropertyDefaultInputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice "
+ "for id=%d",
+ (unsigned int)i, id);
+ } break;
+ case kAudioDevicePropertyDeviceIsAlive: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for "
+ "id=%d",
+ (unsigned int)i, id);
+ // If this is the default input device ignore the event,
+ // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
+ if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
+ LOG("It's the default input device, ignore the event");
+ stm->switching_device = false;
+ return noErr;
+ }
+ } break;
+ case kAudioDevicePropertyDataSource: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d",
+ (unsigned int)i, id);
+ } break;
+ default:
+ LOG("Event[%u] - mSelector == Unexpected Event id %d, return",
+ (unsigned int)i, addresses[i].mSelector);
+ stm->switching_device = false;
+ return noErr;
}
}
+ // Allow restart to choose the new default
+ device_flags_value switch_side = DEV_UNKNOWN;
+ if (has_input(stm)) {
+ switch_side |= DEV_INPUT;
+ }
+ if (has_output(stm)) {
+ switch_side |= DEV_OUTPUT;
+ }
+
for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
+ switch (addresses[i].mSelector) {
case kAudioHardwarePropertyDefaultOutputDevice:
case kAudioHardwarePropertyDefaultInputDevice:
case kAudioDevicePropertyDeviceIsAlive:
/* fall through */
case kAudioDevicePropertyDataSource: {
- auto_lock lock(stm->mutex);
- if (stm->device_changed_callback) {
- stm->device_changed_callback(stm->user_ptr);
- }
- break;
+ auto_lock dev_cb_lock(stm->device_changed_callback_lock);
+ if (stm->device_changed_callback) {
+ stm->device_changed_callback(stm->user_ptr);
}
+ break;
+ }
}
}
- // Use a new thread, through the queue, to avoid deadlock when calling
- // Get/SetProperties method from inside notify callback
- dispatch_async(stm->context->serial_queue, ^() {
- if (audiounit_reinit_stream(stm) != CUBEB_OK) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- LOG("(%p) Could not reopen the stream after switching.", stm);
- }
- stm->switching_device = false;
- });
+ audiounit_reinit_stream_async(stm, switch_side);
return noErr;
}
OSStatus
-audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
- AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
+audiounit_add_listener(const property_listener * listener)
{
- AudioObjectPropertyAddress address = {
- selector,
- scope,
- kAudioObjectPropertyElementMaster
- };
-
- return AudioObjectAddPropertyListener(id, &address, listener, stm);
+ assert(listener);
+ return AudioObjectAddPropertyListener(listener->device_id,
+ listener->property_address,
+ listener->callback, listener->stream);
}
OSStatus
-audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id,
- AudioObjectPropertySelector selector,
- AudioObjectPropertyScope scope,
- AudioObjectPropertyListenerProc listener)
+audiounit_remove_listener(const property_listener * listener)
{
- AudioObjectPropertyAddress address = {
- selector,
- scope,
- kAudioObjectPropertyElementMaster
- };
-
- return AudioObjectRemovePropertyListener(id, &address, listener, stm);
+ assert(listener);
+ return AudioObjectRemovePropertyListener(
+ listener->device_id, listener->property_address, listener->callback,
+ listener->stream);
}
-static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
-
static int
audiounit_install_device_changed_callback(cubeb_stream * stm)
{
- OSStatus r;
+ OSStatus rv;
+ int r = CUBEB_OK;
if (stm->output_unit) {
- /* This event will notify us when the data source on the same device changes,
- * for example when the user plugs in a normal (non-usb) headset in the
- * headphone jack. */
- AudioDeviceID output_dev_id;
- r = audiounit_get_output_device_id(&output_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
+ /* This event will notify us when the data source on the same device
+ * changes, for example when the user plugs in a normal (non-usb) headset in
+ * the headphone jack. */
+ stm->output_source_listener.reset(new property_listener(
+ stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ stm->output_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->output_device.id);
+ r = CUBEB_ERROR;
}
}
if (stm->input_unit) {
- /* This event will notify us when the data source on the input device changes. */
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
+ /* This event will notify us when the data source on the input device
+ * changes. */
+ stm->input_source_listener.reset(new property_listener(
+ stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ stm->input_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource "
+ "rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
/* Event to notify when the input is going away. */
- AudioDeviceID dev = stm->input_device ? reinterpret_cast<intptr_t>(stm->input_device) :
- audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
- r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
- return CUBEB_ERROR;
+ stm->input_alive_listener.reset(new property_listener(
+ stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ stm->input_alive_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/"
+ "kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
}
- return CUBEB_OK;
+ return r;
}
static int
@@ -800,23 +1087,33 @@ audiounit_install_system_changed_callback(cubeb_stream * stm)
if (stm->output_unit) {
/* This event will notify us when the default audio device changes,
- * for example when the user plugs in a USB headset and the system chooses it
- * automatically as the default, or when another device is chosen in the
+ * for example when the user plugs in a USB headset and the system chooses
+ * it automatically as the default, or when another device is chosen in the
* dropdown list. */
- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ stm->default_output_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_output_listener.get());
if (r != noErr) {
- LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r);
+ stm->default_output_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/"
+ "kAudioHardwarePropertyDefaultOutputDevice rv=%d",
+ r);
return CUBEB_ERROR;
}
}
if (stm->input_unit) {
/* This event will notify us when the default input device changes. */
- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ stm->default_input_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_input_listener.get());
if (r != noErr) {
- LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r);
+ stm->default_input_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/"
+ "kAudioHardwarePropertyDefaultInputDevice rv=%d",
+ r);
return CUBEB_ERROR;
}
}
@@ -827,36 +1124,44 @@ audiounit_install_system_changed_callback(cubeb_stream * stm)
static int
audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
{
- OSStatus r;
-
- if (stm->output_unit) {
- AudioDeviceID output_dev_id;
- r = audiounit_get_output_device_id(&output_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
+ OSStatus rv;
+ // Failing to uninstall listeners is not a fatal error.
+ int r = CUBEB_OK;
- r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
+ if (stm->output_source_listener) {
+ rv = audiounit_remove_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/output/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->output_device.id);
+ r = CUBEB_ERROR;
}
+ stm->output_source_listener.reset();
}
- if (stm->input_unit) {
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
+ if (stm->input_source_listener) {
+ rv = audiounit_remove_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
+ stm->input_source_listener.reset();
+ }
- r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
+ if (stm->input_alive_listener) {
+ rv = audiounit_remove_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/"
+ "kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
+ stm->input_alive_listener.reset();
}
- return CUBEB_OK;
+
+ return r;
}
static int
@@ -864,20 +1169,20 @@ audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
{
OSStatus r;
- if (stm->output_unit) {
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ if (stm->default_output_listener) {
+ r = audiounit_remove_listener(stm->default_output_listener.get());
if (r != noErr) {
return CUBEB_ERROR;
}
+ stm->default_output_listener.reset();
}
- if (stm->input_unit) {
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ if (stm->default_input_listener) {
+ r = audiounit_remove_listener(stm->default_input_listener.get());
if (r != noErr) {
return CUBEB_ERROR;
}
+ stm->default_input_listener.reset();
}
return CUBEB_OK;
}
@@ -890,12 +1195,11 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
OSStatus r;
AudioDeviceID output_device_id;
AudioObjectPropertyAddress output_device_buffer_size_range = {
- kAudioDevicePropertyBufferFrameSizeRange,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
+ kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
LOG("Could not get default output device id.");
return CUBEB_ERROR;
}
@@ -904,13 +1208,10 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
size = sizeof(*latency_range);
r = AudioObjectGetPropertyData(output_device_id,
- &output_device_buffer_size_range,
- 0,
- NULL,
- &size,
- latency_range);
+ &output_device_buffer_size_range, 0, NULL,
+ &size, latency_range);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r);
+ LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r);
return CUBEB_ERROR;
}
@@ -921,20 +1222,19 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
static AudioObjectID
audiounit_get_default_device_id(cubeb_device_type type)
{
- AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
- AudioDeviceID devid;
- UInt32 size;
-
+ const AudioObjectPropertyAddress * adr;
if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
- adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS;
} else if (type == CUBEB_DEVICE_TYPE_INPUT) {
- adr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS;
} else {
return kAudioObjectUnknown;
}
- size = sizeof(AudioDeviceID);
- if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) {
+ AudioDeviceID devid;
+ UInt32 size = sizeof(AudioDeviceID);
+ if (AudioObjectGetPropertyData(kAudioObjectSystemObject, adr, 0, NULL, &size,
+ &devid) != noErr) {
return kAudioObjectUnknown;
}
@@ -945,7 +1245,7 @@ int
audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
#if TARGET_OS_IPHONE
- //TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]
+ // TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]
*max_channels = 2;
#else
UInt32 size;
@@ -953,27 +1253,22 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
AudioDeviceID output_device_id;
AudioStreamBasicDescription stream_format;
AudioObjectPropertyAddress stream_format_address = {
- kAudioDevicePropertyStreamFormat,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
+ kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
assert(ctx && max_channels);
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
size = sizeof(stream_format);
- r = AudioObjectGetPropertyData(output_device_id,
- &stream_format_address,
- 0,
- NULL,
- &size,
- &stream_format);
+ r = AudioObjectGetPropertyData(output_device_id, &stream_format_address, 0,
+ NULL, &size, &stream_format);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r);
+ LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
@@ -983,12 +1278,11 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
}
static int
-audiounit_get_min_latency(cubeb * /* ctx */,
- cubeb_stream_params /* params */,
+audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */,
uint32_t * latency_frames)
{
#if TARGET_OS_IPHONE
- //TODO: [[AVAudioSession sharedInstance] inputLatency]
+ // TODO: [[AVAudioSession sharedInstance] inputLatency]
return CUBEB_ERROR_NOT_SUPPORTED;
#else
AudioValueRange latency_range;
@@ -997,8 +1291,8 @@ audiounit_get_min_latency(cubeb * /* ctx */,
return CUBEB_ERROR;
}
- *latency_frames = std::max<uint32_t>(latency_range.mMinimum,
- SAFE_MIN_LATENCY_FRAMES);
+ *latency_frames =
+ max<uint32_t>(latency_range.mMinimum, SAFE_MIN_LATENCY_FRAMES);
#endif
return CUBEB_OK;
@@ -1008,7 +1302,7 @@ static int
audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
{
#if TARGET_OS_IPHONE
- //TODO
+ // TODO
return CUBEB_ERROR_NOT_SUPPORTED;
#else
UInt32 size;
@@ -1016,22 +1310,17 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
Float64 fsamplerate;
AudioDeviceID output_device_id;
AudioObjectPropertyAddress samplerate_address = {
- kAudioDevicePropertyNominalSampleRate,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
+ kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
size = sizeof(fsamplerate);
- r = AudioObjectGetPropertyData(output_device_id,
- &samplerate_address,
- 0,
- NULL,
- &size,
- &fsamplerate);
+ r = AudioObjectGetPropertyData(output_device_id, &samplerate_address, 0, NULL,
+ &size, &fsamplerate);
if (r != noErr) {
return CUBEB_ERROR;
@@ -1042,27 +1331,133 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
return CUBEB_OK;
}
-static OSStatus audiounit_remove_device_listener(cubeb * context);
+static cubeb_channel_layout
+audiounit_convert_channel_layout(AudioChannelLayout * layout)
+{
+ // When having one or two channel, force mono or stereo. Some devices (namely,
+ // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for
+ // some reason.
+ if (layout->mNumberChannelDescriptions == 1) {
+ return CUBEB_LAYOUT_MONO;
+ } else if (layout->mNumberChannelDescriptions == 2) {
+ return CUBEB_LAYOUT_STEREO;
+ }
+
+ if (layout->mChannelLayoutTag !=
+ kAudioChannelLayoutTag_UseChannelDescriptions) {
+ // kAudioChannelLayoutTag_UseChannelBitmap
+ // kAudioChannelLayoutTag_Mono
+ // kAudioChannelLayoutTag_Stereo
+ // ....
+ LOG("Only handle UseChannelDescriptions for now.\n");
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ cubeb_channel_layout cl = 0;
+ for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {
+ cubeb_channel cc = channel_label_to_cubeb_channel(
+ layout->mChannelDescriptions[i].mChannelLabel);
+ if (cc == CHANNEL_UNKNOWN) {
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ cl |= cc;
+ }
+
+ return cl;
+}
+
+static cubeb_channel_layout
+audiounit_get_preferred_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(
+ output_unit, kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout "
+ "rv=%d",
+ rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(
+ output_unit, kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, layout.get(), &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d",
+ rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static cubeb_channel_layout
+audiounit_get_current_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(
+ output_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d",
+ rv);
+ // This property isn't known before macOS 10.12, attempt another method.
+ return audiounit_get_preferred_channel_layout(output_unit);
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(output_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, layout.get(),
+ &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit, device_info * device);
+
+static OSStatus
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype);
static void
audiounit_destroy(cubeb * ctx)
{
- // Disabling this assert for bug 1083664 -- we seem to leak a stream
- // assert(ctx->active_streams == 0);
-
{
auto_lock lock(ctx->mutex);
+
+ // Disabling this assert for bug 1083664 -- we seem to leak a stream
+ // assert(ctx->active_streams == 0);
+ if (audiounit_active_streams(ctx) > 0) {
+ LOG("(%p) API misuse, %d streams active when context destroyed!", ctx,
+ audiounit_active_streams(ctx));
+ }
+
/* Unregister the callback if necessary. */
- if(ctx->collection_changed_callback) {
- audiounit_remove_device_listener(ctx);
+ if (ctx->input_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ }
+ if (ctx->output_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
}
}
- ctx->~cubeb();
- free(ctx);
+ dispatch_release(ctx->serial_queue);
+
+ delete ctx;
}
-static void audiounit_stream_destroy(cubeb_stream * stm);
+static void
+audiounit_stream_destroy(cubeb_stream * stm);
static int
audio_stream_desc_init(AudioStreamBasicDescription * ss,
@@ -1075,8 +1470,8 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss,
break;
case CUBEB_SAMPLE_S16BE:
ss->mBitsPerChannel = 16;
- ss->mFormatFlags = kAudioFormatFlagIsSignedInteger |
- kAudioFormatFlagIsBigEndian;
+ ss->mFormatFlags =
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;
break;
case CUBEB_SAMPLE_FLOAT32LE:
ss->mBitsPerChannel = 32;
@@ -1084,8 +1479,7 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss,
break;
case CUBEB_SAMPLE_FLOAT32BE:
ss->mBitsPerChannel = 32;
- ss->mFormatFlags = kAudioFormatFlagIsFloat |
- kAudioFormatFlagIsBigEndian;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian;
break;
default:
return CUBEB_ERROR_INVALID_FORMAT;
@@ -1105,29 +1499,543 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss,
return CUBEB_OK;
}
+void
+audiounit_init_mixer(cubeb_stream * stm)
+{
+ // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio
+ // data, it silently drop the channels so we need to remix the
+ // audio data by ourselves to keep all the information.
+ stm->mixer.reset(cubeb_mixer_create(
+ stm->output_stream_params.format, stm->output_stream_params.channels,
+ stm->output_stream_params.layout, stm->context->channels,
+ stm->context->layout));
+ assert(stm->mixer);
+}
+
+static int
+audiounit_set_channel_layout(AudioUnit unit, io_side side,
+ cubeb_channel_layout layout)
+{
+ if (side != io_side::OUTPUT) {
+ return CUBEB_ERROR;
+ }
+
+ if (layout == CUBEB_LAYOUT_UNDEFINED) {
+ // We leave everything as-is...
+ return CUBEB_OK;
+ }
+
+ OSStatus r;
+ uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout);
+
+ // We do not use CoreAudio standard layout for lack of documentation on what
+ // the actual channel orders are. So we set a custom layout.
+ size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]);
+ auto au_layout = make_sized_audio_channel_layout(size);
+ au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ au_layout->mNumberChannelDescriptions = nb_channels;
+
+ uint32_t channels = 0;
+ cubeb_channel_layout channelMap = layout;
+ for (uint32_t i = 0; channelMap != 0; ++i) {
+ XASSERT(channels < nb_channels);
+ uint32_t channel = (channelMap & 1) << i;
+ if (channel != 0) {
+ au_layout->mChannelDescriptions[channels].mChannelLabel =
+ cubeb_channel_to_channel_label(static_cast<cubeb_channel>(channel));
+ au_layout->mChannelDescriptions[channels].mChannelFlags =
+ kAudioChannelFlags_AllOff;
+ channels++;
+ }
+ channelMap = channelMap >> 1;
+ }
+
+ r = AudioUnitSetProperty(unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Input, AU_OUT_BUS, au_layout.get(),
+ size);
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d",
+ to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+void
+audiounit_layout_init(cubeb_stream * stm, io_side side)
+{
+ // We currently don't support the input layout setting.
+ if (side == io_side::INPUT) {
+ return;
+ }
+
+ stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit);
+
+ audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT,
+ stm->context->layout);
+}
+
+static vector<AudioObjectID>
+audiounit_get_sub_devices(AudioDeviceID device_id)
+{
+ vector<AudioDeviceID> sub_devices;
+ AudioObjectPropertyAddress property_address = {
+ kAudioAggregateDevicePropertyActiveSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(device_id, &property_address, 0,
+ nullptr, &size);
+
+ if (rv != noErr) {
+ sub_devices.push_back(device_id);
+ return sub_devices;
+ }
+
+ uint32_t count = static_cast<uint32_t>(size / sizeof(AudioObjectID));
+ sub_devices.resize(count);
+ rv = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,
+ &size, sub_devices.data());
+ if (rv != noErr) {
+ sub_devices.clear();
+ sub_devices.push_back(device_id);
+ } else {
+ LOG("Found %u sub-devices", count);
+ }
+ return sub_devices;
+}
+
+static int
+audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id,
+ AudioDeviceID * aggregate_device_id)
+{
+ AudioObjectPropertyAddress address_plugin_bundle_id = {
+ kAudioHardwarePropertyPlugInForBundleID, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus r = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject, &address_plugin_bundle_id, 0, NULL, &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/"
+ "kAudioHardwarePropertyPlugInForBundleID, rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ AudioValueTranslation translation_value;
+ CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio");
+ translation_value.mInputData = &in_bundle_ref;
+ translation_value.mInputDataSize = sizeof(in_bundle_ref);
+ translation_value.mOutputData = plugin_id;
+ translation_value.mOutputDataSize = sizeof(*plugin_id);
+
+ r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &address_plugin_bundle_id, 0, nullptr, &size,
+ &translation_value);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress create_aggregate_device_address = {
+ kAudioPlugInCreateAggregateDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ r = AudioObjectGetPropertyDataSize(
+ *plugin_id, &create_aggregate_device_address, 0, nullptr, &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ struct timeval timestamp;
+ gettimeofday(&timestamp, NULL);
+ long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec;
+ CFStringRef aggregate_device_name = CFStringCreateWithFormat(
+ NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceNameKey),
+ aggregate_device_name);
+ CFRelease(aggregate_device_name);
+
+ CFStringRef aggregate_device_UID =
+ CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"),
+ PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceUIDKey),
+ aggregate_device_UID);
+ CFRelease(aggregate_device_UID);
+
+ int private_value = 1;
+ CFNumberRef aggregate_device_private_key =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceIsPrivateKey),
+ aggregate_device_private_key);
+ CFRelease(aggregate_device_private_key);
+
+ int stacked_value = 0;
+ CFNumberRef aggregate_device_stacked_key =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceIsStackedKey),
+ aggregate_device_stacked_key);
+ CFRelease(aggregate_device_stacked_key);
+
+ r = AudioObjectGetPropertyData(*plugin_id, &create_aggregate_device_address,
+ sizeof(aggregate_device_dict),
+ &aggregate_device_dict, &size,
+ aggregate_device_id);
+ CFRelease(aggregate_device_dict);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+ LOG("New aggregate device %u", *aggregate_device_id);
+
+ return CUBEB_OK;
+}
+
+// The returned CFStringRef object needs to be released (via CFRelease)
+// if it's not NULL, since the reference count of the returned CFStringRef
+// object is increased.
+static CFStringRef
+get_device_name(AudioDeviceID id)
+{
+ UInt32 size = sizeof(CFStringRef);
+ CFStringRef UIname = nullptr;
+ AudioObjectPropertyAddress address_uuid = {kAudioDevicePropertyDeviceUID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ OSStatus err =
+ AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname);
+ return (err == noErr) ? UIname : NULL;
+}
+
+static int
+audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id,
+ AudioDeviceID input_device_id,
+ AudioDeviceID output_device_id)
+{
+ LOG("Add devices input %u and output %u into aggregate device %u",
+ input_device_id, output_device_id, aggregate_device_id);
+ const vector<AudioDeviceID> output_sub_devices =
+ audiounit_get_sub_devices(output_device_id);
+ const vector<AudioDeviceID> input_sub_devices =
+ audiounit_get_sub_devices(input_device_id);
+
+ CFMutableArrayRef aggregate_sub_devices_array =
+ CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ /* The order of the items in the array is significant and is used to determine
+ the order of the streams of the AudioAggregateDevice. */
+ for (UInt32 i = 0; i < output_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(output_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+ for (UInt32 i = 0; i < input_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(input_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+
+ AudioObjectPropertyAddress aggregate_sub_device_list = {
+ kAudioAggregateDevicePropertyFullSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ UInt32 size = sizeof(CFMutableArrayRef);
+ OSStatus rv = AudioObjectSetPropertyData(
+ aggregate_device_id, &aggregate_sub_device_list, 0, nullptr, size,
+ &aggregate_sub_devices_array);
+ CFRelease(aggregate_sub_devices_array);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress master_aggregate_sub_device = {
+ kAudioAggregateDevicePropertyMasterSubDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+
+ // Master become the 1st output sub device
+ AudioDeviceID output_device_id =
+ audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ const vector<AudioDeviceID> output_sub_devices =
+ audiounit_get_sub_devices(output_device_id);
+ CFStringRef master_sub_device = get_device_name(output_sub_devices[0]);
+
+ UInt32 size = sizeof(CFStringRef);
+ OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id,
+ &master_aggregate_sub_device, 0,
+ NULL, size, &master_sub_device);
+ if (master_sub_device) {
+ CFRelease(master_sub_device);
+ }
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioAggregateDevicePropertyMasterSubDevice, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_activate_clock_drift_compensation(
+ const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress address_owned = {
+ kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ UInt32 qualifier_data_size = sizeof(AudioObjectID);
+ AudioClassID class_id = kAudioSubDeviceClassID;
+ void * qualifier_data = &class_id;
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(
+ aggregate_device_id, &address_owned, qualifier_data_size, qualifier_data,
+ &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, "
+ "rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ UInt32 subdevices_num = 0;
+ subdevices_num = size / sizeof(AudioObjectID);
+ AudioObjectID sub_devices[subdevices_num];
+ size = sizeof(sub_devices);
+
+ rv = AudioObjectGetPropertyData(aggregate_device_id, &address_owned,
+ qualifier_data_size, qualifier_data, &size,
+ sub_devices);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress address_drift = {
+ kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ // Start from the second device since the first is the master clock
+ for (UInt32 i = 1; i < subdevices_num; ++i) {
+ UInt32 drift_compensation_value = 1;
+ rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, 0, nullptr,
+ sizeof(UInt32), &drift_compensation_value);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioSubDevicePropertyDriftCompensation, rv=%d",
+ rv);
+ return CUBEB_OK;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id,
+ AudioDeviceID * aggregate_device_id);
+static void
+audiounit_get_available_samplerate(AudioObjectID devid,
+ AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max,
+ uint32_t * def);
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,
+ AudioObjectID devid, cubeb_device_type type);
+static void
+audiounit_device_destroy(cubeb_device_info * device);
+
+static void
+audiounit_workaround_for_airpod(cubeb_stream * stm)
+{
+ cubeb_device_info input_device_info;
+ audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id,
+ CUBEB_DEVICE_TYPE_INPUT);
+
+ cubeb_device_info output_device_info;
+ audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+
+ std::string input_name_str(input_device_info.friendly_name);
+ std::string output_name_str(output_device_info.friendly_name);
+
+ if (input_name_str.find("AirPods") != std::string::npos &&
+ output_name_str.find("AirPods") != std::string::npos) {
+ uint32_t input_min_rate = 0;
+ uint32_t input_max_rate = 0;
+ uint32_t input_nominal_rate = 0;
+ audiounit_get_available_samplerate(
+ stm->input_device.id, kAudioObjectPropertyScopeGlobal, &input_min_rate,
+ &input_max_rate, &input_nominal_rate);
+ LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u",
+ stm, stm->input_device.id, input_device_info.friendly_name,
+ input_min_rate, input_max_rate, input_nominal_rate);
+ uint32_t output_min_rate = 0;
+ uint32_t output_max_rate = 0;
+ uint32_t output_nominal_rate = 0;
+ audiounit_get_available_samplerate(
+ stm->output_device.id, kAudioObjectPropertyScopeGlobal,
+ &output_min_rate, &output_max_rate, &output_nominal_rate);
+ LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u",
+ stm, stm->output_device.id, output_device_info.friendly_name,
+ output_min_rate, output_max_rate, output_nominal_rate);
+
+ Float64 rate = input_nominal_rate;
+ AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, &addr, 0,
+ nullptr, sizeof(Float64), &rate);
+ if (rv != noErr) {
+ LOG("Non fatal error, "
+ "AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, "
+ "rv=%d",
+ rv);
+ }
+ }
+ audiounit_device_destroy(&input_device_info);
+ audiounit_device_destroy(&output_device_info);
+}
+
+/*
+ * Aggregate Device is a virtual audio interface which utilizes inputs and
+ * outputs of one or more physical audio interfaces. It is possible to use the
+ * clock of one of the devices as a master clock for all the combined devices
+ * and enable drift compensation for the devices that are not designated clock
+ * master.
+ *
+ * Creating a new aggregate device programmatically requires [0][1]:
+ * 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
+ * 2. Create a dictionary that describes the aggregate device
+ * (don't add sub-devices in that step, prone to fail [0])
+ * 3. Ask the base plug-in to create the aggregate device (blank)
+ * 4. Add the array of sub-devices.
+ * 5. Set the master device (1st output device in our case)
+ * 6. Enable drift compensation for the non-master devices
+ *
+ * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
+ * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
+ * [2] CoreAudio.framework/Headers/AudioHardware.h
+ * */
+static int
+audiounit_create_aggregate_device(cubeb_stream * stm)
+{
+ int r = audiounit_create_blank_aggregate_device(&stm->plugin_id,
+ &stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to create blank aggregate device", stm);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_aggregate_sub_device_list(
+ stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set aggregate sub-device list", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_master_aggregate_device(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set master sub-device for aggregate device", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to activate clock drift compensation for aggregate device",
+ stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ audiounit_workaround_for_airpod(stm);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id,
+ AudioDeviceID * aggregate_device_id)
+{
+ assert(aggregate_device_id && *aggregate_device_id != kAudioDeviceUnknown &&
+ plugin_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress destroy_aggregate_device_addr = {
+ kAudioPlugInDestroyAggregateDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size;
+ OSStatus rv = AudioObjectGetPropertyDataSize(
+ plugin_id, &destroy_aggregate_device_addr, 0, NULL, &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, "
+ "rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ rv = AudioObjectGetPropertyData(plugin_id, &destroy_aggregate_device_addr, 0,
+ NULL, &size, aggregate_device_id);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ LOG("Destroyed aggregate device %d", *aggregate_device_id);
+ *aggregate_device_id = kAudioObjectUnknown;
+ return CUBEB_OK;
+}
+
static int
-audiounit_create_unit(AudioUnit * unit,
- bool is_input,
- const cubeb_stream_params * /* stream_params */,
- cubeb_devid device)
+audiounit_new_unit_instance(AudioUnit * unit, device_info * device)
{
AudioComponentDescription desc;
AudioComponent comp;
- UInt32 enable;
- AudioDeviceID devid;
OSStatus rv;
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
- bool use_default_output = false;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
// Use the DefaultOutputUnit for output when no device is specified
// so we retain automatic output device switching when the default
// changes. Once we have complete support for device notifications
// and switching, we can use the AUHAL for everything.
- bool use_default_output = device == NULL && !is_input;
- if (use_default_output) {
+ if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
} else {
desc.componentSubType = kAudioUnitSubType_HALOutput;
@@ -1144,96 +2052,116 @@ audiounit_create_unit(AudioUnit * unit,
rv = AudioComponentInstanceNew(comp, unit);
if (rv != noErr) {
- PRINT_ERROR_CODE("AudioComponentInstanceNew", rv);
+ LOG("AudioComponentInstanceNew rv=%d", rv);
return CUBEB_ERROR;
}
+ return CUBEB_OK;
+}
- if (!use_default_output) {
- enable = 1;
- rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
- is_input ? kAudioUnitScope_Input : kAudioUnitScope_Output,
- is_input ? AU_IN_BUS : AU_OUT_BUS, &enable, sizeof(UInt32));
- if (rv != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv);
- return CUBEB_ERROR;
- }
-
- enable = 0;
- rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
- is_input ? kAudioUnitScope_Output : kAudioUnitScope_Input,
- is_input ? AU_OUT_BUS : AU_IN_BUS, &enable, sizeof(UInt32));
- if (rv != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv);
- return CUBEB_ERROR;
- }
+enum enable_state {
+ DISABLE,
+ ENABLE,
+};
- if (device == NULL) {
- assert(is_input);
- devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
- } else {
- devid = reinterpret_cast<intptr_t>(device);
- }
- rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- is_input ? AU_IN_BUS : AU_OUT_BUS,
- &devid, sizeof(AudioDeviceID));
- if (rv != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice", rv);
- return CUBEB_ERROR;
- }
+static int
+audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state)
+{
+ OSStatus rv;
+ UInt32 enable = state;
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
+ (side == io_side::INPUT) ? kAudioUnitScope_Input
+ : kAudioUnitScope_Output,
+ (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS,
+ &enable, sizeof(UInt32));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv);
+ return CUBEB_ERROR;
}
-
return CUBEB_OK;
}
static int
-audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
+audiounit_create_unit(AudioUnit * unit, device_info * device)
{
- if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
- stream->input_linear_buffer = new auto_array_wrapper(
- new auto_array<short>(capacity *
- stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame) );
- } else {
- stream->input_linear_buffer = new auto_array_wrapper(
- new auto_array<float>(capacity *
- stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame) );
- }
+ assert(*unit == nullptr);
+ assert(device);
- if (!stream->input_linear_buffer) {
- return CUBEB_ERROR;
+ OSStatus rv;
+ int r;
+
+ r = audiounit_new_unit_instance(unit, device);
+ if (r != CUBEB_OK) {
+ return r;
}
+ assert(*unit);
- assert(stream->input_linear_buffer->length() == 0);
+ if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {
+ return CUBEB_OK;
+ }
- // Pre-buffer silence if needed
- if (capacity != 1) {
- size_t silence_size = stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame;
- stream->input_linear_buffer->push_silence(silence_size);
+ if (device->flags & DEV_INPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit input scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit output scope");
+ return r;
+ }
+ } else if (device->flags & DEV_OUTPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit output scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit input scope");
+ return r;
+ }
+ } else {
+ assert(false);
+ }
- assert(stream->input_linear_buffer->length() == silence_size);
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, 0, &device->id,
+ sizeof(AudioDeviceID));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d",
+ rv);
+ return CUBEB_ERROR;
}
return CUBEB_OK;
}
-static void
-audiounit_destroy_input_linear_buffer(cubeb_stream * stream)
+static int
+audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
{
- delete stream->input_linear_buffer;
+ uint32_t size =
+ capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame;
+ if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size));
+ } else {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size));
+ }
+ assert(stream->input_linear_buffer->length() == 0);
+
+ return CUBEB_OK;
}
static uint32_t
audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
{
// For the 1st stream set anything within safe min-max
- assert(stm->context->active_streams > 0);
- if (stm->context->active_streams == 1) {
- return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
+ assert(audiounit_active_streams(stm->context) > 0);
+ if (audiounit_active_streams(stm->context) == 1) {
+ return max(min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
}
+ assert(stm->output_unit);
// If more than one stream operates in parallel
// allow only lower values of latency
@@ -1241,42 +2169,42 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
UInt32 output_buffer_size = 0;
UInt32 size = sizeof(output_buffer_size);
if (stm->output_unit) {
- r = AudioUnitGetProperty(stm->output_unit,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Output,
- AU_OUT_BUS,
- &output_buffer_size,
- &size);
+ r = AudioUnitGetProperty(
+ stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, AU_OUT_BUS, &output_buffer_size, &size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
+ LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ r);
return 0;
}
- output_buffer_size = std::max(std::min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
+ output_buffer_size =
+ max(min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
}
UInt32 input_buffer_size = 0;
if (stm->input_unit) {
- r = AudioUnitGetProperty(stm->input_unit,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Input,
- AU_IN_BUS,
- &input_buffer_size,
- &size);
+ r = AudioUnitGetProperty(
+ stm->input_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Input, AU_IN_BUS, &input_buffer_size, &size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
+ LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ r);
return 0;
}
- input_buffer_size = std::max(std::min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
+ input_buffer_size =
+ max(min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
}
// Every following active streams can only set smaller latency
UInt32 upper_latency_limit = 0;
if (input_buffer_size != 0 && output_buffer_size != 0) {
- upper_latency_limit = std::min<uint32_t>(input_buffer_size, output_buffer_size);
+ upper_latency_limit = min<uint32_t>(input_buffer_size, output_buffer_size);
} else if (input_buffer_size != 0) {
upper_latency_limit = input_buffer_size;
} else if (output_buffer_size != 0) {
@@ -1285,8 +2213,8 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
}
- return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
- SAFE_MIN_LATENCY_FRAMES);
+ return max(min<uint32_t>(latency_frames, upper_latency_limit),
+ SAFE_MIN_LATENCY_FRAMES);
}
/*
@@ -1298,130 +2226,103 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
* - property has changed, remove the listener
* */
static void
-buffer_size_changed_callback(void * inClientData,
- AudioUnit inUnit,
- AudioUnitPropertyID inPropertyID,
- AudioUnitScope inScope,
- AudioUnitElement inElement)
+buffer_size_changed_callback(void * inClientData, AudioUnit inUnit,
+ AudioUnitPropertyID inPropertyID,
+ AudioUnitScope inScope, AudioUnitElement inElement)
{
cubeb_stream * stm = (cubeb_stream *)inClientData;
AudioUnit au = inUnit;
AudioUnitScope au_scope = kAudioUnitScope_Input;
AudioUnitElement au_element = inElement;
- const char * au_type = "output";
+ char const * au_type = "output";
- if (au == stm->input_unit) {
+ if (AU_IN_BUS == inElement) {
au_scope = kAudioUnitScope_Output;
au_type = "input";
}
switch (inPropertyID) {
- case kAudioDevicePropertyBufferFrameSize: {
- if (inScope != au_scope) {
- break;
- }
- UInt32 new_buffer_size;
- UInt32 outSize = sizeof(UInt32);
- OSStatus r = AudioUnitGetProperty(au,
- kAudioDevicePropertyBufferFrameSize,
- au_scope,
- au_element,
- &new_buffer_size,
- &outSize);
- if (r != noErr) {
- LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
- } else {
- LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
- au_type, new_buffer_size, inScope);
- }
- stm->buffer_size_change_state = true;
+ case kAudioDevicePropertyBufferFrameSize: {
+ if (inScope != au_scope) {
break;
}
+ UInt32 new_buffer_size;
+ UInt32 outSize = sizeof(UInt32);
+ OSStatus r =
+ AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,
+ au_element, &new_buffer_size, &outSize);
+ if (r != noErr) {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current "
+ "buffer size",
+ stm);
+ } else {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size "
+ "= %d for scope %d",
+ stm, au_type, new_buffer_size, inScope);
+ }
+ stm->buffer_size_change_state = true;
+ break;
+ }
}
}
-enum set_buffer_size_side {
- INPUT,
- OUTPUT,
-};
-
static int
-audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames,
+ io_side side)
{
AudioUnit au = stm->output_unit;
AudioUnitScope au_scope = kAudioUnitScope_Input;
AudioUnitElement au_element = AU_OUT_BUS;
- const char * au_type = "output";
- if (set_side == INPUT) {
+ if (side == io_side::INPUT) {
au = stm->input_unit;
au_scope = kAudioUnitScope_Output;
au_element = AU_IN_BUS;
- au_type = "input";
}
uint32_t buffer_frames = 0;
UInt32 size = sizeof(buffer_frames);
- int r = AudioUnitGetProperty(au,
- kAudioDevicePropertyBufferFrameSize,
- au_scope,
- au_element,
- &buffer_frames,
- &size);
+ int r = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize,
+ au_scope, au_element, &buffer_frames, &size);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",
+ to_string(side), r);
return CUBEB_ERROR;
}
if (new_size_frames == buffer_frames) {
- LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
+ LOG("(%p) No need to update %s buffer size already %u frames", stm,
+ to_string(side), buffer_frames);
return CUBEB_OK;
}
- r = AudioUnitAddPropertyListener(au,
- kAudioDevicePropertyBufferFrameSize,
- buffer_size_changed_callback,
- stm);
+ r = AudioUnitAddPropertyListener(au, kAudioDevicePropertyBufferFrameSize,
+ buffer_size_changed_callback, stm);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
return CUBEB_ERROR;
}
stm->buffer_size_change_state = false;
- r = AudioUnitSetProperty(au,
- kAudioDevicePropertyBufferFrameSize,
- au_scope,
- au_element,
- &new_size_frames,
+ r = AudioUnitSetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,
+ au_element, &new_size_frames,
sizeof(new_size_frames));
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",
+ to_string(side), r);
- r = AudioUnitRemovePropertyListenerWithUserData(au,
- kAudioDevicePropertyBufferFrameSize,
- buffer_size_changed_callback,
- stm);
+ r = AudioUnitRemovePropertyListenerWithUserData(
+ au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,
+ stm);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
}
return CUBEB_ERROR;
@@ -1432,22 +2333,21 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff
struct timespec req, rem;
req.tv_sec = 0;
req.tv_nsec = 100000000L; // 0.1 sec
- if (nanosleep(&req , &rem) < 0 ) {
- LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
+ if (nanosleep(&req, &rem) < 0) {
+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time "
+ "%ld nano secs \n",
+ stm, rem.tv_nsec);
}
LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
}
- r = AudioUnitRemovePropertyListenerWithUserData(au,
- kAudioDevicePropertyBufferFrameSize,
- buffer_size_changed_callback,
- stm);
+ r = AudioUnitRemovePropertyListenerWithUserData(
+ au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,
+ stm);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
return CUBEB_ERROR;
}
@@ -1456,32 +2356,33 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff
return CUBEB_ERROR;
}
- LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
+ LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side),
+ new_size_frames);
return CUBEB_OK;
}
static int
audiounit_configure_input(cubeb_stream * stm)
{
+ assert(stm && stm->input_unit);
+
int r = 0;
UInt32 size;
AURenderCallbackStruct aurcbs_in;
- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in "
+ "frames %u.",
stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
stm->input_stream_params.format, stm->latency_frames);
/* Get input device sample rate. */
AudioStreamBasicDescription input_hw_desc;
size = sizeof(AudioStreamBasicDescription);
- r = AudioUnitGetProperty(stm->input_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- AU_IN_BUS,
- &input_hw_desc,
+ r = AudioUnitGetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AU_IN_BUS, &input_hw_desc,
&size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
stm->input_hw_rate = input_hw_desc.mSampleRate;
@@ -1495,8 +2396,7 @@ audiounit_configure_input(cubeb_stream * stm)
}
// Use latency to set buffer size
- stm->input_buffer_frames = stm->latency_frames;
- r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT);
if (r != CUBEB_OK) {
LOG("(%p) Error in change input buffer size.", stm);
return CUBEB_ERROR;
@@ -1507,26 +2407,22 @@ audiounit_configure_input(cubeb_stream * stm)
we will resample inside input callback. */
src_desc.mSampleRate = stm->input_hw_rate;
- r = AudioUnitSetProperty(stm->input_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- AU_IN_BUS,
- &src_desc,
+ r = AudioUnitSetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AU_IN_BUS, &src_desc,
sizeof(AudioStreamBasicDescription));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
/* Frames per buffer in the input callback. */
- r = AudioUnitSetProperty(stm->input_unit,
- kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Global,
- AU_IN_BUS,
- &stm->input_buffer_frames,
- sizeof(UInt32));
+ r = AudioUnitSetProperty(
+ stm->input_unit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, AU_IN_BUS, &stm->latency_frames, sizeof(UInt32));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
@@ -1540,20 +2436,21 @@ audiounit_configure_input(cubeb_stream * stm)
return CUBEB_ERROR;
}
- assert(stm->input_unit != NULL);
aurcbs_in.inputProc = audiounit_input_callback;
aurcbs_in.inputProcRefCon = stm;
- r = AudioUnitSetProperty(stm->input_unit,
- kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global,
- AU_OUT_BUS,
- &aurcbs_in,
- sizeof(aurcbs_in));
+ r = AudioUnitSetProperty(
+ stm->input_unit, kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_in, sizeof(aurcbs_in));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
+ LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
+
+ stm->frames_read = 0;
+
LOG("(%p) Input audiounit init successfully.", stm);
return CUBEB_OK;
@@ -1562,12 +2459,14 @@ audiounit_configure_input(cubeb_stream * stm)
static int
audiounit_configure_output(cubeb_stream * stm)
{
+ assert(stm && stm->output_unit);
+
int r;
AURenderCallbackStruct aurcbs_out;
UInt32 size;
-
- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in "
+ "frames %u.",
stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
stm->output_stream_params.format, stm->latency_frames);
@@ -1581,62 +2480,75 @@ audiounit_configure_output(cubeb_stream * stm)
AudioStreamBasicDescription output_hw_desc;
size = sizeof(AudioStreamBasicDescription);
memset(&output_hw_desc, 0, size);
- r = AudioUnitGetProperty(stm->output_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- AU_OUT_BUS,
- &output_hw_desc,
+ r = AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AU_OUT_BUS, &output_hw_desc,
&size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
stm->output_hw_rate = output_hw_desc.mSampleRate;
- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
+ LOG("(%p) Output device sampling rate: %.2f", stm,
+ output_hw_desc.mSampleRate);
+ stm->context->channels = output_hw_desc.mChannelsPerFrame;
+
+ // Set the input layout to match the output device layout.
+ audiounit_layout_init(stm, io_side::OUTPUT);
+ if (stm->context->channels != stm->output_stream_params.channels ||
+ stm->context->layout != stm->output_stream_params.layout) {
+ LOG("Incompatible channel layouts detected, setting up remixer");
+ audiounit_init_mixer(stm);
+ // We will be remixing the data before it reaches the output device.
+ // We need to adjust the number of channels and other
+ // AudioStreamDescription details.
+ stm->output_desc.mChannelsPerFrame = stm->context->channels;
+ stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *
+ stm->output_desc.mChannelsPerFrame;
+ stm->output_desc.mBytesPerPacket =
+ stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
+ } else {
+ stm->mixer = nullptr;
+ }
- r = AudioUnitSetProperty(stm->output_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- AU_OUT_BUS,
- &stm->output_desc,
+ r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AU_OUT_BUS, &stm->output_desc,
sizeof(AudioStreamBasicDescription));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
- r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT);
if (r != CUBEB_OK) {
LOG("(%p) Error in change output buffer size.", stm);
return CUBEB_ERROR;
}
/* Frames per buffer in the input callback. */
- r = AudioUnitSetProperty(stm->output_unit,
- kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Global,
- AU_OUT_BUS,
- &stm->latency_frames,
- sizeof(UInt32));
+ r = AudioUnitSetProperty(
+ stm->output_unit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, AU_OUT_BUS, &stm->latency_frames, sizeof(UInt32));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
- assert(stm->output_unit != NULL);
aurcbs_out.inputProc = audiounit_output_callback;
aurcbs_out.inputProcRefCon = stm;
- r = AudioUnitSetProperty(stm->output_unit,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Global,
- AU_OUT_BUS,
- &aurcbs_out,
- sizeof(aurcbs_out));
+ r = AudioUnitSetProperty(
+ stm->output_unit, kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_out, sizeof(aurcbs_out));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
+ stm->frames_written = 0;
+
LOG("(%p) Output audiounit init successfully.", stm);
return CUBEB_OK;
}
@@ -1646,11 +2558,37 @@ audiounit_setup_stream(cubeb_stream * stm)
{
stm->mutex.assert_current_thread_owns();
+ if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||
+ (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {
+ LOG("(%p) Loopback not supported for audiounit.", stm);
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
int r = 0;
+
+ device_info in_dev_info = stm->input_device;
+ device_info out_dev_info = stm->output_device;
+
+ if (has_input(stm) && has_output(stm) &&
+ stm->input_device.id != stm->output_device.id) {
+ r = audiounit_create_aggregate_device(stm);
+ if (r != CUBEB_OK) {
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ LOG("(%p) Create aggregate devices failed.", stm);
+ // !!!NOTE: It is not necessary to return here. If it does not
+ // return it will fallback to the old implementation. The intention
+ // is to investigate how often it fails. I plan to remove
+ // it after a couple of weeks.
+ return r;
+ } else {
+ in_dev_info.id = out_dev_info.id = stm->aggregate_device_id;
+ in_dev_info.flags = DEV_INPUT;
+ out_dev_info.flags = DEV_OUTPUT;
+ }
+ }
+
if (has_input(stm)) {
- r = audiounit_create_unit(&stm->input_unit, true,
- &stm->input_stream_params,
- stm->input_device);
+ r = audiounit_create_unit(&stm->input_unit, &in_dev_info);
if (r != CUBEB_OK) {
LOG("(%p) AudioUnit creation for input failed.", stm);
return r;
@@ -1658,30 +2596,28 @@ audiounit_setup_stream(cubeb_stream * stm)
}
if (has_output(stm)) {
- r = audiounit_create_unit(&stm->output_unit, false,
- &stm->output_stream_params,
- stm->output_device);
+ r = audiounit_create_unit(&stm->output_unit, &out_dev_info);
if (r != CUBEB_OK) {
LOG("(%p) AudioUnit creation for output failed.", stm);
return r;
}
}
- /* Latency cannot change if another stream is operating in parallel. In this case
- * latecy is set to the other stream value. */
- if (stm->context->active_streams > 1) {
+ /* Latency cannot change if another stream is operating in parallel. In this
+ * case latency is set to the other stream value. */
+ if (audiounit_active_streams(stm->context) > 1) {
LOG("(%p) More than one active stream, use global latency.", stm);
stm->latency_frames = stm->context->global_latency_frames;
} else {
/* Silently clamp the latency down to the platform default, because we
- * synthetize the clock from the callbacks, and we want the clock to update
- * often. */
+ * synthetize the clock from the callbacks, and we want the clock to update
+ * often. */
stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
- assert(stm->latency_frames); // Ungly error check
- audiounit_set_global_latency(stm, stm->latency_frames);
+ assert(stm->latency_frames); // Ugly error check
+ audiounit_set_global_latency(stm->context, stm->latency_frames);
}
- /* Setup Input Stream! */
+ /* Configure I/O stream */
if (has_input(stm)) {
r = audiounit_configure_input(stm);
if (r != CUBEB_OK) {
@@ -1690,7 +2626,6 @@ audiounit_setup_stream(cubeb_stream * stm)
}
}
- /* Setup Output Stream! */
if (has_output(stm)) {
r = audiounit_configure_output(stm);
if (r != CUBEB_OK) {
@@ -1735,7 +2670,7 @@ audiounit_setup_stream(cubeb_stream * stm)
return CUBEB_ERROR;
}
}
-#else // TARGET_OS_IPHONE
+#else // TARGET_OS_IPHONE
//TODO: [[AVAudioSession sharedInstance] inputLatency]
// http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios
#endif
@@ -1762,13 +2697,10 @@ audiounit_setup_stream(cubeb_stream * stm)
/* Create resampler. Output params are unchanged
* because we do not need conversion on the output. */
- stm->resampler = cubeb_resampler_create(stm,
- has_input(stm) ? &input_unconverted_params : NULL,
- has_output(stm) ? &stm->output_stream_params : NULL,
- target_sample_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler.reset(cubeb_resampler_create(
+ stm, has_input(stm) ? &input_unconverted_params : NULL,
+ has_output(stm) ? &stm->output_stream_params : NULL, target_sample_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP));
if (!stm->resampler) {
LOG("(%p) Could not create resampler.", stm);
return CUBEB_ERROR;
@@ -1777,7 +2709,7 @@ audiounit_setup_stream(cubeb_stream * stm)
if (stm->input_unit != NULL) {
r = AudioUnitInitialize(stm->input_unit);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitInitialize/input", r);
+ LOG("AudioUnitInitialize/input rv=%d", r);
return CUBEB_ERROR;
}
}
@@ -1785,175 +2717,221 @@ audiounit_setup_stream(cubeb_stream * stm)
if (stm->output_unit != NULL) {
r = AudioUnitInitialize(stm->output_unit);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitInitialize/output", r);
+ LOG("AudioUnitInitialize/output rv=%d", r);
return CUBEB_ERROR;
}
+
+ stm->current_latency_frames = audiounit_get_device_presentation_latency(
+ stm->output_device.id, kAudioDevicePropertyScopeOutput);
+
+ Float64 unit_s;
+ UInt32 size = sizeof(unit_s);
+ if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency,
+ kAudioUnitScope_Global, 0, &unit_s,
+ &size) == noErr) {
+ stm->current_latency_frames +=
+ static_cast<uint32_t>(unit_s * stm->output_desc.mSampleRate);
+ }
}
if (stm->input_unit && stm->output_unit) {
- // According to the I/O hardware rate it is expected a specific pattern of callbacks
- // for example is input is 44100 and output is 48000 we expected no more than 2
- // out callback in a row.
- stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
+ // According to the I/O hardware rate it is expected a specific pattern of
+ // callbacks for example is input is 44100 and output is 48000 we expected
+ // no more than 2 out callback in a row.
+ stm->expected_output_callbacks_in_a_row =
+ ceilf(stm->output_hw_rate / stm->input_hw_rate);
}
r = audiounit_install_device_changed_callback(stm);
if (r != CUBEB_OK) {
- LOG("(%p) Could not install the device change callback.", stm);
- return r;
+ LOG("(%p) Could not install all device change callback.", stm);
}
return CUBEB_OK;
}
+cubeb_stream::cubeb_stream(cubeb * context)
+ : context(context), resampler(nullptr, cubeb_resampler_destroy),
+ mixer(nullptr, cubeb_mixer_destroy)
+{
+ PodZero(&input_desc, 1);
+ PodZero(&output_desc, 1);
+}
+
+static void
+audiounit_stream_destroy_internal(cubeb_stream * stm);
+
static int
-audiounit_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * /* stream_name */,
- cubeb_devid input_device,
+audiounit_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * /* stream_name */, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int latency_frames,
cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
+ cubeb_state_callback state_callback, void * user_ptr)
{
- cubeb_stream * stm;
- int r;
-
assert(context);
+ auto_lock context_lock(context->mutex);
+ audiounit_increment_active_streams(context);
+ unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(
+ new cubeb_stream(context), audiounit_stream_destroy_internal);
+ int r;
*stream = NULL;
-
assert(latency_frames > 0);
- if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
- LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX);
- return CUBEB_ERROR;
- }
-
- stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream));
- assert(stm);
- // Placement new to call the ctors of cubeb_stream members.
- new (stm) cubeb_stream();
/* These could be different in the future if we have both
* full-duplex stream and different devices for input vs output. */
- stm->context = context;
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
stm->latency_frames = latency_frames;
- stm->device_changed_callback = NULL;
+
+ if ((input_device && !input_stream_params) ||
+ (output_device && !output_stream_params)) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
- stm->input_device = input_device;
- stm->is_default_input = input_device == nullptr ||
- (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) ==
- reinterpret_cast<intptr_t>(input_device));
+ r = audiounit_set_device_info(
+ stm.get(), reinterpret_cast<uintptr_t>(input_device), io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for input.", stm.get());
+ return r;
+ }
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
- stm->output_device = output_device;
+ r = audiounit_set_device_info(
+ stm.get(), reinterpret_cast<uintptr_t>(output_device), io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for output.", stm.get());
+ return r;
+ }
}
- /* Init data members where necessary */
- stm->hw_latency_frames = UINT64_MAX;
-
- stm->switching_device = false;
-
- auto_lock context_lock(context->mutex);
{
// It's not critical to lock here, because no other thread has been started
// yet, but it allows to assert that the lock has been taken in
// `audiounit_setup_stream`.
- context->active_streams += 1;
auto_lock lock(stm->mutex);
- r = audiounit_setup_stream(stm);
+ r = audiounit_setup_stream(stm.get());
}
if (r != CUBEB_OK) {
- LOG("(%p) Could not setup the audiounit stream.", stm);
- audiounit_stream_destroy(stm);
+ LOG("(%p) Could not setup the audiounit stream.", stm.get());
return r;
}
- r = audiounit_install_system_changed_callback(stm);
+ r = audiounit_install_system_changed_callback(stm.get());
if (r != CUBEB_OK) {
- LOG("(%p) Could not install the device change callback.", stm);
+ LOG("(%p) Could not install the device change callback.", stm.get());
return r;
}
- *stream = stm;
- LOG("Cubeb stream (%p) init successful.", stm);
+ *stream = stm.release();
+ LOG("(%p) Cubeb stream init successful.", *stream);
return CUBEB_OK;
}
static void
-audiounit_close_stream(cubeb_stream *stm)
+audiounit_close_stream(cubeb_stream * stm)
{
stm->mutex.assert_current_thread_owns();
if (stm->input_unit) {
AudioUnitUninitialize(stm->input_unit);
AudioComponentInstanceDispose(stm->input_unit);
+ stm->input_unit = nullptr;
}
- audiounit_destroy_input_linear_buffer(stm);
+ stm->input_linear_buffer.reset();
if (stm->output_unit) {
AudioUnitUninitialize(stm->output_unit);
AudioComponentInstanceDispose(stm->output_unit);
+ stm->output_unit = nullptr;
}
- cubeb_resampler_destroy(stm->resampler);
+ stm->resampler.reset();
+ stm->mixer.reset();
+
+ if (stm->aggregate_device_id != kAudioObjectUnknown) {
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ }
}
static void
-audiounit_stream_destroy(cubeb_stream * stm)
+audiounit_stream_destroy_internal(cubeb_stream * stm)
{
- stm->shutdown = true;
+ stm->context->mutex.assert_current_thread_owns();
int r = audiounit_uninstall_system_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
}
-
r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
+
+ auto_lock lock(stm->mutex);
+ audiounit_close_stream(stm);
+ assert(audiounit_active_streams(stm->context) >= 1);
+ audiounit_decrement_active_streams(stm->context);
+}
+
+static void
+audiounit_stream_destroy(cubeb_stream * stm)
+{
+ int r = audiounit_uninstall_system_changed_callback(stm);
+ if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
}
+ r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
+ if (!stm->shutdown.load()) {
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_stop_internal(stm);
+ stm->shutdown = true;
+ }
+ stm->destroy_pending = true;
// Execute close in serial queue to avoid collision
// with reinit when un/plug devices
dispatch_sync(stm->context->serial_queue, ^() {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_destroy_internal(stm);
});
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
-
LOG("Cubeb stream (%p) destroyed successful.", stm);
-
- stm->~cubeb_stream();
- free(stm);
+ delete stm;
}
-void
+static int
audiounit_stream_start_internal(cubeb_stream * stm)
{
OSStatus r;
if (stm->input_unit != NULL) {
r = AudioOutputUnitStart(stm->input_unit);
- assert(r == 0);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (input) rv=%d", r);
+ return CUBEB_ERROR;
+ }
}
if (stm->output_unit != NULL) {
r = AudioOutputUnitStart(stm->output_unit);
- assert(r == 0);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (output) rv=%d", r);
+ return CUBEB_ERROR;
+ }
}
+ return CUBEB_OK;
}
static int
@@ -1963,7 +2941,10 @@ audiounit_stream_start(cubeb_stream * stm)
stm->shutdown = false;
stm->draining = false;
- audiounit_stream_start_internal(stm);
+ int r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
@@ -2002,9 +2983,12 @@ audiounit_stream_stop(cubeb_stream * stm)
static int
audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
- auto_lock lock(stm->mutex);
-
- *position = stm->frames_played;
+ assert(stm);
+ if (stm->current_latency_frames > stm->frames_played) {
+ *position = 0;
+ } else {
+ *position = stm->frames_played - stm->current_latency_frames;
+ }
return CUBEB_OK;
}
@@ -2012,77 +2996,10 @@ int
audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
#if TARGET_OS_IPHONE
- //TODO
+ // TODO
return CUBEB_ERROR_NOT_SUPPORTED;
#else
- auto_lock lock(stm->mutex);
- if (stm->hw_latency_frames == UINT64_MAX) {
- UInt32 size;
- uint32_t device_latency_frames, device_safety_offset;
- double unit_latency_sec;
- AudioDeviceID output_device_id;
- OSStatus r;
- AudioObjectPropertyAddress latency_address = {
- kAudioDevicePropertyLatency,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
- AudioObjectPropertyAddress safety_offset_address = {
- kAudioDevicePropertySafetyOffset,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
-
- r = audiounit_get_output_device_id(&output_device_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- size = sizeof(unit_latency_sec);
- r = AudioUnitGetProperty(stm->output_unit,
- kAudioUnitProperty_Latency,
- kAudioUnitScope_Global,
- 0,
- &unit_latency_sec,
- &size);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/kAudioUnitProperty_Latency", r);
- return CUBEB_ERROR;
- }
-
- size = sizeof(device_latency_frames);
- r = AudioObjectGetPropertyData(output_device_id,
- &latency_address,
- 0,
- NULL,
- &size,
- &device_latency_frames);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetPropertyData/latency_frames", r);
- return CUBEB_ERROR;
- }
-
- size = sizeof(device_safety_offset);
- r = AudioObjectGetPropertyData(output_device_id,
- &safety_offset_address,
- 0,
- NULL,
- &size,
- &device_safety_offset);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetPropertyData/safety_offset", r);
- return CUBEB_ERROR;
- }
-
- /* This part is fixed and depend on the stream parameter and the hardware. */
- stm->hw_latency_frames =
- static_cast<uint32_t>(unit_latency_sec * stm->output_desc.mSampleRate)
- + device_latency_frames
- + device_safety_offset;
- }
-
- *latency = stm->hw_latency_frames + stm->current_latency_frames;
-
+ *latency = stm->total_output_latency_frames;
return CUBEB_OK;
#endif
}
@@ -2091,10 +3008,8 @@ static int
audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
{
assert(stm->output_unit);
- OSStatus r = AudioUnitGetParameter(stm->output_unit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0, volume);
+ OSStatus r = AudioUnitGetParameter(stm->output_unit, kHALOutputParam_Volume,
+ kAudioUnitScope_Global, 0, volume);
if (r != noErr) {
LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);
return CUBEB_ERROR;
@@ -2102,182 +3017,137 @@ audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
return CUBEB_OK;
}
-int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
+static int
+audiounit_stream_set_volume(cubeb_stream * stm, float volume)
{
+ assert(stm->output_unit);
OSStatus r;
-
- r = AudioUnitSetParameter(stm->output_unit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0, volume, 0);
+ r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume,
+ kAudioUnitScope_Global, 0, volume, 0);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r);
+ LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r);
return CUBEB_ERROR;
}
return CUBEB_OK;
}
-int audiounit_stream_set_panning(cubeb_stream * stm, float panning)
+unique_ptr<char[]>
+convert_uint32_into_string(UInt32 data)
{
- if (stm->output_desc.mChannelsPerFrame > 2) {
- return CUBEB_ERROR_INVALID_PARAMETER;
+ // Simply create an empty string if no data.
+ size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32.
+ auto str = unique_ptr<char[]>{new char[size + 1]}; // + 1 for '\0'.
+ str[size] = '\0';
+ if (size < 4) {
+ return str;
}
- stm->panning.store(panning, std::memory_order_relaxed);
- return CUBEB_OK;
+ // Reverse 0xWXYZ into 0xZYXW.
+ str[0] = (char)(data >> 24);
+ str[1] = (char)(data >> 16);
+ str[2] = (char)(data >> 8);
+ str[3] = (char)(data);
+ return str;
}
-int audiounit_stream_get_current_device(cubeb_stream * stm,
- cubeb_device ** const device)
+int
+audiounit_get_default_device_datasource(cubeb_device_type type, UInt32 * data)
{
-#if TARGET_OS_IPHONE
- //TODO
- return CUBEB_ERROR_NOT_SUPPORTED;
-#else
- OSStatus r;
- UInt32 size;
- UInt32 data;
- char strdata[4];
- AudioDeviceID output_device_id;
- AudioDeviceID input_device_id;
-
- AudioObjectPropertyAddress datasource_address = {
- kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
-
- AudioObjectPropertyAddress datasource_address_input = {
- kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput,
- kAudioObjectPropertyElementMaster
- };
-
- *device = NULL;
-
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ AudioDeviceID id = audiounit_get_default_device_id(type);
+ if (id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
- *device = new cubeb_device;
- if (!*device) {
- return CUBEB_ERROR;
- }
- PodZero(*device, 1);
-
- size = sizeof(UInt32);
- /* This fails with some USB headset, so simply return an empty string. */
- r = AudioObjectGetPropertyData(output_device_id,
- &datasource_address,
- 0, NULL, &size, &data);
+ UInt32 size = sizeof(*data);
+ /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */
+ OSStatus r = AudioObjectGetPropertyData(
+ id,
+ type == CUBEB_DEVICE_TYPE_INPUT ? &INPUT_DATA_SOURCE_PROPERTY_ADDRESS
+ : &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ 0, NULL, &size, data);
if (r != noErr) {
- size = 0;
- data = 0;
- }
-
- (*device)->output_name = new char[size + 1];
- if (!(*device)->output_name) {
- return CUBEB_ERROR;
+ *data = 0;
}
- // Turn the four chars packed into a uint32 into a string
- strdata[0] = (char)(data >> 24);
- strdata[1] = (char)(data >> 16);
- strdata[2] = (char)(data >> 8);
- strdata[3] = (char)(data);
+ return CUBEB_OK;
+}
- memcpy((*device)->output_name, strdata, size);
- (*device)->output_name[size] = '\0';
+int
+audiounit_get_default_device_name(cubeb_stream * stm,
+ cubeb_device * const device,
+ cubeb_device_type type)
+{
+ assert(stm);
+ assert(device);
- if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) {
- return CUBEB_ERROR;
+ UInt32 data;
+ int r = audiounit_get_default_device_datasource(type, &data);
+ if (r != CUBEB_OK) {
+ return r;
}
-
- size = sizeof(UInt32);
- r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data);
- if (r != noErr) {
- LOG("(%p) Error when getting device !", stm);
- size = 0;
- data = 0;
+ char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? &device->input_name
+ : &device->output_name;
+ *name = convert_uint32_into_string(data).release();
+ if (!strlen(*name)) { // empty string.
+ LOG("(%p) name of %s device is empty!", stm,
+ type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output");
}
+ return CUBEB_OK;
+}
- (*device)->input_name = new char[size + 1];
- if (!(*device)->input_name) {
+int
+audiounit_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+#if TARGET_OS_IPHONE
+ // TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ *device = new cubeb_device;
+ if (!*device) {
return CUBEB_ERROR;
}
+ PodZero(*device, 1);
- // Turn the four chars packed into a uint32 into a string
- strdata[0] = (char)(data >> 24);
- strdata[1] = (char)(data >> 16);
- strdata[2] = (char)(data >> 8);
- strdata[3] = (char)(data);
+ int r =
+ audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
- memcpy((*device)->input_name, strdata, size);
- (*device)->input_name[size] = '\0';
+ r = audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_INPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
return CUBEB_OK;
#endif
}
-int audiounit_stream_device_destroy(cubeb_stream * /* stream */,
- cubeb_device * device)
+int
+audiounit_stream_device_destroy(cubeb_stream * /* stream */,
+ cubeb_device * device)
{
- delete [] device->output_name;
- delete [] device->input_name;
+ delete[] device->output_name;
+ delete[] device->input_name;
delete device;
return CUBEB_OK;
}
-int audiounit_stream_register_device_changed_callback(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback)
+int
+audiounit_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback)
{
+ auto_lock dev_cb_lock(stream->device_changed_callback_lock);
/* Note: second register without unregister first causes 'nope' error.
* Current implementation requires unregister before register a new cb. */
- assert(!stream->device_changed_callback);
-
- auto_lock lock(stream->mutex);
-
+ assert(!device_changed_callback || !stream->device_changed_callback);
stream->device_changed_callback = device_changed_callback;
-
return CUBEB_OK;
}
-static OSStatus
-audiounit_get_devices(AudioObjectID ** devices, uint32_t * count)
-{
- OSStatus ret;
- UInt32 size = 0;
- AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster };
-
- ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
- if (ret != noErr) {
- return ret;
- }
-
- *count = static_cast<uint32_t>(size / sizeof(AudioObjectID));
- if (size >= sizeof(AudioObjectID)) {
- if (*devices != NULL) {
- delete [] (*devices);
- }
- *devices = new AudioObjectID[*count];
- PodZero(*devices, *count);
-
- ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, (void *)*devices);
- if (ret != noErr) {
- delete [] (*devices);
- *devices = NULL;
- }
- } else {
- *devices = NULL;
- ret = -1;
- }
-
- return ret;
-}
-
static char *
audiounit_strref_to_cstr_utf8(CFStringRef strref)
{
@@ -2288,11 +3158,12 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref)
}
len = CFStringGetLength(strref);
- size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8);
- ret = static_cast<char *>(malloc(size));
+ // Add 1 to size to allow for '\0' termination character.
+ size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+ ret = new char[size];
if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {
- free(ret);
+ delete[] ret;
ret = NULL;
}
@@ -2302,15 +3173,18 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref)
static uint32_t
audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
{
- AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
UInt32 size = 0;
uint32_t i, ret = 0;
adr.mSelector = kAudioDevicePropertyStreamConfiguration;
- if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr && size > 0) {
+ if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr &&
+ size > 0) {
AudioBufferList * list = static_cast<AudioBufferList *>(alloca(size));
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == noErr) {
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) ==
+ noErr) {
for (i = 0; i < list->mNumberBuffers; i++)
ret += list->mBuffers[i].mNumberChannels;
}
@@ -2320,16 +3194,20 @@ audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
}
static void
-audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
- uint32_t * min, uint32_t * max, uint32_t * def)
+audiounit_get_available_samplerate(AudioObjectID devid,
+ AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max,
+ uint32_t * def)
{
- AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
adr.mSelector = kAudioDevicePropertyNominalSampleRate;
if (AudioObjectHasProperty(devid, &adr)) {
UInt32 size = sizeof(Float64);
Float64 fvalue = 0.0;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) == noErr) {
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) ==
+ noErr) {
*def = fvalue;
}
}
@@ -2339,32 +3217,33 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope
AudioValueRange range;
if (AudioObjectHasProperty(devid, &adr) &&
AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) {
- uint32_t i, count = size / sizeof(AudioValueRange);
- AudioValueRange * ranges = new AudioValueRange[count];
+ uint32_t count = size / sizeof(AudioValueRange);
+ vector<AudioValueRange> ranges(count);
range.mMinimum = 9999999999.0;
range.mMaximum = 0.0;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) {
- for (i = 0; i < count; i++) {
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size,
+ ranges.data()) == noErr) {
+ for (uint32_t i = 0; i < count; i++) {
if (ranges[i].mMaximum > range.mMaximum)
range.mMaximum = ranges[i].mMaximum;
if (ranges[i].mMinimum < range.mMinimum)
range.mMinimum = ranges[i].mMinimum;
}
}
- delete [] ranges;
*max = static_cast<uint32_t>(range.mMaximum);
*min = static_cast<uint32_t>(range.mMinimum);
} else {
*min = *max = 0;
}
-
}
static UInt32
-audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope)
+audiounit_get_device_presentation_latency(AudioObjectID devid,
+ AudioObjectPropertyScope scope)
{
- AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
- UInt32 size, dev, stream = 0, offset;
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size, dev, stream = 0;
AudioStreamID sid[1];
adr.mSelector = kAudioDevicePropertyLatency;
@@ -2381,354 +3260,411 @@ audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectProper
AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream);
}
- adr.mSelector = kAudioDevicePropertySafetyOffset;
- size = sizeof(UInt32);
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) {
- offset = 0;
- }
-
- return dev + stream + offset;
+ return dev + stream;
}
-static cubeb_device_info *
-audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type)
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,
+ AudioObjectID devid, cubeb_device_type type)
{
- AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster };
- UInt32 size, ch, latency;
- cubeb_device_info * ret;
- CFStringRef str = NULL;
- AudioValueRange range;
+ AudioObjectPropertyAddress adr = {0, 0, kAudioObjectPropertyElementMaster};
+ UInt32 size;
if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
adr.mScope = kAudioDevicePropertyScopeOutput;
} else if (type == CUBEB_DEVICE_TYPE_INPUT) {
adr.mScope = kAudioDevicePropertyScopeInput;
} else {
- return NULL;
+ return CUBEB_ERROR;
}
- ch = audiounit_get_channel_count(devid, adr.mScope);
+ UInt32 ch = audiounit_get_channel_count(devid, adr.mScope);
if (ch == 0) {
- return NULL;
+ return CUBEB_ERROR;
}
- ret = new cubeb_device_info;
- PodZero(ret, 1);
+ PodZero(dev_info, 1);
+ CFStringRef device_id_str = nullptr;
size = sizeof(CFStringRef);
adr.mSelector = kAudioDevicePropertyDeviceUID;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
- ret->device_id = audiounit_strref_to_cstr_utf8(str);
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->group_id = strdup(ret->device_id);
- CFRelease(str);
+ OSStatus ret =
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str);
+ if (ret == noErr && device_id_str != NULL) {
+ dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str);
+ static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)),
+ "cubeb_devid can't represent devid");
+ dev_info->devid = reinterpret_cast<cubeb_devid>(devid);
+ dev_info->group_id = dev_info->device_id;
+ CFRelease(device_id_str);
+ }
+
+ CFStringRef friendly_name_str = nullptr;
+ UInt32 ds;
+ size = sizeof(UInt32);
+ adr.mSelector = kAudioDevicePropertyDataSource;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds);
+ if (ret == noErr) {
+ AudioValueTranslation trl = {&ds, sizeof(ds), &friendly_name_str,
+ sizeof(CFStringRef)};
+ adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
+ size = sizeof(AudioValueTranslation);
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl);
}
- size = sizeof(CFStringRef);
- adr.mSelector = kAudioObjectPropertyName;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
- UInt32 ds;
- size = sizeof(UInt32);
- adr.mSelector = kAudioDevicePropertyDataSource;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) {
- CFStringRef dsname;
- AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) };
- adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
- size = sizeof(AudioValueTranslation);
- // If there is a datasource for this device, use it instead of the device
- // name.
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) {
- CFRelease(str);
- str = dsname;
- }
- }
+ // If there is no datasource for this device, fall back to the
+ // device name.
+ if (!friendly_name_str) {
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioObjectPropertyName;
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str);
+ }
- ret->friendly_name = audiounit_strref_to_cstr_utf8(str);
- CFRelease(str);
+ if (friendly_name_str) {
+ dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str);
+ CFRelease(friendly_name_str);
+ } else {
+ // Couldn't get a datasource name nor a device name, return a
+ // valid string of length 0.
+ char * fallback_name = new char[1];
+ fallback_name[0] = '\0';
+ dev_info->friendly_name = fallback_name;
}
+ CFStringRef vendor_name_str = nullptr;
size = sizeof(CFStringRef);
adr.mSelector = kAudioObjectPropertyManufacturer;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
- ret->vendor_name = audiounit_strref_to_cstr_utf8(str);
- CFRelease(str);
- }
-
- ret->type = type;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = (devid == audiounit_get_default_device_id(type)) ?
- CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
-
- ret->max_channels = ch;
- ret->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
+ ret =
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str);
+ if (ret == noErr && vendor_name_str != NULL) {
+ dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str);
+ CFRelease(vendor_name_str);
+ }
+
+ dev_info->type = type;
+ dev_info->state = CUBEB_DEVICE_STATE_ENABLED;
+ dev_info->preferred = (devid == audiounit_get_default_device_id(type))
+ ? CUBEB_DEVICE_PREF_ALL
+ : CUBEB_DEVICE_PREF_NONE;
+
+ dev_info->max_channels = ch;
+ dev_info->format =
+ (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
/* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */
- ret->default_format = CUBEB_DEVICE_FMT_F32NE;
- audiounit_get_available_samplerate(devid, adr.mScope,
- &ret->min_rate, &ret->max_rate, &ret->default_rate);
+ dev_info->default_format = CUBEB_DEVICE_FMT_F32NE;
+ audiounit_get_available_samplerate(devid, adr.mScope, &dev_info->min_rate,
+ &dev_info->max_rate,
+ &dev_info->default_rate);
- latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+ UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+ AudioValueRange range;
adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
size = sizeof(AudioValueRange);
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) {
- ret->latency_lo = latency + range.mMinimum;
- ret->latency_hi = latency + range.mMaximum;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range);
+ if (ret == noErr) {
+ dev_info->latency_lo = latency + range.mMinimum;
+ dev_info->latency_hi = latency + range.mMaximum;
} else {
- ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */
- ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */
+ dev_info->latency_lo =
+ 10 * dev_info->default_rate / 1000; /* Default to 10ms */
+ dev_info->latency_hi =
+ 100 * dev_info->default_rate / 1000; /* Default to 100ms */
}
- return ret;
+ return CUBEB_OK;
+}
+
+bool
+is_aggregate_device(cubeb_device_info * device_info)
+{
+ assert(device_info->friendly_name);
+ return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME,
+ strlen(PRIVATE_AGGREGATE_DEVICE_NAME));
}
static int
audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,
- cubeb_device_collection ** collection)
+ cubeb_device_collection * collection)
{
- AudioObjectID * hwdevs = NULL;
- uint32_t i, hwdevcount = 0;
- OSStatus err;
+ vector<AudioObjectID> input_devs;
+ vector<AudioObjectID> output_devs;
- if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) {
- return CUBEB_ERROR;
+ // Count number of input and output devices. This is not
+ // necessarily the same as the count of raw devices supported by the
+ // system since, for example, with Soundflower installed, some
+ // devices may report as being both input *and* output and cubeb
+ // separates those into two different devices.
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
}
- *collection = static_cast<cubeb_device_collection *>(malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0)));
- (*collection)->count = 0;
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ }
- if (hwdevcount > 0) {
- cubeb_device_info * cur;
+ auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()];
+ collection->count = 0;
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- for (i = 0; i < hwdevcount; i++) {
- if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT)) != NULL)
- (*collection)->device[(*collection)->count++] = cur;
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ for (auto dev : output_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
}
+ collection->count += 1;
}
+ }
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- for (i = 0; i < hwdevcount; i++) {
- if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_INPUT)) != NULL)
- (*collection)->device[(*collection)->count++] = cur;
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ for (auto dev : input_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev,
+ CUBEB_DEVICE_TYPE_INPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
}
+ collection->count += 1;
}
}
- delete [] hwdevs;
+ if (collection->count > 0) {
+ collection->device = devices;
+ } else {
+ delete[] devices;
+ collection->device = NULL;
+ }
return CUBEB_OK;
}
-/* qsort compare method. */
-int compare_devid(const void * a, const void * b)
+static void
+audiounit_device_destroy(cubeb_device_info * device)
{
- return (*(AudioObjectID*)a - *(AudioObjectID*)b);
+ delete[] device->device_id;
+ delete[] device->friendly_name;
+ delete[] device->vendor_name;
}
-static uint32_t
-audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array)
+static int
+audiounit_device_collection_destroy(cubeb * /* context */,
+ cubeb_device_collection * collection)
{
- assert(devid_array == NULL || *devid_array == NULL);
+ for (size_t i = 0; i < collection->count; i++) {
+ audiounit_device_destroy(&collection->device[i]);
+ }
+ delete[] collection->device;
- AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster };
+ return CUBEB_OK;
+}
+
+static vector<AudioObjectID>
+audiounit_get_devices_of_type(cubeb_device_type devtype)
+{
UInt32 size = 0;
- OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
+ OSStatus ret = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size);
if (ret != noErr) {
- return 0;
+ return vector<AudioObjectID>();
}
- /* Total number of input and output devices. */
- uint32_t count = (uint32_t)(size / sizeof(AudioObjectID));
-
- AudioObjectID devices[count];
- ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devices);
+ vector<AudioObjectID> devices(size / sizeof(AudioObjectID));
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size,
+ devices.data());
if (ret != noErr) {
- return 0;
+ return vector<AudioObjectID>();
}
+
+ // Remove the aggregate device from the list of devices (if any).
+ for (auto it = devices.begin(); it != devices.end();) {
+ CFStringRef name = get_device_name(*it);
+ if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location !=
+ kCFNotFound) {
+ it = devices.erase(it);
+ } else {
+ it++;
+ }
+ if (name) {
+ CFRelease(name);
+ }
+ }
+
/* Expected sorted but did not find anything in the docs. */
- qsort(devices, count, sizeof(AudioObjectID), compare_devid);
+ sort(devices.begin(), devices.end(),
+ [](AudioObjectID a, AudioObjectID b) { return a < b; });
if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
- if (devid_array) {
- *devid_array = new AudioObjectID[count];
- assert(*devid_array);
- memcpy(*devid_array, &devices, count * sizeof(AudioObjectID));
- }
- return count;
+ return devices;
}
- AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ?
- kAudioDevicePropertyScopeInput :
- kAudioDevicePropertyScopeOutput;
+ AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT)
+ ? kAudioDevicePropertyScopeInput
+ : kAudioDevicePropertyScopeOutput;
- uint32_t dev_count = 0;
- AudioObjectID devices_in_scope[count];
- for(uint32_t i = 0; i < count; ++i) {
+ vector<AudioObjectID> devices_in_scope;
+ for (uint32_t i = 0; i < devices.size(); ++i) {
/* For device in the given scope channel must be > 0. */
if (audiounit_get_channel_count(devices[i], scope) > 0) {
- devices_in_scope[dev_count] = devices[i];
- ++dev_count;
+ devices_in_scope.push_back(devices[i]);
}
}
- if (devid_array && dev_count > 0) {
- *devid_array = new AudioObjectID[dev_count];
- assert(*devid_array);
- memcpy(*devid_array, &devices_in_scope, dev_count * sizeof(AudioObjectID));
- }
- return dev_count;
-}
-
-static uint32_t
-audiounit_equal_arrays(AudioObjectID * left, AudioObjectID * right, uint32_t size)
-{
- /* Expected sorted arrays. */
- for (uint32_t i = 0; i < size; ++i) {
- if (left[i] != right[i]) {
- return 0;
- }
- }
- return 1;
+ return devices_in_scope;
}
static OSStatus
-audiounit_collection_changed_callback(AudioObjectID /* inObjectID */,
- UInt32 /* inNumberAddresses */,
- const AudioObjectPropertyAddress * /* inAddresses */,
- void * inClientData)
+audiounit_collection_changed_callback(
+ AudioObjectID /* inObjectID */, UInt32 /* inNumberAddresses */,
+ const AudioObjectPropertyAddress * /* inAddresses */, void * inClientData)
{
cubeb * context = static_cast<cubeb *>(inClientData);
- auto_lock lock(context->mutex);
- if (context->collection_changed_callback == NULL) {
- /* Listener removed while waiting in mutex, abort. */
- return noErr;
- }
-
- /* Differentiate input from output changes. */
- if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT ||
- context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) {
- AudioObjectID * devices = NULL;
- uint32_t new_number_of_devices = audiounit_get_devices_of_type(context->collection_changed_devtype, &devices);
- /* When count is the same examine the devid for the case of coalescing. */
- if (context->devtype_device_count == new_number_of_devices &&
- audiounit_equal_arrays(devices, context->devtype_device_array, new_number_of_devices)) {
- /* Device changed for the other scope, ignore. */
- delete [] devices;
- return noErr;
+ // This can be called from inside an AudioUnit function, dispatch to another
+ // queue.
+ dispatch_async(context->serial_queue, ^() {
+ auto_lock lock(context->mutex);
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ /* Listener removed while waiting in mutex, abort. */
+ return;
}
- /* Device on desired scope changed, reset counter and array. */
- context->devtype_device_count = new_number_of_devices;
- /* Free the old array before replace. */
- delete [] context->devtype_device_array;
- context->devtype_device_array = devices;
- }
-
- context->collection_changed_callback(context, context->collection_changed_user_ptr);
+ if (context->input_collection_changed_callback) {
+ vector<AudioObjectID> devices =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->input_device_array != devices) {
+ context->input_device_array = devices;
+ context->input_collection_changed_callback(
+ context, context->input_collection_changed_user_ptr);
+ }
+ }
+ if (context->output_collection_changed_callback) {
+ vector<AudioObjectID> devices =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->output_device_array != devices) {
+ context->output_device_array = devices;
+ context->output_collection_changed_callback(
+ context, context->output_collection_changed_user_ptr);
+ }
+ }
+ });
return noErr;
}
static OSStatus
-audiounit_add_device_listener(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback collection_changed_callback,
- void * user_ptr)
+audiounit_add_device_listener(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
{
+ context->mutex.assert_current_thread_owns();
+ assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
/* Note: second register without unregister first causes 'nope' error.
* Current implementation requires unregister before register a new cb. */
- assert(context->collection_changed_callback == NULL);
-
- AudioObjectPropertyAddress devAddr;
- devAddr.mSelector = kAudioHardwarePropertyDevices;
- devAddr.mScope = kAudioObjectPropertyScopeGlobal;
- devAddr.mElement = kAudioObjectPropertyElementMaster;
-
- OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
- &devAddr,
- audiounit_collection_changed_callback,
- context);
- if (ret == noErr) {
- /* Expected zero after unregister. */
- assert(context->devtype_device_count == 0);
- assert(context->devtype_device_array == NULL);
- /* Listener works for input and output.
- * When requested one of them we need to differentiate. */
- if (devtype == CUBEB_DEVICE_TYPE_INPUT ||
- devtype == CUBEB_DEVICE_TYPE_OUTPUT) {
- /* Used to differentiate input from output device changes. */
- context->devtype_device_count = audiounit_get_devices_of_type(devtype, &context->devtype_device_array);
- }
- context->collection_changed_devtype = devtype;
- context->collection_changed_callback = collection_changed_callback;
- context->collection_changed_user_ptr = user_ptr;
+ assert((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
+ !context->input_collection_changed_callback ||
+ (devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
+ !context->output_collection_changed_callback);
+
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ OSStatus ret = AudioObjectAddPropertyListener(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback, context);
+ if (ret != noErr) {
+ return ret;
+ }
}
- return ret;
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ /* Expected empty after unregister. */
+ assert(context->input_device_array.empty());
+ context->input_device_array =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ /* Expected empty after unregister. */
+ assert(context->output_device_array.empty());
+ context->output_device_array =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+ return noErr;
}
static OSStatus
-audiounit_remove_device_listener(cubeb * context)
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype)
{
- AudioObjectPropertyAddress devAddr;
- devAddr.mSelector = kAudioHardwarePropertyDevices;
- devAddr.mScope = kAudioObjectPropertyScopeGlobal;
- devAddr.mElement = kAudioObjectPropertyElementMaster;
+ context->mutex.assert_current_thread_owns();
- /* Note: unregister a non registered cb is not a problem, not checking. */
- OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
- &devAddr,
- audiounit_collection_changed_callback,
- context);
- if (ret == noErr) {
- /* Reset all values. */
- context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
- context->collection_changed_callback = NULL;
- context->collection_changed_user_ptr = NULL;
- context->devtype_device_count = 0;
- if (context->devtype_device_array) {
- delete [] context->devtype_device_array;
- context->devtype_device_array = NULL;
- }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ context->input_device_array.clear();
}
- return ret;
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ context->output_device_array.clear();
+ }
+
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ return noErr;
+ }
+ /* Note: unregister a non registered cb is not a problem, not checking. */
+ return AudioObjectRemovePropertyListener(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback, context);
}
-int audiounit_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback collection_changed_callback,
- void * user_ptr)
+int
+audiounit_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
{
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
OSStatus ret;
auto_lock lock(context->mutex);
if (collection_changed_callback) {
ret = audiounit_add_device_listener(context, devtype,
- collection_changed_callback,
- user_ptr);
+ collection_changed_callback, user_ptr);
} else {
- ret = audiounit_remove_device_listener(context);
+ ret = audiounit_remove_device_listener(context, devtype);
}
return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR;
}
cubeb_ops const audiounit_ops = {
- /*.init =*/ audiounit_init,
- /*.get_backend_id =*/ audiounit_get_backend_id,
- /*.get_max_channel_count =*/ audiounit_get_max_channel_count,
- /*.get_min_latency =*/ audiounit_get_min_latency,
- /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate,
- /*.enumerate_devices =*/ audiounit_enumerate_devices,
- /*.destroy =*/ audiounit_destroy,
- /*.stream_init =*/ audiounit_stream_init,
- /*.stream_destroy =*/ audiounit_stream_destroy,
- /*.stream_start =*/ audiounit_stream_start,
- /*.stream_stop =*/ audiounit_stream_stop,
- /*.stream_get_position =*/ audiounit_stream_get_position,
- /*.stream_get_latency =*/ audiounit_stream_get_latency,
- /*.stream_set_volume =*/ audiounit_stream_set_volume,
- /*.stream_set_panning =*/ audiounit_stream_set_panning,
- /*.stream_get_current_device =*/ audiounit_stream_get_current_device,
- /*.stream_device_destroy =*/ audiounit_stream_device_destroy,
- /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback,
- /*.register_device_collection_changed =*/ audiounit_register_device_collection_changed
-};
+ /*.init =*/audiounit_init,
+ /*.get_backend_id =*/audiounit_get_backend_id,
+ /*.get_max_channel_count =*/audiounit_get_max_channel_count,
+ /*.get_min_latency =*/audiounit_get_min_latency,
+ /*.get_preferred_sample_rate =*/audiounit_get_preferred_sample_rate,
+ /*.enumerate_devices =*/audiounit_enumerate_devices,
+ /*.device_collection_destroy =*/audiounit_device_collection_destroy,
+ /*.destroy =*/audiounit_destroy,
+ /*.stream_init =*/audiounit_stream_init,
+ /*.stream_destroy =*/audiounit_stream_destroy,
+ /*.stream_start =*/audiounit_stream_start,
+ /*.stream_stop =*/audiounit_stream_stop,
+ /*.stream_get_position =*/audiounit_stream_get_position,
+ /*.stream_get_latency =*/audiounit_stream_get_latency,
+ /*.stream_get_input_latency =*/NULL,
+ /*.stream_set_volume =*/audiounit_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/audiounit_stream_get_current_device,
+ /*.stream_device_destroy =*/audiounit_stream_device_destroy,
+ /*.stream_register_device_changed_callback =*/
+ audiounit_stream_register_device_changed_callback,
+ /*.register_device_collection_changed =*/
+ audiounit_register_device_collection_changed};
diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp
index 8f995da661..9dc5199e71 100644
--- a/media/libcubeb/src/cubeb_jack.cpp
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -8,53 +8,53 @@
*/
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
+#ifndef __FreeBSD__
#define _POSIX_SOURCE
-#include <algorithm>
+#endif
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
#include <dlfcn.h>
-#include <limits>
-#include <stdio.h>
-#include <sys/time.h>
-#include <assert.h>
-#include <string.h>
#include <limits.h>
-#include <poll.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <pthread.h>
#include <math.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include "cubeb_resampler.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <jack/jack.h>
#include <jack/statistics.h>
-#define JACK_API_VISIT(X) \
- X(jack_activate) \
- X(jack_client_close) \
- X(jack_client_open) \
- X(jack_connect) \
- X(jack_free) \
- X(jack_get_ports) \
- X(jack_get_sample_rate) \
- X(jack_get_xrun_delayed_usecs) \
- X(jack_get_buffer_size) \
- X(jack_port_get_buffer) \
- X(jack_port_name) \
- X(jack_port_register) \
- X(jack_port_unregister) \
- X(jack_port_get_latency_range) \
- X(jack_set_process_callback) \
- X(jack_set_xrun_callback) \
- X(jack_set_graph_order_callback) \
- X(jack_set_error_function) \
+#define JACK_API_VISIT(X) \
+ X(jack_activate) \
+ X(jack_client_close) \
+ X(jack_client_open) \
+ X(jack_connect) \
+ X(jack_free) \
+ X(jack_get_ports) \
+ X(jack_get_sample_rate) \
+ X(jack_get_xrun_delayed_usecs) \
+ X(jack_get_buffer_size) \
+ X(jack_port_get_buffer) \
+ X(jack_port_name) \
+ X(jack_port_register) \
+ X(jack_port_unregister) \
+ X(jack_port_get_latency_range) \
+ X(jack_set_process_callback) \
+ X(jack_set_xrun_callback) \
+ X(jack_set_graph_order_callback) \
+ X(jack_set_error_function) \
X(jack_set_info_function)
#define IMPORT_FUNC(x) static decltype(x) * api_##x;
JACK_API_VISIT(IMPORT_FUNC);
+#define JACK_DEFAULT_IN "JACK capture"
+#define JACK_DEFAULT_OUT "JACK playback"
+
static const int MAX_STREAMS = 16;
-static const int MAX_CHANNELS = 8;
+static const int MAX_CHANNELS = 8;
static const int FIFO_SIZE = 4096 * sizeof(float);
enum devstream {
@@ -64,6 +64,12 @@ enum devstream {
DUPLEX,
};
+enum cbjack_connect_ports_options {
+ CBJACK_CP_OPTIONS_NONE = 0x0,
+ CBJACK_CP_OPTIONS_SKIP_OUTPUT = 0x1,
+ CBJACK_CP_OPTIONS_SKIP_INPUT = 0x2,
+};
+
static void
s16ne_to_float(float * dst, const int16_t * src, size_t n)
{
@@ -75,79 +81,110 @@ static void
float_to_s16ne(int16_t * dst, float * src, size_t n)
{
for (size_t i = 0; i < n; i++) {
- if (*src > 1.f) *src = 1.f;
- if (*src < -1.f) *src = -1.f;
+ if (*src > 1.f)
+ *src = 1.f;
+ if (*src < -1.f)
+ *src = -1.f;
*(dst++) = (int16_t)((int16_t)(*(src++) * 32767));
}
}
-extern "C"
-{
-/*static*/ int jack_init (cubeb ** context, char const * context_name);
+extern "C" {
+/*static*/ int
+jack_init(cubeb ** context, char const * context_name);
}
-static char const * cbjack_get_backend_id(cubeb * context);
-static int cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
-static int cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames);
-static int cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
-static int cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
-static void cbjack_destroy(cubeb * context);
-static void cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch);
-static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short **bufs_in, float **bufs_out, jack_nframes_t nframes);
-static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float **bufs_in, float **bufs_out, jack_nframes_t nframes);
-static int cbjack_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * device);
-static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device);
-static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection);
-static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr);
-static void cbjack_stream_destroy(cubeb_stream * stream);
-static int cbjack_stream_start(cubeb_stream * stream);
-static int cbjack_stream_stop(cubeb_stream * stream);
-static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
-static int cbjack_stream_set_volume(cubeb_stream * stm, float volume);
+static char const *
+cbjack_get_backend_id(cubeb * context);
+static int
+cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
+static int
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames);
+static int
+cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
+static int
+cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
+static void
+cbjack_destroy(cubeb * context);
+static void
+cbjack_interleave_capture(cubeb_stream * stream, float ** in,
+ jack_nframes_t nframes, bool format_mismatch);
+static void
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream,
+ short ** bufs_in, float ** bufs_out,
+ jack_nframes_t nframes);
+static void
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream,
+ float ** bufs_in, float ** bufs_out,
+ jack_nframes_t nframes);
+static int
+cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device);
+static int
+cbjack_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device);
+static int
+cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection);
+static int
+cbjack_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
+static int
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+static void
+cbjack_stream_destroy(cubeb_stream * stream);
+static int
+cbjack_stream_start(cubeb_stream * stream);
+static int
+cbjack_stream_stop(cubeb_stream * stream);
+static int
+cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
+static int
+cbjack_stream_set_volume(cubeb_stream * stm, float volume);
static struct cubeb_ops const cbjack_ops = {
- .init = jack_init,
- .get_backend_id = cbjack_get_backend_id,
- .get_max_channel_count = cbjack_get_max_channel_count,
- .get_min_latency = cbjack_get_min_latency,
- .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
- .enumerate_devices = cbjack_enumerate_devices,
- .destroy = cbjack_destroy,
- .stream_init = cbjack_stream_init,
- .stream_destroy = cbjack_stream_destroy,
- .stream_start = cbjack_stream_start,
- .stream_stop = cbjack_stream_stop,
- .stream_get_position = cbjack_stream_get_position,
- .stream_get_latency = cbjack_get_latency,
- .stream_set_volume = cbjack_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = cbjack_stream_get_current_device,
- .stream_device_destroy = cbjack_stream_device_destroy,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = jack_init,
+ .get_backend_id = cbjack_get_backend_id,
+ .get_max_channel_count = cbjack_get_max_channel_count,
+ .get_min_latency = cbjack_get_min_latency,
+ .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+ .enumerate_devices = cbjack_enumerate_devices,
+ .device_collection_destroy = cbjack_device_collection_destroy,
+ .destroy = cbjack_destroy,
+ .stream_init = cbjack_stream_init,
+ .stream_destroy = cbjack_stream_destroy,
+ .stream_start = cbjack_stream_start,
+ .stream_stop = cbjack_stream_stop,
+ .stream_get_position = cbjack_stream_get_position,
+ .stream_get_latency = cbjack_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = cbjack_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = cbjack_stream_get_current_device,
+ .stream_device_destroy = cbjack_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
+ void * user_ptr;
+ /**/
/**< Mutex for each stream */
pthread_mutex_t mutex;
- bool in_use; /**< Set to false iff the stream is free */
+ bool in_use; /**< Set to false iff the stream is free */
bool ports_ready; /**< Set to true iff the JACK ports are ready */
cubeb_data_callback data_callback;
cubeb_state_callback state_callback;
- void * user_ptr;
cubeb_stream_params in_params;
cubeb_stream_params out_params;
@@ -183,7 +220,6 @@ struct cubeb {
cubeb_stream streams[MAX_STREAMS];
unsigned int active_streams;
- cubeb_device_info * devinfo[2];
cubeb_device_collection_changed_callback collection_changed_callback;
bool active;
@@ -203,25 +239,28 @@ load_jack_lib(cubeb * context)
context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY);
context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY);
#elif defined(__WIN32__)
-# ifdef _WIN64
- context->libjack = LoadLibrary("libjack64.dll");
-# else
- context->libjack = LoadLibrary("libjack.dll");
-# endif
+#ifdef _WIN64
+ context->libjack = LoadLibrary("libjack64.dll");
+#else
+ context->libjack = LoadLibrary("libjack.dll");
+#endif
#else
context->libjack = dlopen("libjack.so.0", RTLD_LAZY);
+ if (!context->libjack) {
+ context->libjack = dlopen("libjack.so", RTLD_LAZY);
+ }
#endif
if (!context->libjack) {
return CUBEB_ERROR;
}
-#define LOAD(x) \
- { \
- api_##x = (decltype(x)*)dlsym(context->libjack, #x); \
- if (!api_##x) { \
- dlclose(context->libjack); \
- return CUBEB_ERROR; \
- } \
+#define LOAD(x) \
+ { \
+ api_##x = (decltype(x) *)dlsym(context->libjack, #x); \
+ if (!api_##x) { \
+ dlclose(context->libjack); \
+ return CUBEB_ERROR; \
+ } \
}
JACK_API_VISIT(LOAD);
@@ -231,41 +270,72 @@ load_jack_lib(cubeb * context)
}
static void
-cbjack_connect_ports (cubeb_stream * stream)
+cbjack_connect_port_out(cubeb_stream * stream, const size_t out_port,
+ const char * const phys_in_port)
+{
+ const char * src_port = api_jack_port_name(stream->output_ports[out_port]);
+
+ api_jack_connect(stream->context->jack_client, src_port, phys_in_port);
+}
+
+static void
+cbjack_connect_port_in(cubeb_stream * stream, const char * const phys_out_port,
+ size_t in_port)
{
- const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client,
- NULL, NULL,
- JackPortIsInput
- | JackPortIsPhysical);
- const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client,
- NULL, NULL,
- JackPortIsOutput
- | JackPortIsPhysical);
-
- if (*phys_in_ports == NULL) {
+ const char * src_port = api_jack_port_name(stream->input_ports[in_port]);
+
+ api_jack_connect(stream->context->jack_client, phys_out_port, src_port);
+}
+
+static int
+cbjack_connect_ports(cubeb_stream * stream,
+ enum cbjack_connect_ports_options options)
+{
+ int r = CUBEB_ERROR;
+ const char ** phys_in_ports =
+ api_jack_get_ports(stream->context->jack_client, NULL, NULL,
+ JackPortIsInput | JackPortIsPhysical);
+ const char ** phys_out_ports =
+ api_jack_get_ports(stream->context->jack_client, NULL, NULL,
+ JackPortIsOutput | JackPortIsPhysical);
+
+ if (phys_in_ports == NULL || *phys_in_ports == NULL ||
+ options & CBJACK_CP_OPTIONS_SKIP_OUTPUT) {
goto skipplayback;
}
// Connect outputs to playback
- for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
- const char *src_port = api_jack_port_name (stream->output_ports[c]);
+ for (unsigned int c = 0;
+ c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
+ cbjack_connect_port_out(stream, c, phys_in_ports[c]);
+ }
- api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]);
+ // Special case playing mono source in stereo
+ if (stream->out_params.channels == 1 && phys_in_ports[1] != NULL) {
+ cbjack_connect_port_out(stream, 0, phys_in_ports[1]);
}
+ r = CUBEB_OK;
+
skipplayback:
- if (*phys_out_ports == NULL) {
+ if (phys_out_ports == NULL || *phys_out_ports == NULL ||
+ options & CBJACK_CP_OPTIONS_SKIP_INPUT) {
goto end;
}
// Connect inputs to capture
- for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
- const char *src_port = api_jack_port_name (stream->input_ports[c]);
-
- api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port);
+ for (unsigned int c = 0;
+ c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
+ cbjack_connect_port_in(stream, phys_out_ports[c], c);
}
+ r = CUBEB_OK;
end:
- api_jack_free(phys_out_ports);
- api_jack_free(phys_in_ports);
+ if (phys_out_ports) {
+ api_jack_free(phys_out_ports);
+ }
+ if (phys_in_ports) {
+ api_jack_free(phys_in_ports);
+ }
+ return r;
}
static int
@@ -274,9 +344,10 @@ cbjack_xrun_callback(void * arg)
cubeb * ctx = (cubeb *)arg;
float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client);
- int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate )
- / (float)(ctx->jack_buffer_size) );
- ctx->jack_xruns += fragments;
+ float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) /
+ ctx->jack_buffer_size);
+
+ ctx->jack_xruns += (unsigned int)fragments;
return 0;
}
@@ -289,7 +360,7 @@ cbjack_graph_order_callback(void * arg)
jack_nframes_t port_latency, max_latency = 0;
for (int j = 0; j < MAX_STREAMS; j++) {
- cubeb_stream *stm = &ctx->streams[j];
+ cubeb_stream * stm = &ctx->streams[j];
if (!stm->in_use)
continue;
@@ -297,10 +368,11 @@ cbjack_graph_order_callback(void * arg)
continue;
for (i = 0; i < (int)stm->out_params.channels; ++i) {
- api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency, &latency_range);
+ api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency,
+ &latency_range);
port_latency = latency_range.max;
if (port_latency > max_latency)
- max_latency = port_latency;
+ max_latency = port_latency;
}
/* Cap minimum latency to 128 frames */
if (max_latency < 128)
@@ -316,22 +388,21 @@ static int
cbjack_process(jack_nframes_t nframes, void * arg)
{
cubeb * ctx = (cubeb *)arg;
- int t_jack_xruns = ctx->jack_xruns;
+ unsigned int t_jack_xruns = ctx->jack_xruns;
int i;
+ ctx->jack_xruns = 0;
+
for (int j = 0; j < MAX_STREAMS; j++) {
- cubeb_stream *stm = &ctx->streams[j];
- float *bufs_out[stm->out_params.channels];
- float *bufs_in[stm->in_params.channels];
+ cubeb_stream * stm = &ctx->streams[j];
+ float * bufs_out[stm->out_params.channels];
+ float * bufs_in[stm->in_params.channels];
if (!stm->in_use)
continue;
// handle xruns by skipping audio that should have been played
- for (i = 0; i < t_jack_xruns; i++) {
- stm->position += ctx->fragment_size * stm->ratio;
- }
- ctx->jack_xruns -= t_jack_xruns;
+ stm->position += t_jack_xruns * ctx->fragment_size * stm->ratio;
if (!stm->ports_ready)
continue;
@@ -339,18 +410,20 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & OUT_ONLY) {
// get jack output buffers
for (i = 0; i < (int)stm->out_params.channels; i++)
- bufs_out[i] = (float*)api_jack_port_get_buffer(stm->output_ports[i], nframes);
+ bufs_out[i] =
+ (float *)api_jack_port_get_buffer(stm->output_ports[i], nframes);
}
if (stm->devs & IN_ONLY) {
// get jack input buffers
for (i = 0; i < (int)stm->in_params.channels; i++)
- bufs_in[i] = (float*)api_jack_port_get_buffer(stm->input_ports[i], nframes);
+ bufs_in[i] =
+ (float *)api_jack_port_get_buffer(stm->input_ports[i], nframes);
}
if (stm->pause) {
// paused, play silence on output
if (stm->devs & OUT_ONLY) {
for (unsigned int c = 0; c < stm->out_params.channels; c++) {
- float* buffer_out = bufs_out[c];
+ float * buffer_out = bufs_out[c];
for (long f = 0; f < nframes; f++) {
buffer_out[f] = 0.f;
}
@@ -359,7 +432,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & IN_ONLY) {
// paused, capture silence
for (unsigned int c = 0; c < stm->in_params.channels; c++) {
- float* buffer_in = bufs_in[c];
+ float * buffer_in = bufs_in[c];
for (long f = 0; f < nframes; f++) {
buffer_in[f] = 0.f;
}
@@ -370,31 +443,38 @@ cbjack_process(jack_nframes_t nframes, void * arg)
// try to lock stream mutex
if (pthread_mutex_trylock(&stm->mutex) == 0) {
- int16_t *in_s16ne = stm->context->in_resampled_interleaved_buffer_s16ne;
- float *in_float = stm->context->in_resampled_interleaved_buffer_float;
+ int16_t * in_s16ne =
+ stm->context->in_resampled_interleaved_buffer_s16ne;
+ float * in_float = stm->context->in_resampled_interleaved_buffer_float;
// unpaused, play audio
if (stm->devs == DUPLEX) {
if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, true);
- cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out,
+ nframes);
} else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, false);
- cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out,
+ nframes);
}
} else if (stm->devs == IN_ONLY) {
if (stm->in_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, true);
- cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr, nframes);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr,
+ nframes);
} else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, false);
- cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr, nframes);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr,
+ nframes);
}
} else if (stm->devs == OUT_ONLY) {
if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
- cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out,
+ nframes);
} else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
- cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out,
+ nframes);
}
}
// unlock stream mutex
@@ -405,7 +485,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
// output silence
if (stm->devs & OUT_ONLY) {
for (unsigned int c = 0; c < stm->out_params.channels; c++) {
- float* buffer_out = bufs_out[c];
+ float * buffer_out = bufs_out[c];
for (long f = 0; f < nframes; f++) {
buffer_out[f] = 0.f;
}
@@ -414,7 +494,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & IN_ONLY) {
// capture silence
for (unsigned int c = 0; c < stm->in_params.channels; c++) {
- float* buffer_in = bufs_in[c];
+ float * buffer_in = bufs_in[c];
for (long f = 0; f < nframes; f++) {
buffer_in[f] = 0.f;
}
@@ -426,9 +506,10 @@ cbjack_process(jack_nframes_t nframes, void * arg)
return 0;
}
-
static void
-cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes)
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in,
+ float ** bufs_out,
+ jack_nframes_t nframes)
{
float * out_interleaved_buffer = nullptr;
@@ -439,21 +520,24 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl
long done_frames = 0;
long input_frames_count = (in != NULL) ? nframes : 0;
+ done_frames = cubeb_resampler_fill(
+ stream->resampler, inptr, &input_frames_count,
+ (bufs_out != NULL)
+ ? stream->context->out_resampled_interleaved_buffer_float
+ : NULL,
+ needed_frames);
- done_frames = cubeb_resampler_fill(stream->resampler,
- inptr,
- &input_frames_count,
- (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL,
- needed_frames);
-
- out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
+ out_interleaved_buffer =
+ stream->context->out_resampled_interleaved_buffer_float;
if (outptr) {
// convert interleaved output buffers to contiguous buffers
for (unsigned int c = 0; c < stream->out_params.channels; c++) {
- float* buffer = bufs_out[c];
+ float * buffer = bufs_out[c];
for (long f = 0; f < done_frames; f++) {
- buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ buffer[f] =
+ out_interleaved_buffer[(f * stream->out_params.channels) + c] *
+ stream->volume;
}
if (done_frames < needed_frames) {
// draining
@@ -487,7 +571,9 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl
}
static void
-cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes)
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in,
+ float ** bufs_out,
+ jack_nframes_t nframes)
{
float * out_interleaved_buffer = nullptr;
@@ -498,22 +584,28 @@ cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, fl
long done_frames = 0;
long input_frames_count = (in != NULL) ? nframes : 0;
- done_frames = cubeb_resampler_fill(stream->resampler,
- inptr,
- &input_frames_count,
- (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL,
- needed_frames);
+ done_frames = cubeb_resampler_fill(
+ stream->resampler, inptr, &input_frames_count,
+ (bufs_out != NULL)
+ ? stream->context->out_resampled_interleaved_buffer_s16ne
+ : NULL,
+ needed_frames);
- s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels);
+ s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float,
+ stream->context->out_resampled_interleaved_buffer_s16ne,
+ done_frames * stream->out_params.channels);
- out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
+ out_interleaved_buffer =
+ stream->context->out_resampled_interleaved_buffer_float;
if (outptr) {
// convert interleaved output buffers to contiguous buffers
for (unsigned int c = 0; c < stream->out_params.channels; c++) {
- float* buffer = bufs_out[c];
+ float * buffer = bufs_out[c];
for (long f = 0; f < done_frames; f++) {
- buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ buffer[f] =
+ out_interleaved_buffer[(f * stream->out_params.channels) + c] *
+ stream->volume;
}
if (done_frames < needed_frames) {
// draining
@@ -547,20 +639,25 @@ cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, fl
}
static void
-cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch)
+cbjack_interleave_capture(cubeb_stream * stream, float ** in,
+ jack_nframes_t nframes, bool format_mismatch)
{
- float *in_buffer = stream->context->in_float_interleaved_buffer;
+ float * in_buffer = stream->context->in_float_interleaved_buffer;
for (unsigned int c = 0; c < stream->in_params.channels; c++) {
for (long f = 0; f < nframes; f++) {
- in_buffer[(f * stream->in_params.channels) + c] = in[c][f] * stream->volume;
+ in_buffer[(f * stream->in_params.channels) + c] =
+ in[c][f] * stream->volume;
}
}
if (format_mismatch) {
- float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne, in_buffer, nframes * stream->in_params.channels);
+ float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne,
+ in_buffer, nframes * stream->in_params.channels);
} else {
- memset(stream->context->in_resampled_interleaved_buffer_float, 0, (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
- memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer, (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
+ memset(stream->context->in_resampled_interleaved_buffer_float, 0,
+ (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
+ memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer,
+ (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
}
}
@@ -570,7 +667,7 @@ silent_jack_error_callback(char const * /*msg*/)
}
/*static*/ int
-jack_init (cubeb ** context, char const * context_name)
+jack_init(cubeb ** context, char const * context_name)
{
int r;
@@ -601,9 +698,8 @@ jack_init (cubeb ** context, char const * context_name)
if (context_name)
jack_client_name = context_name;
- ctx->jack_client = api_jack_client_open(jack_client_name,
- JackNoStartServer,
- NULL);
+ ctx->jack_client =
+ api_jack_client_open(jack_client_name, JackNoStartServer, NULL);
if (ctx->jack_client == NULL) {
cbjack_destroy(ctx);
@@ -612,11 +708,12 @@ jack_init (cubeb ** context, char const * context_name)
ctx->jack_xruns = 0;
- api_jack_set_process_callback (ctx->jack_client, cbjack_process, ctx);
- api_jack_set_xrun_callback (ctx->jack_client, cbjack_xrun_callback, ctx);
- api_jack_set_graph_order_callback (ctx->jack_client, cbjack_graph_order_callback, ctx);
+ api_jack_set_process_callback(ctx->jack_client, cbjack_process, ctx);
+ api_jack_set_xrun_callback(ctx->jack_client, cbjack_xrun_callback, ctx);
+ api_jack_set_graph_order_callback(ctx->jack_client,
+ cbjack_graph_order_callback, ctx);
- if (api_jack_activate (ctx->jack_client)) {
+ if (api_jack_activate(ctx->jack_client)) {
cbjack_destroy(ctx);
return CUBEB_ERROR;
}
@@ -651,7 +748,8 @@ cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms)
}
static int
-cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/, uint32_t * latency_ms)
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/,
+ uint32_t * latency_ms)
{
*latency_ms = ctx->jack_latency;
return CUBEB_OK;
@@ -661,9 +759,8 @@ static int
cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
if (!ctx->jack_client) {
- jack_client_t * testclient = api_jack_client_open("test-samplerate",
- JackNoStartServer,
- NULL);
+ jack_client_t * testclient =
+ api_jack_client_open("test-samplerate", JackNoStartServer, NULL);
if (!testclient) {
return CUBEB_ERROR;
}
@@ -683,7 +780,7 @@ cbjack_destroy(cubeb * context)
context->active = false;
if (context->jack_client != NULL)
- api_jack_client_close (context->jack_client);
+ api_jack_client_close(context->jack_client);
if (context->libjack)
dlclose(context->libjack);
@@ -706,35 +803,42 @@ context_alloc_stream(cubeb * context, char const * stream_name)
}
static int
-cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int /*latency_frames*/,
cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
+ cubeb_state_callback state_callback, void * user_ptr)
{
int stream_actual_rate = 0;
int jack_rate = api_jack_get_sample_rate(context->jack_client);
- if (output_stream_params
- && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
- output_stream_params->format != CUBEB_SAMPLE_S16NE)
- ) {
+ if (output_stream_params &&
+ (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ output_stream_params->format != CUBEB_SAMPLE_S16NE)) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- if (input_stream_params
- && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
- input_stream_params->format != CUBEB_SAMPLE_S16NE)
- ) {
+ if (input_stream_params &&
+ (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ input_stream_params->format != CUBEB_SAMPLE_S16NE)) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- if (input_device || output_device)
+ if ((input_device && input_device != JACK_DEFAULT_IN) ||
+ (output_device && output_device != JACK_DEFAULT_OUT)) {
return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ // Loopback is unsupported
+ if ((input_stream_params &&
+ (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
+ (output_stream_params &&
+ (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
*stream = NULL;
@@ -812,29 +916,17 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
stm->resampler = NULL;
if (stm->devs == DUPLEX) {
- stm->resampler = cubeb_resampler_create(stm,
- &stm->in_params,
- &stm->out_params,
- stream_actual_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler = cubeb_resampler_create(
+ stm, &stm->in_params, &stm->out_params, stream_actual_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
} else if (stm->devs == IN_ONLY) {
- stm->resampler = cubeb_resampler_create(stm,
- &stm->in_params,
- nullptr,
- stream_actual_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler = cubeb_resampler_create(
+ stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback,
+ stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
} else if (stm->devs == OUT_ONLY) {
- stm->resampler = cubeb_resampler_create(stm,
- nullptr,
- &stm->out_params,
- stream_actual_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler = cubeb_resampler_create(
+ stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback,
+ stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
}
if (!stm->resampler) {
@@ -847,11 +939,18 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
for (unsigned int c = 0; c < stm->out_params.channels; c++) {
char portname[256];
snprintf(portname, 255, "%s_out_%d", stm->stream_name, c);
- stm->output_ports[c] = api_jack_port_register(stm->context->jack_client,
- portname,
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput,
- 0);
+ stm->output_ports[c] =
+ api_jack_port_register(stm->context->jack_client, portname,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!(output_stream_params->prefs &
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
+ if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_INPUT) !=
+ CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
}
}
@@ -859,16 +958,21 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
for (unsigned int c = 0; c < stm->in_params.channels; c++) {
char portname[256];
snprintf(portname, 255, "%s_in_%d", stm->stream_name, c);
- stm->input_ports[c] = api_jack_port_register(stm->context->jack_client,
- portname,
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsInput,
- 0);
+ stm->input_ports[c] =
+ api_jack_port_register(stm->context->jack_client, portname,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+ if (!(input_stream_params->prefs &
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
+ if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_OUTPUT) !=
+ CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
}
}
- cbjack_connect_ports(stm);
-
*stream = stm;
stm->ports_ready = true;
@@ -887,7 +991,8 @@ cbjack_stream_destroy(cubeb_stream * stream)
if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) {
for (unsigned int c = 0; c < stream->out_params.channels; c++) {
if (stream->output_ports[c]) {
- api_jack_port_unregister (stream->context->jack_client, stream->output_ports[c]);
+ api_jack_port_unregister(stream->context->jack_client,
+ stream->output_ports[c]);
stream->output_ports[c] = NULL;
}
}
@@ -896,7 +1001,8 @@ cbjack_stream_destroy(cubeb_stream * stream)
if (stream->devs == DUPLEX || stream->devs == IN_ONLY) {
for (unsigned int c = 0; c < stream->in_params.channels; c++) {
if (stream->input_ports[c]) {
- api_jack_port_unregister (stream->context->jack_client, stream->input_ports[c]);
+ api_jack_port_unregister(stream->context->jack_client,
+ stream->input_ports[c]);
stream->input_ports[c] = NULL;
}
}
@@ -940,16 +1046,16 @@ cbjack_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK;
}
-
static int
-cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
+cbjack_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
{
*device = (cubeb_device *)calloc(1, sizeof(cubeb_device));
if (*device == NULL)
return CUBEB_ERROR;
- const char * j_in = "JACK capture";
- const char * j_out = "JACK playback";
+ const char * j_in = JACK_DEFAULT_IN;
+ const char * j_out = JACK_DEFAULT_OUT;
const char * empty = "";
if (stm->devs == DUPLEX) {
@@ -967,8 +1073,7 @@ cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const devic
}
static int
-cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
- cubeb_device * device)
+cbjack_stream_device_destroy(cubeb_stream * /*stream*/, cubeb_device * device)
{
if (device->input_name)
free(device->input_name);
@@ -980,68 +1085,72 @@ cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
static int
cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
+ cubeb_device_collection * collection)
{
if (!context)
return CUBEB_ERROR;
uint32_t rate;
- uint8_t i = 0;
- uint8_t j;
cbjack_get_preferred_sample_rate(context, &rate);
- const char * j_in = "JACK capture";
- const char * j_out = "JACK playback";
+
+ cubeb_device_info * devices = new cubeb_device_info[2];
+ if (!devices)
+ return CUBEB_ERROR;
+ PodZero(devices, 2);
+ collection->count = 0;
if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info));
- context->devinfo[i]->device_id = strdup(j_out);
- context->devinfo[i]->devid = context->devinfo[i]->device_id;
- context->devinfo[i]->friendly_name = strdup(j_out);
- context->devinfo[i]->group_id = strdup(j_out);
- context->devinfo[i]->vendor_name = strdup(j_out);
- context->devinfo[i]->type = CUBEB_DEVICE_TYPE_OUTPUT;
- context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED;
- context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL;
- context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->max_channels = MAX_CHANNELS;
- context->devinfo[i]->min_rate = rate;
- context->devinfo[i]->max_rate = rate;
- context->devinfo[i]->default_rate = rate;
- context->devinfo[i]->latency_lo = 0;
- context->devinfo[i]->latency_hi = 0;
- i++;
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_OUT;
+ cur->devid = (cubeb_devid)cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_OUT;
+ cur->group_id = JACK_DEFAULT_OUT;
+ cur->vendor_name = JACK_DEFAULT_OUT;
+ cur->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
}
if (type & CUBEB_DEVICE_TYPE_INPUT) {
- context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info));
- context->devinfo[i]->device_id = strdup(j_in);
- context->devinfo[i]->devid = context->devinfo[i]->device_id;
- context->devinfo[i]->friendly_name = strdup(j_in);
- context->devinfo[i]->group_id = strdup(j_in);
- context->devinfo[i]->vendor_name = strdup(j_in);
- context->devinfo[i]->type = CUBEB_DEVICE_TYPE_INPUT;
- context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED;
- context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL;
- context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->max_channels = MAX_CHANNELS;
- context->devinfo[i]->min_rate = rate;
- context->devinfo[i]->max_rate = rate;
- context->devinfo[i]->default_rate = rate;
- context->devinfo[i]->latency_lo = 0;
- context->devinfo[i]->latency_hi = 0;
- i++;
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_IN;
+ cur->devid = (cubeb_devid)cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_IN;
+ cur->group_id = JACK_DEFAULT_IN;
+ cur->vendor_name = JACK_DEFAULT_IN;
+ cur->type = CUBEB_DEVICE_TYPE_INPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
}
- *collection = (cubeb_device_collection *)
- malloc(sizeof(cubeb_device_collection) +
- i * sizeof(cubeb_device_info *));
+ collection->device = devices;
- (*collection)->count = i;
+ return CUBEB_OK;
+}
- for (j = 0; j < i; j++) {
- (*collection)->device[j] = context->devinfo[j];
- }
+static int
+cbjack_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+ delete[] collection->device;
return CUBEB_OK;
}
diff --git a/media/libcubeb/src/cubeb_log.cpp b/media/libcubeb/src/cubeb_log.cpp
new file mode 100644
index 0000000000..ff72e0e87a
--- /dev/null
+++ b/media/libcubeb/src/cubeb_log.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define NOMINMAX
+
+#include "cubeb_log.h"
+#include "cubeb_ringbuffer.h"
+#include <cstdarg>
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <time.h>
+#endif
+
+cubeb_log_level g_cubeb_log_level;
+cubeb_log_callback g_cubeb_log_callback;
+
+/** The maximum size of a log message, after having been formatted. */
+const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
+/** The maximum number of log messages that can be queued before dropping
+ * messages. */
+const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
+/** Number of milliseconds to wait before dequeuing log messages. */
+#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
+
+/**
+ * This wraps an inline buffer, that represents a log message, that must be
+ * null-terminated.
+ * This class should not use system calls or other potentially blocking code.
+ */
+class cubeb_log_message {
+public:
+ cubeb_log_message() { *storage = '\0'; }
+ cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ size_t length = strlen(str);
+ /* paranoia against malformed message */
+ assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
+ if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
+ return;
+ }
+ PodCopy(storage, str, length);
+ storage[length] = '\0';
+ }
+ char const * get() { return storage; }
+
+private:
+ char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
+};
+
+/** Lock-free asynchronous logger, made so that logging from a
+ * real-time audio callback does not block the audio thread. */
+class cubeb_async_logger {
+public:
+ /* This is thread-safe since C++11 */
+ static cubeb_async_logger & get()
+ {
+ static cubeb_async_logger instance;
+ return instance;
+ }
+ void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ cubeb_log_message msg(str);
+ msg_queue.enqueue(msg);
+ }
+ void run()
+ {
+ std::thread([this]() {
+ while (true) {
+ cubeb_log_message msg;
+ while (msg_queue.dequeue(&msg, 1)) {
+ LOGV("%s", msg.get());
+ }
+#ifdef _WIN32
+ Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
+#else
+ timespec sleep_duration = sleep_for;
+ timespec remainder;
+ do {
+ if (nanosleep(&sleep_duration, &remainder) == 0 || errno != EINTR) {
+ break;
+ }
+ sleep_duration = remainder;
+ } while (remainder.tv_sec || remainder.tv_nsec);
+#endif
+ }
+ }).detach();
+ }
+ // Tell the underlying queue the producer thread has changed, so it does not
+ // assert in debug. This should be called with the thread stopped.
+ void reset_producer_thread() { msg_queue.reset_thread_ids(); }
+
+private:
+#ifndef _WIN32
+ const struct timespec sleep_for = {
+ CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000,
+ (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000};
+#endif
+ cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); }
+ /** This is quite a big data structure, but is only instantiated if the
+ * asynchronous logger is used.*/
+ lock_free_queue<cubeb_log_message> msg_queue;
+};
+
+void
+cubeb_async_log(char const * fmt, ...)
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ // This is going to copy a 256 bytes array around, which is fine.
+ // We don't want to allocate memory here, because this is made to
+ // be called from a real-time callback.
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ cubeb_async_logger::get().push(msg);
+ va_end(args);
+}
+
+void
+cubeb_async_log_reset_threads()
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ cubeb_async_logger::get().reset_producer_thread();
+}
diff --git a/media/libcubeb/src/cubeb_log.h b/media/libcubeb/src/cubeb_log.h
index bca98c96fe..aee31805e0 100644
--- a/media/libcubeb/src/cubeb_log.h
+++ b/media/libcubeb/src/cubeb_log.h
@@ -8,18 +8,34 @@
#ifndef CUBEB_LOG
#define CUBEB_LOG
+#include "cubeb/cubeb.h"
+
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__GNUC__) || defined(__clang__)
#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
+#if defined(__FILE_NAME__)
+#define __FILENAME__ __FILE_NAME__
+#else
+#define __FILENAME__ \
+ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \
+ : __FILE__)
+#endif
#else
#define PRINTF_FORMAT(fmt, args)
+#include <string.h>
+#define __FILENAME__ \
+ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
-extern cubeb_log_level g_log_level;
-extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2);
+extern cubeb_log_level g_cubeb_log_level;
+extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
+void
+cubeb_async_log(const char * fmt, ...);
+void
+cubeb_async_log_reset_threads();
#ifdef __cplusplus
}
@@ -28,10 +44,19 @@ extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2);
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
-#define LOG_INTERNAL(level, fmt, ...) do { \
- if (g_log_callback && level <= g_log_level) { \
- g_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
- } \
- } while(0)
+#define LOG_INTERNAL(level, fmt, ...) \
+ do { \
+ if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
+ g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \
+ ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+/* Asynchronous verbose logging, to log in real-time callbacks. */
+/* Should not be used on android due to the use of global/static variables. */
+#define ALOGV(fmt, ...) \
+ do { \
+ cubeb_async_log(fmt, ##__VA_ARGS__); \
+ } while (0)
#endif // CUBEB_LOG
diff --git a/media/libcubeb/src/cubeb_mixer.cpp b/media/libcubeb/src/cubeb_mixer.cpp
new file mode 100644
index 0000000000..343e0e2d39
--- /dev/null
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ *
+ * Adapted from code based on libswresample's rematrix.c
+ */
+
+#define NOMINMAX
+
+#include "cubeb_mixer.h"
+#include "cubeb-internal.h"
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <cassert>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <memory>
+#include <type_traits>
+
+#ifndef FF_ARRAY_ELEMS
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#define CHANNELS_MAX 32
+#define FRONT_LEFT 0
+#define FRONT_RIGHT 1
+#define FRONT_CENTER 2
+#define LOW_FREQUENCY 3
+#define BACK_LEFT 4
+#define BACK_RIGHT 5
+#define FRONT_LEFT_OF_CENTER 6
+#define FRONT_RIGHT_OF_CENTER 7
+#define BACK_CENTER 8
+#define SIDE_LEFT 9
+#define SIDE_RIGHT 10
+#define TOP_CENTER 11
+#define TOP_FRONT_LEFT 12
+#define TOP_FRONT_CENTER 13
+#define TOP_FRONT_RIGHT 14
+#define TOP_BACK_LEFT 15
+#define TOP_BACK_CENTER 16
+#define TOP_BACK_RIGHT 17
+#define NUM_NAMED_CHANNELS 18
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
+
+#define C30DB M_SQRT2
+#define C15DB 1.189207115
+#define C__0DB 1.0
+#define C_15DB 0.840896415
+#define C_30DB M_SQRT1_2
+#define C_45DB 0.594603558
+#define C_60DB 0.5
+
+static cubeb_channel_layout
+cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
+{
+ if (l == CUBEB_LAYOUT_UNDEFINED) {
+ switch (c) {
+ case 1:
+ return CUBEB_LAYOUT_MONO;
+ case 2:
+ return CUBEB_LAYOUT_STEREO;
+ }
+ }
+ return l;
+}
+
+unsigned int
+cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
+{
+#if __GNUC__ || __clang__
+ return __builtin_popcount(x);
+#else
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+#endif
+}
+
+struct MixerContext {
+ MixerContext(cubeb_sample_format f, uint32_t in_channels,
+ cubeb_channel_layout in, uint32_t out_channels,
+ cubeb_channel_layout out)
+ : _format(f), _in_ch_layout(cubeb_channel_layout_check(in, in_channels)),
+ _out_ch_layout(cubeb_channel_layout_check(out, out_channels)),
+ _in_ch_count(in_channels), _out_ch_count(out_channels)
+ {
+ if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+ out_channels != cubeb_channel_layout_nb_channels(out)) {
+ // Mismatch between channels and layout, aborting.
+ return;
+ }
+ _valid = init() >= 0;
+ }
+
+ static bool even(cubeb_channel_layout layout)
+ {
+ if (!layout) {
+ return true;
+ }
+ if (layout & (layout - 1)) {
+ return true;
+ }
+ return false;
+ }
+
+ // Ensure that the layout is sane (that is have symmetrical left/right
+ // channels), if not, layout will be treated as mono.
+ static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
+ {
+ if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
+ LOG("Treating layout as mono");
+ return CHANNEL_FRONT_CENTER;
+ }
+
+ return layout;
+ }
+
+ static bool sane_layout(cubeb_channel_layout layout)
+ {
+ if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
+ return false;
+ }
+ if (!even(layout & (CHANNEL_FRONT_LEFT |
+ CHANNEL_FRONT_RIGHT))) { // no asymetric front
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
+ return false;
+ }
+ if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
+ return false;
+ }
+ if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
+ return false;
+ }
+ return true;
+ }
+
+ int auto_matrix();
+ int init();
+
+ const cubeb_sample_format _format;
+ const cubeb_channel_layout _in_ch_layout; ///< input channel layout
+ const cubeb_channel_layout _out_ch_layout; ///< output channel layout
+ const uint32_t _in_ch_count; ///< input channel count
+ const uint32_t _out_ch_count; ///< output channel count
+ const float _surround_mix_level = C_30DB; ///< surround mixing level
+ const float _center_mix_level = C_30DB; ///< center mixing level
+ const float _lfe_mix_level = 1; ///< LFE mixing level
+ double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< floating point rematrixing coefficients
+ float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< single precision floating point rematrixing coefficients
+ int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< 17.15 fixed point rematrixing coefficients
+ uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX + 1] = {
+ {0}}; ///< Lists of input channels per output channel that have non zero
+ ///< rematrixing coefficients
+ bool _clipping = false; ///< Set to true if clipping detection is required
+ bool _valid = false; ///< Set to true if context is valid.
+};
+
+int
+MixerContext::auto_matrix()
+{
+ double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
+ double maxcoef = 0;
+ float maxval;
+
+ cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
+ cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
+
+ if (!sane_layout(in_ch_layout)) {
+ // Channel Not Supported
+ LOG("Input Layout %x is not supported", _in_ch_layout);
+ return -1;
+ }
+
+ if (!sane_layout(out_ch_layout)) {
+ LOG("Output Layout %x is not supported", _out_ch_layout);
+ return -1;
+ }
+
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
+ if (in_ch_layout & out_ch_layout & (1U << i)) {
+ matrix[i][i] = 1.0;
+ }
+ }
+
+ cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
+
+ // Rematrixing is done via a matrix of coefficient that should be applied to
+ // all channels. Channels are treated as pair and must be symmetrical (if a
+ // left channel exists, the corresponding right should exist too) unless the
+ // output layout has similar layout. Channels are then mixed toward the front
+ // center or back center if they exist with a slight bias toward the front.
+
+ if (unaccounted & CHANNEL_FRONT_CENTER) {
+ if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
+ if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
+ matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
+ } else {
+ matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
+ }
+ }
+ }
+ if (unaccounted & CUBEB_LAYOUT_STEREO) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
+ if (in_ch_layout & CHANNEL_FRONT_CENTER)
+ matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_BACK_CENTER) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+ if (unaccounted & CHANNEL_BACK_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ if (in_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_SIDE_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ /* if back channels do not exist in the input, just copy side
+ channels to back channels, otherwise mix side into back */
+ if (in_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
+ if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
+ matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
+ }
+ }
+ /* mix LFE into front left/right or center */
+ if (unaccounted & CHANNEL_LOW_FREQUENCY) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ }
+ }
+
+ // Normalize the conversion matrix.
+ for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
+ double sum = 0;
+ int in_i = 0;
+ if ((out_ch_layout & (1U << i)) == 0) {
+ continue;
+ }
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ if ((in_ch_layout & (1U << j)) == 0) {
+ continue;
+ }
+ if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
+ _matrix[out_i][in_i] = matrix[i][j];
+ } else {
+ _matrix[out_i][in_i] =
+ i == j && (in_ch_layout & out_ch_layout & (1U << i));
+ }
+ sum += fabs(_matrix[out_i][in_i]);
+ in_i++;
+ }
+ maxcoef = std::max(maxcoef, sum);
+ out_i++;
+ }
+
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ maxval = 1.0;
+ } else {
+ maxval = INT_MAX;
+ }
+
+ // Normalize matrix if needed.
+ if (maxcoef > maxval) {
+ maxcoef /= maxval;
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++)
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix[i][j] /= maxcoef;
+ }
+ }
+
+ if (_format == CUBEB_SAMPLE_FLOAT32NE) {
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
+ for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
+ _matrix_flt[i][j] = _matrix[i][j];
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+MixerContext::init()
+{
+ int r = auto_matrix();
+ if (r) {
+ return r;
+ }
+
+ // Determine if matrix operation would overflow
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ int maxsum = 0;
+ for (uint32_t i = 0; i < _out_ch_count; i++) {
+ double rem = 0;
+ int sum = 0;
+
+ for (uint32_t j = 0; j < _in_ch_count; j++) {
+ double target = _matrix[i][j] * 32768 + rem;
+ int value = lrintf(target);
+ rem += target - value;
+ sum += std::abs(value);
+ }
+ maxsum = std::max(maxsum, sum);
+ }
+ if (maxsum > 32768) {
+ _clipping = true;
+ }
+ }
+
+ // FIXME quantize for integers
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
+ int ch_in = 0;
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
+ if (_matrix[i][j]) {
+ _matrix_ch[i][++ch_in] = j;
+ }
+ }
+ _matrix_ch[i][0] = ch_in;
+ }
+
+ return 0;
+}
+
+template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+sum2(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in1,
+ const TYPE_SAMPLE * in2, uint32_t stride_in, TYPE_COEFF coeff1,
+ TYPE_COEFF coeff2, F && operand, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff1 * *in1 + coeff2 * *in2);
+ out += stride_out;
+ in1 += stride_in;
+ in2 += stride_in;
+ }
+}
+
+template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+copy(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in,
+ uint32_t stride_in, TYPE_COEFF coeff, F && operand, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff * *in);
+ out += stride_out;
+ in += stride_in;
+ }
+}
+
+template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
+static int
+rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
+ const TYPE_COEFF (&matrix_coeff)[COLS][COLS], F && aF, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+
+ for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
+ TYPE * out = aOut + out_i;
+ switch (s->_matrix_ch[out_i][0]) {
+ case 0:
+ for (uint32_t i = 0; i < frames; i++) {
+ out[i * s->_out_ch_count] = 0;
+ }
+ break;
+ case 1: {
+ int in_i = s->_matrix_ch[out_i][1];
+ copy(out, s->_out_ch_count, aIn + in_i, s->_in_ch_count,
+ matrix_coeff[out_i][in_i], aF, frames);
+ } break;
+ case 2:
+ sum2(out, s->_out_ch_count, aIn + s->_matrix_ch[out_i][1],
+ aIn + s->_matrix_ch[out_i][2], s->_in_ch_count,
+ matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
+ matrix_coeff[out_i][s->_matrix_ch[out_i][2]], aF, frames);
+ break;
+ default:
+ for (uint32_t i = 0; i < frames; i++) {
+ TYPE_COEFF v = 0;
+ for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
+ uint32_t in_i = s->_matrix_ch[out_i][1 + j];
+ v += *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
+ }
+ out[i * s->_out_ch_count] = aF(v);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+struct cubeb_mixer {
+ cubeb_mixer(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+ : _context(format, in_channels, in_layout, out_channels, out_layout)
+ {
+ }
+
+ template <typename T>
+ void copy_and_trunc(size_t frames, const T * input_buffer,
+ T * output_buffer) const
+ {
+ if (_context._in_ch_count <= _context._out_ch_count) {
+ // Not enough channels to copy, fill the gaps with silence.
+ if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) {
+ // Special case for upmixing mono input to stereo and more. We will
+ // duplicate the mono channel to the first two channels. On most system,
+ // the first two channels are for left and right. It is commonly
+ // expected that mono will on both left+right channels
+ for (uint32_t i = 0; i < frames; i++) {
+ output_buffer[0] = output_buffer[1] = *input_buffer;
+ PodZero(output_buffer + 2, _context._out_ch_count - 2);
+ output_buffer += _context._out_ch_count;
+ input_buffer++;
+ }
+ return;
+ }
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+ output_buffer += _context._in_ch_count;
+ input_buffer += _context._in_ch_count;
+ PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+ output_buffer += _context._out_ch_count - _context._in_ch_count;
+ }
+ } else {
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+ output_buffer += _context._out_ch_count;
+ input_buffer += _context._in_ch_count;
+ }
+ }
+ }
+
+ int mix(size_t frames, const void * input_buffer, size_t input_buffer_size,
+ void * output_buffer, size_t output_buffer_size) const
+ {
+ if (frames <= 0 || _context._out_ch_count == 0) {
+ return 0;
+ }
+
+ // Check if output buffer is of sufficient size.
+ size_t size_read_needed =
+ frames * _context._in_ch_count * cubeb_sample_size(_context._format);
+ if (input_buffer_size < size_read_needed) {
+ // We don't have enough data to read!
+ return -1;
+ }
+ if (output_buffer_size * _context._in_ch_count <
+ size_read_needed * _context._out_ch_count) {
+ return -1;
+ }
+
+ if (!valid()) {
+ // The channel layouts were invalid or unsupported, instead we will simply
+ // either drop the extra channels, or fill with silence the missing ones
+ if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+ copy_and_trunc(frames, static_cast<const float *>(input_buffer),
+ static_cast<float *>(output_buffer));
+ } else {
+ assert(_context._format == CUBEB_SAMPLE_S16NE);
+ copy_and_trunc(frames, static_cast<const int16_t *>(input_buffer),
+ reinterpret_cast<int16_t *>(output_buffer));
+ }
+ return 0;
+ }
+
+ switch (_context._format) {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ auto f = [](float x) { return x; };
+ return rematrix(&_context, static_cast<float *>(output_buffer),
+ static_cast<const float *>(input_buffer),
+ _context._matrix_flt, f, frames);
+ }
+ case CUBEB_SAMPLE_S16NE:
+ if (_context._clipping) {
+ auto f = [](int x) {
+ int y = (x + 16384) >> 15;
+ // clip the signed integer value into the -32768,32767 range.
+ if ((y + 0x8000U) & ~0xFFFF) {
+ return (y >> 31) ^ 0x7FFF;
+ }
+ return y;
+ };
+ return rematrix(&_context, static_cast<int16_t *>(output_buffer),
+ static_cast<const int16_t *>(input_buffer),
+ _context._matrix32, f, frames);
+ } else {
+ auto f = [](int x) { return (x + 16384) >> 15; };
+ return rematrix(&_context, static_cast<int16_t *>(output_buffer),
+ static_cast<const int16_t *>(input_buffer),
+ _context._matrix32, f, frames);
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return -1;
+ }
+
+ // Return false if any of the input or ouput layout were invalid.
+ bool valid() const { return _context._valid; }
+
+ virtual ~cubeb_mixer(){};
+
+ MixerContext _context;
+};
+
+cubeb_mixer *
+cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+{
+ return new cubeb_mixer(format, in_channels, in_layout, out_channels,
+ out_layout);
+}
+
+void
+cubeb_mixer_destroy(cubeb_mixer * mixer)
+{
+ delete mixer;
+}
+
+int
+cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
+ size_t input_buffer_size, void * output_buffer,
+ size_t output_buffer_size)
+{
+ return mixer->mix(frames, input_buffer, input_buffer_size, output_buffer,
+ output_buffer_size);
+}
diff --git a/media/libcubeb/src/cubeb_mixer.h b/media/libcubeb/src/cubeb_mixer.h
new file mode 100644
index 0000000000..1859dab467
--- /dev/null
+++ b/media/libcubeb/src/cubeb_mixer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_MIXER
+#define CUBEB_MIXER
+
+#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct cubeb_mixer cubeb_mixer;
+cubeb_mixer *
+cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout);
+void
+cubeb_mixer_destroy(cubeb_mixer * mixer);
+int
+cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
+ size_t input_buffer_size, void * output_buffer,
+ size_t output_buffer_size);
+
+unsigned int
+cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBEB_MIXER
diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c
index dd54162280..78096d5dd4 100644
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -5,96 +5,262 @@
* accompanying file LICENSE for details.
*/
#undef NDEBUG
+#include <SLES/OpenSLES.h>
#include <assert.h>
#include <dlfcn.h>
-#include <stdlib.h>
-#include <pthread.h>
-#include <SLES/OpenSLES.h>
+#include <errno.h>
#include <math.h>
+#include <pthread.h>
+#include <stdlib.h>
#include <time.h>
#if defined(__ANDROID__)
-#include <dlfcn.h>
-#include <sys/system_properties.h>
#include "android/sles_definitions.h"
#include <SLES/OpenSLES_Android.h>
-#include <android/log.h>
#include <android/api-level.h>
-#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
+#include <android/log.h>
+#include <dlfcn.h>
+#include <sys/system_properties.h>
+#endif
+#include "android/cubeb-output-latency.h"
+#include "cubeb-internal.h"
+#include "cubeb-sles.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_android.h"
+#include "cubeb_array_queue.h"
+#include "cubeb_resampler.h"
+
+#if defined(__ANDROID__)
+#ifdef LOG
+#undef LOG
+#endif
+//#define LOGGING_ENABLED
+#ifdef LOGGING_ENABLED
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL", ##args)
+#else
+#define LOG(...)
+#endif
+
+//#define TIMESTAMP_ENABLED
+#ifdef TIMESTAMP_ENABLED
+#define FILENAME \
+ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#define LOG_TS(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)", \
+ ##args)
+#define TIMESTAMP(msg) \
+ do { \
+ struct timeval timestamp; \
+ int ts_ret = gettimeofday(&timestamp, NULL); \
+ if (ts_ret == 0) { \
+ LOG_TS("%lld: %s (%s %s:%d)", \
+ timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, \
+ __FUNCTION__, FILENAME, __LINE__); \
+ } else { \
+ LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, \
+ __LINE__); \
+ } \
+ } while (0)
+#else
+#define TIMESTAMP(...)
+#endif
+
#define ANDROID_VERSION_GINGERBREAD_MR1 10
+#define ANDROID_VERSION_JELLY_BEAN 18
#define ANDROID_VERSION_LOLLIPOP 21
#define ANDROID_VERSION_MARSHMALLOW 23
+#define ANDROID_VERSION_N_MR1 25
#endif
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include "cubeb_resampler.h"
-#include "cubeb-sles.h"
+
+#define DEFAULT_SAMPLE_RATE 48000
+#define DEFAULT_NUM_OF_FRAMES 480
static struct cubeb_ops const opensl_ops;
struct cubeb {
struct cubeb_ops const * ops;
void * lib;
- void * libmedia;
- int32_t (* get_output_latency)(uint32_t * latency, int stream_type);
SLInterfaceID SL_IID_BUFFERQUEUE;
SLInterfaceID SL_IID_PLAY;
#if defined(__ANDROID__)
SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
+ SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
#endif
SLInterfaceID SL_IID_VOLUME;
+ SLInterfaceID SL_IID_RECORD;
SLObjectItf engObj;
SLEngineItf eng;
SLObjectItf outmixObj;
+ output_latency_function * p_output_latency_function;
};
#define NELEMS(A) (sizeof(A) / sizeof A[0])
-#define NBUFS 4
-#define AUDIO_STREAM_TYPE_MUSIC 3
+#define NBUFS 2
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
+ void * user_ptr;
+ /**/
pthread_mutex_t mutex;
SLObjectItf playerObj;
SLPlayItf play;
SLBufferQueueItf bufq;
SLVolumeItf volume;
- uint8_t *queuebuf[NBUFS];
+ void ** queuebuf;
+ uint32_t queuebuf_capacity;
int queuebuf_idx;
long queuebuf_len;
long bytespersec;
long framesize;
+ /* Total number of played frames.
+ * Synchronized by stream::mutex lock. */
long written;
+ /* Flag indicating draining. Synchronized
+ * by stream::mutex lock. */
int draining;
- cubeb_stream_type stream_type;
-
+ /* Flags to determine in/out.*/
+ uint32_t input_enabled;
+ uint32_t output_enabled;
+ /* Recorder abstract object. */
+ SLObjectItf recorderObj;
+ /* Recorder Itf for input capture. */
+ SLRecordItf recorderItf;
+ /* Buffer queue for input capture. */
+ SLAndroidSimpleBufferQueueItf recorderBufferQueueItf;
+ /* Store input buffers. */
+ void ** input_buffer_array;
+ /* The capacity of the array.
+ * On capture only can be small (4).
+ * On full duplex is calculated to
+ * store 1 sec of data buffers. */
+ uint32_t input_array_capacity;
+ /* Current filled index of input buffer array.
+ * It is initiated to -1 indicating buffering
+ * have not started yet. */
+ int input_buffer_index;
+ /* Length of input buffer.*/
+ uint32_t input_buffer_length;
+ /* Input frame size */
+ uint32_t input_frame_size;
+ /* Device sampling rate. If user rate is not
+ * accepted an compatible rate is set. If it is
+ * accepted this is equal to params.rate. */
+ uint32_t input_device_rate;
+ /* Exchange input buffers between input
+ * and full duplex threads. */
+ array_queue * input_queue;
+ /* Silent input buffer used on full duplex. */
+ void * input_silent_buffer;
+ /* Number of input frames from the start of the stream*/
+ uint32_t input_total_frames;
+ /* Flag to stop the execution of user callback and
+ * close all working threads. Synchronized by
+ * stream::mutex lock. */
+ uint32_t shutdown;
+ /* Store user callback. */
cubeb_data_callback data_callback;
+ /* Store state callback. */
cubeb_state_callback state_callback;
- void * user_ptr;
cubeb_resampler * resampler;
- unsigned int inputrate;
- unsigned int outputrate;
- unsigned int latency;
+ unsigned int user_output_rate;
+ unsigned int output_configured_rate;
+ unsigned int buffer_size_frames;
+ // Audio output latency used in cubeb_stream_get_position().
+ unsigned int output_latency_ms;
int64_t lastPosition;
int64_t lastPositionTimeStamp;
int64_t lastCompensativePosition;
+ int voice_input;
+ int voice_output;
};
+/* Forward declaration. */
+static int
+opensl_stop_player(cubeb_stream * stm);
+static int
+opensl_stop_recorder(cubeb_stream * stm);
+
+static int
+opensl_get_draining(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "get_draining: mutex should be locked but it's not.");
+#endif
+ return stm->draining;
+}
+
+static void
+opensl_set_draining(cubeb_stream * stm, int value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set draining try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "set_draining: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->draining = value;
+}
+
+static void
+opensl_notify_drained(cubeb_stream * stm)
+{
+ assert(stm);
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->play) {
+ LOG("stop player in play_callback");
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+}
+
+static uint32_t
+opensl_get_shutdown(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "get_shutdown: mutex should be locked but it's not.");
+#endif
+ return stm->shutdown;
+}
+
+static void
+opensl_set_shutdown(cubeb_stream * stm, uint32_t value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set shutdown try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "set_shutdown: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->shutdown = value;
+}
+
static void
play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
{
cubeb_stream * stm = user_ptr;
- int draining;
assert(stm);
switch (event) {
case SL_PLAYEVENT_HEADATMARKER:
- pthread_mutex_lock(&stm->mutex);
- draining = stm->draining;
- pthread_mutex_unlock(&stm->mutex);
- if (draining) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
- }
+ opensl_notify_drained(stm);
break;
default:
break;
@@ -102,104 +268,372 @@ play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
}
static void
+recorder_marker_callback(SLRecordItf caller, void * pContext, SLuint32 event)
+{
+ cubeb_stream * stm = pContext;
+ assert(stm);
+
+ if (event == SL_RECORDEVENT_HEADATMARKER) {
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->play) {
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+ }
+}
+
+static void
bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
{
cubeb_stream * stm = user_ptr;
assert(stm);
SLBufferQueueState state;
SLresult res;
+ long written = 0;
res = (*stm->bufq)->GetState(stm->bufq, &state);
assert(res == SL_RESULT_SUCCESS);
- if (state.count > 1)
+ if (state.count > 1) {
return;
+ }
- SLuint32 i;
- for (i = state.count; i < NBUFS; i++) {
- uint8_t *buf = stm->queuebuf[stm->queuebuf_idx];
- long written = 0;
- pthread_mutex_lock(&stm->mutex);
- int draining = stm->draining;
- pthread_mutex_unlock(&stm->mutex);
-
- if (!draining) {
- written = cubeb_resampler_fill(stm->resampler,
- NULL, NULL,
- buf, stm->queuebuf_len / stm->framesize);
- if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
- (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
- return;
- }
+ uint8_t * buf = stm->queuebuf[stm->queuebuf_idx];
+ written = 0;
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (!draining && !shutdown) {
+ written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf,
+ stm->queuebuf_len / stm->framesize);
+ LOG("bufferqueue_callback: resampler fill returned %ld frames", written);
+ if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
}
+ }
- // Keep sending silent data even in draining mode to prevent the audio
- // back-end from being stopped automatically by OpenSL/ES.
- memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize);
- res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
- assert(res == SL_RESULT_SUCCESS);
- stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS;
- if (written > 0) {
- pthread_mutex_lock(&stm->mutex);
- stm->written += written;
- pthread_mutex_unlock(&stm->mutex);
- }
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ assert(stm->queuebuf_len >= written * stm->framesize);
+ memset(buf + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+ res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+
+ if (written > 0) {
+ pthread_mutex_lock(&stm->mutex);
+ stm->written += written;
+ pthread_mutex_unlock(&stm->mutex);
+ }
- if (!draining && written * stm->framesize < stm->queuebuf_len) {
- pthread_mutex_lock(&stm->mutex);
- int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
- stm->draining = 1;
- pthread_mutex_unlock(&stm->mutex);
+ if (!draining && written * stm->framesize < stm->queuebuf_len) {
+ LOG("bufferqueue_callback draining");
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration =
+ INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written_duration == 0) {
+ // since we didn't write any sample, it's not possible to reach the marker
+ // time and trigger the callback. We should initiative notify drained.
+ opensl_notify_drained(stm);
+ } else {
// Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
// to make sure all the data has been processed.
- (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
- return;
+ (*stm->play)
+ ->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
}
+ return;
}
}
-#if defined(__ANDROID__)
-static SLuint32
-convert_stream_type_to_sl_stream(cubeb_stream_type stream_type)
-{
- switch(stream_type) {
- case CUBEB_STREAM_TYPE_SYSTEM:
- return SL_ANDROID_STREAM_SYSTEM;
- case CUBEB_STREAM_TYPE_MUSIC:
- return SL_ANDROID_STREAM_MEDIA;
- case CUBEB_STREAM_TYPE_NOTIFICATION:
- return SL_ANDROID_STREAM_NOTIFICATION;
- case CUBEB_STREAM_TYPE_ALARM:
- return SL_ANDROID_STREAM_ALARM;
- case CUBEB_STREAM_TYPE_VOICE_CALL:
- return SL_ANDROID_STREAM_VOICE;
- case CUBEB_STREAM_TYPE_RING:
- return SL_ANDROID_STREAM_RING;
- case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED:
- return SL_ANDROID_STREAM_SYSTEM_ENFORCED;
- default:
- return 0xFFFFFFFF;
+static int
+opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
+{
+ assert(stm);
+
+ int current_index = stm->input_buffer_index;
+ void * last_buffer = NULL;
+
+ if (current_index < 0) {
+ // This is the first enqueue
+ current_index = 0;
+ } else {
+ // The current index hold the last filled buffer get it before advance
+ // index.
+ last_buffer = stm->input_buffer_array[current_index];
+ // Advance to get next available buffer
+ current_index = (current_index + 1) % stm->input_array_capacity;
}
+ // enqueue next empty buffer to be filled by the recorder
+ SLresult res = (*stm->recorderBufferQueueItf)
+ ->Enqueue(stm->recorderBufferQueueItf,
+ stm->input_buffer_array[current_index],
+ stm->input_buffer_length);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Enqueue recorder failed. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // All good, update buffer and index.
+ stm->input_buffer_index = current_index;
+ if (last_filled_buffer) {
+ *last_filled_buffer = last_buffer;
+ }
+ return CUBEB_OK;
}
-#endif
-static void opensl_destroy(cubeb * ctx);
+// input data callback
+void
+recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = context;
+ assert(stm->recorderBufferQueueItf);
-#if defined(__ANDROID__)
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf
+ // page 184, on transition to the SL_RECORDSTATE_STOPPED state,
+ // the application should continue to enqueue buffers onto the queue
+ // to retrieve the residual recorded data in the system.
+ r = opensl_enqueue_recorder(stm, NULL);
+ assert(r == CUBEB_OK);
+ return;
+ }
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = NULL;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+ // Fill resampler with last input
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long got = cubeb_resampler_fill(stm->resampler, input_buffer,
+ &input_frame_count, NULL, 0);
+ // Error case
+ if (got < 0 || got > input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+
+ // Advance total stream frames
+ stm->input_total_frames += got;
+
+ if (got < input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ int64_t duration =
+ INT64_C(1000) * stm->input_total_frames / stm->input_device_rate;
+ (*stm->recorderItf)
+ ->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration);
+ return;
+ }
+}
+
+void
+recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = context;
+ assert(stm->recorderBufferQueueItf);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ /* On draining and shutdown the recorder should have been stoped from
+ * the one set the flags. Accordint to the doc, on transition to
+ * the SL_RECORDSTATE_STOPPED state, the application should
+ * continue to enqueue buffers onto the queue to retrieve the residual
+ * recorded data in the system. */
+ LOG("Input shutdown %d or drain %d", shutdown, draining);
+ int r = opensl_enqueue_recorder(stm, NULL);
+ assert(r == CUBEB_OK);
+ return;
+ }
+
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = NULL;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+
+ assert(stm->input_queue);
+ r = array_queue_push(stm->input_queue, input_buffer);
+ if (r == -1) {
+ LOG("Input queue is full, drop input ...");
+ return;
+ }
+
+ LOG("Input pushed in the queue, input array %zu",
+ array_queue_get_size(stm->input_queue));
+}
+
+static void
+player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ TIMESTAMP("ENTER");
+ cubeb_stream * stm = user_ptr;
+ assert(stm);
+ SLresult res;
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Get output
+ void * output_buffer = NULL;
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ output_buffer = stm->queuebuf[stm->queuebuf_idx];
+ // Advance the output buffer queue index
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ LOG("Shutdown/draining, send silent");
+ // Set silent on buffer
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Get input.
+ void * input_buffer = array_queue_pop(stm->input_queue);
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long frames_needed = stm->queuebuf_len / stm->framesize;
+ if (!input_buffer) {
+ LOG("Input hole set silent input buffer");
+ input_buffer = stm->input_silent_buffer;
+ }
+
+ long written = 0;
+ // Trigger user callback through resampler
+ written =
+ cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count,
+ output_buffer, frames_needed);
+
+ LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written,
+ frames_needed, array_queue_get_size(stm->input_queue));
+
+ if (written < 0 || written > frames_needed) {
+ // Error case
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ opensl_stop_recorder(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Advance total out written frames counter
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ stm->written += written;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written < frames_needed) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration =
+ INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+ // to make sure all the data has been processed.
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
+ }
+
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ memset((uint8_t *)output_buffer + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ TIMESTAMP("EXIT");
+}
+
+static void
+opensl_destroy(cubeb * ctx);
+
+#if defined(__ANDROID__)
#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
-typedef int (system_property_get)(const char*, char*);
+typedef int(system_property_get)(const char *, char *);
static int
-__system_property_get(const char* name, char* value)
+wrap_system_property_get(const char * name, char * value)
{
- void* libc = dlopen("libc.so", RTLD_LAZY);
+ void * libc = dlopen("libc.so", RTLD_LAZY);
if (!libc) {
LOG("Failed to open libc.so");
return -1;
}
- system_property_get* func = (system_property_get*)
- dlsym(libc, "__system_property_get");
+ system_property_get * func =
+ (system_property_get *)dlsym(libc, "__system_property_get");
int ret = -1;
if (func) {
ret = func(name, value);
@@ -216,14 +650,18 @@ get_android_version(void)
memset(version_string, 0, PROP_VALUE_MAX);
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ int len = wrap_system_property_get("ro.build.version.sdk", version_string);
+#else
int len = __system_property_get("ro.build.version.sdk", version_string);
+#endif
if (len <= 0) {
LOG("Failed to get Android version!\n");
return len;
}
int version = (int)strtol(version_string, NULL, 10);
- LOG("%d", version);
+ LOG("Android version %d", version);
return version;
}
#endif
@@ -235,7 +673,8 @@ opensl_init(cubeb ** context, char const * context_name)
#if defined(__ANDROID__)
int android_version = get_android_version();
- if (android_version > 0 && android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
+ if (android_version > 0 &&
+ android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
// Don't even attempt to run on Gingerbread and lower
return CUBEB_ERROR;
}
@@ -249,54 +688,39 @@ opensl_init(cubeb ** context, char const * context_name)
ctx->ops = &opensl_ops;
ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
- ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY);
- if (!ctx->lib || !ctx->libmedia) {
+ if (!ctx->lib) {
free(ctx);
return CUBEB_ERROR;
}
- /* Get the latency, in ms, from AudioFlinger */
- /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
- * audio_stream_type_t streamType) */
- /* First, try the most recent signature. */
- ctx->get_output_latency =
- dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
- if (!ctx->get_output_latency) {
- /* in case of failure, try the legacy version. */
- /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
- * int streamType) */
- ctx->get_output_latency =
- dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
- if (!ctx->get_output_latency) {
- opensl_destroy(ctx);
- return CUBEB_ERROR;
- }
- }
-
- typedef SLresult (*slCreateEngine_t)(SLObjectItf *,
- SLuint32,
- const SLEngineOption *,
- SLuint32,
- const SLInterfaceID *,
- const SLboolean *);
+ typedef SLresult (*slCreateEngine_t)(
+ SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32,
+ const SLInterfaceID *, const SLboolean *);
slCreateEngine_t f_slCreateEngine =
- (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
- SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
- SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
+ (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
+ SLInterfaceID SL_IID_ENGINE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
+ SLInterfaceID SL_IID_OUTPUTMIX =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
- ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
+ ctx->SL_IID_BUFFERQUEUE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
#if defined(__ANDROID__)
- ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
+ ctx->SL_IID_ANDROIDCONFIGURATION =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
+ ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
#endif
ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
- if (!f_slCreateEngine ||
- !SL_IID_ENGINE ||
- !SL_IID_OUTPUTMIX ||
+ ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD");
+
+ if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX ||
!ctx->SL_IID_BUFFERQUEUE ||
#if defined(__ANDROID__)
!ctx->SL_IID_ANDROIDCONFIGURATION ||
+ !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE ||
#endif
- !ctx->SL_IID_PLAY) {
+ !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) {
opensl_destroy(ctx);
return CUBEB_ERROR;
}
@@ -325,7 +749,8 @@ opensl_init(cubeb ** context, char const * context_name)
const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
- res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
+ res =
+ (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
if (res != SL_RESULT_SUCCESS) {
opensl_destroy(ctx);
return CUBEB_ERROR;
@@ -337,8 +762,16 @@ opensl_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR;
}
+ ctx->p_output_latency_function =
+ cubeb_output_latency_load_method(android_version);
+ if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
+ LOG("Warning: output latency is not available, cubeb_stream_get_position() "
+ "is not supported");
+ }
+
*context = ctx;
+ LOG("Cubeb init (%p) success", ctx);
return CUBEB_OK;
}
@@ -353,225 +786,365 @@ opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
assert(ctx && max_channels);
/* The android mixer handles up to two channels, see
- http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
+ http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
+ */
*max_channels = 2;
return CUBEB_OK;
}
-static int
-opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
- * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
- * so we just dlopen the library and get the two symbols we need. */
- int r;
- void * libmedia;
- uint32_t (*get_primary_output_samplingrate)();
- uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType);
-
- libmedia = dlopen("libmedia.so", RTLD_LAZY);
- if (!libmedia) {
- return CUBEB_ERROR;
- }
+static void
+opensl_destroy(cubeb * ctx)
+{
+ if (ctx->outmixObj)
+ (*ctx->outmixObj)->Destroy(ctx->outmixObj);
+ if (ctx->engObj)
+ cubeb_destroy_sles_engine(&ctx->engObj);
+ dlclose(ctx->lib);
+ if (ctx->p_output_latency_function)
+ cubeb_output_latency_unload_method(ctx->p_output_latency_function);
+ free(ctx);
+}
- /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */
- get_primary_output_samplingrate =
- dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv");
- if (!get_primary_output_samplingrate) {
- /* fallback to
- * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType)
- * if we cannot find getPrimaryOutputSamplingRate. */
- get_output_samplingrate =
- dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t");
- if (!get_output_samplingrate) {
- /* Another signature exists, with a int instead of an audio_stream_type_t */
- get_output_samplingrate =
- dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii");
- if (!get_output_samplingrate) {
- dlclose(libmedia);
- return CUBEB_ERROR;
- }
- }
- }
+static void
+opensl_stream_destroy(cubeb_stream * stm);
- if (get_primary_output_samplingrate) {
- *rate = get_primary_output_samplingrate();
- } else {
- /* We don't really know about the type, here, so we just pass music. */
- r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC);
- if (r) {
- dlclose(libmedia);
- return CUBEB_ERROR;
- }
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+static int
+opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format,
+ cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
+
+ format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ format->numChannels = params->channels;
+ // sampleRate is in milliHertz
+ format->sampleRate = params->rate * 1000;
+ format->channelMask = params->channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
}
+ return CUBEB_OK;
+}
+#endif
- dlclose(libmedia);
+static int
+opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
- /* Depending on which method we called above, we can get a zero back, yet have
- * a non-error return value, especially if the audio system is not
- * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger
- * thread). */
- if (*rate == 0) {
- return CUBEB_ERROR;
+ format->formatType = SL_DATAFORMAT_PCM;
+ format->numChannels = params->channels;
+ // samplesPerSec is in milliHertz
+ format->samplesPerSec = params->rate * 1000;
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->channelMask = params->channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
}
-
return CUBEB_OK;
}
static int
-opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
{
- /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
- * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
- * so we just dlopen the library and get the two symbols we need. */
-
- int r;
- void * libmedia;
- size_t (*get_primary_output_frame_count)(void);
- int (*get_output_frame_count)(size_t * frameCount, int streamType);
- uint32_t primary_sampling_rate;
- size_t primary_buffer_size;
+ assert(stm);
+ assert(params);
- r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate);
+ SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut;
+ lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ lDataLocatorOut.numBuffers = NBUFS;
- if (r) {
- return CUBEB_ERROR;
+ SLDataFormat_PCM lDataFormat;
+ int r = opensl_set_format(&lDataFormat, params);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
}
- libmedia = dlopen("libmedia.so", RTLD_LAZY);
- if (!libmedia) {
- return CUBEB_ERROR;
- }
+ /* For now set device rate to params rate. */
+ stm->input_device_rate = params->rate;
+
+ SLDataSink lDataSink;
+ lDataSink.pLocator = &lDataLocatorOut;
+ lDataSink.pFormat = &lDataFormat;
+
+ SLDataLocator_IODevice lDataLocatorIn;
+ lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE;
+ lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT;
+ lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ lDataLocatorIn.device = NULL;
+
+ SLDataSource lDataSource;
+ lDataSource.pLocator = &lDataLocatorIn;
+ lDataSource.pFormat = NULL;
+
+ const SLInterfaceID lSoundRecorderIIDs[] = {
+ stm->context->SL_IID_RECORD,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
+
+ const SLboolean lSoundRecorderReqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE};
+ // create the audio recorder abstract object
+ SLresult res = (*stm->context->eng)
+ ->CreateAudioRecorder(
+ stm->context->eng, &stm->recorderObj, &lDataSource,
+ &lDataSink, NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs, lSoundRecorderReqs);
+ // Sample rate not supported. Try again with default sample rate!
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
+ if (stm->output_enabled && stm->output_configured_rate != 0) {
+ // Set the same with the player. Since there is no
+ // api for input device this is a safe choice.
+ stm->input_device_rate = stm->output_configured_rate;
+ } else {
+ // The output preferred rate is used for an input only scenario.
+ // The default rate expected to be supported from all android devices.
+ stm->input_device_rate = DEFAULT_SAMPLE_RATE;
+ }
+ lDataFormat.samplesPerSec = stm->input_device_rate * 1000;
+ res = (*stm->context->eng)
+ ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj,
+ &lDataSource, &lDataSink,
+ NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs, lSoundRecorderReqs);
- /* JB variant */
- /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */
- get_primary_output_frame_count =
- dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv");
- if (!get_primary_output_frame_count) {
- /* ICS variant */
- /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */
- get_output_frame_count =
- dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii");
- if (!get_output_frame_count) {
- dlclose(libmedia);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to create recorder. Error code: %lu", res);
return CUBEB_ERROR;
}
}
- if (get_primary_output_frame_count) {
- primary_buffer_size = get_primary_output_frame_count();
- } else {
- if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) {
+ if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) {
+ SLAndroidConfigurationItf recorderConfig;
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &recorderConfig);
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get the android configuration interface for recorder. "
+ "Error "
+ "code: %lu",
+ res);
return CUBEB_ERROR;
}
- }
- /* To get a fast track in Android's mixer, we need to be at the native
- * samplerate, which is device dependant. Some devices might be able to
- * resample when playing a fast track, but it's pretty rare. */
- *latency_frames = NBUFS * primary_buffer_size;
+ // Voice recognition is the lowest latency, according to the docs. Camcorder
+ // uses a microphone that is in the same direction as the camera.
+ SLint32 streamType = stm->voice_input
+ ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
+ : SL_ANDROID_RECORDING_PRESET_CAMCORDER;
- dlclose(libmedia);
+ res =
+ (*recorderConfig)
+ ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
+ &streamType, sizeof(SLint32));
- return CUBEB_OK;
-}
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set the android configuration to VOICE for the recorder. "
+ "Error code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+ }
+ // realize the audio recorder
+ res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to realize recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the record interface
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD,
+ &stm->recorderItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
-static void
-opensl_destroy(cubeb * ctx)
-{
- if (ctx->outmixObj)
- (*ctx->outmixObj)->Destroy(ctx->outmixObj);
- if (ctx->engObj)
- cubeb_destroy_sles_engine(&ctx->engObj);
- dlclose(ctx->lib);
- dlclose(ctx->libmedia);
- free(ctx);
-}
+ res = (*stm->recorderItf)
+ ->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder marker callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
-static void opensl_stream_destroy(cubeb_stream * stm);
+ (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0);
-static int
-opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback, cubeb_state_callback state_callback,
- void * user_ptr)
-{
- cubeb_stream * stm;
+ res = (*stm->recorderItf)
+ ->SetCallbackEventsMask(stm->recorderItf,
+ (SLuint32)SL_RECORDEVENT_HEADATMARKER);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the simple android buffer queue interface
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder (android) buffer queue interface. Error code: "
+ "%lu",
+ res);
+ return CUBEB_ERROR;
+ }
- assert(ctx);
- assert(!input_stream_params && "not supported");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ // register callback on record (input) buffer queue
+ slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback;
+ if (stm->output_enabled) {
+ // Register full duplex callback instead.
+ rec_callback = recorder_fullduplex_callback;
+ }
+ res = (*stm->recorderBufferQueueItf)
+ ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder buffer queue callback. Error code: %lu",
+ res);
+ return CUBEB_ERROR;
}
- *stream = NULL;
+ // Calculate length of input buffer according to requested latency
+ stm->input_frame_size = params->channels * sizeof(int16_t);
+ stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames);
- SLDataFormat_PCM format;
+ // Calculate the capacity of input array
+ stm->input_array_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->input_array_capacity =
+ 1 * stm->input_device_rate / stm->input_buffer_length;
+ }
+ // Allocate input array
+ stm->input_buffer_array =
+ (void **)calloc(1, sizeof(void *) * stm->input_array_capacity);
+ // Buffering has not started yet.
+ stm->input_buffer_index = -1;
+ // Prepare input buffers
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length);
+ }
- format.formatType = SL_DATAFORMAT_PCM;
- format.numChannels = output_stream_params->channels;
- // samplesPerSec is in milliHertz
- format.samplesPerSec = output_stream_params->rate * 1000;
- format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
- format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
- format.channelMask = output_stream_params->channels == 1 ?
- SL_SPEAKER_FRONT_CENTER :
- SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
-
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16LE:
- format.endianness = SL_BYTEORDER_LITTLEENDIAN;
- break;
- case CUBEB_SAMPLE_S16BE:
- format.endianness = SL_BYTEORDER_BIGENDIAN;
- break;
- default:
- return CUBEB_ERROR_INVALID_FORMAT;
+ // On full duplex allocate input queue and silent buffer
+ if (stm->output_enabled) {
+ stm->input_queue = array_queue_create(stm->input_array_capacity);
+ assert(stm->input_queue);
+ stm->input_silent_buffer = calloc(1, stm->input_buffer_length);
+ assert(stm->input_silent_buffer);
}
- stm = calloc(1, sizeof(*stm));
- assert(stm);
+ // Enqueue buffer to start rolling once recorder started
+ r = opensl_enqueue_recorder(stm, NULL);
+ if (r != CUBEB_OK) {
+ return r;
+ }
- stm->context = ctx;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
+ LOG("Cubeb stream init recorder success");
+
+ return CUBEB_OK;
+}
- stm->inputrate = output_stream_params->rate;
- stm->latency = latency_frames;
- stm->stream_type = output_stream_params->stream_type;
- stm->framesize = output_stream_params->channels * sizeof(int16_t);
+static int
+opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params)
+{
+ assert(stm);
+ assert(params);
+
+ stm->user_output_rate = params->rate;
+ if (params->format == CUBEB_SAMPLE_S16NE ||
+ params->format == CUBEB_SAMPLE_S16BE) {
+ stm->framesize = params->channels * sizeof(int16_t);
+ } else if (params->format == CUBEB_SAMPLE_FLOAT32NE ||
+ params->format == CUBEB_SAMPLE_FLOAT32BE) {
+ stm->framesize = params->channels * sizeof(float);
+ }
stm->lastPosition = -1;
stm->lastPositionTimeStamp = 0;
stm->lastCompensativePosition = -1;
- int r = pthread_mutex_init(&stm->mutex, NULL);
- assert(r == 0);
+ void * format = NULL;
+ SLuint32 * format_sample_rate = NULL;
+
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ SLAndroidDataFormat_PCM_EX pcm_ext_format;
+ if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) {
+ if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_ext_format;
+ format_sample_rate = &pcm_ext_format.sampleRate;
+ }
+#endif
+
+ SLDataFormat_PCM pcm_format;
+ if (!format) {
+ if (opensl_set_format(&pcm_format, params) != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_format;
+ format_sample_rate = &pcm_format.samplesPerSec;
+ }
SLDataLocator_BufferQueue loc_bufq;
loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
loc_bufq.numBuffers = NBUFS;
SLDataSource source;
source.pLocator = &loc_bufq;
- source.pFormat = &format;
+ source.pFormat = format;
SLDataLocator_OutputMix loc_outmix;
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
- loc_outmix.outputMix = ctx->outmixObj;
+ loc_outmix.outputMix = stm->context->outmixObj;
SLDataSink sink;
sink.pLocator = &loc_outmix;
sink.pFormat = NULL;
#if defined(__ANDROID__)
- const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE,
- ctx->SL_IID_VOLUME,
- ctx->SL_IID_ANDROIDCONFIGURATION};
+ const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE,
+ stm->context->SL_IID_VOLUME,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
#else
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
@@ -579,130 +1152,182 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
#endif
assert(NELEMS(ids) == NELEMS(req));
- uint32_t preferred_sampling_rate = stm->inputrate;
-#if defined(__ANDROID__)
- if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) {
- // Reset preferred samping rate to trigger fallback to native sampling rate.
- preferred_sampling_rate = 0;
- if (opensl_get_min_latency(ctx, *output_stream_params, &latency_frames) != CUBEB_OK) {
- // Default to AudioFlinger's advertised fast track latency of 10ms.
- latency_frames = 440;
- }
- stm->latency = latency_frames;
- }
-#endif
-
+ uint32_t preferred_sampling_rate = stm->user_output_rate;
SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
if (preferred_sampling_rate) {
- res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source,
- &sink, NELEMS(ids), ids, req);
+ res = (*stm->context->eng)
+ ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source,
+ &sink, NELEMS(ids), ids, req);
}
// Sample rate not supported? Try again with primary sample rate!
- if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
- if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) {
- opensl_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- format.samplesPerSec = preferred_sampling_rate * 1000;
- res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj,
- &source, &sink, NELEMS(ids), ids, req);
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
+ preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
+ preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
+ *format_sample_rate = preferred_sampling_rate * 1000;
+ res = (*stm->context->eng)
+ ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source,
+ &sink, NELEMS(ids), ids, req);
}
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to create audio player. Error code: %lu", res);
return CUBEB_ERROR;
}
- stm->outputrate = preferred_sampling_rate;
- stm->bytespersec = stm->outputrate * stm->framesize;
- stm->queuebuf_len = stm->framesize * latency_frames / NBUFS;
- // round up to the next multiple of stm->framesize, if needed.
- if (stm->queuebuf_len % stm->framesize) {
- stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
- }
-
- cubeb_stream_params params = *output_stream_params;
- params.rate = preferred_sampling_rate;
+ stm->output_configured_rate = preferred_sampling_rate;
+ stm->bytespersec = stm->output_configured_rate * stm->framesize;
+ stm->queuebuf_len = stm->framesize * stm->buffer_size_frames;
- stm->resampler = cubeb_resampler_create(stm, NULL, &params,
- output_stream_params->rate,
- data_callback,
- user_ptr,
- CUBEB_RESAMPLER_QUALITY_DEFAULT);
-
- if (!stm->resampler) {
- opensl_stream_destroy(stm);
- return CUBEB_ERROR;
+ // Calculate the capacity of input array
+ stm->queuebuf_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->queuebuf_capacity =
+ 1 * stm->output_configured_rate / stm->queuebuf_len;
}
-
- int i;
- for (i = 0; i < NBUFS; i++) {
- stm->queuebuf[i] = malloc(stm->queuebuf_len);
+ // Allocate input array
+ stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity);
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ stm->queuebuf[i] = calloc(1, stm->queuebuf_len);
assert(stm->queuebuf[i]);
}
-#if defined(__ANDROID__)
- SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type);
- if (stream_type != 0xFFFFFFFF) {
- SLAndroidConfigurationItf playerConfig;
- res = (*stm->playerObj)->GetInterface(stm->playerObj,
- ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig);
- res = (*playerConfig)->SetConfiguration(playerConfig,
- SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32));
+ SLAndroidConfigurationItf playerConfig = NULL;
+
+ if (get_android_version() >= ANDROID_VERSION_N_MR1) {
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &playerConfig);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get Android configuration interface. Error code: %lu",
+ res);
return CUBEB_ERROR;
}
+
+ SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
+ if (stm->voice_output) {
+ streamType = SL_ANDROID_STREAM_VOICE;
+ }
+ res = (*playerConfig)
+ ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE,
+ &streamType, sizeof(streamType));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android configuration to %d Error code: %lu",
+ streamType, res);
+ }
+
+ SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY;
+ if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+ }
+
+ res = (*playerConfig)
+ ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode, sizeof(performanceMode));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android performance mode to %d Error code: %lu. This "
+ "is"
+ " not fatal",
+ performanceMode, res);
+ }
}
-#endif
res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to realize player object. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play);
+ // There are two ways of getting the audio output latency:
+ // - a configuration value, only available on some devices (notably devices
+ // running FireOS)
+ // - A Java method, that we call using JNI.
+ //
+ // The first method is prefered, if available, because it can account for more
+ // latency causes, and is more precise.
+
+ // Latency has to be queried after the realization of the interface, when
+ // using SL_IID_ANDROIDCONFIGURATION.
+ SLuint32 audioLatency = 0;
+ SLuint32 paramSize = sizeof(SLuint32);
+ // The reported latency is in milliseconds.
+ if (playerConfig) {
+ res = (*playerConfig)
+ ->GetConfiguration(playerConfig,
+ (const SLchar *)"androidGetAudioLatency",
+ &paramSize, &audioLatency);
+ if (res == SL_RESULT_SUCCESS) {
+ LOG("Got playback latency using android configuration extension");
+ stm->output_latency_ms = audioLatency;
+ }
+ }
+ // `playerConfig` is available, but the above failed, or `playerConfig` is not
+ // available. In both cases, we need to acquire the output latency by an other
+ // mean.
+ if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) {
+ if (cubeb_output_latency_method_is_loaded(
+ stm->context->p_output_latency_function)) {
+ LOG("Got playback latency using JNI");
+ stm->output_latency_ms =
+ cubeb_get_output_latency(stm->context->p_output_latency_function);
+ } else {
+ LOG("No alternate latency querying method loaded, A/V sync will be off.");
+ stm->output_latency_ms = 0;
+ }
+ }
+
+ LOG("Audio output latency: %dms", stm->output_latency_ms);
+
+ res =
+ (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get play interface. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE,
- &stm->bufq);
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE,
+ &stm->bufq);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get bufferqueue interface. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME,
- &stm->volume);
-
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME,
+ &stm->volume);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get volume interface. Error code: %lu", res);
return CUBEB_ERROR;
}
res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to register play callback. Error code: %lu", res);
return CUBEB_ERROR;
}
// Work around wilhelm/AudioTrack badness, bug 1221228
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);
- res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER);
+ res = (*stm->play)
+ ->SetCallbackEventsMask(stm->play,
+ (SLuint32)SL_PLAYEVENT_HEADATMARKER);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm);
+ slBufferQueueCallback player_callback = bufferqueue_callback;
+ if (stm->input_enabled) {
+ player_callback = player_fullduplex_callback;
+ }
+ res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to register bufferqueue callback. Error code: %lu", res);
return CUBEB_ERROR;
}
@@ -711,61 +1336,354 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
// will be consumed and kick off the buffer queue callback.
// Note the duration of a single frame is less than 1ms. We don't bother
// adjusting the playback position.
- uint8_t *buf = stm->queuebuf[stm->queuebuf_idx++];
+ uint8_t * buf = stm->queuebuf[stm->queuebuf_idx++];
memset(buf, 0, stm->framesize);
res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize);
assert(res == SL_RESULT_SUCCESS);
}
- *stream = stm;
+ LOG("Cubeb stream init playback success");
return CUBEB_OK;
}
-static void
-opensl_stream_destroy(cubeb_stream * stm)
+static int
+opensl_validate_stream_param(cubeb_stream_params * stream_params)
{
- if (stm->playerObj)
- (*stm->playerObj)->Destroy(stm->playerObj);
- int i;
- for (i = 0; i < NBUFS; i++) {
- free(stm->queuebuf[i]);
+ if ((stream_params &&
+ (stream_params->channels < 1 || stream_params->channels > 32))) {
+ return CUBEB_ERROR_INVALID_FORMAT;
}
- pthread_mutex_destroy(&stm->mutex);
+ if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ LOG("Loopback is not supported");
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ return CUBEB_OK;
+}
- cubeb_resampler_destroy(stm->resampler);
+int
+has_pref_set(cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params, cubeb_stream_prefs pref)
+{
+ return (input_params && input_params->prefs & pref) ||
+ (output_params && output_params->prefs & pref);
+}
- free(stm);
+static int
+opensl_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * stm;
+
+ assert(ctx);
+ if (input_device || output_device) {
+ LOG("Device selection is not supported in Android. The default will be "
+ "used");
+ }
+
+ *stream = NULL;
+
+ int r = opensl_validate_stream_param(output_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Output stream params not valid");
+ return r;
+ }
+ r = opensl_validate_stream_param(input_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Input stream params not valid");
+ return r;
+ }
+
+ stm = calloc(1, sizeof(*stm));
+ assert(stm);
+
+ stm->context = ctx;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->buffer_size_frames =
+ latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES;
+ stm->input_enabled = (input_stream_params) ? 1 : 0;
+ stm->output_enabled = (output_stream_params) ? 1 : 0;
+ stm->shutdown = 1;
+ stm->voice_input =
+ has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output =
+ has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE);
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
+ stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+#ifdef DEBUG
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ r = pthread_mutex_init(&stm->mutex, &attr);
+#else
+ r = pthread_mutex_init(&stm->mutex, NULL);
+#endif
+ assert(r == 0);
+
+ if (output_stream_params) {
+ LOG("Playback params: Rate %d, channels %d, format %d, latency in frames "
+ "%d.",
+ output_stream_params->rate, output_stream_params->channels,
+ output_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_playback(stm, output_stream_params);
+ if (r != CUBEB_OK) {
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ if (input_stream_params) {
+ LOG("Capture params: Rate %d, channels %d, format %d, latency in frames "
+ "%d.",
+ input_stream_params->rate, input_stream_params->channels,
+ input_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_capture(stm, input_stream_params);
+ if (r != CUBEB_OK) {
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ /* Configure resampler*/
+ uint32_t target_sample_rate;
+ if (input_stream_params) {
+ target_sample_rate = input_stream_params->rate;
+ } else {
+ assert(output_stream_params);
+ target_sample_rate = output_stream_params->rate;
+ }
+
+ // Use the actual configured rates for input
+ // and output.
+ cubeb_stream_params input_params;
+ if (input_stream_params) {
+ input_params = *input_stream_params;
+ input_params.rate = stm->input_device_rate;
+ }
+ cubeb_stream_params output_params;
+ if (output_stream_params) {
+ output_params = *output_stream_params;
+ output_params.rate = stm->output_configured_rate;
+ }
+
+ stm->resampler = cubeb_resampler_create(
+ stm, input_stream_params ? &input_params : NULL,
+ output_stream_params ? &output_params : NULL, target_sample_rate,
+ data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+ LOG("Cubeb stream (%p) init success", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_player(cubeb_stream * stm)
+{
+ assert(stm->playerObj);
+ SLuint32 playerState;
+ (*stm->playerObj)->GetState(stm->playerObj, &playerState);
+ if (playerState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ SLuint32 recorderState;
+ (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState);
+ if (recorderState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res =
+ (*stm->recorderItf)
+ ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
}
static int
opensl_stream_start(cubeb_stream * stm)
{
- SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
- if (res != SL_RESULT_SUCCESS)
- return CUBEB_ERROR;
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 0);
+ opensl_set_draining(stm, 0);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_start_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_start_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ LOG("Cubeb stream (%p) started", stm);
return CUBEB_OK;
}
static int
-opensl_stream_stop(cubeb_stream * stm)
+opensl_stop_player(cubeb_stream * stm)
{
+ assert(stm->playerObj);
+ assert(stm->shutdown || stm->draining);
+
SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
- if (res != SL_RESULT_SUCCESS)
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop player. Error code: %lu", res);
return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stop_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ assert(stm->shutdown || stm->draining);
+
+ SLresult res = (*stm->recorderItf)
+ ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_stop(cubeb_stream * stm)
+{
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_stop_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_stop_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ LOG("Cubeb stream (%p) stopped", stm);
return CUBEB_OK;
}
static int
+opensl_destroy_recorder(cubeb_stream * stm)
+{
+ assert(stm);
+ assert(stm->recorderObj);
+
+ if (stm->recorderBufferQueueItf) {
+ SLresult res =
+ (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to clear recorder buffer queue. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ stm->recorderBufferQueueItf = NULL;
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ free(stm->input_buffer_array[i]);
+ }
+ }
+
+ (*stm->recorderObj)->Destroy(stm->recorderObj);
+ stm->recorderObj = NULL;
+ stm->recorderItf = NULL;
+
+ if (stm->input_queue) {
+ array_queue_destroy(stm->input_queue);
+ }
+ free(stm->input_silent_buffer);
+
+ return CUBEB_OK;
+}
+
+static void
+opensl_stream_destroy(cubeb_stream * stm)
+{
+ assert(stm->draining || stm->shutdown);
+
+ if (stm->playerObj) {
+ (*stm->playerObj)->Destroy(stm->playerObj);
+ stm->playerObj = NULL;
+ stm->play = NULL;
+ stm->bufq = NULL;
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ free(stm->queuebuf[i]);
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_destroy_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ }
+
+ pthread_mutex_destroy(&stm->mutex);
+
+ LOG("Cubeb stream (%p) destroyed", stm);
+ free(stm);
+}
+
+static int
opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
SLmillisecond msec;
- uint64_t samplerate;
- SLresult res;
- int r;
- uint32_t mixer_latency;
uint32_t compensation_msec = 0;
+ SLresult res;
res = (*stm->play)->GetPosition(stm->play, &msec);
if (res != SL_RESULT_SUCCESS)
@@ -773,61 +1691,53 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
- if(stm->lastPosition == msec) {
+ if (stm->lastPosition == msec) {
compensation_msec =
- (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000;
+ (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) /
+ 1000000;
} else {
- stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec;
+ stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec;
stm->lastPosition = msec;
}
- samplerate = stm->inputrate;
-
- r = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
- if (r) {
- return CUBEB_ERROR;
- }
+ uint64_t samplerate = stm->user_output_rate;
+ uint32_t output_latency = stm->output_latency_ms;
pthread_mutex_lock(&stm->mutex);
- int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate;
+ int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate /
+ stm->output_configured_rate;
pthread_mutex_unlock(&stm->mutex);
assert(maximum_position >= 0);
- if (msec > mixer_latency) {
+ if (msec > output_latency) {
int64_t unadjusted_position;
if (stm->lastCompensativePosition > msec + compensation_msec) {
// Over compensation, use lastCompensativePosition.
unadjusted_position =
- samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000;
+ samplerate * (stm->lastCompensativePosition - output_latency) / 1000;
} else {
unadjusted_position =
- samplerate * (msec - mixer_latency + compensation_msec) / 1000;
+ samplerate * (msec - output_latency + compensation_msec) / 1000;
stm->lastCompensativePosition = msec + compensation_msec;
}
- *position = unadjusted_position < maximum_position ?
- unadjusted_position : maximum_position;
+ *position = unadjusted_position < maximum_position ? unadjusted_position
+ : maximum_position;
} else {
*position = 0;
}
return CUBEB_OK;
}
-int
+static int
opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
- int r;
- uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms.
-
- /* audio_stream_type_t is an int, so this is okay. */
- r = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
- if (r) {
- return CUBEB_ERROR;
- }
+ assert(stm);
+ assert(latency);
- *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency
- mixer_latency * stm->inputrate / 1000; // AudioFlinger latency
+ uint32_t stream_latency_frames =
+ stm->user_output_rate * stm->output_latency_ms / 1000;
- return CUBEB_OK;
+ return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
}
int
@@ -862,23 +1772,24 @@ opensl_stream_set_volume(cubeb_stream * stm, float volume)
}
static struct cubeb_ops const opensl_ops = {
- .init = opensl_init,
- .get_backend_id = opensl_get_backend_id,
- .get_max_channel_count = opensl_get_max_channel_count,
- .get_min_latency = opensl_get_min_latency,
- .get_preferred_sample_rate = opensl_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = opensl_destroy,
- .stream_init = opensl_stream_init,
- .stream_destroy = opensl_stream_destroy,
- .stream_start = opensl_stream_start,
- .stream_stop = opensl_stream_stop,
- .stream_get_position = opensl_stream_get_position,
- .stream_get_latency = opensl_stream_get_latency,
- .stream_set_volume = opensl_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = opensl_init,
+ .get_backend_id = opensl_get_backend_id,
+ .get_max_channel_count = opensl_get_max_channel_count,
+ .get_min_latency = NULL,
+ .get_preferred_sample_rate = NULL,
+ .enumerate_devices = NULL,
+ .device_collection_destroy = NULL,
+ .destroy = opensl_destroy,
+ .stream_init = opensl_stream_init,
+ .stream_destroy = opensl_stream_destroy,
+ .stream_start = opensl_stream_start,
+ .stream_stop = opensl_stream_stop,
+ .stream_get_position = opensl_stream_get_position,
+ .stream_get_latency = opensl_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = opensl_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_oss.c b/media/libcubeb/src/cubeb_oss.c
new file mode 100644
index 0000000000..083c37ffeb
--- /dev/null
+++ b/media/libcubeb/src/cubeb_oss.c
@@ -0,0 +1,1329 @@
+/*
+ * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
+ * Copyright © 2020 Ka Ho Ng <khng300@gmail.com>
+ * Copyright © 2020 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Ka Ho Ng
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "cubeb_strings.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* Supported well by most hardware. */
+#ifndef OSS_PREFER_RATE
+#define OSS_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef OSS_LATENCY_MS
+#define OSS_LATENCY_MS (8)
+#endif
+
+#ifndef OSS_NFRAGS
+#define OSS_NFRAGS (4)
+#endif
+
+#ifndef OSS_DEFAULT_DEVICE
+#define OSS_DEFAULT_DEVICE "/dev/dsp"
+#endif
+
+#ifndef OSS_DEFAULT_MIXER
+#define OSS_DEFAULT_MIXER "/dev/mixer"
+#endif
+
+#define ENV_AUDIO_DEVICE "AUDIO_DEVICE"
+
+#ifndef OSS_MAX_CHANNELS
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+/*
+ * The current maximum number of channels supported
+ * on FreeBSD is 8.
+ *
+ * Reference: FreeBSD 12.1-RELEASE
+ */
+#define OSS_MAX_CHANNELS (8)
+#elif defined(__sun__)
+/*
+ * The current maximum number of channels supported
+ * on Illumos is 16.
+ *
+ * Reference: PSARC 2008/318
+ */
+#define OSS_MAX_CHANNELS (16)
+#else
+#define OSS_MAX_CHANNELS (2)
+#endif
+#endif
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+#define SNDSTAT_BEGIN_STR "Installed devices:"
+#define SNDSTAT_USER_BEGIN_STR "Installed devices from userspace:"
+#define SNDSTAT_FV_BEGIN_STR "File Versions:"
+#endif
+
+static struct cubeb_ops const oss_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+
+ /* Our intern string store */
+ pthread_mutex_t mutex; /* protects devid_strs */
+ cubeb_strings * devid_strs;
+};
+
+struct oss_stream {
+ oss_devnode_t name;
+ int fd;
+ void * buf;
+
+ struct stream_info {
+ int channels;
+ int sample_rate;
+ int fmt;
+ int precision;
+ } info;
+
+ unsigned int frame_size; /* precision in bytes * channels */
+ bool floating;
+};
+
+struct cubeb_stream {
+ struct cubeb * context;
+ void * user_ptr;
+ pthread_t thread;
+ bool doorbell; /* (m) */
+ pthread_cond_t doorbell_cv; /* (m) */
+ pthread_cond_t stopped_cv; /* (m) */
+ pthread_mutex_t mtx; /* Members protected by this should be marked (m) */
+ bool thread_created; /* (m) */
+ bool running; /* (m) */
+ bool destroying; /* (m) */
+ cubeb_state state; /* (m) */
+ float volume /* (m) */;
+ struct oss_stream play;
+ struct oss_stream record;
+ cubeb_data_callback data_cb;
+ cubeb_state_callback state_cb;
+ uint64_t frames_written /* (m) */;
+ unsigned int nfr; /* Number of frames allocated */
+ unsigned int nfrags;
+ unsigned int bufframes;
+};
+
+static char const *
+oss_cubeb_devid_intern(cubeb * context, char const * devid)
+{
+ char const * is;
+ pthread_mutex_lock(&context->mutex);
+ is = cubeb_strings_intern(context->devid_strs, devid);
+ pthread_mutex_unlock(&context->mutex);
+ return is;
+}
+
+int
+oss_init(cubeb ** context, char const * context_name)
+{
+ cubeb * c;
+
+ (void)context_name;
+ if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ if (cubeb_strings_init(&c->devid_strs) == CUBEB_ERROR) {
+ goto fail;
+ }
+
+ if (pthread_mutex_init(&c->mutex, NULL) != 0) {
+ goto fail;
+ }
+
+ c->ops = &oss_ops;
+ *context = c;
+ return CUBEB_OK;
+
+fail:
+ cubeb_strings_destroy(c->devid_strs);
+ free(c);
+ return CUBEB_ERROR;
+}
+
+static void
+oss_destroy(cubeb * context)
+{
+ pthread_mutex_destroy(&context->mutex);
+ cubeb_strings_destroy(context->devid_strs);
+ free(context);
+}
+
+static char const *
+oss_get_backend_id(cubeb * context)
+{
+ return "oss";
+}
+
+static int
+oss_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+ (void)context;
+
+ *rate = OSS_PREFER_RATE;
+ return CUBEB_OK;
+}
+
+static int
+oss_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ (void)context;
+
+ *max_channels = OSS_MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+oss_get_min_latency(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)context;
+
+ *latency_frames = (OSS_LATENCY_MS * params.rate) / 1000;
+ return CUBEB_OK;
+}
+
+static void
+oss_free_cubeb_device_info_strings(cubeb_device_info * cdi)
+{
+ free((char *)cdi->device_id);
+ free((char *)cdi->friendly_name);
+ free((char *)cdi->group_id);
+ cdi->device_id = NULL;
+ cdi->friendly_name = NULL;
+ cdi->group_id = NULL;
+}
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+/*
+ * Check if the specified DSP is okay for the purpose specified
+ * in type. Here type can only specify one operation each time
+ * this helper is called.
+ *
+ * Return 0 if OK, otherwise 1.
+ */
+static int
+oss_probe_open(const char * dsppath, cubeb_device_type type, int * fdp,
+ oss_audioinfo * resai)
+{
+ oss_audioinfo ai;
+ int error;
+ int oflags = (type == CUBEB_DEVICE_TYPE_INPUT) ? O_RDONLY : O_WRONLY;
+ int dspfd = open(dsppath, oflags);
+ if (dspfd == -1)
+ return 1;
+
+ ai.dev = -1;
+ error = ioctl(dspfd, SNDCTL_AUDIOINFO, &ai);
+ if (error < 0) {
+ close(dspfd);
+ return 1;
+ }
+
+ if (resai)
+ *resai = ai;
+ if (fdp)
+ *fdp = dspfd;
+ else
+ close(dspfd);
+ return 0;
+}
+
+struct sndstat_info {
+ oss_devnode_t devname;
+ const char * desc;
+ cubeb_device_type type;
+ int preferred;
+};
+
+static int
+oss_sndstat_line_parse(char * line, int is_ud, struct sndstat_info * sinfo)
+{
+ char *matchptr = line, *n = NULL;
+ struct sndstat_info res;
+
+ memset(&res, 0, sizeof(res));
+
+ n = strchr(matchptr, ':');
+ if (n == NULL)
+ goto fail;
+ if (is_ud == 0) {
+ unsigned int devunit;
+
+ if (sscanf(matchptr, "pcm%u: ", &devunit) < 1)
+ goto fail;
+
+ if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1)
+ goto fail;
+ } else {
+ if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/")))
+ goto fail;
+
+ strlcpy(res.devname, "/dev/", sizeof(res.devname));
+ strncat(res.devname, matchptr, n - matchptr);
+ }
+ matchptr = n + 1;
+
+ n = strchr(matchptr, '<');
+ if (n == NULL)
+ goto fail;
+ matchptr = n + 1;
+ n = strrchr(matchptr, '>');
+ if (n == NULL)
+ goto fail;
+ *n = 0;
+ res.desc = matchptr;
+ matchptr = n + 1;
+
+ n = strchr(matchptr, '(');
+ if (n == NULL)
+ goto fail;
+ matchptr = n + 1;
+ n = strrchr(matchptr, ')');
+ if (n == NULL)
+ goto fail;
+ *n = 0;
+ if (!isdigit(matchptr[0])) {
+ if (strstr(matchptr, "play") != NULL)
+ res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ if (strstr(matchptr, "rec") != NULL)
+ res.type |= CUBEB_DEVICE_TYPE_INPUT;
+ } else {
+ int p, r;
+ if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2)
+ goto fail;
+ if (p > 0)
+ res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ if (r > 0)
+ res.type |= CUBEB_DEVICE_TYPE_INPUT;
+ }
+ matchptr = n + 1;
+ if (strstr(matchptr, "default") != NULL)
+ res.preferred = 1;
+
+ *sinfo = res;
+ return 0;
+
+fail:
+ return 1;
+}
+
+/*
+ * XXX: On FreeBSD we have to rely on SNDCTL_CARDINFO to get all
+ * the usable audio devices currently, as SNDCTL_AUDIOINFO will
+ * never return directly usable audio device nodes.
+ */
+static int
+oss_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info * devinfop = NULL;
+ char * line = NULL;
+ size_t linecap = 0;
+ FILE * sndstatfp = NULL;
+ int collection_cnt = 0;
+ int is_ud = 0;
+ int skipall = 0;
+
+ devinfop = calloc(1, sizeof(cubeb_device_info));
+ if (devinfop == NULL)
+ goto fail;
+
+ sndstatfp = fopen("/dev/sndstat", "r");
+ if (sndstatfp == NULL)
+ goto fail;
+ while (getline(&line, &linecap, sndstatfp) > 0) {
+ const char * devid = NULL;
+ struct sndstat_info sinfo;
+ oss_audioinfo ai;
+
+ if (!strncmp(line, SNDSTAT_FV_BEGIN_STR, strlen(SNDSTAT_FV_BEGIN_STR))) {
+ skipall = 1;
+ continue;
+ }
+ if (!strncmp(line, SNDSTAT_BEGIN_STR, strlen(SNDSTAT_BEGIN_STR))) {
+ is_ud = 0;
+ skipall = 0;
+ continue;
+ }
+ if (!strncmp(line, SNDSTAT_USER_BEGIN_STR,
+ strlen(SNDSTAT_USER_BEGIN_STR))) {
+ is_ud = 1;
+ skipall = 0;
+ continue;
+ }
+ if (skipall || isblank(line[0]))
+ continue;
+
+ if (oss_sndstat_line_parse(line, is_ud, &sinfo))
+ continue;
+
+ devinfop[collection_cnt].type = 0;
+ switch (sinfo.type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT)
+ continue;
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ if (type & CUBEB_DEVICE_TYPE_INPUT)
+ continue;
+ break;
+ case 0:
+ continue;
+ }
+
+ if (oss_probe_open(sinfo.devname, type, NULL, &ai))
+ continue;
+
+ devid = oss_cubeb_devid_intern(context, sinfo.devname);
+ if (devid == NULL)
+ continue;
+
+ devinfop[collection_cnt].device_id = strdup(sinfo.devname);
+ asprintf((char **)&devinfop[collection_cnt].friendly_name, "%s: %s",
+ sinfo.devname, sinfo.desc);
+ devinfop[collection_cnt].group_id = strdup(sinfo.devname);
+ devinfop[collection_cnt].vendor_name = NULL;
+ if (devinfop[collection_cnt].device_id == NULL ||
+ devinfop[collection_cnt].friendly_name == NULL ||
+ devinfop[collection_cnt].group_id == NULL) {
+ oss_free_cubeb_device_info_strings(&devinfop[collection_cnt]);
+ continue;
+ }
+
+ devinfop[collection_cnt].type = type;
+ devinfop[collection_cnt].devid = devid;
+ devinfop[collection_cnt].state = CUBEB_DEVICE_STATE_ENABLED;
+ devinfop[collection_cnt].preferred =
+ (sinfo.preferred) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+ devinfop[collection_cnt].format = CUBEB_DEVICE_FMT_S16NE;
+ devinfop[collection_cnt].default_format = CUBEB_DEVICE_FMT_S16NE;
+ devinfop[collection_cnt].max_channels = ai.max_channels;
+ devinfop[collection_cnt].default_rate = OSS_PREFER_RATE;
+ devinfop[collection_cnt].max_rate = ai.max_rate;
+ devinfop[collection_cnt].min_rate = ai.min_rate;
+ devinfop[collection_cnt].latency_lo = 0;
+ devinfop[collection_cnt].latency_hi = 0;
+
+ collection_cnt++;
+
+ void * newp =
+ reallocarray(devinfop, collection_cnt + 1, sizeof(cubeb_device_info));
+ if (newp == NULL)
+ goto fail;
+ devinfop = newp;
+ }
+
+ free(line);
+ fclose(sndstatfp);
+
+ collection->count = collection_cnt;
+ collection->device = devinfop;
+
+ return CUBEB_OK;
+
+fail:
+ free(line);
+ if (sndstatfp)
+ fclose(sndstatfp);
+ free(devinfop);
+ return CUBEB_ERROR;
+}
+
+#else
+
+static int
+oss_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ oss_sysinfo si;
+ int error, i;
+ cubeb_device_info * devinfop = NULL;
+ int collection_cnt = 0;
+ int mixer_fd = -1;
+
+ mixer_fd = open(OSS_DEFAULT_MIXER, O_RDWR);
+ if (mixer_fd == -1) {
+ LOG("Failed to open mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno);
+ return CUBEB_ERROR;
+ }
+
+ error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si);
+ if (error) {
+ LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d",
+ OSS_DEFAULT_MIXER, errno);
+ goto fail;
+ }
+
+ devinfop = calloc(si.numaudios, sizeof(cubeb_device_info));
+ if (devinfop == NULL)
+ goto fail;
+
+ collection->count = 0;
+ for (i = 0; i < si.numaudios; i++) {
+ oss_audioinfo ai;
+ cubeb_device_info cdi = {0};
+ const char * devid = NULL;
+
+ ai.dev = i;
+ error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai);
+ if (error)
+ goto fail;
+
+ assert(ai.dev < si.numaudios);
+ if (!ai.enabled)
+ continue;
+
+ cdi.type = 0;
+ switch (ai.caps & DSP_CAP_DUPLEX) {
+ case DSP_CAP_INPUT:
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT)
+ continue;
+ break;
+ case DSP_CAP_OUTPUT:
+ if (type & CUBEB_DEVICE_TYPE_INPUT)
+ continue;
+ break;
+ case 0:
+ continue;
+ }
+ cdi.type = type;
+
+ devid = oss_cubeb_devid_intern(context, ai.devnode);
+ cdi.device_id = strdup(ai.name);
+ cdi.friendly_name = strdup(ai.name);
+ cdi.group_id = strdup(ai.name);
+ if (devid == NULL || cdi.device_id == NULL || cdi.friendly_name == NULL ||
+ cdi.group_id == NULL) {
+ oss_free_cubeb_device_info_strings(&cdi);
+ continue;
+ }
+
+ cdi.devid = devid;
+ cdi.vendor_name = NULL;
+ cdi.state = CUBEB_DEVICE_STATE_ENABLED;
+ cdi.preferred = CUBEB_DEVICE_PREF_NONE;
+ cdi.format = CUBEB_DEVICE_FMT_S16NE;
+ cdi.default_format = CUBEB_DEVICE_FMT_S16NE;
+ cdi.max_channels = ai.max_channels;
+ cdi.default_rate = OSS_PREFER_RATE;
+ cdi.max_rate = ai.max_rate;
+ cdi.min_rate = ai.min_rate;
+ cdi.latency_lo = 0;
+ cdi.latency_hi = 0;
+
+ devinfop[collection_cnt++] = cdi;
+ }
+
+ collection->count = collection_cnt;
+ collection->device = devinfop;
+
+ if (mixer_fd != -1)
+ close(mixer_fd);
+ return CUBEB_OK;
+
+fail:
+ if (mixer_fd != -1)
+ close(mixer_fd);
+ free(devinfop);
+ return CUBEB_ERROR;
+}
+
+#endif
+
+static int
+oss_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ size_t i;
+ for (i = 0; i < collection->count; i++) {
+ oss_free_cubeb_device_info_strings(&collection->device[i]);
+ }
+ free(collection->device);
+ collection->device = NULL;
+ collection->count = 0;
+ return 0;
+}
+
+static unsigned int
+oss_chn_from_cubeb(cubeb_channel chn)
+{
+ switch (chn) {
+ case CHANNEL_FRONT_LEFT:
+ return CHID_L;
+ case CHANNEL_FRONT_RIGHT:
+ return CHID_R;
+ case CHANNEL_FRONT_CENTER:
+ return CHID_C;
+ case CHANNEL_LOW_FREQUENCY:
+ return CHID_LFE;
+ case CHANNEL_BACK_LEFT:
+ return CHID_LR;
+ case CHANNEL_BACK_RIGHT:
+ return CHID_RR;
+ case CHANNEL_SIDE_LEFT:
+ return CHID_LS;
+ case CHANNEL_SIDE_RIGHT:
+ return CHID_RS;
+ default:
+ return CHID_UNDEF;
+ }
+}
+
+static unsigned long long
+oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout)
+{
+ unsigned int i, nchns = 0;
+ unsigned long long chnorder = 0;
+
+ for (i = 0; layout; i++, layout >>= 1) {
+ unsigned long long chid = oss_chn_from_cubeb((layout & 1) << i);
+ if (chid == CHID_UNDEF)
+ continue;
+
+ chnorder |= (chid & 0xf) << nchns * 4;
+ nchns++;
+ }
+
+ return chnorder;
+}
+
+static int
+oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+ struct stream_info * sinfo)
+{
+ unsigned long long chnorder;
+
+ sinfo->channels = params->channels;
+ sinfo->sample_rate = params->rate;
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ sinfo->fmt = AFMT_S16_LE;
+ sinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ sinfo->fmt = AFMT_S16_BE;
+ sinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ sinfo->fmt = AFMT_S32_NE;
+ sinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ if (ioctl(fd, SNDCTL_DSP_CHANNELS, &sinfo->channels) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, SNDCTL_DSP_SETFMT, &sinfo->fmt) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, SNDCTL_DSP_SPEED, &sinfo->sample_rate) == -1) {
+ return CUBEB_ERROR;
+ }
+ /* Mono layout is an exception */
+ if (params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ params->layout != CUBEB_LAYOUT_MONO) {
+ chnorder = oss_cubeb_layout_to_chnorder(params->layout);
+ if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1)
+ LOG("Non-fatal error %d occured when setting channel order.", errno);
+ }
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_stop(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mtx);
+ if (s->thread_created && s->running) {
+ s->running = false;
+ s->doorbell = false;
+ pthread_cond_wait(&s->stopped_cv, &s->mtx);
+ }
+ if (s->state != CUBEB_STATE_STOPPED) {
+ s->state = CUBEB_STATE_STOPPED;
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STOPPED);
+ } else {
+ pthread_mutex_unlock(&s->mtx);
+ }
+ return CUBEB_OK;
+}
+
+static void
+oss_stream_destroy(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mtx);
+ if (s->thread_created) {
+ s->destroying = true;
+ s->doorbell = true;
+ pthread_cond_signal(&s->doorbell_cv);
+ }
+ pthread_mutex_unlock(&s->mtx);
+ pthread_join(s->thread, NULL);
+
+ pthread_cond_destroy(&s->doorbell_cv);
+ pthread_cond_destroy(&s->stopped_cv);
+ pthread_mutex_destroy(&s->mtx);
+ if (s->play.fd != -1) {
+ close(s->play.fd);
+ }
+ if (s->record.fd != -1) {
+ close(s->record.fd);
+ }
+ free(s->play.buf);
+ free(s->record.buf);
+ free(s);
+}
+
+static void
+oss_float_to_linear32(void * buf, unsigned sample_count, float vol)
+{
+ float * in = buf;
+ int32_t * out = buf;
+ int32_t * tail = out + sample_count;
+
+ while (out < tail) {
+ int64_t f = *(in++) * vol * 0x80000000LL;
+ if (f < -INT32_MAX)
+ f = -INT32_MAX;
+ else if (f > INT32_MAX)
+ f = INT32_MAX;
+ *(out++) = f;
+ }
+}
+
+static void
+oss_linear32_to_float(void * buf, unsigned sample_count)
+{
+ int32_t * in = buf;
+ float * out = buf;
+ float * tail = out + sample_count;
+
+ while (out < tail) {
+ *(out++) = (1.0 / 0x80000000LL) * *(in++);
+ }
+}
+
+static void
+oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
+{
+ unsigned i;
+ int32_t multiplier = vol * 0x8000;
+
+ for (i = 0; i < sample_count; ++i) {
+ buf[i] = (buf[i] * multiplier) >> 15;
+ }
+}
+
+static int
+oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->record.frame_size;
+ size_t read_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) <
+ 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ read_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+static int
+oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->play.frame_size;
+ size_t write_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ pthread_mutex_lock(&s->mtx);
+ s->frames_written += n / s->play.frame_size;
+ pthread_mutex_unlock(&s->mtx);
+ write_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+static int
+oss_wait_playfd_for_space(cubeb_stream * s)
+{
+ struct pollfd pfd;
+
+ pfd.events = POLLOUT | POLLHUP;
+ pfd.revents = 0;
+ pfd.fd = s->play.fd;
+
+ if (poll(&pfd, 1, 2000) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ if (pfd.revents & POLLHUP) {
+ return CUBEB_ERROR;
+ }
+ return 0;
+}
+
+static int
+oss_wait_recfd_for_space(cubeb_stream * s)
+{
+ struct pollfd pfd;
+
+ pfd.events = POLLIN | POLLHUP;
+ pfd.revents = 0;
+ pfd.fd = s->record.fd;
+
+ if (poll(&pfd, 1, 2000) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ if (pfd.revents & POLLHUP) {
+ return CUBEB_ERROR;
+ }
+ return 0;
+}
+
+/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
+static int
+oss_audio_loop(cubeb_stream * s, cubeb_state * new_state)
+{
+ cubeb_state state = CUBEB_STATE_STOPPED;
+ int trig = 0, drain = 0;
+ const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
+ long nfr = 0;
+
+ if (record_on) {
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
+ LOG("Error %d occured when setting trigger on record fd", errno);
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ trig |= PCM_ENABLE_INPUT;
+ memset(s->record.buf, 0, s->bufframes * s->record.frame_size);
+
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
+ LOG("Error %d occured when setting trigger on record fd", errno);
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+
+ if (!play_on && !record_on) {
+ /*
+ * Stop here if the stream is not play & record stream,
+ * play-only stream or record-only stream
+ */
+
+ goto breakdown;
+ }
+
+ while (1) {
+ pthread_mutex_lock(&s->mtx);
+ if (!s->running || s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+
+ long got = 0;
+ if (nfr > 0) {
+ if (record_on) {
+ if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (s->record.floating) {
+ oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
+ }
+ }
+ got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
+ if (got == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (got < nfr) {
+ if (s->play.fd != -1) {
+ drain = 1;
+ } else {
+ /*
+ * This is a record-only stream and number of frames
+ * returned from data_cb() is smaller than number
+ * of frames required to read. Stop here.
+ */
+ state = CUBEB_STATE_STOPPED;
+ goto breakdown;
+ }
+ }
+
+ if (got > 0 && play_on) {
+ float vol;
+
+ pthread_mutex_lock(&s->mtx);
+ vol = s->volume;
+ pthread_mutex_unlock(&s->mtx);
+
+ if (s->play.floating) {
+ oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
+ } else {
+ oss_linear16_set_vol((int16_t *)s->play.buf,
+ s->play.info.channels * got, vol);
+ }
+ if (oss_put_play_frames(s, got) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+ if (drain) {
+ state = CUBEB_STATE_DRAINED;
+ goto breakdown;
+ }
+ }
+
+ nfr = s->bufframes;
+
+ if (record_on) {
+ long mfr;
+
+ if (oss_wait_recfd_for_space(s) != 0) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ audio_buf_info bi;
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ mfr = (bi.fragsize * bi.fragments) / s->record.frame_size;
+ if (nfr > mfr)
+ nfr = mfr;
+ }
+
+ if (play_on) {
+ long mfr;
+
+ if (oss_wait_playfd_for_space(s) != 0) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ audio_buf_info bi;
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ mfr = (bi.fragsize * bi.fragments) / s->play.frame_size;
+ if (nfr > mfr)
+ nfr = mfr;
+ }
+ }
+
+ return 1;
+
+breakdown:
+ pthread_mutex_lock(&s->mtx);
+ *new_state = s->state = state;
+ s->running = false;
+ pthread_mutex_unlock(&s->mtx);
+ return 0;
+}
+
+static void *
+oss_io_routine(void * arg)
+{
+ cubeb_stream * s = arg;
+ cubeb_state new_state;
+ int stopped;
+
+ do {
+ pthread_mutex_lock(&s->mtx);
+ if (s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+
+ stopped = oss_audio_loop(s, &new_state);
+ if (s->record.fd != -1)
+ ioctl(s->record.fd, SNDCTL_DSP_HALT_INPUT, NULL);
+ if (!stopped)
+ s->state_cb(s, s->user_ptr, new_state);
+
+ pthread_mutex_lock(&s->mtx);
+ pthread_cond_signal(&s->stopped_cv);
+ if (s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ while (!s->doorbell) {
+ pthread_cond_wait(&s->doorbell_cv, &s->mtx);
+ }
+ s->doorbell = false;
+ pthread_mutex_unlock(&s->mtx);
+ } while (1);
+
+ pthread_mutex_lock(&s->mtx);
+ s->thread_created = false;
+ pthread_mutex_unlock(&s->mtx);
+ return NULL;
+}
+
+static inline int
+oss_calc_frag_shift(unsigned int frames, unsigned int frame_size)
+{
+ int n = 4;
+ int blksize = (frames * frame_size + OSS_NFRAGS - 1) / OSS_NFRAGS;
+ while ((1 << n) < blksize)
+ n++;
+ return n;
+}
+
+static inline int
+oss_get_frag_params(unsigned int shift)
+{
+ return (OSS_NFRAGS << 16) | shift;
+}
+
+static int
+oss_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int ret = CUBEB_OK;
+ unsigned int playnfr = 0, recnfr = 0;
+ cubeb_stream * s = NULL;
+ const char * defdsp;
+
+ if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0')
+ defdsp = OSS_DEFAULT_DEVICE;
+
+ (void)stream_name;
+ if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->state = CUBEB_STATE_STOPPED;
+ s->record.fd = s->play.fd = -1;
+ s->nfr = latency_frames;
+ if (input_device != NULL) {
+ strlcpy(s->record.name, input_device, sizeof(s->record.name));
+ } else {
+ strlcpy(s->record.name, defdsp, sizeof(s->record.name));
+ }
+ if (output_device != NULL) {
+ strlcpy(s->play.name, output_device, sizeof(s->play.name));
+ } else {
+ strlcpy(s->play.name, defdsp, sizeof(s->play.name));
+ }
+ if (input_stream_params != NULL) {
+ unsigned int nb_channels;
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout);
+ if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ nb_channels != input_stream_params->channels) {
+ LOG("input_stream_params->layout does not match "
+ "input_stream_params->channels");
+ ret = CUBEB_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ if (s->record.fd == -1) {
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
+ LOG("Audio device \"%s\" could not be opened as read-only",
+ s->record.name);
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ if ((ret = oss_copy_params(s->record.fd, s, input_stream_params,
+ &s->record.info)) != CUBEB_OK) {
+ LOG("Setting record params failed");
+ goto error;
+ }
+ s->record.floating =
+ (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ s->record.frame_size =
+ s->record.info.channels * (s->record.info.precision / 8);
+ recnfr = (1 << oss_calc_frag_shift(s->nfr, s->record.frame_size)) /
+ s->record.frame_size;
+ }
+ if (output_stream_params != NULL) {
+ unsigned int nb_channels;
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ nb_channels =
+ cubeb_channel_layout_nb_channels(output_stream_params->layout);
+ if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ nb_channels != output_stream_params->channels) {
+ LOG("output_stream_params->layout does not match "
+ "output_stream_params->channels");
+ ret = CUBEB_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ if (s->play.fd == -1) {
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
+ LOG("Audio device \"%s\" could not be opened as write-only",
+ s->play.name);
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ if ((ret = oss_copy_params(s->play.fd, s, output_stream_params,
+ &s->play.info)) != CUBEB_OK) {
+ LOG("Setting play params failed");
+ goto error;
+ }
+ s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
+ playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) /
+ s->play.frame_size;
+ }
+ /*
+ * Use the largest nframes among playing and recording streams to set OSS
+ * buffer size. After that, use the smallest allocated nframes among both
+ * direction to allocate our temporary buffers.
+ */
+ s->nfr = (playnfr > recnfr) ? playnfr : recnfr;
+ s->nfrags = OSS_NFRAGS;
+ if (s->play.fd != -1) {
+ int frag =
+ oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size));
+ if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ frag);
+ audio_buf_info bi;
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
+ LOG("Failed to get play fd's buffer info.");
+ else {
+ if (bi.fragsize / s->play.frame_size < s->nfr)
+ s->nfr = bi.fragsize / s->play.frame_size;
+ }
+ }
+ if (s->record.fd != -1) {
+ int frag =
+ oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size));
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ frag);
+ audio_buf_info bi;
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
+ LOG("Failed to get record fd's buffer info.");
+ else {
+ if (bi.fragsize / s->record.frame_size < s->nfr)
+ s->nfr = bi.fragsize / s->record.frame_size;
+ }
+ }
+ s->bufframes = s->nfr * s->nfrags;
+ s->context = context;
+ s->volume = 1.0;
+ s->state_cb = state_callback;
+ s->data_cb = data_callback;
+ s->user_ptr = user_ptr;
+
+ if (pthread_mutex_init(&s->mtx, NULL) != 0) {
+ LOG("Failed to create mutex");
+ goto error;
+ }
+ if (pthread_cond_init(&s->doorbell_cv, NULL) != 0) {
+ LOG("Failed to create cv");
+ goto error;
+ }
+ if (pthread_cond_init(&s->stopped_cv, NULL) != 0) {
+ LOG("Failed to create cv");
+ goto error;
+ }
+ s->doorbell = false;
+
+ if (s->play.fd != -1) {
+ if ((s->play.buf = calloc(s->bufframes, s->play.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ }
+ if (s->record.fd != -1) {
+ if ((s->record.buf = calloc(s->bufframes, s->record.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ }
+
+ *stream = s;
+ return CUBEB_OK;
+error:
+ if (s != NULL) {
+ oss_stream_destroy(s);
+ }
+ return ret;
+}
+
+static int
+oss_stream_thr_create(cubeb_stream * s)
+{
+ if (s->thread_created) {
+ s->doorbell = true;
+ pthread_cond_signal(&s->doorbell_cv);
+ return CUBEB_OK;
+ }
+
+ if (pthread_create(&s->thread, NULL, oss_io_routine, s) != 0) {
+ LOG("Couldn't create thread");
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_start(cubeb_stream * s)
+{
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+ pthread_mutex_lock(&s->mtx);
+ /* Disallow starting an already started stream */
+ assert(!s->running && s->state != CUBEB_STATE_STARTED);
+ if (oss_stream_thr_create(s) != CUBEB_OK) {
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_ERROR);
+ return CUBEB_ERROR;
+ }
+ s->state = CUBEB_STATE_STARTED;
+ s->thread_created = true;
+ s->running = true;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_get_position(cubeb_stream * s, uint64_t * position)
+{
+ pthread_mutex_lock(&s->mtx);
+ *position = s->frames_written;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_get_latency(cubeb_stream * s, uint32_t * latency)
+{
+ int delay;
+
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETODELAY, &delay) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ /* Return number of frames there */
+ *latency = delay / s->play.frame_size;
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ if (volume < 0.0)
+ volume = 0.0;
+ else if (volume > 1.0)
+ volume = 1.0;
+ pthread_mutex_lock(&stream->mtx);
+ stream->volume = volume;
+ pthread_mutex_unlock(&stream->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
+{
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL) {
+ return CUBEB_ERROR;
+ }
+ (*device)->input_name =
+ stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
+ (*device)->output_name =
+ stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const oss_ops = {
+ .init = oss_init,
+ .get_backend_id = oss_get_backend_id,
+ .get_max_channel_count = oss_get_max_channel_count,
+ .get_min_latency = oss_get_min_latency,
+ .get_preferred_sample_rate = oss_get_preferred_sample_rate,
+ .enumerate_devices = oss_enumerate_devices,
+ .device_collection_destroy = oss_device_collection_destroy,
+ .destroy = oss_destroy,
+ .stream_init = oss_stream_init,
+ .stream_destroy = oss_stream_destroy,
+ .stream_start = oss_stream_start,
+ .stream_stop = oss_stream_stop,
+ .stream_get_position = oss_stream_get_position,
+ .stream_get_latency = oss_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = oss_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = oss_get_current_device,
+ .stream_device_destroy = oss_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_osx_run_loop.h b/media/libcubeb/src/cubeb_osx_run_loop.h
new file mode 100644
index 0000000000..8d88a37140
--- /dev/null
+++ b/media/libcubeb/src/cubeb_osx_run_loop.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* On OSX 10.6 and after, the notification callbacks from the audio hardware are
+ * called on the main thread. Setting the kAudioHardwarePropertyRunLoop property
+ * to null tells the OSX to use a separate thread for that.
+ *
+ * This has to be called only once per process, so it is in a separate header
+ * for easy integration in other code bases. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void
+cubeb_set_coreaudio_notification_runloop();
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c
deleted file mode 100644
index 4f474452d4..0000000000
--- a/media/libcubeb/src/cubeb_pulse.c
+++ /dev/null
@@ -1,1385 +0,0 @@
-/*
- * Copyright © 2011 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-#undef NDEBUG
-#include <assert.h>
-#include <dlfcn.h>
-#include <stdlib.h>
-#include <pulse/pulseaudio.h>
-#include <string.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include <stdio.h>
-
-#ifdef DISABLE_LIBPULSE_DLOPEN
-#define WRAP(x) x
-#else
-#define WRAP(x) cubeb_##x
-#define LIBPULSE_API_VISIT(X) \
- X(pa_channel_map_can_balance) \
- X(pa_channel_map_init_auto) \
- X(pa_context_connect) \
- X(pa_context_disconnect) \
- X(pa_context_drain) \
- X(pa_context_get_server_info) \
- X(pa_context_get_sink_info_by_name) \
- X(pa_context_get_sink_info_list) \
- X(pa_context_get_source_info_list) \
- X(pa_context_get_state) \
- X(pa_context_new) \
- X(pa_context_rttime_new) \
- X(pa_context_set_sink_input_volume) \
- X(pa_context_set_state_callback) \
- X(pa_context_unref) \
- X(pa_cvolume_set) \
- X(pa_cvolume_set_balance) \
- X(pa_frame_size) \
- X(pa_operation_get_state) \
- X(pa_operation_unref) \
- X(pa_proplist_gets) \
- X(pa_rtclock_now) \
- X(pa_stream_begin_write) \
- X(pa_stream_cancel_write) \
- X(pa_stream_connect_playback) \
- X(pa_stream_cork) \
- X(pa_stream_disconnect) \
- X(pa_stream_get_channel_map) \
- X(pa_stream_get_index) \
- X(pa_stream_get_latency) \
- X(pa_stream_get_sample_spec) \
- X(pa_stream_get_state) \
- X(pa_stream_get_time) \
- X(pa_stream_new) \
- X(pa_stream_set_state_callback) \
- X(pa_stream_set_write_callback) \
- X(pa_stream_unref) \
- X(pa_stream_update_timing_info) \
- X(pa_stream_write) \
- X(pa_sw_volume_from_linear) \
- X(pa_threaded_mainloop_free) \
- X(pa_threaded_mainloop_get_api) \
- X(pa_threaded_mainloop_in_thread) \
- X(pa_threaded_mainloop_lock) \
- X(pa_threaded_mainloop_new) \
- X(pa_threaded_mainloop_signal) \
- X(pa_threaded_mainloop_start) \
- X(pa_threaded_mainloop_stop) \
- X(pa_threaded_mainloop_unlock) \
- X(pa_threaded_mainloop_wait) \
- X(pa_usec_to_bytes) \
- X(pa_stream_set_read_callback) \
- X(pa_stream_connect_record) \
- X(pa_stream_readable_size) \
- X(pa_stream_writable_size) \
- X(pa_stream_peek) \
- X(pa_stream_drop) \
- X(pa_stream_get_buffer_attr) \
- X(pa_stream_get_device_name) \
- X(pa_context_set_subscribe_callback) \
- X(pa_context_subscribe) \
- X(pa_mainloop_api_once) \
-
-#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
-LIBPULSE_API_VISIT(MAKE_TYPEDEF);
-#undef MAKE_TYPEDEF
-#endif
-
-static struct cubeb_ops const pulse_ops;
-
-struct cubeb {
- struct cubeb_ops const * ops;
- void * libpulse;
- pa_threaded_mainloop * mainloop;
- pa_context * context;
- pa_sink_info * default_sink_info;
- char * context_name;
- int error;
- cubeb_device_collection_changed_callback collection_changed_callback;
- void * collection_changed_user_ptr;
-};
-
-struct cubeb_stream {
- cubeb * context;
- pa_stream * output_stream;
- pa_stream * input_stream;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * user_ptr;
- pa_time_event * drain_timer;
- pa_sample_spec output_sample_spec;
- pa_sample_spec input_sample_spec;
- int shutdown;
- float volume;
- cubeb_state state;
-};
-
-static const float PULSE_NO_GAIN = -1.0;
-
-enum cork_state {
- UNCORK = 0,
- CORK = 1 << 0,
- NOTIFY = 1 << 1
-};
-
-static void
-sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
-{
- (void)context;
- cubeb * ctx = u;
- if (!eol) {
- free(ctx->default_sink_info);
- ctx->default_sink_info = malloc(sizeof(pa_sink_info));
- memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
- }
- WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
-}
-
-static void
-server_info_callback(pa_context * context, const pa_server_info * info, void * u)
-{
- WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
-}
-
-static void
-context_state_callback(pa_context * c, void * u)
-{
- cubeb * ctx = u;
- if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
- ctx->error = 1;
- }
- WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
-}
-
-static void
-context_notify_callback(pa_context * c, void * u)
-{
- (void)c;
- cubeb * ctx = u;
- WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
-}
-
-static void
-stream_success_callback(pa_stream * s, int success, void * u)
-{
- (void)s;
- (void)success;
- cubeb_stream * stm = u;
- WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
-}
-
-static void
-stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
-{
- stm->state = s;
- stm->state_callback(stm, stm->user_ptr, s);
-}
-
-static void
-stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
-{
- (void)a;
- (void)tv;
- cubeb_stream * stm = u;
- assert(stm->drain_timer == e);
- stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
- /* there's no pa_rttime_free, so use this instead. */
- a->time_free(stm->drain_timer);
- stm->drain_timer = NULL;
- WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
-}
-
-static void
-stream_state_callback(pa_stream * s, void * u)
-{
- cubeb_stream * stm = u;
- if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
- stream_state_change_callback(stm, CUBEB_STATE_ERROR);
- }
- WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
-}
-
-static void
-trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cubeb_stream * stm)
-{
- void * buffer;
- size_t size;
- int r;
- long got;
- size_t towrite, read_offset;
- size_t frame_size;
-
- frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
- assert(nbytes % frame_size == 0);
-
- towrite = nbytes;
- read_offset = 0;
- while (towrite) {
- size = towrite;
- r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
- // Note: this has failed running under rr on occassion - needs investigation.
- assert(r == 0);
- assert(size > 0);
- assert(size % frame_size == 0);
-
- LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd", size, read_offset);
- got = stm->data_callback(stm, stm->user_ptr, (uint8_t const *)input_data + read_offset, buffer, size / frame_size);
- if (got < 0) {
- WRAP(pa_stream_cancel_write)(s);
- stm->shutdown = 1;
- return;
- }
- // If more iterations move offset of read buffer
- if (input_data) {
- size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
- read_offset += (size / frame_size) * in_frame_size;
- }
-
- if (stm->volume != PULSE_NO_GAIN) {
- uint32_t samples = size * stm->output_sample_spec.channels / frame_size ;
-
- if (stm->output_sample_spec.format == PA_SAMPLE_S16BE ||
- stm->output_sample_spec.format == PA_SAMPLE_S16LE) {
- short * b = buffer;
- for (uint32_t i = 0; i < samples; i++) {
- b[i] *= stm->volume;
- }
- } else {
- float * b = buffer;
- for (uint32_t i = 0; i < samples; i++) {
- b[i] *= stm->volume;
- }
- }
- }
-
- r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
- assert(r == 0);
-
- if ((size_t) got < size / frame_size) {
- pa_usec_t latency = 0;
- r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
- if (r == -PA_ERR_NODATA) {
- /* this needs a better guess. */
- latency = 100 * PA_USEC_PER_MSEC;
- }
- assert(r == 0 || r == -PA_ERR_NODATA);
- /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
- /* arbitrary safety margin: double the current latency. */
- assert(!stm->drain_timer);
- stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
- stm->shutdown = 1;
- return;
- }
-
- towrite -= size;
- }
-
- assert(towrite == 0);
-}
-
-static int
-read_from_input(pa_stream * s, void const ** buffer, size_t * size)
-{
- size_t readable_size = WRAP(pa_stream_readable_size)(s);
- if (readable_size > 0) {
- if (WRAP(pa_stream_peek)(s, buffer, size) < 0) {
- return -1;
- }
- }
- return readable_size;
-}
-
-static void
-stream_write_callback(pa_stream * s, size_t nbytes, void * u)
-{
- LOGV("Output callback to be written buffer size %zd", nbytes);
- cubeb_stream * stm = u;
- if (stm->shutdown ||
- stm->state != CUBEB_STATE_STARTED) {
- return;
- }
-
- if (!stm->input_stream){
- // Output/playback only operation.
- // Write directly to output
- assert(!stm->input_stream && stm->output_stream);
- trigger_user_callback(s, NULL, nbytes, stm);
- }
-}
-
-static void
-stream_read_callback(pa_stream * s, size_t nbytes, void * u)
-{
- LOGV("Input callback buffer size %zd", nbytes);
- cubeb_stream * stm = u;
- if (stm->shutdown) {
- return;
- }
-
- void const * read_data = NULL;
- size_t read_size;
- while (read_from_input(s, &read_data, &read_size) > 0) {
- /* read_data can be NULL in case of a hole. */
- if (read_data) {
- size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
- size_t read_frames = read_size / in_frame_size;
-
- if (stm->output_stream) {
- // input/capture + output/playback operation
- size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
- size_t write_size = read_frames * out_frame_size;
- // Offer full duplex data for writing
- trigger_user_callback(stm->output_stream, read_data, write_size, stm);
- } else {
- // input/capture only operation. Call callback directly
- long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL, read_frames);
- if (got < 0 || (size_t) got != read_frames) {
- WRAP(pa_stream_cancel_write)(s);
- stm->shutdown = 1;
- break;
- }
- }
- }
- if (read_size > 0) {
- WRAP(pa_stream_drop)(s);
- }
-
- if (stm->shutdown) {
- return;
- }
- }
-}
-
-static int
-wait_until_context_ready(cubeb * ctx)
-{
- for (;;) {
- pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
- if (!PA_CONTEXT_IS_GOOD(state))
- return -1;
- if (state == PA_CONTEXT_READY)
- break;
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- }
- return 0;
-}
-
-static int
-wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop)
-{
- if (!stream || !mainloop){
- return -1;
- }
- for (;;) {
- pa_stream_state_t state = WRAP(pa_stream_get_state)(stream);
- if (!PA_STREAM_IS_GOOD(state))
- return -1;
- if (state == PA_STREAM_READY)
- break;
- WRAP(pa_threaded_mainloop_wait)(mainloop);
- }
- return 0;
-}
-
-static int
-wait_until_stream_ready(cubeb_stream * stm)
-{
- if (stm->output_stream &&
- wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) == -1) {
- return -1;
- }
- if(stm->input_stream &&
- wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) == -1) {
- return -1;
- }
- return 0;
-}
-
-static int
-operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
-{
- while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
- return -1;
- }
- if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
- return -1;
- }
- }
- return 0;
-}
-
-static void
-cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
-{
- pa_operation * o;
- if (!io_stream) {
- return;
- }
- o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback, stm);
- if (o) {
- operation_wait(stm->context, io_stream, o);
- WRAP(pa_operation_unref)(o);
- }
-}
-
-static void
-stream_cork(cubeb_stream * stm, enum cork_state state)
-{
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- cork_io_stream(stm, stm->output_stream, state);
- cork_io_stream(stm, stm->input_stream, state);
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- if (state & NOTIFY) {
- stream_state_change_callback(stm, state & CORK ? CUBEB_STATE_STOPPED
- : CUBEB_STATE_STARTED);
- }
-}
-
-static int
-stream_update_timing_info(cubeb_stream * stm)
-{
- int r = -1;
- pa_operation * o = NULL;
- if (stm->output_stream) {
- o = WRAP(pa_stream_update_timing_info)(stm->output_stream, stream_success_callback, stm);
- if (o) {
- r = operation_wait(stm->context, stm->output_stream, o);
- WRAP(pa_operation_unref)(o);
- }
- if (r != 0) {
- return r;
- }
- }
-
- if (stm->input_stream) {
- o = WRAP(pa_stream_update_timing_info)(stm->input_stream, stream_success_callback, stm);
- if (o) {
- r = operation_wait(stm->context, stm->input_stream, o);
- WRAP(pa_operation_unref)(o);
- }
- }
-
- return r;
-}
-
-static void pulse_context_destroy(cubeb * ctx);
-static void pulse_destroy(cubeb * ctx);
-
-static int
-pulse_context_init(cubeb * ctx)
-{
- if (ctx->context) {
- assert(ctx->error == 1);
- pulse_context_destroy(ctx);
- }
-
- ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
- ctx->context_name);
- if (!ctx->context) {
- return -1;
- }
- WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
-
- if (wait_until_context_ready(ctx) != 0) {
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
- pulse_context_destroy(ctx);
- ctx->context = NULL;
- return -1;
- }
-
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- ctx->error = 0;
-
- return 0;
-}
-
-/*static*/ int
-pulse_init(cubeb ** context, char const * context_name)
-{
- void * libpulse = NULL;
- cubeb * ctx;
-
- *context = NULL;
-
-#ifndef DISABLE_LIBPULSE_DLOPEN
- libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
- if (!libpulse) {
- return CUBEB_ERROR;
- }
-
-#define LOAD(x) { \
- cubeb_##x = dlsym(libpulse, #x); \
- if (!cubeb_##x) { \
- dlclose(libpulse); \
- return CUBEB_ERROR; \
- } \
- }
-
- LIBPULSE_API_VISIT(LOAD);
-#undef LOAD
-#endif
-
- ctx = calloc(1, sizeof(*ctx));
- assert(ctx);
-
- ctx->ops = &pulse_ops;
- ctx->libpulse = libpulse;
-
- ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
- ctx->default_sink_info = NULL;
-
- WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
-
- ctx->context_name = context_name ? strdup(context_name) : NULL;
- if (pulse_context_init(ctx) != 0) {
- pulse_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- *context = ctx;
-
- return CUBEB_OK;
-}
-
-static char const *
-pulse_get_backend_id(cubeb * ctx)
-{
- (void)ctx;
- return "pulse";
-}
-
-static int
-pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
-{
- (void)ctx;
- assert(ctx && max_channels);
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- while (!ctx->default_sink_info) {
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- }
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- *max_channels = ctx->default_sink_info->channel_map.channels;
-
- return CUBEB_OK;
-}
-
-static int
-pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- assert(ctx && rate);
- (void)ctx;
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- while (!ctx->default_sink_info) {
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- }
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- *rate = ctx->default_sink_info->sample_spec.rate;
-
- return CUBEB_OK;
-}
-
-static int
-pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
-{
- (void)ctx;
- // According to PulseAudio developers, this is a safe minimum.
- *latency_frames = 25 * params.rate / 1000;
-
- return CUBEB_OK;
-}
-
-static void
-pulse_context_destroy(cubeb * ctx)
-{
- pa_operation * o;
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
- if (o) {
- operation_wait(ctx, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
- WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
- WRAP(pa_context_disconnect)(ctx->context);
- WRAP(pa_context_unref)(ctx->context);
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-}
-
-static void
-pulse_destroy(cubeb * ctx)
-{
- if (ctx->context_name) {
- free(ctx->context_name);
- }
- if (ctx->context) {
- pulse_context_destroy(ctx);
- }
-
- if (ctx->mainloop) {
- WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
- WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
- }
-
- if (ctx->libpulse) {
- dlclose(ctx->libpulse);
- }
- if (ctx->default_sink_info) {
- free(ctx->default_sink_info);
- }
- free(ctx);
-}
-
-static void pulse_stream_destroy(cubeb_stream * stm);
-
-static pa_sample_format_t
-to_pulse_format(cubeb_sample_format format)
-{
- switch (format) {
- case CUBEB_SAMPLE_S16LE:
- return PA_SAMPLE_S16LE;
- case CUBEB_SAMPLE_S16BE:
- return PA_SAMPLE_S16BE;
- case CUBEB_SAMPLE_FLOAT32LE:
- return PA_SAMPLE_FLOAT32LE;
- case CUBEB_SAMPLE_FLOAT32BE:
- return PA_SAMPLE_FLOAT32BE;
- default:
- return PA_SAMPLE_INVALID;
- }
-}
-
-static int
-create_pa_stream(cubeb_stream * stm,
- pa_stream ** pa_stm,
- cubeb_stream_params * stream_params,
- char const * stream_name)
-{
- assert(stm && stream_params);
- *pa_stm = NULL;
- pa_sample_spec ss;
- ss.format = to_pulse_format(stream_params->format);
- if (ss.format == PA_SAMPLE_INVALID)
- return CUBEB_ERROR_INVALID_FORMAT;
- ss.rate = stream_params->rate;
- ss.channels = stream_params->channels;
-
- *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
- return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
-}
-
-static pa_buffer_attr
-set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spec)
-{
- pa_buffer_attr battr;
- battr.maxlength = -1;
- battr.prebuf = -1;
- battr.tlength = latency_frames * WRAP(pa_frame_size)(sample_spec);
- battr.minreq = battr.tlength / 4;
- battr.fragsize = battr.minreq;
-
- LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",
- battr.maxlength, battr.tlength, battr.prebuf, battr.minreq, battr.fragsize);
-
- return battr;
-}
-
-static int
-pulse_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
-{
- cubeb_stream * stm;
- pa_buffer_attr battr;
- int r;
-
- assert(context);
-
- // If the connection failed for some reason, try to reconnect
- if (context->error == 1 && pulse_context_init(context) != 0) {
- return CUBEB_ERROR;
- }
-
- *stream = NULL;
-
- stm = calloc(1, sizeof(*stm));
- assert(stm);
-
- stm->context = context;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->volume = PULSE_NO_GAIN;
- stm->state = -1;
- assert(stm->shutdown == 0);
-
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- if (output_stream_params) {
- r = create_pa_stream(stm, &stm->output_stream, output_stream_params, stream_name);
- if (r != CUBEB_OK) {
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- pulse_stream_destroy(stm);
- return r;
- }
-
- stm->output_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->output_stream));
-
- WRAP(pa_stream_set_state_callback)(stm->output_stream, stream_state_callback, stm);
- WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm);
-
- battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
- WRAP(pa_stream_connect_playback)(stm->output_stream,
- output_device,
- &battr,
- PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
- PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
- NULL, NULL);
- }
-
- // Set up input stream
- if (input_stream_params) {
- r = create_pa_stream(stm, &stm->input_stream, input_stream_params, stream_name);
- if (r != CUBEB_OK) {
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- pulse_stream_destroy(stm);
- return r;
- }
-
- stm->input_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->input_stream));
-
- WRAP(pa_stream_set_state_callback)(stm->input_stream, stream_state_callback, stm);
- WRAP(pa_stream_set_read_callback)(stm->input_stream, stream_read_callback, stm);
-
- battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
- WRAP(pa_stream_connect_record)(stm->input_stream,
- input_device,
- &battr,
- PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
- PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
- }
-
- r = wait_until_stream_ready(stm);
- if (r == 0) {
- /* force a timing update now, otherwise timing info does not become valid
- until some point after initialization has completed. */
- r = stream_update_timing_info(stm);
- }
-
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- if (r != 0) {
- pulse_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- if (g_log_level) {
- if (output_stream_params){
- const pa_buffer_attr * output_att;
- output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
- LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",output_att->maxlength, output_att->tlength,
- output_att->prebuf, output_att->minreq, output_att->fragsize);
- }
-
- if (input_stream_params){
- const pa_buffer_attr * input_att;
- input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
- LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",input_att->maxlength, input_att->tlength,
- input_att->prebuf, input_att->minreq, input_att->fragsize);
- }
- }
-
- *stream = stm;
-
- return CUBEB_OK;
-}
-
-static void
-pulse_stream_destroy(cubeb_stream * stm)
-{
- stream_cork(stm, CORK);
-
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- if (stm->output_stream) {
-
- if (stm->drain_timer) {
- /* there's no pa_rttime_free, so use this instead. */
- WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
- }
-
- WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
- WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL);
- WRAP(pa_stream_disconnect)(stm->output_stream);
- WRAP(pa_stream_unref)(stm->output_stream);
- }
-
- if (stm->input_stream) {
- WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL);
- WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL);
- WRAP(pa_stream_disconnect)(stm->input_stream);
- WRAP(pa_stream_unref)(stm->input_stream);
- }
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- free(stm);
-}
-
-static void
-pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
-{
- (void)a;
- cubeb_stream * stm = userdata;
- if (stm->shutdown) {
- return;
- }
- size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
- trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
-}
-
-static int
-pulse_stream_start(cubeb_stream * stm)
-{
- stm->shutdown = 0;
- stream_cork(stm, UNCORK | NOTIFY);
-
- if (stm->output_stream && !stm->input_stream) {
- /* On output only case need to manually call user cb once in order to make
- * things roll. This is done via a defer event in order to execute it
- * from PA server thread. */
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- WRAP(pa_mainloop_api_once)(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
- pulse_defer_event_cb, stm);
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- }
-
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_stop(cubeb_stream * stm)
-{
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- stm->shutdown = 1;
- // If draining is taking place wait to finish
- while (stm->drain_timer) {
- WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
- }
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- stream_cork(stm, CORK | NOTIFY);
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
-{
- int r, in_thread;
- pa_usec_t r_usec;
- uint64_t bytes;
-
- if (!stm || !stm->output_stream) {
- return CUBEB_ERROR;
- }
-
- in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
-
- if (!in_thread) {
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- }
- r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec);
- if (!in_thread) {
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- }
-
- if (r != 0) {
- return CUBEB_ERROR;
- }
-
- bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec);
- *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec);
-
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
-{
- pa_usec_t r_usec;
- int negative, r;
-
- if (!stm || !stm->output_stream) {
- return CUBEB_ERROR;
- }
-
- r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative);
- assert(!negative);
- if (r) {
- return CUBEB_ERROR;
- }
-
- *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC;
- return CUBEB_OK;
-}
-
-static void
-volume_success(pa_context *c, int success, void *userdata)
-{
- (void)success;
- (void)c;
- cubeb_stream * stream = userdata;
- assert(success);
- WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
-}
-
-static int
-pulse_stream_set_volume(cubeb_stream * stm, float volume)
-{
- uint32_t index;
- pa_operation * op;
- pa_volume_t vol;
- pa_cvolume cvol;
- const pa_sample_spec * ss;
-
- if (!stm->output_stream) {
- return CUBEB_ERROR;
- }
-
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
-
- while (!stm->context->default_sink_info) {
- WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
- }
-
- /* if the pulse daemon is configured to use flat volumes,
- * apply our own gain instead of changing the input volume on the sink. */
- if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) {
- stm->volume = volume;
- } else {
- ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
-
- vol = WRAP(pa_sw_volume_from_linear)(volume);
- WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
-
- index = WRAP(pa_stream_get_index)(stm->output_stream);
-
- op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
- index, &cvol, volume_success,
- stm);
- if (op) {
- operation_wait(stm->context, stm->output_stream, op);
- WRAP(pa_operation_unref)(op);
- }
- }
-
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_set_panning(cubeb_stream * stream, float panning)
-{
- const pa_channel_map * map;
- pa_cvolume vol;
-
- if (!stream->output_stream) {
- return CUBEB_ERROR;
- }
-
- map = WRAP(pa_stream_get_channel_map)(stream->output_stream);
-
- if (!WRAP(pa_channel_map_can_balance)(map)) {
- return CUBEB_ERROR;
- }
-
- WRAP(pa_cvolume_set_balance)(&vol, map, panning);
-
- return CUBEB_OK;
-}
-
-typedef struct {
- char * default_sink_name;
- char * default_source_name;
-
- cubeb_device_info ** devinfo;
- uint32_t max;
- uint32_t count;
- cubeb * context;
-} pulse_dev_list_data;
-
-static cubeb_device_fmt
-pulse_format_to_cubeb_format(pa_sample_format_t format)
-{
- switch (format) {
- case PA_SAMPLE_S16LE:
- return CUBEB_DEVICE_FMT_S16LE;
- case PA_SAMPLE_S16BE:
- return CUBEB_DEVICE_FMT_S16BE;
- case PA_SAMPLE_FLOAT32LE:
- return CUBEB_DEVICE_FMT_F32LE;
- case PA_SAMPLE_FLOAT32BE:
- return CUBEB_DEVICE_FMT_F32BE;
- default:
- return CUBEB_DEVICE_FMT_F32NE;
- }
-}
-
-static void
-pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data)
-{
- if (list_data->count == list_data->max) {
- list_data->max += 8;
- list_data->devinfo = realloc(list_data->devinfo,
- sizeof(cubeb_device_info *) * list_data->max);
- }
-}
-
-static cubeb_device_state
-pulse_get_state_from_sink_port(pa_sink_port_info * info)
-{
- if (info != NULL) {
-#if PA_CHECK_VERSION(2, 0, 0)
- if (info->available == PA_PORT_AVAILABLE_NO)
- return CUBEB_DEVICE_STATE_UNPLUGGED;
- else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
-#endif
- return CUBEB_DEVICE_STATE_ENABLED;
- }
-
- return CUBEB_DEVICE_STATE_DISABLED;
-}
-
-static void
-pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
- int eol, void * user_data)
-{
- pulse_dev_list_data * list_data = user_data;
- cubeb_device_info * devinfo;
- const char * prop;
-
- (void)context;
-
- if (eol || info == NULL)
- return;
-
- devinfo = calloc(1, sizeof(cubeb_device_info));
-
- devinfo->device_id = strdup(info->name);
- devinfo->devid = devinfo->device_id;
- devinfo->friendly_name = strdup(info->description);
- prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
- if (prop)
- devinfo->group_id = strdup(prop);
- prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
- if (prop)
- devinfo->vendor_name = strdup(prop);
-
- devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
- devinfo->state = pulse_get_state_from_sink_port(info->active_port);
- devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0;
-
- devinfo->format = CUBEB_DEVICE_FMT_ALL;
- devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
- devinfo->max_channels = info->channel_map.channels;
- devinfo->min_rate = 1;
- devinfo->max_rate = PA_RATE_MAX;
- devinfo->default_rate = info->sample_spec.rate;
-
- devinfo->latency_lo = 0;
- devinfo->latency_hi = 0;
-
- pulse_ensure_dev_list_data_list_size (list_data);
- list_data->devinfo[list_data->count++] = devinfo;
-
- WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
-}
-
-static cubeb_device_state
-pulse_get_state_from_source_port(pa_source_port_info * info)
-{
- if (info != NULL) {
-#if PA_CHECK_VERSION(2, 0, 0)
- if (info->available == PA_PORT_AVAILABLE_NO)
- return CUBEB_DEVICE_STATE_UNPLUGGED;
- else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
-#endif
- return CUBEB_DEVICE_STATE_ENABLED;
- }
-
- return CUBEB_DEVICE_STATE_DISABLED;
-}
-
-static void
-pulse_source_info_cb(pa_context * context, const pa_source_info * info,
- int eol, void * user_data)
-{
- pulse_dev_list_data * list_data = user_data;
- cubeb_device_info * devinfo;
- const char * prop;
-
- (void)context;
-
- if (eol)
- return;
-
- devinfo = calloc(1, sizeof(cubeb_device_info));
-
- devinfo->device_id = strdup(info->name);
- devinfo->devid = devinfo->device_id;
- devinfo->friendly_name = strdup(info->description);
- prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
- if (prop)
- devinfo->group_id = strdup(prop);
- prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
- if (prop)
- devinfo->vendor_name = strdup(prop);
-
- devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
- devinfo->state = pulse_get_state_from_source_port(info->active_port);
- devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0;
-
- devinfo->format = CUBEB_DEVICE_FMT_ALL;
- devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
- devinfo->max_channels = info->channel_map.channels;
- devinfo->min_rate = 1;
- devinfo->max_rate = PA_RATE_MAX;
- devinfo->default_rate = info->sample_spec.rate;
-
- devinfo->latency_lo = 0;
- devinfo->latency_hi = 0;
-
- pulse_ensure_dev_list_data_list_size (list_data);
- list_data->devinfo[list_data->count++] = devinfo;
-
- WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
-}
-
-static void
-pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
-{
- pulse_dev_list_data * list_data = userdata;
-
- (void)c;
-
- free(list_data->default_sink_name);
- free(list_data->default_source_name);
- list_data->default_sink_name = strdup(i->default_sink_name);
- list_data->default_source_name = strdup(i->default_source_name);
-
- WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
-}
-
-static int
-pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
-{
- pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context };
- pa_operation * o;
- uint32_t i;
-
- WRAP(pa_threaded_mainloop_lock)(context->mainloop);
-
- o = WRAP(pa_context_get_server_info)(context->context,
- pulse_server_info_cb, &user_data);
- if (o) {
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
-
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- o = WRAP(pa_context_get_sink_info_list)(context->context,
- pulse_sink_info_cb, &user_data);
- if (o) {
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
- }
-
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- o = WRAP(pa_context_get_source_info_list)(context->context,
- pulse_source_info_cb, &user_data);
- if (o) {
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
- }
-
- WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
-
- *collection = malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info *) * (user_data.count > 0 ? user_data.count - 1 : 0));
- (*collection)->count = user_data.count;
- for (i = 0; i < user_data.count; i++)
- (*collection)->device[i] = user_data.devinfo[i];
-
- free(user_data.default_sink_name);
- free(user_data.default_source_name);
- free(user_data.devinfo);
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
-{
-#if PA_CHECK_VERSION(0, 9, 8)
- *device = calloc(1, sizeof(cubeb_device));
- if (*device == NULL)
- return CUBEB_ERROR;
-
- if (stm->input_stream) {
- const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream);
- (*device)->input_name = (name == NULL) ? NULL : strdup(name);
- }
-
- if (stm->output_stream) {
- const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream);
- (*device)->output_name = (name == NULL) ? NULL : strdup(name);
- }
-
- return CUBEB_OK;
-#else
- return CUBEB_ERROR_NOT_SUPPORTED;
-#endif
-}
-
-static int
-pulse_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * device)
-{
- (void)stream;
- free(device->input_name);
- free(device->output_name);
- free(device);
- return CUBEB_OK;
-}
-
-static void
-pulse_subscribe_callback(pa_context * ctx,
- pa_subscription_event_type_t t,
- uint32_t index, void * userdata)
-{
- (void)ctx;
- cubeb * context = userdata;
-
- switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
- case PA_SUBSCRIPTION_EVENT_SOURCE:
- case PA_SUBSCRIPTION_EVENT_SINK:
-
- if (g_log_level) {
- if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
- LOG("Removing sink index %d", index);
- } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
- LOG("Adding sink index %d", index);
- }
- if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
- LOG("Removing source index %d", index);
- } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
- LOG("Adding source index %d", index);
- }
- }
-
- if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
- context->collection_changed_callback(context, context->collection_changed_user_ptr);
- }
- break;
- }
-}
-
-static void
-subscribe_success(pa_context *c, int success, void *userdata)
-{
- (void)c;
- cubeb * context = userdata;
- assert(success);
- WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0);
-}
-
-static int
-pulse_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback collection_changed_callback,
- void * user_ptr)
-{
- context->collection_changed_callback = collection_changed_callback;
- context->collection_changed_user_ptr = user_ptr;
-
- WRAP(pa_threaded_mainloop_lock)(context->mainloop);
-
- pa_subscription_mask_t mask;
- if (context->collection_changed_callback == NULL) {
- // Unregister subscription
- WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
- mask = PA_SUBSCRIPTION_MASK_NULL;
- } else {
- WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
- if (devtype == CUBEB_DEVICE_TYPE_INPUT)
- mask = PA_SUBSCRIPTION_MASK_SOURCE;
- else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT)
- mask = PA_SUBSCRIPTION_MASK_SINK;
- else
- mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
- }
-
- pa_operation * o;
- o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
- if (o == NULL) {
- LOG("Context subscribe failed");
- return CUBEB_ERROR;
- }
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
-
- WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
-
- return CUBEB_OK;
-}
-
-static struct cubeb_ops const pulse_ops = {
- .init = pulse_init,
- .get_backend_id = pulse_get_backend_id,
- .get_max_channel_count = pulse_get_max_channel_count,
- .get_min_latency = pulse_get_min_latency,
- .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
- .enumerate_devices = pulse_enumerate_devices,
- .destroy = pulse_destroy,
- .stream_init = pulse_stream_init,
- .stream_destroy = pulse_stream_destroy,
- .stream_start = pulse_stream_start,
- .stream_stop = pulse_stream_stop,
- .stream_get_position = pulse_stream_get_position,
- .stream_get_latency = pulse_stream_get_latency,
- .stream_set_volume = pulse_stream_set_volume,
- .stream_set_panning = pulse_stream_set_panning,
- .stream_get_current_device = pulse_stream_get_current_device,
- .stream_device_destroy = pulse_stream_device_destroy,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = pulse_register_device_collection_changed
-};
diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp
index f6676946c0..d61b1daf26 100644
--- a/media/libcubeb/src/cubeb_resampler.cpp
+++ b/media/libcubeb/src/cubeb_resampler.cpp
@@ -8,21 +8,21 @@
#define NOMINMAX
#endif // NOMINMAX
-#include <algorithm>
-#include <cmath>
-#include <cassert>
-#include <cstring>
-#include <cstddef>
-#include <cstdio>
#include "cubeb_resampler.h"
#include "cubeb-speex-resampler.h"
#include "cubeb_resampler_internal.h"
#include "cubeb_utils.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstring>
int
to_speex_quality(cubeb_resampler_quality q)
{
- switch(q) {
+ switch (q) {
case CUBEB_RESAMPLER_QUALITY_VOIP:
return SPEEX_RESAMPLER_QUALITY_VOIP;
case CUBEB_RESAMPLER_QUALITY_DEFAULT:
@@ -35,157 +35,230 @@ to_speex_quality(cubeb_resampler_quality q)
}
}
-long noop_resampler::fill(void * input_buffer, long * input_frames_count,
- void * output_buffer, long output_frames)
+uint32_t
+min_buffered_audio_frame(uint32_t sample_rate)
+{
+ return sample_rate / 20;
+}
+
+template <typename T>
+passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr,
+ uint32_t input_channels,
+ uint32_t sample_rate)
+ : processor(input_channels), stream(s), data_callback(cb), user_ptr(ptr),
+ sample_rate(sample_rate)
+{
+}
+
+template <typename T>
+long
+passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames)
{
if (input_buffer) {
assert(input_frames_count);
}
- assert((input_buffer && output_buffer &&
- *input_frames_count >= output_frames) ||
- (!input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
- (!output_buffer && output_frames == 0));
-
- if (output_buffer == nullptr) {
- assert(input_buffer);
+ assert((input_buffer && output_buffer) ||
+ (output_buffer && !input_buffer &&
+ (!input_frames_count || *input_frames_count == 0)) ||
+ (input_buffer && !output_buffer && output_frames == 0));
+
+ // When we have no pending input data and exactly as much input
+ // as output data, we don't need to copy it into the internal buffer
+ // and can directly forward it to the callback.
+ void * in_buf = input_buffer;
+ unsigned long pop_input_count = 0u;
+ if (input_buffer && !output_buffer) {
output_frames = *input_frames_count;
+ } else if (input_buffer) {
+ if (internal_input_buffer.length() != 0 ||
+ *input_frames_count < output_frames) {
+ // If we have pending input data left and have to first append the input
+ // so we can pass it as one pointer to the callback. Or this is a glitch.
+ // It can happen when system's performance is poor. Audible silence is
+ // being pushed at the end of the short input buffer. An improvement for
+ // the future is to resample to the output number of frames, when that
+ // happens.
+ internal_input_buffer.push(static_cast<T *>(input_buffer),
+ frames_to_samples(*input_frames_count));
+ if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
+ // This is unxpected but it can happen when a glitch occurs. Fill the
+ // buffer with silence. First keep the actual number of input samples
+ // used without the silence.
+ pop_input_count = internal_input_buffer.length();
+ internal_input_buffer.push_silence(frames_to_samples(output_frames) -
+ internal_input_buffer.length());
+ } else {
+ pop_input_count = frames_to_samples(output_frames);
+ }
+ in_buf = internal_input_buffer.data();
+ } else if (*input_frames_count > output_frames) {
+ // In this case we have more input that we need output and
+ // fill the overflowing input into internal_input_buffer
+ // Since we have no other pending data, we can nonetheless
+ // pass the current input data directly to the callback
+ assert(pop_input_count == 0);
+ unsigned long samples_off = frames_to_samples(output_frames);
+ internal_input_buffer.push(
+ static_cast<T *>(input_buffer) + samples_off,
+ frames_to_samples(*input_frames_count - output_frames));
+ }
}
- if (input_buffer && *input_frames_count != output_frames) {
- assert(*input_frames_count > output_frames);
- *input_frames_count = output_frames;
+ long rv =
+ data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
+
+ if (input_buffer) {
+ if (pop_input_count) {
+ internal_input_buffer.pop(nullptr, pop_input_count);
+ *input_frames_count = samples_to_frames(pop_input_count);
+ } else {
+ *input_frames_count = output_frames;
+ }
+ drop_audio_if_needed();
}
- return data_callback(stream, user_ptr,
- input_buffer, output_buffer, output_frames);
+ return rv;
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
- ::cubeb_resampler_speex(InputProcessor * input_processor,
- OutputProcessor * output_processor,
- cubeb_stream * s,
- cubeb_data_callback cb,
- void * ptr)
- : input_processor(input_processor)
- , output_processor(output_processor)
- , stream(s)
- , data_callback(cb)
- , user_ptr(ptr)
+// Explicit instantiation of template class.
+template class passthrough_resampler<float>;
+template class passthrough_resampler<short>;
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::
+ cubeb_resampler_speex(InputProcessor * input_processor,
+ OutputProcessor * output_processor, cubeb_stream * s,
+ cubeb_data_callback cb, void * ptr)
+ : input_processor(input_processor), output_processor(output_processor),
+ stream(s), data_callback(cb), user_ptr(ptr)
{
if (input_processor && output_processor) {
- // Add some delay on the processor that has the lowest delay so that the
- // streams are synchronized.
- uint32_t in_latency = input_processor->latency();
- uint32_t out_latency = output_processor->latency();
- if (in_latency > out_latency) {
- uint32_t latency_diff = in_latency - out_latency;
- output_processor->add_latency(latency_diff);
- } else if (in_latency < out_latency) {
- uint32_t latency_diff = out_latency - in_latency;
- input_processor->add_latency(latency_diff);
- }
fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
- } else if (input_processor) {
+ } else if (input_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_input;
- } else if (output_processor) {
+ } else if (output_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_output;
}
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
- ::~cubeb_resampler_speex()
-{ }
+template <typename T, typename InputProcessor, typename OutputProcessor>
+cubeb_resampler_speex<T, InputProcessor,
+ OutputProcessor>::~cubeb_resampler_speex()
+{
+}
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill(void * input_buffer, long * input_frames_count,
- void * output_buffer, long output_frames_needed)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill(
+ void * input_buffer, long * input_frames_count, void * output_buffer,
+ long output_frames_needed)
{
/* Input and output buffers, typed */
- T * in_buffer = reinterpret_cast<T*>(input_buffer);
- T * out_buffer = reinterpret_cast<T*>(output_buffer);
- return (this->*fill_internal)(in_buffer, input_frames_count,
- out_buffer, output_frames_needed);
+ T * in_buffer = reinterpret_cast<T *>(input_buffer);
+ T * out_buffer = reinterpret_cast<T *>(output_buffer);
+ return (this->*fill_internal)(in_buffer, input_frames_count, out_buffer,
+ output_frames_needed);
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill_internal_output(T * input_buffer, long * input_frames_count,
- T * output_buffer, long output_frames_needed)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_output(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long output_frames_needed)
{
assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) &&
output_buffer && output_frames_needed);
- long got = 0;
- T * out_unprocessed = nullptr;
- long output_frames_before_processing = 0;
+ if (!draining) {
+ long got = 0;
+ T * out_unprocessed = nullptr;
+ long output_frames_before_processing = 0;
+ /* fill directly the input buffer of the output processor to save a copy */
+ output_frames_before_processing =
+ output_processor->input_needed_for_output(output_frames_needed);
- /* fill directly the input buffer of the output processor to save a copy */
- output_frames_before_processing =
- output_processor->input_needed_for_output(output_frames_needed);
+ out_unprocessed =
+ output_processor->input_buffer(output_frames_before_processing);
- out_unprocessed =
- output_processor->input_buffer(output_frames_before_processing);
+ got = data_callback(stream, user_ptr, nullptr, out_unprocessed,
+ output_frames_before_processing);
- got = data_callback(stream, user_ptr,
- nullptr, out_unprocessed,
- output_frames_before_processing);
+ if (got < output_frames_before_processing) {
+ draining = true;
- if (got < 0) {
- return got;
- }
+ if (got < 0) {
+ return got;
+ }
+ }
- output_processor->written(got);
+ output_processor->written(got);
+ }
/* Process the output. If not enough frames have been returned from the
- * callback, drain the processors. */
+ * callback, drain the processors. */
return output_processor->output(output_buffer, output_frames_needed);
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill_internal_input(T * input_buffer, long * input_frames_count,
- T * output_buffer, long /*output_frames_needed*/)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_input(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long /*output_frames_needed*/)
{
assert(input_buffer && input_frames_count && *input_frames_count &&
!output_buffer);
- /* The input data, after eventual resampling. This is passed to the callback. */
+ /* The input data, after eventual resampling. This is passed to the callback.
+ */
T * resampled_input = nullptr;
- uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count);
+ uint32_t resampled_frame_count =
+ input_processor->output_for_input(*input_frames_count);
/* process the input, and present exactly `output_frames_needed` in the
- * callback. */
+ * callback. */
input_processor->input(input_buffer, *input_frames_count);
- resampled_input = input_processor->output(resampled_frame_count);
- long got = data_callback(stream, user_ptr,
- resampled_input, nullptr, resampled_frame_count);
+ /* resampled_frame_count == 0 happens if the resampler
+ * doesn't have enough input frames buffered to produce 1 resampled frame. */
+ if (resampled_frame_count == 0) {
+ return *input_frames_count;
+ }
+
+ size_t frames_resampled = 0;
+ resampled_input =
+ input_processor->output(resampled_frame_count, &frames_resampled);
+ *input_frames_count = frames_resampled;
+
+ long got = data_callback(stream, user_ptr, resampled_input, nullptr,
+ resampled_frame_count);
/* Return the number of initial input frames or part of it.
- * Since output_frames_needed == 0 in input scenario, the only
- * available number outside resampler is the initial number of frames. */
+ * Since output_frames_needed == 0 in input scenario, the only
+ * available number outside resampler is the initial number of frames. */
return (*input_frames_count) * (got / resampled_frame_count);
}
-
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill_internal_duplex(T * in_buffer, long * input_frames_count,
- T * out_buffer, long output_frames_needed)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
+ T * in_buffer, long * input_frames_count, T * out_buffer,
+ long output_frames_needed)
{
- /* The input data, after eventual resampling. This is passed to the callback. */
+ if (draining) {
+ // discard input and drain any signal remaining in the resampler.
+ return output_processor->output(out_buffer, output_frames_needed);
+ }
+
+ /* The input data, after eventual resampling. This is passed to the callback.
+ */
T * resampled_input = nullptr;
/* The output buffer passed down in the callback, that might be resampled. */
T * out_unprocessed = nullptr;
- size_t output_frames_before_processing = 0;
+ long output_frames_before_processing = 0;
/* The number of frames returned from the callback. */
long got = 0;
@@ -201,34 +274,46 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
* caller. */
output_frames_before_processing =
- output_processor->input_needed_for_output(output_frames_needed);
- /* fill directly the input buffer of the output processor to save a copy */
+ output_processor->input_needed_for_output(output_frames_needed);
+ /* fill directly the input buffer of the output processor to save a copy */
out_unprocessed =
- output_processor->input_buffer(output_frames_before_processing);
+ output_processor->input_buffer(output_frames_before_processing);
if (in_buffer) {
/* process the input, and present exactly `output_frames_needed` in the
- * callback. */
+ * callback. */
input_processor->input(in_buffer, *input_frames_count);
- resampled_input =
- input_processor->output(output_frames_before_processing);
+
+ size_t frames_resampled = 0;
+ resampled_input = input_processor->output(output_frames_before_processing,
+ &frames_resampled);
+ *input_frames_count = frames_resampled;
} else {
resampled_input = nullptr;
}
- got = data_callback(stream, user_ptr,
- resampled_input, out_unprocessed,
+ got = data_callback(stream, user_ptr, resampled_input, out_unprocessed,
output_frames_before_processing);
- if (got < 0) {
- return got;
+ if (got < output_frames_before_processing) {
+ draining = true;
+
+ if (got < 0) {
+ return got;
+ }
}
output_processor->written(got);
+ input_processor->drop_audio_if_needed();
+
/* Process the output. If not enough frames have been returned from the
* callback, drain the processors. */
- return output_processor->output(out_buffer, output_frames_needed);
+ got = output_processor->output(out_buffer, output_frames_needed);
+
+ output_processor->drop_audio_if_needed();
+
+ return got;
}
/* Resampler C API */
@@ -237,10 +322,8 @@ cubeb_resampler *
cubeb_resampler_create(cubeb_stream * stream,
cubeb_stream_params * input_params,
cubeb_stream_params * output_params,
- unsigned int target_rate,
- cubeb_data_callback callback,
- void * user_ptr,
- cubeb_resampler_quality quality)
+ unsigned int target_rate, cubeb_data_callback callback,
+ void * user_ptr, cubeb_resampler_quality quality)
{
cubeb_sample_format format;
@@ -252,38 +335,28 @@ cubeb_resampler_create(cubeb_stream * stream,
format = output_params->format;
}
- switch(format) {
- case CUBEB_SAMPLE_S16NE:
- return cubeb_resampler_create_internal<short>(stream,
- input_params,
- output_params,
- target_rate,
- callback,
- user_ptr,
- quality);
- case CUBEB_SAMPLE_FLOAT32NE:
- return cubeb_resampler_create_internal<float>(stream,
- input_params,
- output_params,
- target_rate,
- callback,
- user_ptr,
- quality);
- default:
- assert(false);
- return nullptr;
+ switch (format) {
+ case CUBEB_SAMPLE_S16NE:
+ return cubeb_resampler_create_internal<short>(stream, input_params,
+ output_params, target_rate,
+ callback, user_ptr, quality);
+ case CUBEB_SAMPLE_FLOAT32NE:
+ return cubeb_resampler_create_internal<float>(stream, input_params,
+ output_params, target_rate,
+ callback, user_ptr, quality);
+ default:
+ assert(false);
+ return nullptr;
}
}
long
-cubeb_resampler_fill(cubeb_resampler * resampler,
- void * input_buffer,
- long * input_frames_count,
- void * output_buffer,
+cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
+ long * input_frames_count, void * output_buffer,
long output_frames_needed)
{
- return resampler->fill(input_buffer, input_frames_count,
- output_buffer, output_frames_needed);
+ return resampler->fill(input_buffer, input_frames_count, output_buffer,
+ output_frames_needed);
}
void
diff --git a/media/libcubeb/src/cubeb_resampler.h b/media/libcubeb/src/cubeb_resampler.h
index 020ccc17ab..e9b95324ef 100644
--- a/media/libcubeb/src/cubeb_resampler.h
+++ b/media/libcubeb/src/cubeb_resampler.h
@@ -25,20 +25,26 @@ typedef enum {
* Create a resampler to adapt the requested sample rate into something that
* is accepted by the audio backend.
* @param stream A cubeb_stream instance supplied to the data callback.
- * @param params Used to calculate bytes per frame and buffer size for resampling.
- * @param target_rate The sampling rate after resampling.
+ * @param input_params Used to calculate bytes per frame and buffer size for
+ * resampling of the input side of the stream. NULL if input should not be
+ * resampled.
+ * @param output_params Used to calculate bytes per frame and buffer size for
+ * resampling of the output side of the stream. NULL if output should not be
+ * resampled.
+ * @param target_rate The sampling rate after resampling for the input side of
+ * the stream, and/or the sampling rate prior to resampling of the output side
+ * of the stream.
* @param callback A callback to request data for resampling.
* @param user_ptr User data supplied to the data callback.
* @param quality Quality of the resampler.
* @retval A non-null pointer if success.
*/
-cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
- cubeb_stream_params * input_params,
- cubeb_stream_params * output_params,
- unsigned int target_rate,
- cubeb_data_callback callback,
- void * user_ptr,
- cubeb_resampler_quality quality);
+cubeb_resampler *
+cubeb_resampler_create(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate, cubeb_data_callback callback,
+ void * user_ptr, cubeb_resampler_quality quality);
/**
* Fill the buffer with frames acquired using the data callback. Resampling will
@@ -47,29 +53,30 @@ cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
* @param input_buffer A buffer of input samples
* @param input_frame_count The size of the buffer. Returns the number of frames
* consumed.
- * @param buffer The buffer to be filled.
- * @param frames_needed Number of frames that should be produced.
+ * @param output_buffer The buffer to be filled.
+ * @param output_frames_needed Number of frames that should be produced.
* @retval Number of frames that are actually produced.
* @retval CUBEB_ERROR on error.
*/
-long cubeb_resampler_fill(cubeb_resampler * resampler,
- void * input_buffer,
- long * input_frame_count,
- void * output_buffer,
- long output_frames_needed);
+long
+cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
+ long * input_frame_count, void * output_buffer,
+ long output_frames_needed);
/**
* Destroy a cubeb_resampler.
* @param resampler A cubeb_resampler instance.
*/
-void cubeb_resampler_destroy(cubeb_resampler * resampler);
+void
+cubeb_resampler_destroy(cubeb_resampler * resampler);
/**
* Returns the latency, in frames, of the resampler.
* @param resampler A cubeb resampler instance.
* @retval The latency, in frames, induced by the resampler.
*/
-long cubeb_resampler_latency(cubeb_resampler * resampler);
+long
+cubeb_resampler_latency(cubeb_resampler * resampler);
#if defined(__cplusplus)
}
diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h
index 3c37a04b9c..2eabb7c4c4 100644
--- a/media/libcubeb/src/cubeb_resampler_internal.h
+++ b/media/libcubeb/src/cubeb_resampler_internal.h
@@ -8,9 +8,9 @@
#if !defined(CUBEB_RESAMPLER_INTERNAL)
#define CUBEB_RESAMPLER_INTERNAL
-#include <cmath>
-#include <cassert>
#include <algorithm>
+#include <cassert>
+#include <cmath>
#include <memory>
#ifdef CUBEB_GECKO_BUILD
#include "mozilla/UniquePtr.h"
@@ -25,21 +25,32 @@
#define MOZ_END_STD_NAMESPACE }
#endif
MOZ_BEGIN_STD_NAMESPACE
- using mozilla::DefaultDelete;
- using mozilla::UniquePtr;
- #define default_delete DefaultDelete
- #define unique_ptr UniquePtr
+using mozilla::DefaultDelete;
+using mozilla::UniquePtr;
+#define default_delete DefaultDelete
+#define unique_ptr UniquePtr
MOZ_END_STD_NAMESPACE
#endif
-#include "cubeb/cubeb.h"
-#include "cubeb_utils.h"
#include "cubeb-speex-resampler.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_log.h"
#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
#include <stdio.h>
-/* This header file contains the internal C++ API of the resamplers, for testing. */
+/* This header file contains the internal C++ API of the resamplers, for
+ * testing. */
-int to_speex_quality(cubeb_resampler_quality q);
+// When dropping audio input frames to prevent building
+// an input delay, this function returns the number of frames
+// to keep in the buffer.
+// @parameter sample_rate The sample rate of the stream.
+// @return A number of frames to keep.
+uint32_t
+min_buffered_audio_frame(uint32_t sample_rate);
+
+int
+to_speex_quality(cubeb_resampler_quality q);
struct cubeb_resampler {
virtual long fill(void * input_buffer, long * input_frames_count,
@@ -48,62 +59,62 @@ struct cubeb_resampler {
virtual ~cubeb_resampler() {}
};
-class noop_resampler : public cubeb_resampler {
+/** Base class for processors. This is just used to share methods for now. */
+class processor {
public:
- noop_resampler(cubeb_stream * s,
- cubeb_data_callback cb,
- void * ptr)
- : stream(s)
- , data_callback(cb)
- , user_ptr(ptr)
+ explicit processor(uint32_t channels) : channels(channels) {}
+
+protected:
+ size_t frames_to_samples(size_t frames) const { return frames * channels; }
+ size_t samples_to_frames(size_t samples) const
{
+ assert(!(samples % channels));
+ return samples / channels;
}
+ /** The number of channel of the audio buffers to be resampled. */
+ const uint32_t channels;
+};
+
+template <typename T>
+class passthrough_resampler : public cubeb_resampler, public processor {
+public:
+ passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr,
+ uint32_t input_channels, uint32_t sample_rate);
virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames);
- virtual long latency()
+ virtual long latency() { return 0; }
+
+ void drop_audio_if_needed()
{
- return 0;
+ uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+ uint32_t available = samples_to_frames(internal_input_buffer.length());
+ if (available > to_keep) {
+ internal_input_buffer.pop(nullptr,
+ frames_to_samples(available - to_keep));
+ }
}
private:
cubeb_stream * const stream;
const cubeb_data_callback data_callback;
void * const user_ptr;
-};
-
-/** Base class for processors. This is just used to share methods for now. */
-class processor {
-public:
- explicit processor(uint32_t channels)
- : channels(channels)
- {}
-protected:
- size_t frames_to_samples(size_t frames)
- {
- return frames * channels;
- }
- size_t samples_to_frames(size_t samples)
- {
- assert(!(samples % channels));
- return samples / channels;
- }
- /** The number of channel of the audio buffers to be resampled. */
- const uint32_t channels;
+ /* This allows to buffer some input to account for the fact that we buffer
+ * some inputs. */
+ auto_array<T> internal_input_buffer;
+ uint32_t sample_rate;
};
/** Bidirectional resampler, can resample an input and an output stream, or just
* an input stream or output stream. In this case a delay is inserted in the
* opposite direction to keep the streams synchronized. */
-template<typename T, typename InputProcessing, typename OutputProcessing>
+template <typename T, typename InputProcessing, typename OutputProcessing>
class cubeb_resampler_speex : public cubeb_resampler {
public:
cubeb_resampler_speex(InputProcessing * input_processor,
- OutputProcessing * output_processor,
- cubeb_stream * s,
- cubeb_data_callback cb,
- void * ptr);
+ OutputProcessing * output_processor, cubeb_stream * s,
+ cubeb_data_callback cb, void * ptr);
virtual ~cubeb_resampler_speex();
@@ -123,7 +134,9 @@ public:
}
private:
- typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed);
+ typedef long (cubeb_resampler_speex::*processing_callback)(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long output_frames_needed);
long fill_internal_duplex(T * input_buffer, long * input_frames_count,
T * output_buffer, long output_frames_needed);
@@ -138,14 +151,14 @@ private:
cubeb_stream * const stream;
const cubeb_data_callback data_callback;
void * const user_ptr;
+ bool draining = false;
};
/** Handles one way of a (possibly) duplex resampler, working on interleaved
* audio buffers of type T. This class is designed so that the number of frames
* coming out of the resampler can be precisely controled. It manages its own
* input buffer, and can use the caller's output buffer, or allocate its own. */
-template<typename T>
-class cubeb_resampler_speex_one_way : public processor {
+template <typename T> class cubeb_resampler_speex_one_way : public processor {
public:
/** The sample type of this resampler, either 16-bit integers or 32-bit
* floats. */
@@ -157,19 +170,26 @@ public:
* @parameter target_rate The sample-rate of the audio output.
* @parameter quality A number between 0 (fast, low quality) and 10 (slow,
* high quality). */
- cubeb_resampler_speex_one_way(uint32_t channels,
- uint32_t source_rate,
- uint32_t target_rate,
- int quality)
- : processor(channels)
- , resampling_ratio(static_cast<float>(source_rate) / target_rate)
- , additional_latency(0)
- , leftover_samples(0)
+ cubeb_resampler_speex_one_way(uint32_t channels, uint32_t source_rate,
+ uint32_t target_rate, int quality)
+ : processor(channels),
+ resampling_ratio(static_cast<float>(source_rate) / target_rate),
+ source_rate(source_rate), additional_latency(0), leftover_samples(0)
{
int r;
- speex_resampler = speex_resampler_init(channels, source_rate,
- target_rate, quality, &r);
+ speex_resampler =
+ speex_resampler_init(channels, source_rate, target_rate, quality, &r);
assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
+
+ uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
+ const size_t LATENCY_SAMPLES = 8192;
+ T input_buffer[LATENCY_SAMPLES] = {};
+ T output_buffer[LATENCY_SAMPLES] = {};
+ uint32_t input_frame_count = input_latency;
+ uint32_t output_frame_count = LATENCY_SAMPLES;
+ assert(input_latency * channels <= LATENCY_SAMPLES);
+ speex_resample(input_buffer, &input_frame_count, output_buffer,
+ &output_frame_count);
}
/** Destructor, deallocate the resampler */
@@ -178,17 +198,6 @@ public:
speex_resampler_destroy(speex_resampler);
}
- /** Sometimes, it is necessary to add latency on one way of a two-way
- * resampler so that the stream are synchronized. This must be called only on
- * a fresh resampler, otherwise, silent samples will be inserted in the
- * stream.
- * @param frames the number of frames of latency to add. */
- void add_latency(size_t frames)
- {
- additional_latency += frames;
- resampling_in_buffer.push_silence(frames_to_samples(frames));
- }
-
/* Fill the resampler with `input_frame_count` frames. */
void input(T * input_buffer, size_t input_frame_count)
{
@@ -197,14 +206,14 @@ public:
}
/** Outputs exactly `output_frame_count` into `output_buffer`.
- * `output_buffer` has to be at least `output_frame_count` long. */
+ * `output_buffer` has to be at least `output_frame_count` long. */
size_t output(T * output_buffer, size_t output_frame_count)
{
uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
uint32_t out_len = output_frame_count;
- speex_resample(resampling_in_buffer.data(), &in_len,
- output_buffer, &out_len);
+ speex_resample(resampling_in_buffer.data(), &in_len, output_buffer,
+ &out_len);
/* This shifts back any unresampled samples to the beginning of the input
buffer. */
@@ -215,15 +224,17 @@ public:
size_t output_for_input(uint32_t input_frames)
{
- return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
- / resampling_ratio);
+ return (size_t)floorf(
+ (input_frames + samples_to_frames(resampling_in_buffer.length())) /
+ resampling_ratio);
}
/** Returns a buffer containing exactly `output_frame_count` resampled frames.
- * The consumer should not hold onto the pointer. */
- T * output(size_t output_frame_count)
+ * The consumer should not hold onto the pointer. */
+ T * output(size_t output_frame_count, size_t * input_frames_used)
{
- if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
+ if (resampling_out_buffer.capacity() <
+ frames_to_samples(output_frame_count)) {
resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
}
@@ -233,11 +244,21 @@ public:
speex_resample(resampling_in_buffer.data(), &in_len,
resampling_out_buffer.data(), &out_len);
- assert(out_len == output_frame_count);
+ if (out_len < output_frame_count) {
+ LOGV("underrun during resampling: got %u frames, expected %zu",
+ (unsigned)out_len, output_frame_count);
+ // silence the rightmost part
+ T * data = resampling_out_buffer.data();
+ for (uint32_t i = frames_to_samples(out_len);
+ i < frames_to_samples(output_frame_count); i++) {
+ data[i] = 0;
+ }
+ }
/* This shifts back any unresampled samples to the beginning of the input
buffer. */
resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+ *input_frames_used = in_len;
return resampling_out_buffer.data();
}
@@ -249,8 +270,8 @@ public:
* only consider a single channel here so it's the same number of frames. */
int latency = 0;
- latency =
- speex_resampler_get_output_latency(speex_resampler) + additional_latency;
+ latency = speex_resampler_get_output_latency(speex_resampler) +
+ additional_latency;
assert(latency >= 0);
@@ -261,13 +282,16 @@ public:
* exactly `output_frame_count` resampled frames. This can return a number
* slightly bigger than what is strictly necessary, but it guaranteed that the
* number of output frames will be exactly equal. */
- uint32_t input_needed_for_output(uint32_t output_frame_count)
+ uint32_t input_needed_for_output(int32_t output_frame_count) const
{
- int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
- int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
+ assert(output_frame_count >= 0); // Check overflow
+ int32_t unresampled_frames_left =
+ samples_to_frames(resampling_in_buffer.length());
+ int32_t resampled_frames_left =
+ samples_to_frames(resampling_out_buffer.length());
float input_frames_needed =
- (output_frame_count - unresampled_frames_left) * resampling_ratio
- - resampled_frames_left;
+ (output_frame_count - unresampled_frames_left) * resampling_ratio -
+ resampled_frames_left;
if (input_frames_needed < 0) {
return 0;
}
@@ -294,9 +318,20 @@ public:
resampling_in_buffer.set_length(leftover_samples +
frames_to_samples(written_frames));
}
+
+ void drop_audio_if_needed()
+ {
+ // Keep at most 100ms buffered.
+ uint32_t available = samples_to_frames(resampling_in_buffer.length());
+ uint32_t to_keep = min_buffered_audio_frame(source_rate);
+ if (available > to_keep) {
+ resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+ }
+ }
+
private:
/** Wrapper for the speex resampling functions to have a typed
- * interface. */
+ * interface. */
void speex_resample(float * input_buffer, uint32_t * input_frame_count,
float * output_buffer, uint32_t * output_frame_count)
{
@@ -304,11 +339,9 @@ private:
int rv;
rv =
#endif
- speex_resampler_process_interleaved_float(speex_resampler,
- input_buffer,
- input_frame_count,
- output_buffer,
- output_frame_count);
+ speex_resampler_process_interleaved_float(
+ speex_resampler, input_buffer, input_frame_count, output_buffer,
+ output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS);
}
@@ -319,17 +352,16 @@ private:
int rv;
rv =
#endif
- speex_resampler_process_interleaved_int(speex_resampler,
- input_buffer,
- input_frame_count,
- output_buffer,
- output_frame_count);
+ speex_resampler_process_interleaved_int(
+ speex_resampler, input_buffer, input_frame_count, output_buffer,
+ output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS);
}
/** The state for the speex resampler used internaly. */
SpeexResamplerState * speex_resampler;
/** Source rate / target rate. */
const float resampling_ratio;
+ const uint32_t source_rate;
/** Storage for the input frames, to be resampled. Also contains
* any unresampled frames after resampling. */
auto_array<T> resampling_in_buffer;
@@ -343,27 +375,20 @@ private:
};
/** This class allows delaying an audio stream by `frames` frames. */
-template<typename T>
-class delay_line : public processor {
+template <typename T> class delay_line : public processor {
public:
/** Constructor
* @parameter frames the number of frames of delay.
- * @parameter channels the number of channels of this delay line. */
- delay_line(uint32_t frames, uint32_t channels)
- : processor(channels)
- , length(frames)
- , leftover_samples(0)
+ * @parameter channels the number of channels of this delay line.
+ * @parameter sample_rate sample-rate of the audio going through this delay
+ * line */
+ delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
+ : processor(channels), length(frames), leftover_samples(0),
+ sample_rate(sample_rate)
{
/* Fill the delay line with some silent frames to add latency. */
delay_input_buffer.push_silence(frames * channels);
}
- /* Add some latency to the delay line.
- * @param frames the number of frames of latency to add. */
- void add_latency(size_t frames)
- {
- length += frames;
- delay_input_buffer.push_silence(frames_to_samples(frames));
- }
/** Push some frames into the delay line.
* @parameter buffer the frames to push.
* @parameter frame_count the number of frames in #buffer. */
@@ -375,7 +400,7 @@ public:
* @parameter frames_needed the number of frames to be returned.
* @return a buffer containing the delayed frames. The consumer should not
* hold onto the pointer. */
- T * output(uint32_t frames_needed)
+ T * output(uint32_t frames_needed, size_t * input_frames_used)
{
if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
delay_output_buffer.reserve(frames_to_samples(frames_needed));
@@ -385,6 +410,7 @@ public:
delay_output_buffer.push(delay_input_buffer.data(),
frames_to_samples(frames_needed));
delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
+ *input_frames_used = frames_needed;
return delay_output_buffer.data();
}
@@ -396,7 +422,8 @@ public:
T * input_buffer(uint32_t frames_needed)
{
leftover_samples = delay_input_buffer.length();
- delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed));
+ delay_input_buffer.reserve(leftover_samples +
+ frames_to_samples(frames_needed));
return delay_input_buffer.data() + leftover_samples;
}
/** This method works with `input_buffer`, and allows to inform the processor
@@ -426,21 +453,27 @@ public:
* @parameter frames_needed the number of frames one want to write into the
* delay_line
* @returns the number of frames one will get. */
- size_t input_needed_for_output(uint32_t frames_needed)
+ uint32_t input_needed_for_output(int32_t frames_needed) const
{
+ assert(frames_needed >= 0); // Check overflow
return frames_needed;
}
- /** Returns the number of frames produces for `input_frames` frames in input */
- size_t output_for_input(uint32_t input_frames)
- {
- return input_frames;
- }
+ /** Returns the number of frames produces for `input_frames` frames in input
+ */
+ size_t output_for_input(uint32_t input_frames) { return input_frames; }
/** The number of frames this delay line delays the stream by.
* @returns The number of frames of delay. */
- size_t latency()
+ size_t latency() { return length; }
+
+ void drop_audio_if_needed()
{
- return length;
+ size_t available = samples_to_frames(delay_input_buffer.length());
+ uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+ if (available > to_keep) {
+ delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+ }
}
+
private:
/** The length, in frames, of this delay line */
uint32_t length;
@@ -452,17 +485,17 @@ private:
/** The output buffer. This is only ever used if using the ::output with a
* single argument. */
auto_array<T> delay_output_buffer;
+ uint32_t sample_rate;
};
/** This sits behind the C API and is more typed. */
-template<typename T>
+template <typename T>
cubeb_resampler *
cubeb_resampler_create_internal(cubeb_stream * stream,
cubeb_stream_params * input_params,
cubeb_stream_params * output_params,
unsigned int target_rate,
- cubeb_data_callback callback,
- void * user_ptr,
+ cubeb_data_callback callback, void * user_ptr,
cubeb_resampler_quality quality)
{
std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
@@ -477,31 +510,31 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
sample rate, use a no-op resampler, that simply forwards the buffers to the
callback. */
if (((input_params && input_params->rate == target_rate) &&
- (output_params && output_params->rate == target_rate)) ||
+ (output_params && output_params->rate == target_rate)) ||
(input_params && !output_params && (input_params->rate == target_rate)) ||
- (output_params && !input_params && (output_params->rate == target_rate))) {
- return new noop_resampler(stream, callback, user_ptr);
+ (output_params && !input_params &&
+ (output_params->rate == target_rate))) {
+ LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
+ return new passthrough_resampler<T>(
+ stream, callback, user_ptr, input_params ? input_params->channels : 0,
+ target_rate);
}
/* Determine if we need to resampler one or both directions, and create the
resamplers. */
if (output_params && (output_params->rate != target_rate)) {
- output_resampler.reset(
- new cubeb_resampler_speex_one_way<T>(output_params->channels,
- target_rate,
- output_params->rate,
- to_speex_quality(quality)));
+ output_resampler.reset(new cubeb_resampler_speex_one_way<T>(
+ output_params->channels, target_rate, output_params->rate,
+ to_speex_quality(quality)));
if (!output_resampler) {
return NULL;
}
}
if (input_params && (input_params->rate != target_rate)) {
- input_resampler.reset(
- new cubeb_resampler_speex_one_way<T>(input_params->channels,
- input_params->rate,
- target_rate,
- to_speex_quality(quality)));
+ input_resampler.reset(new cubeb_resampler_speex_one_way<T>(
+ input_params->channels, input_params->rate, target_rate,
+ to_speex_quality(quality)));
if (!input_resampler) {
return NULL;
}
@@ -512,39 +545,42 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
* other direction so that the streams are synchronized. */
if (input_resampler && !output_resampler && input_params && output_params) {
output_delay.reset(new delay_line<T>(input_resampler->latency(),
- output_params->channels));
+ output_params->channels,
+ output_params->rate));
if (!output_delay) {
return NULL;
}
- } else if (output_resampler && !input_resampler && input_params && output_params) {
+ } else if (output_resampler && !input_resampler && input_params &&
+ output_params) {
input_delay.reset(new delay_line<T>(output_resampler->latency(),
- input_params->channels));
+ input_params->channels,
+ output_params->rate));
if (!input_delay) {
return NULL;
}
}
if (input_resampler && output_resampler) {
- return new cubeb_resampler_speex<T,
- cubeb_resampler_speex_one_way<T>,
- cubeb_resampler_speex_one_way<T>>
- (input_resampler.release(),
- output_resampler.release(),
- stream, callback, user_ptr);
+ LOG("Resampling input (%d) and output (%d) to target rate of %dHz",
+ input_params->rate, output_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
+ cubeb_resampler_speex_one_way<T>>(
+ input_resampler.release(), output_resampler.release(), stream, callback,
+ user_ptr);
} else if (input_resampler) {
- return new cubeb_resampler_speex<T,
- cubeb_resampler_speex_one_way<T>,
- delay_line<T>>
- (input_resampler.release(),
- output_delay.release(),
- stream, callback, user_ptr);
+ LOG("Resampling input (%d) to target and output rate of %dHz",
+ input_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
+ delay_line<T>>(input_resampler.release(),
+ output_delay.release(),
+ stream, callback, user_ptr);
} else {
- return new cubeb_resampler_speex<T,
- delay_line<T>,
- cubeb_resampler_speex_one_way<T>>
- (input_delay.release(),
- output_resampler.release(),
- stream, callback, user_ptr);
+ LOG("Resampling output (%dHz) to target and input rate of %dHz",
+ output_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, delay_line<T>,
+ cubeb_resampler_speex_one_way<T>>(
+ input_delay.release(), output_resampler.release(), stream, callback,
+ user_ptr);
}
}
diff --git a/media/libcubeb/src/cubeb_ring_array.h b/media/libcubeb/src/cubeb_ring_array.h
index 51b3b321a3..05a8fe9620 100644
--- a/media/libcubeb/src/cubeb_ring_array.h
+++ b/media/libcubeb/src/cubeb_ring_array.h
@@ -16,17 +16,16 @@
them in the correct order. */
typedef struct {
- AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated space for the buffers. */
- unsigned int tail; /**< Index of the last element (first to deliver). */
- unsigned int count; /**< Number of elements in the array. */
- unsigned int capacity; /**< Total length of the array. */
+ AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated
+ space for the buffers. */
+ unsigned int tail; /**< Index of the last element (first to deliver). */
+ unsigned int count; /**< Number of elements in the array. */
+ unsigned int capacity; /**< Total length of the array. */
} ring_array;
static int
-single_audiobuffer_init(AudioBuffer * buffer,
- uint32_t bytesPerFrame,
- uint32_t channelsPerFrame,
- uint32_t frames)
+single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame, uint32_t frames)
{
assert(buffer);
assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
@@ -36,7 +35,7 @@ single_audiobuffer_init(AudioBuffer * buffer,
if (buffer->mData == NULL) {
return CUBEB_ERROR;
}
- PodZero(static_cast<char*>(buffer->mData), size);
+ PodZero(static_cast<char *>(buffer->mData), size);
buffer->mNumberChannels = channelsPerFrame;
buffer->mDataByteSize = size;
@@ -48,15 +47,12 @@ single_audiobuffer_init(AudioBuffer * buffer,
@param ra The ring_array pointer of allocated structure.
@retval 0 on success. */
int
-ring_array_init(ring_array * ra,
- uint32_t capacity,
- uint32_t bytesPerFrame,
- uint32_t channelsPerFrame,
- uint32_t framesPerBuffer)
+ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame, uint32_t framesPerBuffer)
{
assert(ra);
- if (capacity == 0 || bytesPerFrame == 0 ||
- channelsPerFrame == 0 || framesPerBuffer == 0) {
+ if (capacity == 0 || bytesPerFrame == 0 || channelsPerFrame == 0 ||
+ framesPerBuffer == 0) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
ra->capacity = capacity;
@@ -70,8 +66,7 @@ ring_array_init(ring_array * ra,
}
for (unsigned int i = 0; i < ra->capacity; ++i) {
- if (single_audiobuffer_init(&ra->buffer_array[i],
- bytesPerFrame,
+ if (single_audiobuffer_init(&ra->buffer_array[i], bytesPerFrame,
channelsPerFrame,
framesPerBuffer) != CUBEB_OK) {
return CUBEB_ERROR;
@@ -87,7 +82,7 @@ void
ring_array_destroy(ring_array * ra)
{
assert(ra);
- if (ra->buffer_array == NULL){
+ if (ra->buffer_array == NULL) {
return;
}
for (unsigned int i = 0; i < ra->capacity; ++i) {
@@ -95,12 +90,13 @@ ring_array_destroy(ring_array * ra)
operator delete(ra->buffer_array[i].mData);
}
}
- delete [] ra->buffer_array;
+ delete[] ra->buffer_array;
}
/** Get the allocated buffer to be stored with fresh data.
@param ra The ring_array pointer.
- @retval Pointer of the allocated space to be stored with fresh data or NULL if full. */
+ @retval Pointer of the allocated space to be stored with fresh data or NULL
+ if full. */
AudioBuffer *
ring_array_get_free_buffer(ring_array * ra)
{
@@ -156,4 +152,4 @@ ring_array_get_dummy_buffer(ring_array * ra)
return &ra->buffer_array[0];
}
-#endif //CUBEB_RING_ARRAY_H
+#endif // CUBEB_RING_ARRAY_H
diff --git a/media/libcubeb/src/cubeb_ringbuffer.h b/media/libcubeb/src/cubeb_ringbuffer.h
new file mode 100644
index 0000000000..28381849fc
--- /dev/null
+++ b/media/libcubeb/src/cubeb_ringbuffer.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_RING_BUFFER_H
+#define CUBEB_RING_BUFFER_H
+
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <thread>
+
+/**
+ * Single producer single consumer lock-free and wait-free ring buffer.
+ *
+ * This data structure allows producing data from one thread, and consuming it
+ * on another thread, safely and without explicit synchronization. If used on
+ * two threads, this data structure uses atomics for thread safety. It is
+ * possible to disable the use of atomics at compile time and only use this data
+ * structure on one thread.
+ *
+ * The role for the producer and the consumer must be constant, i.e., the
+ * producer should always be on one thread and the consumer should always be on
+ * another thread.
+ *
+ * Some words about the inner workings of this class:
+ * - Capacity is fixed. Only one allocation is performed, in the constructor.
+ * When reading and writing, the return value of the method allows checking if
+ * the ring buffer is empty or full.
+ * - We always keep the read index at least one element ahead of the write
+ * index, so we can distinguish between an empty and a full ring buffer: an
+ * empty ring buffer is when the write index is at the same position as the
+ * read index. A full buffer is when the write index is exactly one position
+ * before the read index.
+ * - We synchronize updates to the read index after having read the data, and
+ * the write index after having written the data. This means that the each
+ * thread can only touch a portion of the buffer that is not touched by the
+ * other thread.
+ * - Callers are expected to provide buffers. When writing to the queue,
+ * elements are copied into the internal storage from the buffer passed in.
+ * When reading from the queue, the user is expected to provide a buffer.
+ * Because this is a ring buffer, data might not be contiguous in memory,
+ * providing an external buffer to copy into is an easy way to have linear
+ * data for further processing.
+ */
+template <typename T> class ring_buffer_base {
+public:
+ /**
+ * Constructor for a ring buffer.
+ *
+ * This performs an allocation, but is the only allocation that will happen
+ * for the life time of a `ring_buffer_base`.
+ *
+ * @param capacity The maximum number of element this ring buffer will hold.
+ */
+ ring_buffer_base(int capacity)
+ /* One more element to distinguish from empty and full buffer. */
+ : capacity_(capacity + 1)
+ {
+ assert(storage_capacity() < std::numeric_limits<int>::max() / 2 &&
+ "buffer too large for the type of index used.");
+ assert(capacity_ > 0);
+
+ data_.reset(new T[storage_capacity()]);
+ /* If this queue is using atomics, initializing those members as the last
+ * action in the constructor acts as a full barrier, and allow capacity() to
+ * be thread-safe. */
+ write_index_ = 0;
+ read_index_ = 0;
+ }
+ /**
+ * Push `count` zero or default constructed elements in the array.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param count The number of elements to enqueue.
+ * @return The number of element enqueued.
+ */
+ int enqueue_default(int count) { return enqueue(nullptr, count); }
+ /**
+ * @brief Put an element in the queue
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param element The element to put in the queue.
+ *
+ * @return 1 if the element was inserted, 0 otherwise.
+ */
+ int enqueue(T & element) { return enqueue(&element, 1); }
+ /**
+ * Push `count` elements in the ring buffer.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param elements a pointer to a buffer containing at least `count` elements.
+ * If `elements` is nullptr, zero or default constructed elements are
+ * enqueued.
+ * @param count The number of elements to read from `elements`
+ * @return The number of elements successfully coped from `elements` and
+ * inserted into the ring buffer.
+ */
+ int enqueue(T * elements, int count)
+ {
+#ifndef NDEBUG
+ assert_correct_thread(producer_id);
+#endif
+
+ int rd_idx = read_index_.load(std::memory_order_relaxed);
+ int wr_idx = write_index_.load(std::memory_order_relaxed);
+
+ if (full_internal(rd_idx, wr_idx)) {
+ return 0;
+ }
+
+ int to_write = std::min(available_write_internal(rd_idx, wr_idx), count);
+
+ /* First part, from the write index to the end of the array. */
+ int first_part = std::min(storage_capacity() - wr_idx, to_write);
+ /* Second part, from the beginning of the array */
+ int second_part = to_write - first_part;
+
+ if (elements) {
+ Copy(data_.get() + wr_idx, elements, first_part);
+ Copy(data_.get(), elements + first_part, second_part);
+ } else {
+ ConstructDefault(data_.get() + wr_idx, first_part);
+ ConstructDefault(data_.get(), second_part);
+ }
+
+ write_index_.store(increment_index(wr_idx, to_write),
+ std::memory_order_release);
+
+ return to_write;
+ }
+ /**
+ * Retrieve at most `count` elements from the ring buffer, and copy them to
+ * `elements`, if non-null.
+ *
+ * Only safely called on the consumer side.
+ *
+ * @param elements A pointer to a buffer with space for at least `count`
+ * elements. If `elements` is `nullptr`, `count` element will be discarded.
+ * @param count The maximum number of elements to dequeue.
+ * @return The number of elements written to `elements`.
+ */
+ int dequeue(T * elements, int count)
+ {
+#ifndef NDEBUG
+ assert_correct_thread(consumer_id);
+#endif
+
+ int wr_idx = write_index_.load(std::memory_order_acquire);
+ int rd_idx = read_index_.load(std::memory_order_relaxed);
+
+ if (empty_internal(rd_idx, wr_idx)) {
+ return 0;
+ }
+
+ int to_read = std::min(available_read_internal(rd_idx, wr_idx), count);
+
+ int first_part = std::min(storage_capacity() - rd_idx, to_read);
+ int second_part = to_read - first_part;
+
+ if (elements) {
+ Copy(elements, data_.get() + rd_idx, first_part);
+ Copy(elements + first_part, data_.get(), second_part);
+ }
+
+ read_index_.store(increment_index(rd_idx, to_read),
+ std::memory_order_relaxed);
+
+ return to_read;
+ }
+ /**
+ * Get the number of available element for consuming.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @return The number of available elements for reading.
+ */
+ int available_read() const
+ {
+#ifndef NDEBUG
+ assert_correct_thread(consumer_id);
+#endif
+ return available_read_internal(
+ read_index_.load(std::memory_order_relaxed),
+ write_index_.load(std::memory_order_relaxed));
+ }
+ /**
+ * Get the number of available elements for consuming.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @return The number of empty slots in the buffer, available for writing.
+ */
+ int available_write() const
+ {
+#ifndef NDEBUG
+ assert_correct_thread(producer_id);
+#endif
+ return available_write_internal(
+ read_index_.load(std::memory_order_relaxed),
+ write_index_.load(std::memory_order_relaxed));
+ }
+ /**
+ * Get the total capacity, for this ring buffer.
+ *
+ * Can be called safely on any thread.
+ *
+ * @return The maximum capacity of this ring buffer.
+ */
+ int capacity() const { return storage_capacity() - 1; }
+ /**
+ * Reset the consumer and producer thread identifier, in case the thread are
+ * being changed. This has to be externally synchronized. This is no-op when
+ * asserts are disabled.
+ */
+ void reset_thread_ids()
+ {
+#ifndef NDEBUG
+ consumer_id = producer_id = std::thread::id();
+#endif
+ }
+
+private:
+ /** Return true if the ring buffer is empty.
+ *
+ * @param read_index the read index to consider
+ * @param write_index the write index to consider
+ * @return true if the ring buffer is empty, false otherwise.
+ **/
+ bool empty_internal(int read_index, int write_index) const
+ {
+ return write_index == read_index;
+ }
+ /** Return true if the ring buffer is full.
+ *
+ * This happens if the write index is exactly one element behind the read
+ * index.
+ *
+ * @param read_index the read index to consider
+ * @param write_index the write index to consider
+ * @return true if the ring buffer is full, false otherwise.
+ **/
+ bool full_internal(int read_index, int write_index) const
+ {
+ return (write_index + 1) % storage_capacity() == read_index;
+ }
+ /**
+ * Return the size of the storage. It is one more than the number of elements
+ * that can be stored in the buffer.
+ *
+ * @return the number of elements that can be stored in the buffer.
+ */
+ int storage_capacity() const { return capacity_; }
+ /**
+ * Returns the number of elements available for reading.
+ *
+ * @return the number of available elements for reading.
+ */
+ int available_read_internal(int read_index, int write_index) const
+ {
+ if (write_index >= read_index) {
+ return write_index - read_index;
+ } else {
+ return write_index + storage_capacity() - read_index;
+ }
+ }
+ /**
+ * Returns the number of empty elements, available for writing.
+ *
+ * @return the number of elements that can be written into the array.
+ */
+ int available_write_internal(int read_index, int write_index) const
+ {
+ /* We substract one element here to always keep at least one sample
+ * free in the buffer, to distinguish between full and empty array. */
+ int rv = read_index - write_index - 1;
+ if (write_index >= read_index) {
+ rv += storage_capacity();
+ }
+ return rv;
+ }
+ /**
+ * Increments an index, wrapping it around the storage.
+ *
+ * @param index a reference to the index to increment.
+ * @param increment the number by which `index` is incremented.
+ * @return the new index.
+ */
+ int increment_index(int index, int increment) const
+ {
+ assert(increment >= 0);
+ return (index + increment) % storage_capacity();
+ }
+ /**
+ * @brief This allows checking that enqueue (resp. dequeue) are always called
+ * by the right thread.
+ *
+ * @param id the id of the thread that has called the calling method first.
+ */
+#ifndef NDEBUG
+ static void assert_correct_thread(std::thread::id & id)
+ {
+ if (id == std::thread::id()) {
+ id = std::this_thread::get_id();
+ return;
+ }
+ assert(id == std::this_thread::get_id());
+ }
+#endif
+ /** Index at which the oldest element is at, in samples. */
+ std::atomic<int> read_index_;
+ /** Index at which to write new elements. `write_index` is always at
+ * least one element ahead of `read_index_`. */
+ std::atomic<int> write_index_;
+ /** Maximum number of elements that can be stored in the ring buffer. */
+ const int capacity_;
+ /** Data storage */
+ std::unique_ptr<T[]> data_;
+#ifndef NDEBUG
+ /** The id of the only thread that is allowed to read from the queue. */
+ mutable std::thread::id consumer_id;
+ /** The id of the only thread that is allowed to write from the queue. */
+ mutable std::thread::id producer_id;
+#endif
+};
+
+/**
+ * Adapter for `ring_buffer_base` that exposes an interface in frames.
+ */
+template <typename T> class audio_ring_buffer_base {
+public:
+ /**
+ * @brief Constructor.
+ *
+ * @param channel_count Number of channels.
+ * @param capacity_in_frames The capacity in frames.
+ */
+ audio_ring_buffer_base(int channel_count, int capacity_in_frames)
+ : channel_count(channel_count),
+ ring_buffer(frames_to_samples(capacity_in_frames))
+ {
+ assert(channel_count > 0);
+ }
+ /**
+ * @brief Enqueue silence.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param frame_count The number of frames of silence to enqueue.
+ * @return The number of frames of silence actually written to the queue.
+ */
+ int enqueue_default(int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.enqueue(nullptr, frames_to_samples(frame_count)));
+ }
+ /**
+ * @brief Enqueue `frames_count` frames of audio.
+ *
+ * Only safely called from the producer thread.
+ *
+ * @param [in] frames If non-null, the frames to enqueue.
+ * Otherwise, silent frames are enqueued.
+ * @param frame_count The number of frames to enqueue.
+ *
+ * @return The number of frames enqueued
+ */
+
+ int enqueue(T * frames, int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.enqueue(frames, frames_to_samples(frame_count)));
+ }
+
+ /**
+ * @brief Removes `frame_count` frames from the buffer, and
+ * write them to `frames` if it is non-null.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @param frames If non-null, the frames are copied to `frames`.
+ * Otherwise, they are dropped.
+ * @param frame_count The number of frames to remove.
+ *
+ * @return The number of frames actually dequeud.
+ */
+ int dequeue(T * frames, int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.dequeue(frames, frames_to_samples(frame_count)));
+ }
+ /**
+ * Get the number of available frames of audio for consuming.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @return The number of available frames of audio for reading.
+ */
+ int available_read() const
+ {
+ return samples_to_frames(ring_buffer.available_read());
+ }
+ /**
+ * Get the number of available frames of audio for consuming.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @return The number of empty slots in the buffer, available for writing.
+ */
+ int available_write() const
+ {
+ return samples_to_frames(ring_buffer.available_write());
+ }
+ /**
+ * Get the total capacity, for this ring buffer.
+ *
+ * Can be called safely on any thread.
+ *
+ * @return The maximum capacity of this ring buffer.
+ */
+ int capacity() const { return samples_to_frames(ring_buffer.capacity()); }
+
+private:
+ /**
+ * @brief Frames to samples conversion.
+ *
+ * @param frames The number of frames.
+ *
+ * @return A number of samples.
+ */
+ int frames_to_samples(int frames) const { return frames * channel_count; }
+ /**
+ * @brief Samples to frames conversion.
+ *
+ * @param samples The number of samples.
+ *
+ * @return A number of frames.
+ */
+ int samples_to_frames(int samples) const { return samples / channel_count; }
+ /** Number of channels of audio that will stream through this ring buffer. */
+ int channel_count;
+ /** The underlying ring buffer that is used to store the data. */
+ ring_buffer_base<T> ring_buffer;
+};
+
+/**
+ * Lock-free instantiation of the `ring_buffer_base` type. This is safe to use
+ * from two threads, one producer, one consumer (that never change role),
+ * without explicit synchronization.
+ */
+template <typename T> using lock_free_queue = ring_buffer_base<T>;
+/**
+ * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use
+ * from two threads, one producer, one consumer (that never change role),
+ * without explicit synchronization.
+ */
+template <typename T>
+using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>;
+
+#endif // CUBEB_RING_BUFFER_H
diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c
index c7ac184465..d7fb79202e 100644
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -4,56 +4,109 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <assert.h>
+#include <dlfcn.h>
+#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <sndio.h>
#include <stdbool.h>
-#include <stdlib.h>
#include <stdio.h>
-#include <assert.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
+#include <stdlib.h>
#if defined(CUBEB_SNDIO_DEBUG)
#define DPR(...) fprintf(stderr, __VA_ARGS__);
#else
-#define DPR(...) do {} while(0)
+#define DPR(...) \
+ do { \
+ } while (0)
+#endif
+
+#ifdef DISABLE_LIBSNDIO_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBSNDIO_API_VISIT(X) \
+ X(sio_close) \
+ X(sio_eof) \
+ X(sio_getpar) \
+ X(sio_initpar) \
+ X(sio_nfds) \
+ X(sio_onmove) \
+ X(sio_open) \
+ X(sio_pollfd) \
+ X(sio_read) \
+ X(sio_revents) \
+ X(sio_setpar) \
+ X(sio_start) \
+ X(sio_stop) \
+ X(sio_write)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
#endif
static struct cubeb_ops const sndio_ops;
struct cubeb {
struct cubeb_ops const * ops;
+ void * libsndio;
};
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
- pthread_t th; /* to run real-time audio i/o */
- pthread_mutex_t mtx; /* protects hdl and pos */
- struct sio_hdl *hdl; /* link us to sndio */
- int active; /* cubec_start() called */
- int conv; /* need float->s16 conversion */
- unsigned char *buf; /* data is prepared here */
- unsigned int nfr; /* number of frames in buf */
- unsigned int bpf; /* bytes per frame */
- unsigned int pchan; /* number of play channels */
- uint64_t rdpos; /* frame number Joe hears right now */
- uint64_t wrpos; /* number of written frames */
- cubeb_data_callback data_cb; /* cb to preapare data */
- cubeb_state_callback state_cb; /* cb to notify about state changes */
- void *arg; /* user arg to {data,state}_cb */
+ void * arg; /* user arg to {data,state}_cb */
+ /**/
+ pthread_t th; /* to run real-time audio i/o */
+ pthread_mutex_t mtx; /* protects hdl and pos */
+ struct sio_hdl * hdl; /* link us to sndio */
+ int mode; /* bitmap of SIO_{PLAY,REC} */
+ int active; /* cubec_start() called */
+ int conv; /* need float->s16 conversion */
+ unsigned char * rbuf; /* rec data consumed from here */
+ unsigned char * pbuf; /* play data is prepared here */
+ unsigned int nfr; /* number of frames in ibuf and obuf */
+ unsigned int rbpf; /* rec bytes per frame */
+ unsigned int pbpf; /* play bytes per frame */
+ unsigned int rchan; /* number of rec channels */
+ unsigned int pchan; /* number of play channels */
+ unsigned int nblks; /* number of blocks in the buffer */
+ uint64_t hwpos; /* frame number Joe hears right now */
+ uint64_t swpos; /* number of frames produced/consumed */
+ cubeb_data_callback data_cb; /* cb to preapare data */
+ cubeb_state_callback state_cb; /* cb to notify about state changes */
+ float volume; /* current volume */
};
static void
-float_to_s16(void *ptr, long nsamp)
+s16_setvol(void * ptr, long nsamp, float volume)
{
- int16_t *dst = ptr;
- float *src = ptr;
+ int16_t * dst = ptr;
+ int32_t mult = volume * 32768;
+ int32_t s;
+
+ while (nsamp-- > 0) {
+ s = *dst;
+ s = (s * mult) >> 15;
+ *(dst++) = s;
+ }
+}
+
+static void
+float_to_s16(void * ptr, long nsamp, float volume)
+{
+ int16_t * dst = ptr;
+ float * src = ptr;
+ float mult = volume * 32768;
int s;
while (nsamp-- > 0) {
- s = lrintf(*(src++) * 32768);
+ s = lrintf(*(src++) * mult);
if (s < -32768)
s = -32768;
else if (s > 32767)
@@ -63,61 +116,146 @@ float_to_s16(void *ptr, long nsamp)
}
static void
-sndio_onmove(void *arg, int delta)
+s16_to_float(void * ptr, long nsamp)
{
- cubeb_stream *s = (cubeb_stream *)arg;
+ int16_t * src = ptr;
+ float * dst = ptr;
- s->rdpos += delta * s->bpf;
+ src += nsamp;
+ dst += nsamp;
+ while (nsamp-- > 0)
+ *(--dst) = (1. / 32768) * *(--src);
+}
+
+static const char *
+sndio_get_device()
+{
+#ifdef __linux__
+ /*
+ * On other platforms default to sndio devices,
+ * so cubebs other backends can be used instead.
+ */
+ const char * dev = getenv("AUDIODEVICE");
+ if (dev == NULL || *dev == '\0')
+ return "snd/0";
+ return dev;
+#else
+ return SIO_DEVANY;
+#endif
+}
+
+static void
+sndio_onmove(void * arg, int delta)
+{
+ cubeb_stream * s = (cubeb_stream *)arg;
+
+ s->hwpos += delta;
}
static void *
-sndio_mainloop(void *arg)
+sndio_mainloop(void * arg)
{
-#define MAXFDS 8
- struct pollfd pfds[MAXFDS];
- cubeb_stream *s = arg;
- int n, nfds, revents, state = CUBEB_STATE_STARTED;
- size_t start = 0, end = 0;
+ struct pollfd * pfds;
+ cubeb_stream * s = arg;
+ int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
+ size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
long nfr;
+ nfds = WRAP(sio_nfds)(s->hdl);
+ pfds = calloc(nfds, sizeof(struct pollfd));
+ if (pfds == NULL)
+ return NULL;
+
DPR("sndio_mainloop()\n");
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
pthread_mutex_lock(&s->mtx);
- if (!sio_start(s->hdl)) {
+ if (!WRAP(sio_start)(s->hdl)) {
pthread_mutex_unlock(&s->mtx);
+ free(pfds);
return NULL;
}
DPR("sndio_mainloop(), started\n");
- start = end = s->nfr;
+ if (s->mode & SIO_PLAY) {
+ pstart = pend = s->nfr * s->pbpf;
+ prime = s->nblks;
+ if (s->mode & SIO_REC) {
+ memset(s->rbuf, 0, s->nfr * s->rbpf);
+ rstart = rend = s->nfr * s->rbpf;
+ }
+ } else {
+ prime = 0;
+ rstart = 0;
+ rend = s->nfr * s->rbpf;
+ }
+
for (;;) {
if (!s->active) {
DPR("sndio_mainloop() stopped\n");
state = CUBEB_STATE_STOPPED;
break;
}
- if (start == end) {
- if (end < s->nfr) {
+
+ /* do we have a complete block? */
+ if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
+ (!(s->mode & SIO_REC) || rstart == rend)) {
+
+ if (eof) {
DPR("sndio_mainloop() drained\n");
state = CUBEB_STATE_DRAINED;
break;
}
+
+ if ((s->mode & SIO_REC) && s->conv)
+ s16_to_float(s->rbuf, s->nfr * s->rchan);
+
+ /* invoke call-back, it returns less that s->nfr if done */
pthread_mutex_unlock(&s->mtx);
- nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr);
+ nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr);
pthread_mutex_lock(&s->mtx);
if (nfr < 0) {
DPR("sndio_mainloop() cb err\n");
state = CUBEB_STATE_ERROR;
break;
}
- if (s->conv)
- float_to_s16(s->buf, nfr * s->pchan);
- start = 0;
- end = nfr * s->bpf;
+ s->swpos += nfr;
+
+ /* was this last call-back invocation (aka end-of-stream) ? */
+ if (nfr < s->nfr) {
+
+ if (!(s->mode & SIO_PLAY) || nfr == 0) {
+ state = CUBEB_STATE_DRAINED;
+ break;
+ }
+
+ /* need to write (aka drain) the partial play block we got */
+ pend = nfr * s->pbpf;
+ eof = 1;
+ }
+
+ if (prime > 0)
+ prime--;
+
+ if (s->mode & SIO_PLAY) {
+ if (s->conv)
+ float_to_s16(s->pbuf, nfr * s->pchan, s->volume);
+ else
+ s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
+ }
+
+ if (s->mode & SIO_REC)
+ rstart = 0;
+ if (s->mode & SIO_PLAY)
+ pstart = 0;
}
- if (end == 0)
- continue;
- nfds = sio_pollfd(s->hdl, pfds, POLLOUT);
+
+ events = 0;
+ if ((s->mode & SIO_REC) && rstart < rend && prime == 0)
+ events |= POLLIN;
+ if ((s->mode & SIO_PLAY) && pstart < pend)
+ events |= POLLOUT;
+ nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
+
if (nfds > 0) {
pthread_mutex_unlock(&s->mtx);
n = poll(pfds, nfds, -1);
@@ -125,88 +263,165 @@ sndio_mainloop(void *arg)
if (n < 0)
continue;
}
- revents = sio_revents(s->hdl, pfds);
- if (revents & POLLHUP)
+
+ revents = WRAP(sio_revents)(s->hdl, pfds);
+
+ if (revents & POLLHUP) {
+ state = CUBEB_STATE_ERROR;
break;
+ }
+
if (revents & POLLOUT) {
- n = sio_write(s->hdl, s->buf + start, end - start);
- if (n == 0) {
+ n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
+ if (n == 0 && WRAP(sio_eof)(s->hdl)) {
DPR("sndio_mainloop() werr\n");
state = CUBEB_STATE_ERROR;
break;
}
- s->wrpos += n;
- start += n;
+ pstart += n;
}
+
+ if (revents & POLLIN) {
+ n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
+ if (n == 0 && WRAP(sio_eof)(s->hdl)) {
+ DPR("sndio_mainloop() rerr\n");
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ rstart += n;
+ }
+
+ /* skip rec block, if not recording (yet) */
+ if (prime > 0 && (s->mode & SIO_REC))
+ rstart = rend;
}
- sio_stop(s->hdl);
- s->rdpos = s->wrpos;
+ WRAP(sio_stop)(s->hdl);
+ s->hwpos = s->swpos;
pthread_mutex_unlock(&s->mtx);
s->state_cb(s, s->arg, state);
+ free(pfds);
return NULL;
}
/*static*/ int
-sndio_init(cubeb **context, char const *context_name)
+sndio_init(cubeb ** context, char const * context_name)
{
+ void * libsndio = NULL;
+ struct sio_hdl * hdl;
+
+ assert(context);
+
+#ifndef DISABLE_LIBSNDIO_DLOPEN
+ libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
+ if (!libsndio) {
+ libsndio = dlopen("libsndio.so", RTLD_LAZY);
+ if (!libsndio) {
+ DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libsndio, #x); \
+ if (!cubeb_##x) { \
+ DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
+ dlclose(libsndio); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBSNDIO_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ /* test if sndio works */
+ hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
+ if (hdl == NULL) {
+ return CUBEB_ERROR;
+ }
+ WRAP(sio_close)(hdl);
+
DPR("sndio_init(%s)\n", context_name);
- *context = malloc(sizeof(*context));
+ *context = malloc(sizeof(**context));
+ if (*context == NULL)
+ return CUBEB_ERROR;
+ (*context)->libsndio = libsndio;
(*context)->ops = &sndio_ops;
(void)context_name;
return CUBEB_OK;
}
static char const *
-sndio_get_backend_id(cubeb *context)
+sndio_get_backend_id(cubeb * context)
{
return "sndio";
}
static void
-sndio_destroy(cubeb *context)
+sndio_destroy(cubeb * context)
{
DPR("sndio_destroy()\n");
+ if (context->libsndio)
+ dlclose(context->libsndio);
free(context);
}
static int
-sndio_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
+sndio_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int latency_frames,
cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void *user_ptr)
+ cubeb_state_callback state_callback, void * user_ptr)
{
- cubeb_stream *s;
+ cubeb_stream * s;
struct sio_par wpar, rpar;
- DPR("sndio_stream_init(%s)\n", stream_name);
- size_t size;
+ cubeb_sample_format format;
+ int rate;
+ size_t bps;
- assert(!input_stream_params && "not supported.");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
+ DPR("sndio_stream_init(%s)\n", stream_name);
s = malloc(sizeof(cubeb_stream));
if (s == NULL)
return CUBEB_ERROR;
+ memset(s, 0, sizeof(cubeb_stream));
+ s->mode = 0;
+ if (input_stream_params) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
+ s->mode |= SIO_REC;
+ format = input_stream_params->format;
+ rate = input_stream_params->rate;
+ }
+ if (output_stream_params) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
+ s->mode |= SIO_PLAY;
+ format = output_stream_params->format;
+ rate = output_stream_params->rate;
+ }
+ if (s->mode == 0) {
+ DPR("sndio_stream_init(), neither playing nor recording\n");
+ goto err;
+ }
s->context = context;
- s->hdl = sio_open(NULL, SIO_PLAY, 1);
+ s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
if (s->hdl == NULL) {
- free(s);
DPR("sndio_stream_init(), sio_open() failed\n");
- return CUBEB_ERROR;
+ goto err;
}
- sio_initpar(&wpar);
+ WRAP(sio_initpar)(&wpar);
wpar.sig = 1;
wpar.bits = 16;
- switch (output_stream_params->format) {
+ switch (format) {
case CUBEB_SAMPLE_S16LE:
wpar.le = 1;
break;
@@ -218,53 +433,70 @@ sndio_stream_init(cubeb * context,
break;
default:
DPR("sndio_stream_init() unsupported format\n");
- return CUBEB_ERROR_INVALID_FORMAT;
+ goto err;
}
- wpar.rate = output_stream_params->rate;
- wpar.pchan = output_stream_params->channels;
+ wpar.rate = rate;
+ if (s->mode & SIO_REC)
+ wpar.rchan = input_stream_params->channels;
+ if (s->mode & SIO_PLAY)
+ wpar.pchan = output_stream_params->channels;
wpar.appbufsz = latency_frames;
- if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) {
- sio_close(s->hdl);
- free(s);
+ if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
DPR("sndio_stream_init(), sio_setpar() failed\n");
- return CUBEB_ERROR;
+ goto err;
}
- if (rpar.bits != wpar.bits || rpar.le != wpar.le ||
- rpar.sig != wpar.sig || rpar.rate != wpar.rate ||
- rpar.pchan != wpar.pchan) {
- sio_close(s->hdl);
- free(s);
+ if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
+ rpar.rate != wpar.rate ||
+ ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
+ ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
DPR("sndio_stream_init() unsupported params\n");
- return CUBEB_ERROR_INVALID_FORMAT;
+ goto err;
}
- sio_onmove(s->hdl, sndio_onmove, s);
+ WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
s->active = 0;
s->nfr = rpar.round;
- s->bpf = rpar.bps * rpar.pchan;
+ s->rbpf = rpar.bps * rpar.rchan;
+ s->pbpf = rpar.bps * rpar.pchan;
+ s->rchan = rpar.rchan;
s->pchan = rpar.pchan;
+ s->nblks = rpar.bufsz / rpar.round;
s->data_cb = data_callback;
s->state_cb = state_callback;
s->arg = user_ptr;
s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
- s->rdpos = s->wrpos = 0;
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
+ s->hwpos = s->swpos = 0;
+ if (format == CUBEB_SAMPLE_FLOAT32LE) {
s->conv = 1;
- size = rpar.round * rpar.pchan * sizeof(float);
+ bps = sizeof(float);
} else {
s->conv = 0;
- size = rpar.round * rpar.pchan * rpar.bps;
+ bps = rpar.bps;
}
- s->buf = malloc(size);
- if (s->buf == NULL) {
- sio_close(s->hdl);
- free(s);
- return CUBEB_ERROR;
+ if (s->mode & SIO_PLAY) {
+ s->pbuf = malloc(bps * rpar.pchan * rpar.round);
+ if (s->pbuf == NULL)
+ goto err;
+ }
+ if (s->mode & SIO_REC) {
+ s->rbuf = malloc(bps * rpar.rchan * rpar.round);
+ if (s->rbuf == NULL)
+ goto err;
}
+ s->volume = 1.;
*stream = s;
DPR("sndio_stream_init() end, ok\n");
(void)context;
(void)stream_name;
return CUBEB_OK;
+err:
+ if (s->hdl)
+ WRAP(sio_close)(s->hdl);
+ if (s->pbuf)
+ free(s->pbuf);
+ if (s->rbuf)
+ free(s->pbuf);
+ free(s);
+ return CUBEB_ERROR;
}
static int
@@ -280,31 +512,41 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
static int
sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
- // XXX Not yet implemented.
- *rate = 44100;
-
+ /*
+ * We've no device-independent prefered rate; any rate will work if
+ * sndiod is running. If it isn't, 48kHz is what is most likely to
+ * work as most (but not all) devices support it.
+ */
+ *rate = 48000;
return CUBEB_OK;
}
static int
-sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
{
- // XXX Not yet implemented.
+ /*
+ * We've no device-independent minimum latency.
+ */
*latency_frames = 2048;
return CUBEB_OK;
}
static void
-sndio_stream_destroy(cubeb_stream *s)
+sndio_stream_destroy(cubeb_stream * s)
{
DPR("sndio_stream_destroy()\n");
- sio_close(s->hdl);
+ WRAP(sio_close)(s->hdl);
+ if (s->mode & SIO_PLAY)
+ free(s->pbuf);
+ if (s->mode & SIO_REC)
+ free(s->rbuf);
free(s);
}
static int
-sndio_stream_start(cubeb_stream *s)
+sndio_stream_start(cubeb_stream * s)
{
int err;
@@ -319,9 +561,9 @@ sndio_stream_start(cubeb_stream *s)
}
static int
-sndio_stream_stop(cubeb_stream *s)
+sndio_stream_stop(cubeb_stream * s)
{
- void *dummy;
+ void * dummy;
DPR("sndio_stream_stop()\n");
if (s->active) {
@@ -332,21 +574,25 @@ sndio_stream_stop(cubeb_stream *s)
}
static int
-sndio_stream_get_position(cubeb_stream *s, uint64_t *p)
+sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
{
pthread_mutex_lock(&s->mtx);
- DPR("sndio_stream_get_position() %lld\n", s->rdpos);
- *p = s->rdpos / s->bpf;
+ DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
+ *p = s->hwpos;
pthread_mutex_unlock(&s->mtx);
return CUBEB_OK;
}
static int
-sndio_stream_set_volume(cubeb_stream *s, float volume)
+sndio_stream_set_volume(cubeb_stream * s, float volume)
{
DPR("sndio_stream_set_volume(%f)\n", volume);
pthread_mutex_lock(&s->mtx);
- sio_setvol(s->hdl, SIO_MAXVOL * volume);
+ if (volume < 0.)
+ volume = 0.;
+ else if (volume > 1.0)
+ volume = 1.;
+ s->volume = volume;
pthread_mutex_unlock(&s->mtx);
return CUBEB_OK;
}
@@ -356,28 +602,68 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
// http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
// in the "Measuring the latency and buffers usage" paragraph.
- *latency = (stm->wrpos - stm->rdpos) / stm->bpf;
+ *latency = stm->swpos - stm->hwpos;
+ return CUBEB_OK;
+}
+
+static int
+sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ static char dev[] = SIO_DEVANY;
+ cubeb_device_info * device;
+
+ device = malloc(sizeof(cubeb_device_info));
+ if (device == NULL)
+ return CUBEB_ERROR;
+
+ device->devid = dev; /* passed to stream_init() */
+ device->device_id = dev; /* printable in UI */
+ device->friendly_name = dev; /* same, but friendly */
+ device->group_id = dev; /* actual device if full-duplex */
+ device->vendor_name = NULL; /* may be NULL */
+ device->type = type; /* Input/Output */
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
+ device->default_rate = 48000;
+ device->min_rate = 4000;
+ device->max_rate = 192000;
+ device->latency_lo = 480;
+ device->latency_hi = 9600;
+ collection->device = device;
+ collection->count = 1;
+ return CUBEB_OK;
+}
+
+static int
+sndio_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ free(collection->device);
return CUBEB_OK;
}
static struct cubeb_ops const sndio_ops = {
- .init = sndio_init,
- .get_backend_id = sndio_get_backend_id,
- .get_max_channel_count = sndio_get_max_channel_count,
- .get_min_latency = sndio_get_min_latency,
- .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = sndio_destroy,
- .stream_init = sndio_stream_init,
- .stream_destroy = sndio_stream_destroy,
- .stream_start = sndio_stream_start,
- .stream_stop = sndio_stream_stop,
- .stream_get_position = sndio_stream_get_position,
- .stream_get_latency = sndio_stream_get_latency,
- .stream_set_volume = sndio_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = sndio_init,
+ .get_backend_id = sndio_get_backend_id,
+ .get_max_channel_count = sndio_get_max_channel_count,
+ .get_min_latency = sndio_get_min_latency,
+ .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
+ .enumerate_devices = sndio_enumerate_devices,
+ .device_collection_destroy = sndio_device_collection_destroy,
+ .destroy = sndio_destroy,
+ .stream_init = sndio_stream_init,
+ .stream_destroy = sndio_stream_destroy,
+ .stream_start = sndio_stream_start,
+ .stream_stop = sndio_stream_stop,
+ .stream_get_position = sndio_stream_get_position,
+ .stream_get_latency = sndio_stream_get_latency,
+ .stream_set_volume = sndio_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_strings.c b/media/libcubeb/src/cubeb_strings.c
new file mode 100644
index 0000000000..5fe5b791e2
--- /dev/null
+++ b/media/libcubeb/src/cubeb_strings.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_strings.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CUBEB_STRINGS_INLINE_COUNT 4
+
+struct cubeb_strings {
+ uint32_t size;
+ uint32_t count;
+ char ** data;
+ char * small_store[CUBEB_STRINGS_INLINE_COUNT];
+};
+
+int
+cubeb_strings_init(cubeb_strings ** strings)
+{
+ cubeb_strings * strs = NULL;
+
+ if (!strings) {
+ return CUBEB_ERROR;
+ }
+
+ strs = calloc(1, sizeof(cubeb_strings));
+ assert(strs);
+
+ if (!strs) {
+ return CUBEB_ERROR;
+ }
+
+ strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]);
+ strs->count = 0;
+ strs->data = strs->small_store;
+
+ *strings = strs;
+
+ return CUBEB_OK;
+}
+
+void
+cubeb_strings_destroy(cubeb_strings * strings)
+{
+ char ** sp = NULL;
+ char ** se = NULL;
+
+ if (!strings) {
+ return;
+ }
+
+ sp = strings->data;
+ se = sp + strings->count;
+
+ for (; sp != se; sp++) {
+ if (*sp) {
+ free(*sp);
+ }
+ }
+
+ if (strings->data != strings->small_store) {
+ free(strings->data);
+ }
+
+ free(strings);
+}
+
+/** Look for string in string storage.
+ @param strings Opaque pointer to interned string storage.
+ @param s String to look up.
+ @retval Read-only string or NULL if not found. */
+static char const *
+cubeb_strings_lookup(cubeb_strings * strings, char const * s)
+{
+ char ** sp = NULL;
+ char ** se = NULL;
+
+ if (!strings || !s) {
+ return NULL;
+ }
+
+ sp = strings->data;
+ se = sp + strings->count;
+
+ for (; sp != se; sp++) {
+ if (*sp && strcmp(*sp, s) == 0) {
+ return *sp;
+ }
+ }
+
+ return NULL;
+}
+
+static char const *
+cubeb_strings_push(cubeb_strings * strings, char const * s)
+{
+ char * is = NULL;
+
+ if (strings->count == strings->size) {
+ char ** new_data;
+ uint32_t value_size = sizeof(char const *);
+ uint32_t new_size = strings->size * 2;
+ if (!new_size || value_size > (uint32_t)-1 / new_size) {
+ // overflow
+ return NULL;
+ }
+
+ if (strings->small_store == strings->data) {
+ // First time heap allocation.
+ new_data = malloc(new_size * value_size);
+ if (new_data) {
+ memcpy(new_data, strings->small_store, sizeof(strings->small_store));
+ }
+ } else {
+ new_data = realloc(strings->data, new_size * value_size);
+ }
+
+ if (!new_data) {
+ // out of memory
+ return NULL;
+ }
+
+ strings->size = new_size;
+ strings->data = new_data;
+ }
+
+ is = strdup(s);
+ strings->data[strings->count++] = is;
+
+ return is;
+}
+
+char const *
+cubeb_strings_intern(cubeb_strings * strings, char const * s)
+{
+ char const * is = NULL;
+
+ if (!strings || !s) {
+ return NULL;
+ }
+
+ is = cubeb_strings_lookup(strings, s);
+ if (is) {
+ return is;
+ }
+
+ return cubeb_strings_push(strings, s);
+}
diff --git a/media/libcubeb/src/cubeb_strings.h b/media/libcubeb/src/cubeb_strings.h
new file mode 100644
index 0000000000..cfffbbc68a
--- /dev/null
+++ b/media/libcubeb/src/cubeb_strings.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_STRINGS_H
+#define CUBEB_STRINGS_H
+
+#include "cubeb/cubeb.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** Opaque handle referencing interned string storage. */
+typedef struct cubeb_strings cubeb_strings;
+
+/** Initialize an interned string structure.
+ @param strings An out param where an opaque pointer to the
+ interned string storage will be returned.
+ @retval CUBEB_OK in case of success.
+ @retval CUBEB_ERROR in case of error. */
+CUBEB_EXPORT int
+cubeb_strings_init(cubeb_strings ** strings);
+
+/** Destroy an interned string structure freeing all associated memory.
+ @param strings An opaque pointer to the interned string storage to
+ destroy. */
+CUBEB_EXPORT void
+cubeb_strings_destroy(cubeb_strings * strings);
+
+/** Add string to internal storage.
+ @param strings Opaque pointer to interned string storage.
+ @param s String to add to storage.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR
+ */
+CUBEB_EXPORT char const *
+cubeb_strings_intern(cubeb_strings * strings, char const * s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // !CUBEB_STRINGS_H
diff --git a/media/libcubeb/src/cubeb_sun.c b/media/libcubeb/src/cubeb_sun.c
index b768bca561..3b7bef71d6 100644
--- a/media/libcubeb/src/cubeb_sun.c
+++ b/media/libcubeb/src/cubeb_sun.c
@@ -1,504 +1,733 @@
/*
- * Copyright (c) 2013, 2017 Ginn Chen <ginnchen@gmail.com>
+ * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
-#include <poll.h>
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <fcntl.h>
+#include <limits.h>
#include <pthread.h>
-#include <stdlib.h>
+#include <stdbool.h>
#include <stdio.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/audio.h>
-#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
#include <unistd.h>
-#include <sys/stropts.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-/* Macros copied from audio_oss.h */
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License (the "License").
- * You may not use this file except in compliance with the License.
- *
- * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
- * or http://www.opensolaris.org/os/licensing.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information: Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- */
+/* Default to 4 + 1 for the default device. */
+#ifndef SUN_DEVICE_COUNT
+#define SUN_DEVICE_COUNT (5)
+#endif
+
+/* Supported well by most hardware. */
+#ifndef SUN_PREFER_RATE
+#define SUN_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef SUN_LATENCY_MS
+#define SUN_LATENCY_MS (40)
+#endif
+
+#ifndef SUN_DEFAULT_DEVICE
+#define SUN_DEFAULT_DEVICE "/dev/audio"
+#endif
+
+#ifndef SUN_BUFFER_FRAMES
+#define SUN_BUFFER_FRAMES (32)
+#endif
+
/*
- * Copyright (C) 4Front Technologies 1996-2008.
- *
- * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
- * Use is subject to license terms.
+ * Supported on NetBSD regardless of hardware.
*/
-#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */
-#define OSSIOC_VOID 0x00000000 /* no parameters */
-#define OSSIOC_OUT 0x20000000 /* copy out parameters */
-#define OSSIOC_IN 0x40000000 /* copy in parameters */
-#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT)
-#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16)
-#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y))
-#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y))
-#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y))
-#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int)
-#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int)
-#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */
-#define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int)
-#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34)
-#define AFMT_S16_LE 0x00000010
-#define AFMT_S16_BE 0x00000020
-
-#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
-#define AFMT_S16_NE AFMT_S16_BE
+
+#ifndef SUN_MAX_CHANNELS
+#ifdef __NetBSD__
+#define SUN_MAX_CHANNELS (12)
#else
-#define AFMT_S16_NE AFMT_S16_LE
+#define SUN_MAX_CHANNELS (2)
+#endif
#endif
-#define DEFAULT_AUDIO_DEVICE "/dev/audio"
-#define DEFAULT_DSP_DEVICE "/dev/dsp"
-
-#define BUF_SIZE_MS 10
+#ifndef SUN_MIN_RATE
+#define SUN_MIN_RATE (1000)
+#endif
-#if defined(CUBEB_SUNAUDIO_DEBUG)
-#define DPR(...) fprintf(stderr, __VA_ARGS__);
-#else
-#define DPR(...) do {} while(0)
+#ifndef SUN_MAX_RATE
+#define SUN_MAX_RATE (192000)
#endif
-static struct cubeb_ops const sunaudio_ops;
+static struct cubeb_ops const sun_ops;
struct cubeb {
struct cubeb_ops const * ops;
};
+struct sun_stream {
+ char name[32];
+ int fd;
+ void * buf;
+ struct audio_info info;
+ unsigned frame_size; /* precision in bytes * channels */
+ bool floating;
+};
+
struct cubeb_stream {
- cubeb * context;
- pthread_t th; /* to run real-time audio i/o */
- pthread_mutex_t mutex; /* protects fd and frm_played */
- int fd; /* link us to sunaudio */
- int active; /* cubec_start() called */
- int conv; /* need float->s16 conversion */
- int using_oss;
- unsigned char *buf; /* data is prepared here */
- unsigned int rate;
- unsigned int n_channles;
- unsigned int bytes_per_ch;
- unsigned int n_frm;
- unsigned int buffer_size;
- int64_t frm_played;
- cubeb_data_callback data_cb; /* cb to preapare data */
- cubeb_state_callback state_cb; /* cb to notify about state changes */
- void *arg; /* user arg to {data,state}_cb */
+ struct cubeb * context;
+ void * user_ptr;
+ pthread_t thread;
+ pthread_mutex_t mutex; /* protects running, volume, frames_written */
+ bool running;
+ float volume;
+ struct sun_stream play;
+ struct sun_stream record;
+ cubeb_data_callback data_cb;
+ cubeb_state_callback state_cb;
+ uint64_t frames_written;
+ uint64_t blocks_written;
};
+int
+sun_init(cubeb ** context, char const * context_name)
+{
+ cubeb * c;
+
+ (void)context_name;
+ if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+ return CUBEB_ERROR;
+ }
+ c->ops = &sun_ops;
+ *context = c;
+ return CUBEB_OK;
+}
+
static void
-float_to_s16(void *ptr, long nsamp)
+sun_destroy(cubeb * context)
{
- int16_t *dst = ptr;
- float *src = ptr;
+ free(context);
+}
- while (nsamp-- > 0)
- *(dst++) = *(src++) * 32767;
+static char const *
+sun_get_backend_id(cubeb * context)
+{
+ return "sun";
}
-static void *
-sunaudio_mainloop(void *arg)
+static int
+sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
{
- struct cubeb_stream *s = arg;
- int state;
+ (void)context;
- DPR("sunaudio_mainloop()\n");
+ *rate = SUN_PREFER_RATE;
+ return CUBEB_OK;
+}
- s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
+static int
+sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ (void)context;
- pthread_mutex_lock(&s->mutex);
- DPR("sunaudio_mainloop(), started\n");
+ *max_channels = SUN_MAX_CHANNELS;
+ return CUBEB_OK;
+}
- for (;;) {
- if (!s->active) {
- DPR("sunaudio_mainloop() stopped\n");
- state = CUBEB_STATE_STOPPED;
- break;
- }
+static int
+sun_get_min_latency(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)context;
- if (!s->using_oss) {
- audio_info_t info;
- ioctl(s->fd, AUDIO_GETINFO, &info);
- if (s->frm_played > info.play.samples + 3 * s->n_frm) {
- pthread_mutex_unlock(&s->mutex);
- struct timespec ts = {0, 10000}; // 10 ms
- nanosleep(&ts, NULL);
- pthread_mutex_lock(&s->mutex);
- continue;
- }
- }
+ *latency_frames = SUN_LATENCY_MS * params.rate / 1000;
+ return CUBEB_OK;
+}
- pthread_mutex_unlock(&s->mutex);
- unsigned int got = s->data_cb(s, s->arg, NULL, s->buf, s->n_frm);
- DPR("sunaudio_mainloop() ask %d got %d\n", s->n_frm, got);
- pthread_mutex_lock(&s->mutex);
+static int
+sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
+ struct audio_device * dev)
+{
+ int fd = -1;
- if (got < 0) {
- DPR("sunaudio_mainloop() cb err\n");
- state = CUBEB_STATE_ERROR;
- break;
- }
+ if ((fd = open(device, O_RDONLY)) == -1) {
+ goto error;
+ }
+#ifdef AUDIO_GETFORMAT
+ if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
+ goto error;
+ }
+#endif
+#ifdef AUDIO_GETPROPS
+ if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
+ goto error;
+ }
+#endif
+ if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
+ goto error;
+ }
+ close(fd);
+ return CUBEB_OK;
+error:
+ if (fd != -1) {
+ close(fd);
+ }
+ return CUBEB_ERROR;
+}
- if (s->conv) {
- float_to_s16(s->buf, got * s->n_channles);
- }
+/*
+ * XXX: PR kern/54264
+ */
+static int
+sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
+{
+ return prinfo->precision >= 8 && prinfo->precision <= 32 &&
+ prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
+ prinfo->sample_rate < SUN_MAX_RATE &&
+ prinfo->sample_rate > SUN_MIN_RATE;
+}
- unsigned int avail = got * 2 * s->n_channles; // coverted to s16
- unsigned int pos = 0;
+static int
+sun_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ unsigned i;
+ cubeb_device_info device = {0};
+ char dev[16] = SUN_DEFAULT_DEVICE;
+ char dev_friendly[64];
+ struct audio_info hwfmt;
+ struct audio_device hwname;
+ struct audio_prinfo * prinfo = NULL;
+ int hwprops;
+
+ collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
+ if (collection->device == NULL) {
+ return CUBEB_ERROR;
+ }
+ collection->count = 0;
- while (avail > 0 && s->active) {
- int written = write(s->fd, s->buf + pos, avail);
- if (written == -1) {
- if (errno != EINTR && errno != EWOULDBLOCK) {
- DPR("sunaudio_mainloop() write err\n");
- state = CUBEB_STATE_ERROR;
- break;
- }
- pthread_mutex_unlock(&s->mutex);
- struct timespec ts = {0, 10000}; // 10 ms
- nanosleep(&ts, NULL);
- pthread_mutex_lock(&s->mutex);
- } else {
- pos += written;
- DPR("sunaudio_mainloop() write %d pos %d\n", written, pos);
- s->frm_played += written / 2 / s->n_channles;
- avail -= written;
- }
+ for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
+ if (i > 0) {
+ (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
}
-
- if ((got < s->n_frm)) {
- DPR("sunaudio_mainloop() drained\n");
- state = CUBEB_STATE_DRAINED;
+ if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
+ continue;
+ }
+#ifdef AUDIO_GETPROPS
+ device.type = 0;
+ if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
+ sun_prinfo_verify_sanity(&hwfmt.record)) {
+ /* the device supports recording, probably */
+ device.type |= CUBEB_DEVICE_TYPE_INPUT;
+ }
+ if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
+ sun_prinfo_verify_sanity(&hwfmt.play)) {
+ /* the device supports playback, probably */
+ device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ }
+ switch (device.type) {
+ case 0:
+ /* device doesn't do input or output, aliens probably involved */
+ continue;
+ case CUBEB_DEVICE_TYPE_INPUT:
+ if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
+ /* this device is input only, not scanning for those, skip it */
+ continue;
+ }
break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
+ /* this device is output only, not scanning for those, skip it */
+ continue;
+ }
+ break;
+ }
+ if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
+ prinfo = &hwfmt.record;
+ }
+ if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
+ prinfo = &hwfmt.play;
}
+#endif
+ if (i > 0) {
+ (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
+ hwname.name, hwname.version, hwname.config, i - 1);
+ } else {
+ (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
+ hwname.name, hwname.version, hwname.config);
+ }
+ device.devid = (void *)(uintptr_t)i;
+ device.device_id = strdup(dev);
+ device.friendly_name = strdup(dev_friendly);
+ device.group_id = strdup(dev);
+ device.vendor_name = strdup(hwname.name);
+ device.type = type;
+ device.state = CUBEB_DEVICE_STATE_ENABLED;
+ device.preferred =
+ (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+#ifdef AUDIO_GETFORMAT
+ device.max_channels = prinfo->channels;
+ device.default_rate = prinfo->sample_rate;
+#else
+ device.max_channels = 2;
+ device.default_rate = SUN_PREFER_RATE;
+#endif
+ device.default_format = CUBEB_DEVICE_FMT_S16NE;
+ device.format = CUBEB_DEVICE_FMT_S16NE;
+ device.min_rate = SUN_MIN_RATE;
+ device.max_rate = SUN_MAX_RATE;
+ device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
+ device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
+ collection->device[collection->count++] = device;
}
+ return CUBEB_OK;
+}
- pthread_mutex_unlock(&s->mutex);
- s->state_cb(s, s->arg, state);
+static int
+sun_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ unsigned i;
- return NULL;
+ for (i = 0; i < collection->count; ++i) {
+ free((char *)collection->device[i].device_id);
+ free((char *)collection->device[i].friendly_name);
+ free((char *)collection->device[i].group_id);
+ free((char *)collection->device[i].vendor_name);
+ }
+ free(collection->device);
+ return CUBEB_OK;
}
-/*static*/ int
-sunaudio_init(cubeb **context, char const *context_name)
+static int
+sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+ struct audio_info * info, struct audio_prinfo * prinfo)
{
- DPR("sunaudio_init(%s)\n", context_name);
- *context = malloc(sizeof(*context));
- (*context)->ops = &sunaudio_ops;
- (void)context_name;
+ prinfo->channels = params->channels;
+ prinfo->sample_rate = params->rate;
+#ifdef AUDIO_ENCODING_SLINEAR_LE
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR;
+ prinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+#else
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ prinfo->encoding = AUDIO_ENCODING_LINEAR;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ prinfo->encoding = AUDIO_ENCODING_LINEAR;
+ prinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+#endif
+ if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
+ return CUBEB_ERROR;
+ }
return CUBEB_OK;
}
-static char const *
-sunaudio_get_backend_id(cubeb *context)
+static int
+sun_stream_stop(cubeb_stream * s)
{
- return "sunaudio";
+ pthread_mutex_lock(&s->mutex);
+ if (s->running) {
+ s->running = false;
+ pthread_mutex_unlock(&s->mutex);
+ pthread_join(s->thread, NULL);
+ } else {
+ pthread_mutex_unlock(&s->mutex);
+ }
+ return CUBEB_OK;
}
static void
-sunaudio_destroy(cubeb *context)
+sun_stream_destroy(cubeb_stream * s)
{
- DPR("sunaudio_destroy()\n");
- free(context);
+ sun_stream_stop(s);
+ pthread_mutex_destroy(&s->mutex);
+ if (s->play.fd != -1) {
+ close(s->play.fd);
+ }
+ if (s->record.fd != -1) {
+ close(s->record.fd);
+ }
+ free(s->play.buf);
+ free(s->record.buf);
+ free(s);
}
-static int
-sunaudio_stream_init(cubeb *context,
- cubeb_stream **stream,
- char const *stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void *user_ptr)
+static void
+sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
{
- struct cubeb_stream *s;
- DPR("sunaudio_stream_init(%s)\n", stream_name);
- size_t size;
-
- s = malloc(sizeof(struct cubeb_stream));
- if (s == NULL)
- return CUBEB_ERROR;
- s->context = context;
-
- // If UTAUDIODEV is set, use it with Sun Audio interface
- char * sa_device_name = getenv("UTAUDIODEV");
- char * dsp_device_name = NULL;
- if (!sa_device_name) {
- dsp_device_name = getenv("AUDIODSP");
- if (!dsp_device_name) {
- dsp_device_name = DEFAULT_DSP_DEVICE;
- }
- sa_device_name = getenv("AUDIODEV");
- if (!sa_device_name) {
- sa_device_name = DEFAULT_AUDIO_DEVICE;
- }
+ float * in = buf;
+ int32_t * out = buf;
+ int32_t * tail = out + sample_count;
+
+ while (out < tail) {
+ float f = *(in++) * vol;
+ if (f < -1.0)
+ f = -1.0;
+ else if (f > 1.0)
+ f = 1.0;
+ *(out++) = f * (float)INT32_MAX;
}
+}
- s->using_oss = 0;
- // Try to use OSS if available
- if (dsp_device_name) {
- s->fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK);
- if (s->fd >= 0) {
- s->using_oss = 1;
- }
- }
+static void
+sun_linear32_to_float(void * buf, unsigned sample_count)
+{
+ int32_t * in = buf;
+ float * out = buf;
+ float * tail = out + sample_count;
- // Try Sun Audio
- if (!s->using_oss) {
- s->fd = open(sa_device_name, O_WRONLY | O_NONBLOCK);
+ while (out < tail) {
+ *(out++) = (1.0 / 0x80000000) * *(in++);
}
+}
- if (s->fd < 0) {
- free(s);
- DPR("sunaudio_stream_init(), open() failed\n");
- return CUBEB_ERROR;
+static void
+sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
+{
+ unsigned i;
+ int32_t multiplier = vol * 0x8000;
+
+ for (i = 0; i < sample_count; ++i) {
+ buf[i] = (buf[i] * multiplier) >> 15;
}
+}
- if (s->using_oss) {
- if (ioctl(s->fd, SNDCTL_DSP_SPEED, &output_stream_params->rate) < 0) {
- DPR("ioctl SNDCTL_DSP_SPEED failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+static void *
+sun_io_routine(void * arg)
+{
+ cubeb_stream * s = arg;
+ cubeb_state state = CUBEB_STATE_STARTED;
+ size_t to_read = 0;
+ long to_write = 0;
+ size_t write_ofs = 0;
+ size_t read_ofs = 0;
+ int drain = 0;
+
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+ while (state != CUBEB_STATE_ERROR) {
+ pthread_mutex_lock(&s->mutex);
+ if (!s->running) {
+ pthread_mutex_unlock(&s->mutex);
+ state = CUBEB_STATE_STOPPED;
+ break;
}
-
- if (ioctl(s->fd, SNDCTL_DSP_CHANNELS, &output_stream_params->channels) < 0) {
- DPR("ioctl SNDCTL_DSP_CHANNELS failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ pthread_mutex_unlock(&s->mutex);
+ if (s->record.fd != -1 && s->record.floating) {
+ sun_linear32_to_float(s->record.buf,
+ s->record.info.record.channels * SUN_BUFFER_FRAMES);
}
+ to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
+ SUN_BUFFER_FRAMES);
+ if (to_write == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ if (s->play.fd != -1) {
+ float vol;
+
+ pthread_mutex_lock(&s->mutex);
+ vol = s->volume;
+ pthread_mutex_unlock(&s->mutex);
- int format = AFMT_S16_NE;
- if (ioctl(s->fd, SNDCTL_DSP_SETFMT, &format) < 0) {
- DPR("ioctl SNDCTL_DSP_SETFMT failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ if (s->play.floating) {
+ sun_float_to_linear32(s->play.buf,
+ s->play.info.play.channels * to_write, vol);
+ } else {
+ sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
+ vol);
+ }
}
- } else {
- audio_info_t audio_info;
- AUDIO_INITINFO(&audio_info)
- audio_info.play.sample_rate = output_stream_params->rate;
- audio_info.play.channels = output_stream_params->channels;
- audio_info.play.encoding = AUDIO_ENCODING_LINEAR;
- audio_info.play.precision = 16;
- if (ioctl(s->fd, AUDIO_SETINFO, &audio_info) == -1) {
- DPR("ioctl AUDIO_SETINFO failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ if (to_write < SUN_BUFFER_FRAMES) {
+ drain = 1;
}
- }
-
- s->conv = 0;
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16NE:
- s->bytes_per_ch = 2;
- break;
- case CUBEB_SAMPLE_FLOAT32NE:
- s->bytes_per_ch = 4;
- s->conv = 1;
+ to_write = s->play.fd != -1 ? to_write : 0;
+ to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
+ write_ofs = 0;
+ read_ofs = 0;
+ while (to_write > 0 || to_read > 0) {
+ size_t bytes;
+ ssize_t n, frames;
+
+ if (to_write > 0) {
+ bytes = to_write * s->play.frame_size;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
+ 0) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ frames = n / s->play.frame_size;
+ pthread_mutex_lock(&s->mutex);
+ s->frames_written += frames;
+ pthread_mutex_unlock(&s->mutex);
+ to_write -= frames;
+ write_ofs += n;
+ }
+ if (to_read > 0) {
+ bytes = to_read * s->record.frame_size;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
+ bytes)) < 0) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ frames = n / s->record.frame_size;
+ to_read -= frames;
+ read_ofs += n;
+ }
+ }
+ if (drain && state != CUBEB_STATE_ERROR) {
+ state = CUBEB_STATE_DRAINED;
break;
- default:
- DPR("sunaudio_stream_init() unsupported format\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ }
}
+ s->state_cb(s, s->user_ptr, state);
+ return NULL;
+}
- s->active = 0;
- s->rate = output_stream_params->rate;
- s->n_channles = output_stream_params->channels;
- s->data_cb = data_callback;
+static int
+sun_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int ret = CUBEB_OK;
+ cubeb_stream * s = NULL;
+
+ (void)stream_name;
+ (void)latency_frames;
+ if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->record.fd = -1;
+ s->play.fd = -1;
+ if (input_device != 0) {
+ snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
+ (uintptr_t)input_device - 1);
+ } else {
+ snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
+ }
+ if (output_device != 0) {
+ snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
+ (uintptr_t)output_device - 1);
+ } else {
+ snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
+ }
+ if (input_stream_params != NULL) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ if (s->record.fd == -1) {
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
+ LOG("Audio device could not be opened as read-only");
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ AUDIO_INITINFO(&s->record.info);
+#ifdef AUMODE_RECORD
+ s->record.info.mode = AUMODE_RECORD;
+#endif
+ if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
+ &s->record.info, &s->record.info.record)) !=
+ CUBEB_OK) {
+ LOG("Setting record params failed");
+ goto error;
+ }
+ s->record.floating =
+ (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ }
+ if (output_stream_params != NULL) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ if (s->play.fd == -1) {
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
+ LOG("Audio device could not be opened as write-only");
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ AUDIO_INITINFO(&s->play.info);
+#ifdef AUMODE_PLAY
+ s->play.info.mode = AUMODE_PLAY;
+#endif
+ if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
+ &s->play.info, &s->play.info.play)) !=
+ CUBEB_OK) {
+ LOG("Setting play params failed");
+ goto error;
+ }
+ s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ }
+ s->context = context;
+ s->volume = 1.0;
s->state_cb = state_callback;
- s->arg = user_ptr;
+ s->data_cb = data_callback;
+ s->user_ptr = user_ptr;
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
- free(s);
- return CUBEB_ERROR;
+ LOG("Failed to create mutex");
+ goto error;
}
- s->frm_played = 0;
- s->n_frm = s->rate * BUF_SIZE_MS / 1000;
- s->buffer_size = s->bytes_per_ch * s->n_channles * s->n_frm;
- s->buf = malloc(s->buffer_size);
- if (s->buf == NULL) {
- close(s->fd);
- free(s);
- return CUBEB_ERROR;
+ s->play.frame_size =
+ s->play.info.play.channels * (s->play.info.play.precision / 8);
+ if (s->play.fd != -1 &&
+ (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->record.frame_size =
+ s->record.info.record.channels * (s->record.info.record.precision / 8);
+ if (s->record.fd != -1 &&
+ (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
+ NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
}
-
*stream = s;
- DPR("sunaudio_stream_init() end, ok\n");
return CUBEB_OK;
-}
-
-static void
-sunaudio_stream_destroy(cubeb_stream *s)
-{
- DPR("sunaudio_stream_destroy()\n");
- if (s->fd > 0) {
- // Flush buffer
- if (s->using_oss) {
- ioctl(s->fd, SNDCTL_DSP_HALT_OUTPUT);
- } else {
- ioctl(s->fd, I_FLUSH);
- }
- close(s->fd);
+error:
+ if (s != NULL) {
+ sun_stream_destroy(s);
}
- free(s->buf);
- free(s);
+ return ret;
}
static int
-sunaudio_stream_start(cubeb_stream *s)
+sun_stream_start(cubeb_stream * s)
{
- int err;
-
- DPR("sunaudio_stream_start()\n");
- s->active = 1;
- err = pthread_create(&s->th, NULL, sunaudio_mainloop, s);
- if (err) {
- s->active = 0;
+ s->running = true;
+ if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
+ LOG("Couldn't create thread");
return CUBEB_ERROR;
}
return CUBEB_OK;
}
static int
-sunaudio_stream_stop(cubeb_stream *s)
+sun_stream_get_position(cubeb_stream * s, uint64_t * position)
{
- void *dummy;
+#ifdef AUDIO_GETOOFFS
+ struct audio_offset offset;
- DPR("sunaudio_stream_stop()\n");
- if (s->active) {
- s->active = 0;
- pthread_join(s->th, &dummy);
+ if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
+ return CUBEB_ERROR;
}
+ s->blocks_written += offset.deltablks;
+ *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
return CUBEB_OK;
-}
-
-static int
-sunaudio_stream_get_position(cubeb_stream *s, uint64_t *p)
-{
- int rv = CUBEB_OK;
+#else
pthread_mutex_lock(&s->mutex);
- if (s->active && s->fd > 0) {
- if (s->using_oss) {
- int delay;
- ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay);
- int64_t t = s->frm_played - delay / s->n_channles / 2;
- if (t < 0) {
- *p = 0;
- } else {
- *p = t;
- }
- } else {
- audio_info_t info;
- ioctl(s->fd, AUDIO_GETINFO, &info);
- *p = info.play.samples;
- }
- DPR("sunaudio_stream_get_position() %lld\n", *p);
- } else {
- rv = CUBEB_ERROR;
- }
+ *position = s->frames_written;
pthread_mutex_unlock(&s->mutex);
- return rv;
+ return CUBEB_OK;
+#endif
}
static int
-sunaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
{
- if (!ctx || !max_channels)
- return CUBEB_ERROR;
+#ifdef AUDIO_GETBUFINFO
+ struct audio_info info;
- *max_channels = 2;
+ if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
+ return CUBEB_ERROR;
+ }
+ *latency = (info.play.seek + info.blocksize) / s->play.frame_size;
return CUBEB_OK;
+#else
+ cubeb_stream_params params;
+
+ params.rate = s->play.info.play.sample_rate;
+
+ return sun_get_min_latency(NULL, params, latency);
+#endif
}
static int
-sunaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+sun_stream_set_volume(cubeb_stream * stream, float volume)
{
- if (!ctx || !rate)
- return CUBEB_ERROR;
-
- // XXX Not yet implemented.
- *rate = 44100;
-
+ pthread_mutex_lock(&stream->mutex);
+ stream->volume = volume;
+ pthread_mutex_unlock(&stream->mutex);
return CUBEB_OK;
}
static int
-sunaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
{
- if (!ctx || !latency_ms)
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL) {
return CUBEB_ERROR;
-
- // XXX Not yet implemented.
- *latency_ms = 20;
-
+ }
+ (*device)->input_name =
+ stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
+ (*device)->output_name =
+ stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
return CUBEB_OK;
}
static int
-sunaudio_stream_get_latency(cubeb_stream * s, uint32_t * latency)
+sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
{
- if (!s || !latency)
- return CUBEB_ERROR;
-
- int rv = CUBEB_OK;
- pthread_mutex_lock(&s->mutex);
- if (s->active && s->fd > 0) {
- if (s->using_oss) {
- int delay;
- ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay);
- *latency = delay / s->n_channles / 2 / s->rate;
- } else {
- audio_info_t info;
- ioctl(s->fd, AUDIO_GETINFO, &info);
- *latency = (s->frm_played - info.play.samples) / s->rate;
- }
- DPR("sunaudio_stream_get_position() %lld\n", *p);
- } else {
- rv = CUBEB_ERROR;
- }
- pthread_mutex_unlock(&s->mutex);
- return rv;
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
}
-static struct cubeb_ops const sunaudio_ops = {
- .init = sunaudio_init,
- .get_backend_id = sunaudio_get_backend_id,
- .destroy = sunaudio_destroy,
- .get_preferred_sample_rate = sunaudio_get_preferred_sample_rate,
- .stream_init = sunaudio_stream_init,
- .stream_destroy = sunaudio_stream_destroy,
- .stream_start = sunaudio_stream_start,
- .stream_stop = sunaudio_stream_stop,
- .stream_get_position = sunaudio_stream_get_position,
- .get_max_channel_count = sunaudio_get_max_channel_count,
- .get_min_latency = sunaudio_get_min_latency,
- .stream_get_latency = sunaudio_stream_get_latency
-};
+static struct cubeb_ops const sun_ops = {
+ .init = sun_init,
+ .get_backend_id = sun_get_backend_id,
+ .get_max_channel_count = sun_get_max_channel_count,
+ .get_min_latency = sun_get_min_latency,
+ .get_preferred_sample_rate = sun_get_preferred_sample_rate,
+ .enumerate_devices = sun_enumerate_devices,
+ .device_collection_destroy = sun_device_collection_destroy,
+ .destroy = sun_destroy,
+ .stream_init = sun_stream_init,
+ .stream_destroy = sun_stream_destroy,
+ .stream_start = sun_stream_start,
+ .stream_stop = sun_stream_stop,
+ .stream_get_position = sun_stream_get_position,
+ .stream_get_latency = sun_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = sun_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = sun_get_current_device,
+ .stream_device_destroy = sun_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_utils.cpp b/media/libcubeb/src/cubeb_utils.cpp
new file mode 100644
index 0000000000..dd1aef7b82
--- /dev/null
+++ b/media/libcubeb/src/cubeb_utils.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2018 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_utils.h"
+
+size_t
+cubeb_sample_size(cubeb_sample_format format)
+{
+ switch (format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_S16BE:
+ return sizeof(int16_t);
+ case CUBEB_SAMPLE_FLOAT32LE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return sizeof(float);
+ default:
+ // should never happen as all cases are handled above.
+ assert(false);
+ return 0;
+ }
+}
diff --git a/media/libcubeb/src/cubeb_utils.h b/media/libcubeb/src/cubeb_utils.h
index d8e9928fe2..fd7a3d7406 100644
--- a/media/libcubeb/src/cubeb_utils.h
+++ b/media/libcubeb/src/cubeb_utils.h
@@ -8,97 +8,154 @@
#if !defined(CUBEB_UTILS)
#define CUBEB_UTILS
+#include "cubeb/cubeb.h"
+
+#ifdef __cplusplus
+
+#include <assert.h>
+#include <mutex>
#include <stdint.h>
#include <string.h>
-#include <assert.h>
#include <type_traits>
-#if defined(WIN32)
+#if defined(_WIN32)
#include "cubeb_utils_win.h"
#else
#include "cubeb_utils_unix.h"
#endif
/** Similar to memcpy, but accounts for the size of an element. */
-template<typename T>
-void PodCopy(T * destination, const T * source, size_t count)
+template <typename T>
+void
+PodCopy(T * destination, const T * source, size_t count)
{
static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination && source);
memcpy(destination, source, count * sizeof(T));
}
/** Similar to memmove, but accounts for the size of an element. */
-template<typename T>
-void PodMove(T * destination, const T * source, size_t count)
+template <typename T>
+void
+PodMove(T * destination, const T * source, size_t count)
{
static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination && source);
memmove(destination, source, count * sizeof(T));
}
/** Similar to a memset to zero, but accounts for the size of an element. */
-template<typename T>
-void PodZero(T * destination, size_t count)
+template <typename T>
+void
+PodZero(T * destination, size_t count)
{
static_assert(std::is_trivial<T>::value, "Requires trivial type");
- memset(destination, 0, count * sizeof(T));
+ assert(destination);
+ memset(destination, 0, count * sizeof(T));
+}
+
+namespace {
+template <typename T, typename Trait>
+void
+Copy(T * destination, const T * source, size_t count, Trait)
+{
+ for (size_t i = 0; i < count; i++) {
+ destination[i] = source[i];
+ }
+}
+
+template <typename T>
+void
+Copy(T * destination, const T * source, size_t count, std::true_type)
+{
+ PodCopy(destination, source, count);
+}
+} // namespace
+
+/**
+ * This allows copying a number of elements from a `source` pointer to a
+ * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
+ * calls the constructors and destructors otherwise.
+ */
+template <typename T>
+void
+Copy(T * destination, const T * source, size_t count)
+{
+ assert(destination && source);
+ Copy(destination, source, count, typename std::is_trivial<T>::type());
}
-template<typename T>
-class auto_array
+namespace {
+template <typename T, typename Trait>
+void
+ConstructDefault(T * destination, size_t count, Trait)
{
+ for (size_t i = 0; i < count; i++) {
+ destination[i] = T();
+ }
+}
+
+template <typename T>
+void
+ConstructDefault(T * destination, size_t count, std::true_type)
+{
+ PodZero(destination, count);
+}
+} // namespace
+
+/**
+ * This allows zeroing (using memset) or default-constructing a number of
+ * elements calling the constructors and destructors if necessary.
+ */
+template <typename T>
+void
+ConstructDefault(T * destination, size_t count)
+{
+ assert(destination);
+ ConstructDefault(destination, count, typename std::is_arithmetic<T>::type());
+}
+
+template <typename T> class auto_array {
public:
explicit auto_array(uint32_t capacity = 0)
- : data_(capacity ? new T[capacity] : nullptr)
- , capacity_(capacity)
- , length_(0)
- {}
-
- ~auto_array()
+ : data_(capacity ? new T[capacity] : nullptr), capacity_(capacity),
+ length_(0)
{
- delete [] data_;
}
+ ~auto_array() { delete[] data_; }
+
/** Get a constant pointer to the underlying data. */
- T * data() const
- {
- return data_;
- }
+ T * data() const { return data_; }
+
+ T * end() const { return data_ + length_; }
- const T& at(size_t index) const
+ const T & at(size_t index) const
{
assert(index < length_ && "out of range");
return data_[index];
}
- T& at(size_t index)
+ T & at(size_t index)
{
assert(index < length_ && "out of range");
return data_[index];
}
/** Get how much underlying storage this auto_array has. */
- size_t capacity() const
- {
- return capacity_;
- }
+ size_t capacity() const { return capacity_; }
/** Get how much elements this auto_array contains. */
- size_t length() const
- {
- return length_;
- }
+ size_t length() const { return length_; }
/** Keeps the storage, but removes all the elements from the array. */
- void clear()
- {
- length_ = 0;
- }
+ void clear() { length_ = 0; }
- /** Change the storage of this auto array, copying the elements to the new
- * storage.
- * @returns true in case of success
- * @returns false if the new capacity is not big enough to accomodate for the
- * elements in the array.
- */
+ /** Change the storage of this auto array, copying the elements to the new
+ * storage.
+ * @returns true in case of success
+ * @returns false if the new capacity is not big enough to accomodate for the
+ * elements in the array.
+ */
bool reserve(size_t new_capacity)
{
if (new_capacity < length_) {
@@ -109,17 +166,17 @@ public:
PodCopy(new_data, data_, length_);
}
capacity_ = new_capacity;
- delete [] data_;
+ delete[] data_;
data_ = new_data;
return true;
}
- /** Append `length` elements to the end of the array, resizing the array if
- * needed.
- * @parameter elements the elements to append to the array.
- * @parameter length the number of elements to append to the array.
- */
+ /** Append `length` elements to the end of the array, resizing the array if
+ * needed.
+ * @parameter elements the elements to append to the array.
+ * @parameter length the number of elements to append to the array.
+ */
void push(const T * elements, size_t length)
{
if (length_ + length > capacity_) {
@@ -157,17 +214,14 @@ public:
}
/** Return the number of free elements in the array. */
- size_t available() const
- {
- return capacity_ - length_;
- }
+ size_t available() const { return capacity_ - length_; }
/** Copies `length` elements to `elements` if it is not null, and shift
- * the remaining elements of the `auto_array` to the beginning.
- * @parameter elements a buffer to copy the elements to, or nullptr.
- * @parameter length the number of elements to copy.
- * @returns true in case of success.
- * @returns false if the auto_array contains less than `length` elements. */
+ * the remaining elements of the `auto_array` to the beginning.
+ * @parameter elements a buffer to copy the elements to, or nullptr.
+ * @parameter length the number of elements to copy.
+ * @returns true in case of success.
+ * @returns false if the auto_array contains less than `length` elements. */
bool pop(T * elements, size_t length)
{
if (length > length_) {
@@ -198,18 +252,58 @@ private:
size_t length_;
};
-struct auto_lock {
- explicit auto_lock(owned_critical_section & lock)
- : lock(lock)
- {
- lock.enter();
- }
- ~auto_lock()
+struct auto_array_wrapper {
+ virtual void push(void * elements, size_t length) = 0;
+ virtual size_t length() = 0;
+ virtual void push_silence(size_t length) = 0;
+ virtual bool pop(size_t length) = 0;
+ virtual void * data() = 0;
+ virtual void * end() = 0;
+ virtual void clear() = 0;
+ virtual bool reserve(size_t capacity) = 0;
+ virtual void set_length(size_t length) = 0;
+ virtual ~auto_array_wrapper() {}
+};
+
+template <typename T>
+struct auto_array_wrapper_impl : public auto_array_wrapper {
+ auto_array_wrapper_impl() {}
+
+ explicit auto_array_wrapper_impl(uint32_t size) : ar(size) {}
+
+ void push(void * elements, size_t length) override
{
- lock.leave();
+ ar.push(static_cast<T *>(elements), length);
}
+
+ size_t length() override { return ar.length(); }
+
+ void push_silence(size_t length) override { ar.push_silence(length); }
+
+ bool pop(size_t length) override { return ar.pop(nullptr, length); }
+
+ void * data() override { return ar.data(); }
+
+ void * end() override { return ar.end(); }
+
+ void clear() override { ar.clear(); }
+
+ bool reserve(size_t capacity) override { return ar.reserve(capacity); }
+
+ void set_length(size_t length) override { ar.set_length(length); }
+
+ ~auto_array_wrapper_impl() { ar.clear(); }
+
private:
- owned_critical_section & lock;
+ auto_array<T> ar;
};
+extern "C" {
+size_t
+cubeb_sample_size(cubeb_sample_format format);
+}
+
+using auto_lock = std::lock_guard<owned_critical_section>;
+#endif // __cplusplus
+
#endif /* CUBEB_UTILS */
diff --git a/media/libcubeb/src/cubeb_utils_unix.h b/media/libcubeb/src/cubeb_utils_unix.h
index 80219d58b1..b6618ca45e 100644
--- a/media/libcubeb/src/cubeb_utils_unix.h
+++ b/media/libcubeb/src/cubeb_utils_unix.h
@@ -8,13 +8,12 @@
#if !defined(CUBEB_UTILS_UNIX)
#define CUBEB_UTILS_UNIX
-#include <pthread.h>
#include <errno.h>
+#include <pthread.h>
#include <stdio.h>
/* This wraps a critical section to track the owner in debug mode. */
-class owned_critical_section
-{
+class owned_critical_section {
public:
owned_critical_section()
{
@@ -29,7 +28,7 @@ public:
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_init(&mutex, &attr);
+ pthread_mutex_init(&mutex, &attr);
#ifndef NDEBUG
assert(r == 0);
#endif
@@ -42,29 +41,29 @@ public:
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_destroy(&mutex);
+ pthread_mutex_destroy(&mutex);
#ifndef NDEBUG
assert(r == 0);
#endif
}
- void enter()
+ void lock()
{
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_lock(&mutex);
+ pthread_mutex_lock(&mutex);
#ifndef NDEBUG
assert(r == 0 && "Deadlock");
#endif
}
- void leave()
+ void unlock()
{
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_unlock(&mutex);
+ pthread_mutex_unlock(&mutex);
#ifndef NDEBUG
assert(r == 0 && "Unlocking unlocked mutex");
#endif
@@ -82,8 +81,8 @@ private:
pthread_mutex_t mutex;
// Disallow copy and assignment because pthread_mutex_t cannot be copied.
- owned_critical_section(const owned_critical_section&);
- owned_critical_section& operator=(const owned_critical_section&);
+ owned_critical_section(const owned_critical_section &);
+ owned_critical_section & operator=(const owned_critical_section &);
};
#endif /* CUBEB_UTILS_UNIX */
diff --git a/media/libcubeb/src/cubeb_utils_win.h b/media/libcubeb/src/cubeb_utils_win.h
index 2b094cd93d..4c47f454c7 100644
--- a/media/libcubeb/src/cubeb_utils_win.h
+++ b/media/libcubeb/src/cubeb_utils_win.h
@@ -8,28 +8,25 @@
#if !defined(CUBEB_UTILS_WIN)
#define CUBEB_UTILS_WIN
-#include <windows.h>
#include "cubeb-internal.h"
+#include <windows.h>
/* This wraps a critical section to track the owner in debug mode, adapted from
- NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */
-class owned_critical_section
-{
+ NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx
+ */
+class owned_critical_section {
public:
owned_critical_section()
#ifndef NDEBUG
- : owner(0)
+ : owner(0)
#endif
{
InitializeCriticalSection(&critical_section);
}
- ~owned_critical_section()
- {
- DeleteCriticalSection(&critical_section);
- }
+ ~owned_critical_section() { DeleteCriticalSection(&critical_section); }
- void enter()
+ void lock()
{
EnterCriticalSection(&critical_section);
#ifndef NDEBUG
@@ -38,7 +35,7 @@ public:
#endif
}
- void leave()
+ void unlock()
{
#ifndef NDEBUG
/* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
@@ -64,8 +61,8 @@ private:
#endif
// Disallow copy and assignment because CRICICAL_SECTION cannot be copied.
- owned_critical_section(const owned_critical_section&);
- owned_critical_section& operator=(const owned_critical_section&);
+ owned_critical_section(const owned_critical_section &);
+ owned_critical_section & operator=(const owned_critical_section &);
};
#endif /* CUBEB_UTILS_WIN */
diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
index e88d6becd8..0c794dcbd2 100644
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -4,121 +4,232 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
+#define _WIN32_WINNT 0x0603
#define NOMINMAX
-#include <initguid.h>
-#include <windows.h>
-#include <mmdeviceapi.h>
-#include <windef.h>
+#include <algorithm>
+#include <atomic>
#include <audioclient.h>
+#include <avrt.h>
+#include <cmath>
#include <devicetopology.h>
+#include <initguid.h>
+#include <limits>
+#include <memory>
+#include <mmdeviceapi.h>
#include <process.h>
-#include <avrt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdint.h>
-#include <cmath>
-#include <algorithm>
-#include <memory>
-#include <limits>
-#include <atomic>
+#include <vector>
+#include <windef.h>
+#include <windows.h>
-#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
#include "cubeb_resampler.h"
+#include "cubeb_strings.h"
#include "cubeb_utils.h"
-/* devicetopology.h missing in MinGW. */
-#ifndef __devicetopology_h__
-#include "cubeb_devicetopology.h"
+// Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
+// Copy the interface definition from audioclient.h here to make the code
+// simpler and so that we can still access IAudioClient3 via COM if cubeb was
+// compiled against an older SDK.
+#ifndef __IAudioClient3_INTERFACE_DEFINED__
+#define __IAudioClient3_INTERFACE_DEFINED__
+MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42")
+IAudioClient3 : public IAudioClient
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
+ /* [annotation][in] */
+ _In_ const WAVEFORMATEX * pFormat,
+ /* [annotation][out] */
+ _Out_ UINT32 * pDefaultPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pFundamentalPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pMinPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pMaxPeriodInFrames) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
+ /* [unique][annotation][out] */
+ _Out_ WAVEFORMATEX * *ppFormat,
+ /* [annotation][out] */
+ _Out_ UINT32 * pCurrentPeriodInFrames) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
+ /* [annotation][in] */
+ _In_ DWORD StreamFlags,
+ /* [annotation][in] */
+ _In_ UINT32 PeriodInFrames,
+ /* [annotation][in] */
+ _In_ const WAVEFORMATEX * pFormat,
+ /* [annotation][in] */
+ _In_opt_ LPCGUID AudioSessionGuid) = 0;
+};
+#ifdef __CRT_UUID_DECL
+// Required for MinGW
+__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B,
+ 0x7A, 0x59, 0x87, 0xAD, 0x42)
#endif
-
-/* Taken from winbase.h, Not in MinGW. */
-#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
-#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
+#endif
+// Copied from audioclient.h in the Windows 10 SDK
+#ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED
+#define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028)
#endif
#ifndef PKEY_Device_FriendlyName
-DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
+DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,
+ 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0,
+ 14); // DEVPROP_TYPE_STRING
#endif
#ifndef PKEY_Device_InstanceId
-DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); // VT_LPWSTR
+DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e,
+ 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57,
+ 0x00000100); // VT_LPWSTR
#endif
namespace {
-template<typename T, size_t N>
-constexpr size_t
-ARRAY_LENGTH(T(&)[N])
+
+const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
+
+struct com_heap_ptr_deleter {
+ void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
+};
+
+template <typename T>
+using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>;
+
+template <typename T, size_t N> constexpr size_t ARRAY_LENGTH(T (&)[N])
{
return N;
}
-void
-SafeRelease(HANDLE handle)
-{
- if (handle) {
- CloseHandle(handle);
+template <typename T> class no_addref_release : public T {
+ ULONG STDMETHODCALLTYPE AddRef() = 0;
+ ULONG STDMETHODCALLTYPE Release() = 0;
+};
+
+template <typename T> class com_ptr {
+public:
+ com_ptr() noexcept = default;
+
+ com_ptr(com_ptr const & other) noexcept = delete;
+ com_ptr & operator=(com_ptr const & other) noexcept = delete;
+ T ** operator&() const noexcept = delete;
+
+ ~com_ptr() noexcept { release(); }
+
+ com_ptr(com_ptr && other) noexcept : ptr(other.ptr) { other.ptr = nullptr; }
+
+ com_ptr & operator=(com_ptr && other) noexcept
+ {
+ if (ptr != other.ptr) {
+ release();
+ ptr = other.ptr;
+ other.ptr = nullptr;
+ }
+ return *this;
}
-}
-template <typename T>
-void SafeRelease(T * ptr)
-{
- if (ptr) {
- ptr->Release();
+ explicit operator bool() const noexcept { return nullptr != ptr; }
+
+ no_addref_release<T> * operator->() const noexcept
+ {
+ return static_cast<no_addref_release<T> *>(ptr);
}
-}
-struct auto_com {
- auto_com() {
- result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ T * get() const noexcept { return ptr; }
+
+ T ** receive() noexcept
+ {
+ XASSERT(ptr == nullptr);
+ return &ptr;
}
- ~auto_com() {
- if (result == RPC_E_CHANGED_MODE) {
- // This is not an error, COM was not initialized by this function, so it is
- // not necessary to uninit it.
- LOG("COM was already initialized in STA.");
- } else if (result == S_FALSE) {
- // This is not an error. We are allowed to call CoInitializeEx more than
- // once, as long as it is matches by an CoUninitialize call.
- // We do that in the dtor which is guaranteed to be called.
- LOG("COM was already initialized in MTA");
- }
- if (SUCCEEDED(result)) {
- CoUninitialize();
- }
+
+ void ** receive_vpp() noexcept
+ {
+ return reinterpret_cast<void **>(receive());
}
- bool ok() {
- return result == RPC_E_CHANGED_MODE || SUCCEEDED(result);
+
+ com_ptr & operator=(std::nullptr_t) noexcept
+ {
+ release();
+ return *this;
}
-private:
- HRESULT result;
-};
-typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)(
- const char * TaskName, LPDWORD TaskIndex);
-typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle);
+ void reset(T * p = nullptr) noexcept
+ {
+ release();
+ ptr = p;
+ }
-extern cubeb_ops const wasapi_ops;
+private:
+ void release() noexcept
+ {
+ T * temp = ptr;
-int wasapi_stream_stop(cubeb_stream * stm);
-int wasapi_stream_start(cubeb_stream * stm);
-void close_wasapi_stream(cubeb_stream * stm);
-int setup_wasapi_stream(cubeb_stream * stm);
-static char * wstr_to_utf8(const wchar_t * str);
-static std::unique_ptr<const wchar_t[]> utf8_to_wstr(char* str);
+ if (temp) {
+ ptr = nullptr;
+ temp->Release();
+ }
+ }
-}
+ T * ptr = nullptr;
+};
-struct cubeb
-{
- cubeb_ops const * ops;
- /* Library dynamically opened to increase the render thread priority, and
- the two function pointers we need. */
- HMODULE mmcss_module;
- set_mm_thread_characteristics_function set_mm_thread_characteristics;
- revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
+extern cubeb_ops const wasapi_ops;
+
+int
+wasapi_stream_stop(cubeb_stream * stm);
+int
+wasapi_stream_start(cubeb_stream * stm);
+void
+close_wasapi_stream(cubeb_stream * stm);
+int
+setup_wasapi_stream(cubeb_stream * stm);
+ERole
+pref_to_role(cubeb_stream_prefs param);
+int
+wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev);
+void
+wasapi_destroy_device(cubeb_device_info * device_info);
+static int
+wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * out);
+static int
+wasapi_device_collection_destroy(cubeb * ctx,
+ cubeb_device_collection * collection);
+static char const *
+wstr_to_utf8(wchar_t const * str);
+static std::unique_ptr<wchar_t const[]>
+utf8_to_wstr(char const * str);
+
+} // namespace
+
+class wasapi_collection_notification_client;
+class monitor_device_notifications;
+
+struct cubeb {
+ cubeb_ops const * ops = &wasapi_ops;
+ cubeb_strings * device_ids;
+ /* Device enumerator to get notifications when the
+ device collection change. */
+ com_ptr<IMMDeviceEnumerator> device_collection_enumerator;
+ com_ptr<wasapi_collection_notification_client> collection_notification_client;
+ /* Collection changed for input (capture) devices. */
+ cubeb_device_collection_changed_callback input_collection_changed_callback =
+ nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ /* Collection changed for output (render) devices. */
+ cubeb_device_collection_changed_callback output_collection_changed_callback =
+ nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ UINT64 performance_counter_frequency;
};
class wasapi_endpoint_notification_client;
@@ -132,27 +243,49 @@ class wasapi_endpoint_notification_client;
*/
typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
-struct cubeb_stream
-{
- cubeb * context;
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context = nullptr;
+ void * user_ptr = nullptr;
+ /**/
+
/* Mixer pameters. We need to convert the input stream to this
samplerate/channel layout, as WASAPI does not resample nor upmix
itself. */
- cubeb_stream_params input_mix_params;
- cubeb_stream_params output_mix_params;
+ cubeb_stream_params input_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
/* Stream parameters. This is what the client requested,
* and what will be presented in the callback. */
- cubeb_stream_params input_stream_params;
- cubeb_stream_params output_stream_params;
+ cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ /* A MMDevice role for this stream: either communication or console here. */
+ ERole role;
+ /* True if this stream will transport voice-data. */
+ bool voice;
+ /* True if the input device of this stream is using bluetooth handsfree. */
+ bool input_bluetooth_handsfree;
/* The input and output device, or NULL for default. */
- cubeb_devid input_device;
- cubeb_devid output_device;
+ std::unique_ptr<const wchar_t[]> input_device_id;
+ std::unique_ptr<const wchar_t[]> output_device_id;
+ com_ptr<IMMDevice> input_device;
+ com_ptr<IMMDevice> output_device;
/* The latency initially requested for this stream, in frames. */
- unsigned latency;
- cubeb_state_callback state_callback;
- cubeb_data_callback data_callback;
- wasapi_refill_callback refill_callback;
- void * user_ptr;
+ unsigned latency = 0;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_data_callback data_callback = nullptr;
+ wasapi_refill_callback refill_callback = nullptr;
+ /* True when a loopback device is requested with no output device. In this
+ case a dummy output device is opened to drive the loopback, but should not
+ be exposed. */
+ bool has_dummy_output = false;
/* Lifetime considerations:
- client, render_client, audio_clock and audio_stream_volume are interface
pointer to the IAudioClient.
@@ -160,84 +293,338 @@ struct cubeb_stream
mix_buffer are the same as the cubeb_stream instance. */
/* Main handle on the WASAPI stream. */
- IAudioClient * output_client;
+ com_ptr<IAudioClient> output_client;
/* Interface pointer to use the event-driven interface. */
- IAudioRenderClient * render_client;
+ com_ptr<IAudioRenderClient> render_client;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
/* Interface pointer to use the volume facilities. */
- IAudioStreamVolume * audio_stream_volume;
+ com_ptr<IAudioStreamVolume> audio_stream_volume;
+#endif
/* Interface pointer to use the stream audio clock. */
- IAudioClock * audio_clock;
+ com_ptr<IAudioClock> audio_clock;
/* Frames written to the stream since it was opened. Reset on device
change. Uses mix_params.rate. */
- UINT64 frames_written;
+ UINT64 frames_written = 0;
/* Frames written to the (logical) stream since it was first
created. Updated on device change. Uses stream_params.rate. */
- UINT64 total_frames_written;
+ UINT64 total_frames_written = 0;
/* Last valid reported stream position. Used to ensure the position
reported by stream_get_position increases monotonically. */
- UINT64 prev_position;
+ UINT64 prev_position = 0;
/* Device enumerator to be able to be notified when the default
device change. */
- IMMDeviceEnumerator * device_enumerator;
+ com_ptr<IMMDeviceEnumerator> device_enumerator;
/* Device notification client, to be able to be notified when the default
audio device changes and route the audio to the new default audio output
device */
- wasapi_endpoint_notification_client * notification_client;
+ com_ptr<wasapi_endpoint_notification_client> notification_client;
/* Main andle to the WASAPI capture stream. */
- IAudioClient * input_client;
+ com_ptr<IAudioClient> input_client;
/* Interface to use the event driven capture interface */
- IAudioCaptureClient * capture_client;
+ com_ptr<IAudioCaptureClient> capture_client;
/* This event is set by the stream_stop and stream_destroy
function, so the render loop can exit properly. */
- HANDLE shutdown_event;
+ HANDLE shutdown_event = 0;
/* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
The reconfiguration is handled by the render loop thread. */
- HANDLE reconfigure_event;
+ HANDLE reconfigure_event = 0;
/* This is set by WASAPI when we should refill the stream. */
- HANDLE refill_event;
+ HANDLE refill_event = 0;
/* This is set by WASAPI when we should read from the input stream. In
* practice, we read from the input stream in the output callback, so
* this is not used, but it is necessary to start getting input data. */
- HANDLE input_available_event;
+ HANDLE input_available_event = 0;
/* Each cubeb_stream has its own thread. */
- HANDLE thread;
+ HANDLE thread = 0;
/* The lock protects all members that are touched by the render thread or
change during a device reset, including: audio_clock, audio_stream_volume,
client, frames_written, mix_params, total_frames_written, prev_position. */
owned_critical_section stream_reset_lock;
/* Maximum number of frames that can be passed down in a callback. */
- uint32_t input_buffer_frame_count;
+ uint32_t input_buffer_frame_count = 0;
/* Maximum number of frames that can be requested in a callback. */
- uint32_t output_buffer_frame_count;
+ uint32_t output_buffer_frame_count = 0;
/* Resampler instance. Resampling will only happen if necessary. */
- cubeb_resampler * resampler;
- /* A buffer for up/down mixing multi-channel audio. */
- float * mix_buffer;
+ std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)>
+ resampler = {nullptr, cubeb_resampler_destroy};
+ /* Mixer interfaces */
+ std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> output_mixer = {
+ nullptr, cubeb_mixer_destroy};
+ std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> input_mixer = {
+ nullptr, cubeb_mixer_destroy};
+ /* A buffer for up/down mixing multi-channel audio output. */
+ std::vector<BYTE> mix_buffer;
/* WASAPI input works in "packets". We re-linearize the audio packets
* into this buffer before handing it to the resampler. */
- auto_array<float> linear_input_buffer;
+ std::unique_ptr<auto_array_wrapper> linear_input_buffer;
+ /* Bytes per sample. This multiplied by the number of channels is the number
+ * of bytes per frame. */
+ size_t bytes_per_sample = 0;
+ /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */
+ GUID waveformatextensible_sub_format = GUID_NULL;
/* Stream volume. Set via stream_set_volume and used to reset volume on
device changes. */
- float volume;
+ float volume = 1.0;
/* True if the stream is draining. */
- bool draining;
+ bool draining = false;
/* True when we've destroyed the stream. This pointer is leaked on stream
* destruction if we could not join the thread. */
- std::atomic<std::atomic<bool>*> emergency_bailout;
+ std::atomic<std::atomic<bool> *> emergency_bailout{nullptr};
+ /* Synchronizes render thread start to ensure safe access to
+ * emergency_bailout. */
+ HANDLE thread_ready_event = 0;
+ /* This needs an active audio input stream to be known, and is updated in the
+ * first audio input callback. */
+ std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
+
+ /* Those attributes count the number of frames requested (resp. received) by
+ the OS, to be able to detect drifts. This is only used for logging for now. */
+ size_t total_input_frames = 0;
+ size_t total_output_frames = 0;
};
-class wasapi_endpoint_notification_client : public IMMNotificationClient
-{
+class monitor_device_notifications {
+public:
+ monitor_device_notifications(cubeb * context) : cubeb_context(context)
+ {
+ create_thread();
+ }
+
+ ~monitor_device_notifications()
+ {
+ SetEvent(begin_shutdown);
+ WaitForSingleObject(shutdown_complete, INFINITE);
+ CloseHandle(thread);
+
+ CloseHandle(input_changed);
+ CloseHandle(output_changed);
+ CloseHandle(begin_shutdown);
+ CloseHandle(shutdown_complete);
+ }
+
+ void notify(EDataFlow flow)
+ {
+ XASSERT(cubeb_context);
+ if (flow == eCapture && cubeb_context->input_collection_changed_callback) {
+ bool res = SetEvent(input_changed);
+ if (!res) {
+ LOG("Failed to set input changed event");
+ }
+ return;
+ }
+ if (flow == eRender && cubeb_context->output_collection_changed_callback) {
+ bool res = SetEvent(output_changed);
+ if (!res) {
+ LOG("Failed to set output changed event");
+ }
+ }
+ }
+
+private:
+ static unsigned int __stdcall thread_proc(LPVOID args)
+ {
+ XASSERT(args);
+ auto mdn = static_cast<monitor_device_notifications *>(args);
+ mdn->notification_thread_loop();
+ SetEvent(mdn->shutdown_complete);
+ return 0;
+ }
+
+ void notification_thread_loop()
+ {
+ struct auto_com {
+ auto_com()
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ XASSERT(SUCCEEDED(hr));
+ }
+ ~auto_com() { CoUninitialize(); }
+ } com;
+
+ HANDLE wait_array[3] = {
+ input_changed,
+ output_changed,
+ begin_shutdown,
+ };
+
+ while (true) {
+ Sleep(200);
+
+ DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
+ wait_array, FALSE, INFINITE);
+ if (wait_result == WAIT_OBJECT_0) { // input changed
+ cubeb_context->input_collection_changed_callback(
+ cubeb_context, cubeb_context->input_collection_changed_user_ptr);
+ } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed
+ cubeb_context->output_collection_changed_callback(
+ cubeb_context, cubeb_context->output_collection_changed_user_ptr);
+ } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown
+ break;
+ } else {
+ LOG("Unexpected result %lu", wait_result);
+ }
+ } // loop
+ }
+
+ void create_thread()
+ {
+ output_changed = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!output_changed) {
+ LOG("Failed to create output changed event.");
+ return;
+ }
+
+ input_changed = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!input_changed) {
+ LOG("Failed to create input changed event.");
+ return;
+ }
+
+ begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!begin_shutdown) {
+ LOG("Failed to create begin_shutdown event.");
+ return;
+ }
+
+ shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!shutdown_complete) {
+ LOG("Failed to create shutdown_complete event.");
+ return;
+ }
+
+ thread = (HANDLE)_beginthreadex(nullptr, 256 * 1024, thread_proc, this,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
+ if (!thread) {
+ LOG("Failed to create thread.");
+ return;
+ }
+ }
+
+ HANDLE thread = INVALID_HANDLE_VALUE;
+ HANDLE output_changed = INVALID_HANDLE_VALUE;
+ HANDLE input_changed = INVALID_HANDLE_VALUE;
+ HANDLE begin_shutdown = INVALID_HANDLE_VALUE;
+ HANDLE shutdown_complete = INVALID_HANDLE_VALUE;
+
+ cubeb * cubeb_context = nullptr;
+};
+
+class wasapi_collection_notification_client : public IMMNotificationClient {
public:
/* The implementation of MSCOM was copied from MSDN. */
- ULONG STDMETHODCALLTYPE
- AddRef()
+ ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
+
+ ULONG STDMETHODCALLTYPE Release()
{
- return InterlockedIncrement(&ref_count);
+ ULONG ulRef = InterlockedDecrement(&ref_count);
+ if (0 == ulRef) {
+ delete this;
+ }
+ return ulRef;
}
- ULONG STDMETHODCALLTYPE
- Release()
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
+ {
+ if (__uuidof(IUnknown) == riid) {
+ AddRef();
+ *ppvInterface = (IUnknown *)this;
+ } else if (__uuidof(IMMNotificationClient) == riid) {
+ AddRef();
+ *ppvInterface = (IMMNotificationClient *)this;
+ } else {
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+ }
+
+ wasapi_collection_notification_client(cubeb * context)
+ : ref_count(1), cubeb_context(context), monitor_notifications(context)
+ {
+ XASSERT(cubeb_context);
+ }
+
+ virtual ~wasapi_collection_notification_client() {}
+
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+ LPCWSTR device_id)
+ {
+ LOG("collection: Audio device default changed, id = %S.", device_id);
+ return S_OK;
+ }
+
+ /* The remaining methods are not implemented, they simply log when called (if
+ log is enabled), for debugging. */
+ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
+ {
+ LOG("collection: Audio device added.");
+ return S_OK;
+ };
+
+ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
+ {
+ LOG("collection: Audio device removed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state)
+ {
+ XASSERT(cubeb_context->output_collection_changed_callback ||
+ cubeb_context->input_collection_changed_callback);
+ LOG("collection: Audio device state changed, id = %S, state = %lu.",
+ device_id, new_state);
+ EDataFlow flow;
+ HRESULT hr = GetDataFlow(device_id, &flow);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ monitor_notifications.notify(flow);
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
+ const PROPERTYKEY key)
+ {
+ // Audio device property value changed.
+ return S_OK;
+ }
+
+private:
+ HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow)
+ {
+ com_ptr<IMMDevice> device;
+ com_ptr<IMMEndpoint> endpoint;
+
+ HRESULT hr = cubeb_context->device_collection_enumerator->GetDevice(
+ device_id, device.receive());
+ if (FAILED(hr)) {
+ LOG("collection: Could not get device: %lx", hr);
+ return hr;
+ }
+
+ hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
+ if (FAILED(hr)) {
+ LOG("collection: Could not get endpoint: %lx", hr);
+ return hr;
+ }
+
+ return endpoint->GetDataFlow(flow);
+ }
+
+ /* refcount for this instance, necessary to implement MSCOM semantics. */
+ LONG ref_count;
+
+ cubeb * cubeb_context = nullptr;
+ monitor_device_notifications monitor_notifications;
+};
+
+class wasapi_endpoint_notification_client : public IMMNotificationClient {
+public:
+ /* The implementation of MSCOM was copied from MSDN. */
+ ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
+
+ ULONG STDMETHODCALLTYPE Release()
{
ULONG ulRef = InterlockedDecrement(&ref_count);
if (0 == ulRef) {
@@ -246,15 +633,14 @@ public:
return ulRef;
}
- HRESULT STDMETHODCALLTYPE
- QueryInterface(REFIID riid, VOID **ppvInterface)
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
{
if (__uuidof(IUnknown) == riid) {
AddRef();
- *ppvInterface = (IUnknown*)this;
+ *ppvInterface = (IUnknown *)this;
} else if (__uuidof(IMMNotificationClient) == riid) {
AddRef();
- *ppvInterface = (IMMNotificationClient*)this;
+ *ppvInterface = (IMMNotificationClient *)this;
} else {
*ppvInterface = NULL;
return E_NOINTERFACE;
@@ -262,27 +648,27 @@ public:
return S_OK;
}
- wasapi_endpoint_notification_client(HANDLE event)
- : ref_count(1)
- , reconfigure_event(event)
- { }
+ wasapi_endpoint_notification_client(HANDLE event, ERole role)
+ : ref_count(1), reconfigure_event(event), role(role)
+ {
+ }
- virtual ~wasapi_endpoint_notification_client()
- { }
+ virtual ~wasapi_endpoint_notification_client() {}
- HRESULT STDMETHODCALLTYPE
- OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+ LPCWSTR device_id)
{
- LOG("Audio device default changed.");
+ LOG("endpoint: Audio device default changed.");
/* we only support a single stream type for now. */
- if (flow != eRender && role != eConsole) {
+ if (flow != eRender && role != this->role) {
return S_OK;
}
BOOL ok = SetEvent(reconfigure_event);
if (!ok) {
- LOG("SetEvent on reconfigure_event failed: %x", GetLastError());
+ LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
+ GetLastError());
}
return S_OK;
@@ -292,163 +678,123 @@ public:
log is enabled), for debugging. */
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
{
- LOG("Audio device added.");
+ LOG("endpoint: Audio device added.");
return S_OK;
};
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
{
- LOG("Audio device removed.");
+ LOG("endpoint: Audio device removed.");
return S_OK;
}
- HRESULT STDMETHODCALLTYPE
- OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state)
{
- LOG("Audio device state changed.");
+ LOG("endpoint: Audio device state changed.");
return S_OK;
}
- HRESULT STDMETHODCALLTYPE
- OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
+ const PROPERTYKEY key)
{
- LOG("Audio device property value changed.");
+ // Audio device property value changed.
return S_OK;
}
+
private:
/* refcount for this instance, necessary to implement MSCOM semantics. */
LONG ref_count;
HANDLE reconfigure_event;
+ ERole role;
};
namespace {
-bool has_input(cubeb_stream * stm)
-{
- return stm->input_stream_params.rate != 0;
-}
-bool has_output(cubeb_stream * stm)
+char const *
+intern_device_id(cubeb * ctx, wchar_t const * id)
{
- return stm->output_stream_params.rate != 0;
+ XASSERT(id);
+
+ char const * tmp = wstr_to_utf8(id);
+ if (!tmp) {
+ return nullptr;
+ }
+
+ char const * interned = cubeb_strings_intern(ctx->device_ids, tmp);
+
+ free((void *)tmp);
+
+ return interned;
}
-bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+bool
+has_input(cubeb_stream * stm)
{
- return mixer.channels > stream.channels;
+ return stm->input_stream_params.rate != 0;
}
-bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+bool
+has_output(cubeb_stream * stm)
{
- return mixer.channels < stream.channels;
+ return stm->output_stream_params.rate != 0;
}
-double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+double
+stream_to_mix_samplerate_ratio(cubeb_stream_params & stream,
+ cubeb_stream_params & mixer)
{
return double(stream.rate) / mixer.rate;
}
+/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG.
+ See more:
+ https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx
+ */
-uint32_t
-get_rate(cubeb_stream * stm)
+cubeb_channel_layout
+mask_to_channel_layout(WAVEFORMATEX const * fmt)
{
- return has_input(stm) ? stm->input_stream_params.rate
- : stm->output_stream_params.rate;
+ cubeb_channel_layout mask = 0;
+
+ if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE const * ext =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE const *>(fmt);
+ mask = ext->dwChannelMask;
+ } else if (fmt->wFormatTag == WAVE_FORMAT_PCM ||
+ fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ if (fmt->nChannels == 1) {
+ mask = CHANNEL_FRONT_CENTER;
+ } else if (fmt->nChannels == 2) {
+ mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT;
+ }
+ }
+ return mask;
}
uint32_t
-ms_to_hns(uint32_t ms)
+get_rate(cubeb_stream * stm)
{
- return ms * 10000;
+ return has_input(stm) ? stm->input_stream_params.rate
+ : stm->output_stream_params.rate;
}
uint32_t
-hns_to_ms(REFERENCE_TIME hns)
-{
- return static_cast<uint32_t>(hns / 10000);
-}
-
-double
-hns_to_s(REFERENCE_TIME hns)
+hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
{
- return static_cast<double>(hns) / 10000000;
+ return std::ceil((hns - 1) / 10000000.0 * rate);
}
uint32_t
hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns)
{
- return hns_to_ms(hns * get_rate(stm)) / 1000;
-}
-
-uint32_t
-hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
-{
- return hns_to_ms(hns * rate) / 1000;
+ return hns_to_frames(get_rate(stm), hns);
}
REFERENCE_TIME
-frames_to_hns(cubeb_stream * stm, uint32_t frames)
-{
- return frames * 1000 / get_rate(stm);
-}
-
-/* Upmix function, copies a mono channel into L and R */
-template<typename T>
-void
-mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
-{
- for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
- out[j] = out[j + 1] = in[i];
- }
-}
-
-template<typename T>
-void
-upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
-{
- XASSERT(out_channels >= in_channels && in_channels > 0);
-
- /* Either way, if we have 2 or more channels, the first two are L and R. */
- /* If we are playing a mono stream over stereo speakers, copy the data over. */
- if (in_channels == 1 && out_channels >= 2) {
- mono_to_stereo(in, inframes, out, out_channels);
- } else {
- /* Copy through. */
- for (int i = 0, o = 0; i < inframes * in_channels;
- i += in_channels, o += out_channels) {
- for (int j = 0; j < in_channels; ++j) {
- out[o + j] = in[i + j];
- }
- }
- }
-
- /* Check if more channels. */
- if (out_channels <= 2) {
- return;
- }
-
- /* Put silence in remaining channels. */
- for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
- for (int j = 2; j < out_channels; ++j) {
- out[o + j] = 0.0;
- }
- }
-}
-
-template<typename T>
-void
-downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
+frames_to_hns(uint32_t rate, uint32_t frames)
{
- XASSERT(in_channels >= out_channels);
- /* We could use a downmix matrix here, applying mixing weight based on the
- channel, but directsound and winmm simply drop the channels that cannot be
- rendered by the hardware, so we do the same for consistency. */
- long out_index = 0;
- for (long i = 0; i < inframes * in_channels; i += in_channels) {
- for (int j = 0; j < out_channels; ++j) {
- out[out_index + j] = in[i + j];
- }
- out_index += out_channels;
- }
+ return std::ceil(frames * 10000000.0 / rate);
}
/* This returns the size of a frame in the stream, before the eventual upmix
@@ -456,157 +802,240 @@ downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channel
static size_t
frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
{
- size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
- return stream_frame_size * frames;
+ // This is called only when we has a output client.
+ XASSERT(has_output(stm));
+ return stm->output_stream_params.channels * stm->bytes_per_sample * frames;
}
/* This function handles the processing of the input and output audio,
* converting it to rate and channel layout specified at initialization.
* It then calls the data callback, via the resampler. */
long
-refill(cubeb_stream * stm, float * input_buffer, long input_frames_count,
- float * output_buffer, long output_frames_needed)
+refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
+ void * output_buffer, long output_frames_needed)
{
+ XASSERT(!stm->draining);
/* If we need to upmix after resampling, resample into the mix buffer to
- avoid a copy. */
- float * dest = nullptr;
- if (has_output(stm)) {
- if (should_upmix(stm->output_stream_params, stm->output_mix_params) ||
- should_downmix(stm->output_stream_params, stm->output_mix_params)) {
- dest = stm->mix_buffer;
+ avoid a copy. Avoid exposing output if it is a dummy stream. */
+ void * dest = nullptr;
+ if (has_output(stm) && !stm->has_dummy_output) {
+ if (stm->output_mixer) {
+ dest = stm->mix_buffer.data();
} else {
dest = output_buffer;
}
}
- long out_frames = cubeb_resampler_fill(stm->resampler,
- input_buffer,
- &input_frames_count,
- dest,
- output_frames_needed);
+ long out_frames =
+ cubeb_resampler_fill(stm->resampler.get(), input_buffer,
+ &input_frames_count, dest, output_frames_needed);
/* TODO: Report out_frames < 0 as an error via the API. */
XASSERT(out_frames >= 0);
+ float volume = 1.0;
{
auto_lock lock(stm->stream_reset_lock);
stm->frames_written += out_frames;
+ volume = stm->volume;
}
- /* Go in draining mode if we got fewer frames than requested. */
- if (out_frames < output_frames_needed) {
+ /* Go in draining mode if we got fewer frames than requested. If the stream
+ has no output we still expect the callback to return number of frames read
+ from input, otherwise we stop. */
+ if ((out_frames < output_frames_needed) ||
+ (!has_output(stm) && out_frames < input_frames_count)) {
LOG("start draining.");
stm->draining = true;
}
/* If this is not true, there will be glitches.
It is alright to have produced less frames if we are draining, though. */
- XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
+ XASSERT(out_frames == output_frames_needed || stm->draining ||
+ !has_output(stm) || stm->has_dummy_output);
+
+#ifndef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ if (has_output(stm) && !stm->has_dummy_output && volume != 1.0) {
+ // Adjust the output volume.
+ // Note: This could be integrated with the remixing below.
+ long out_samples = out_frames * stm->output_stream_params.channels;
+ if (volume == 0.0) {
+ memset(dest, 0, out_samples * stm->bytes_per_sample);
+ } else {
+ switch (stm->output_stream_params.format) {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ float * buf = static_cast<float *>(dest);
+ for (long i = 0; i < out_samples; ++i) {
+ buf[i] *= volume;
+ }
+ break;
+ }
+ case CUBEB_SAMPLE_S16NE: {
+ short * buf = static_cast<short *>(dest);
+ for (long i = 0; i < out_samples; ++i) {
+ buf[i] = static_cast<short>(static_cast<float>(buf[i]) * volume);
+ }
+ break;
+ }
+ default:
+ XASSERT(false);
+ }
+ }
+ }
+#endif
- if (has_output(stm)) {
- if (should_upmix(stm->output_stream_params, stm->output_mix_params)) {
- upmix(dest, out_frames, output_buffer,
- stm->output_stream_params.channels, stm->output_mix_params.channels);
- } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) {
- downmix(dest, out_frames, output_buffer,
- stm->output_stream_params.channels, stm->output_mix_params.channels);
+ // We don't bother mixing dummy output as it will be silenced, otherwise mix
+ // output if needed
+ if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) {
+ XASSERT(dest == stm->mix_buffer.data());
+ size_t dest_size =
+ out_frames * stm->output_stream_params.channels * stm->bytes_per_sample;
+ XASSERT(dest_size <= stm->mix_buffer.size());
+ size_t output_buffer_size =
+ out_frames * stm->output_mix_params.channels * stm->bytes_per_sample;
+ int ret = cubeb_mixer_mix(stm->output_mixer.get(), out_frames, dest,
+ dest_size, output_buffer, output_buffer_size);
+ if (ret < 0) {
+ LOG("Error remixing content (%d)", ret);
}
}
return out_frames;
}
-/* This helper grabs all the frames available from a capture client, put them in
- * linear_input_buffer. linear_input_buffer should be cleared before the
- * callback exits. */
-bool get_input_buffer(cubeb_stream * stm)
+int
+trigger_async_reconfigure(cubeb_stream * stm)
{
- HRESULT hr;
- UINT32 padding_in;
+ XASSERT(stm && stm->reconfigure_event);
+ BOOL ok = SetEvent(stm->reconfigure_event);
+ if (!ok) {
+ LOG("SetEvent on reconfigure_event failed: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+/* This helper grabs all the frames available from a capture client, put them in
+ * the linear_input_buffer. This helper does not work with exclusive mode
+ * streams. */
+bool
+get_input_buffer(cubeb_stream * stm)
+{
XASSERT(has_input(stm));
- hr = stm->input_client->GetCurrentPadding(&padding_in);
- if (FAILED(hr)) {
- LOG("Failed to get padding");
- return false;
- }
- XASSERT(padding_in <= stm->input_buffer_frame_count);
- UINT32 total_available_input = padding_in;
-
+ HRESULT hr;
BYTE * input_packet = NULL;
DWORD flags;
UINT64 dev_pos;
+ UINT64 pc_position;
UINT32 next;
/* Get input packets until we have captured enough frames, and put them in a
* contiguous buffer. */
uint32_t offset = 0;
- while (offset != total_available_input) {
- hr = stm->capture_client->GetNextPacketSize(&next);
+ // If the input stream is event driven we should only ever expect to read a
+ // single packet each time. However, if we're pulling from the stream we may
+ // need to grab multiple packets worth of frames that have accumulated (so
+ // need a loop).
+ for (hr = stm->capture_client->GetNextPacketSize(&next); next > 0;
+ hr = stm->capture_client->GetNextPacketSize(&next)) {
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ // Application can recover from this error. More info
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
+ LOG("Device invalidated error, reset default device");
+ trigger_async_reconfigure(stm);
+ return true;
+ }
+
if (FAILED(hr)) {
- LOG("cannot get next packet size: %x", hr);
+ LOG("cannot get next packet size: %lx", hr);
return false;
}
- /* This can happen if the capture stream has stopped. Just return in this
- * case. */
- if (!next) {
- break;
- }
- UINT32 packet_size;
- hr = stm->capture_client->GetBuffer(&input_packet,
- &packet_size,
- &flags,
- &dev_pos,
- NULL);
+ UINT32 frames;
+ hr = stm->capture_client->GetBuffer(&input_packet, &frames, &flags,
+ &dev_pos, &pc_position);
+
if (FAILED(hr)) {
- LOG("GetBuffer failed for capture: %x", hr);
+ LOG("GetBuffer failed for capture: %lx", hr);
return false;
}
- XASSERT(packet_size == next);
+ XASSERT(frames == next);
+
+ if (stm->context->performance_counter_frequency) {
+ LARGE_INTEGER now;
+ UINT64 now_hns;
+ // See
+ // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer,
+ // section "Remarks".
+ QueryPerformanceCounter(&now);
+ now_hns =
+ 10000000 * now.QuadPart / stm->context->performance_counter_frequency;
+ if (now_hns >= pc_position) {
+ stm->input_latency_hns = now_hns - pc_position;
+ }
+ }
+
+ stm->total_input_frames += frames;
+
+ UINT32 input_stream_samples = frames * stm->input_stream_params.channels;
+ // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
+ // flag. There a two primary (non exhaustive) scenarios we anticipate this
+ // flag being set in:
+ // - The first GetBuffer after Start has this flag undefined. In this
+ // case the flag may be set but is meaningless and can be ignored.
+ // - If a glitch is introduced into the input. This should not happen
+ // for event based inputs, and should be mitigated by using a dummy
+ // stream to drive input in the case of input only loopback. Without
+ // a dummy output, input only loopback would glitch on silence. However,
+ // the dummy input should push silence to the loopback and prevent
+ // discontinuities. See
+ // https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
+ // As the first scenario can be ignored, and we anticipate the second
+ // scenario is mitigated, we ignore the flag.
+ // For more info:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx,
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
- LOG("insert silence: ps=%u", packet_size);
- stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels);
+ LOG("insert silence: ps=%u", frames);
+ stm->linear_input_buffer->push_silence(input_stream_samples);
} else {
- if (should_upmix(stm->input_mix_params, stm->input_stream_params)) {
- bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
- packet_size * stm->input_stream_params.channels);
- assert(ok);
- upmix(reinterpret_cast<float*>(input_packet), packet_size,
- stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
- stm->input_mix_params.channels,
- stm->input_stream_params.channels);
- stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
- } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) {
- bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
- packet_size * stm->input_stream_params.channels);
- assert(ok);
- downmix(reinterpret_cast<float*>(input_packet), packet_size,
- stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
- stm->input_mix_params.channels,
- stm->input_stream_params.channels);
- stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
+ if (stm->input_mixer) {
+ bool ok = stm->linear_input_buffer->reserve(
+ stm->linear_input_buffer->length() + input_stream_samples);
+ XASSERT(ok);
+ size_t input_packet_size =
+ frames * stm->input_mix_params.channels *
+ cubeb_sample_size(stm->input_mix_params.format);
+ size_t linear_input_buffer_size =
+ input_stream_samples *
+ cubeb_sample_size(stm->input_stream_params.format);
+ cubeb_mixer_mix(stm->input_mixer.get(), frames, input_packet,
+ input_packet_size, stm->linear_input_buffer->end(),
+ linear_input_buffer_size);
+ stm->linear_input_buffer->set_length(
+ stm->linear_input_buffer->length() + input_stream_samples);
} else {
- stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
- packet_size * stm->input_stream_params.channels);
+ stm->linear_input_buffer->push(input_packet, input_stream_samples);
}
}
- hr = stm->capture_client->ReleaseBuffer(packet_size);
+ hr = stm->capture_client->ReleaseBuffer(frames);
if (FAILED(hr)) {
LOG("FAILED to release intput buffer");
return false;
}
- offset += packet_size;
+ offset += input_stream_samples;
}
- assert(stm->linear_input_buffer.length() >= total_available_input &&
- offset == total_available_input);
+ ALOGV("get_input_buffer: got %d frames", offset);
+
+ XASSERT(stm->linear_input_buffer->length() >= offset);
return true;
}
/* Get an output buffer from the render_client. It has to be released before
* exiting the callback. */
-bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count)
+bool
+get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
{
UINT32 padding_out;
HRESULT hr;
@@ -614,10 +1043,19 @@ bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count
XASSERT(has_output(stm));
hr = stm->output_client->GetCurrentPadding(&padding_out);
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ // Application can recover from this error. More info
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
+ LOG("Device invalidated error, reset default device");
+ trigger_async_reconfigure(stm);
+ return true;
+ }
+
if (FAILED(hr)) {
- LOG("Failed to get padding: %x", hr);
+ LOG("Failed to get padding: %lx", hr);
return false;
}
+
XASSERT(padding_out <= stm->output_buffer_frame_count);
if (stm->draining) {
@@ -639,7 +1077,7 @@ bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count
return false;
}
- buffer = reinterpret_cast<float*>(output_buffer);
+ buffer = output_buffer;
return true;
}
@@ -651,22 +1089,22 @@ bool
refill_callback_duplex(cubeb_stream * stm)
{
HRESULT hr;
- float * output_buffer = nullptr;
+ void * output_buffer = nullptr;
size_t output_frames = 0;
size_t input_frames;
bool rv;
XASSERT(has_input(stm) && has_output(stm));
- rv = get_input_buffer(stm);
- if (!rv) {
- return rv;
+ if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ HRESULT rv = get_input_buffer(stm);
+ if (FAILED(rv)) {
+ return rv;
+ }
}
- input_frames = stm->linear_input_buffer.length() / stm->input_stream_params.channels;
- if (!input_frames) {
- return true;
- }
+ input_frames =
+ stm->linear_input_buffer->length() / stm->input_stream_params.channels;
rv = get_output_buffer(stm, output_buffer, output_frames);
if (!rv) {
@@ -680,29 +1118,45 @@ refill_callback_duplex(cubeb_stream * stm)
return true;
}
- // When WASAPI has not filled the input buffer yet, send silence.
- double output_duration = double(output_frames) / stm->output_mix_params.rate;
- double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate;
- if (input_duration < output_duration) {
- size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate));
- LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding);
- stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels);
+ /* Wait for draining is not important on duplex. */
+ if (stm->draining) {
+ return false;
}
- LOGV("Duplex callback: input frames: %zu, output frames: %zu",
- stm->linear_input_buffer.length(), output_frames);
+ stm->total_output_frames += output_frames;
+
+ ALOGV("in: %zu, out: %zu, missing: %ld, ratio: %f", stm->total_input_frames,
+ stm->total_output_frames,
+ static_cast<long>(stm->total_output_frames) - stm->total_input_frames,
+ static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
+
+ if (stm->has_dummy_output) {
+ ALOGV(
+ "Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
- refill(stm,
- stm->linear_input_buffer.data(),
- stm->linear_input_buffer.length(),
- output_buffer,
- output_frames);
+ // We don't want to expose the dummy output to the callback so don't pass
+ // the output buffer (it will be released later with silence in it)
+ refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
+ } else {
+ ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
+
+ refill(stm, stm->linear_input_buffer->data(), input_frames, output_buffer,
+ output_frames);
+ }
- stm->linear_input_buffer.clear();
+ stm->linear_input_buffer->clear();
- hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ if (stm->has_dummy_output) {
+ // If output is a dummy output, make sure it's silent
+ hr = stm->render_client->ReleaseBuffer(output_frames,
+ AUDCLNT_BUFFERFLAGS_SILENT);
+ } else {
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ }
if (FAILED(hr)) {
- LOG("failed to release buffer: %x", hr);
+ LOG("failed to release buffer: %lx", hr);
return false;
}
return true;
@@ -711,7 +1165,8 @@ refill_callback_duplex(cubeb_stream * stm)
bool
refill_callback_input(cubeb_stream * stm)
{
- bool rv, consumed_all_buffer;
+ bool rv;
+ size_t input_frames;
XASSERT(has_input(stm) && !has_output(stm));
@@ -720,24 +1175,22 @@ refill_callback_input(cubeb_stream * stm)
return rv;
}
- // This can happen at the very beginning of the stream.
- if (!stm->linear_input_buffer.length()) {
+ input_frames =
+ stm->linear_input_buffer->length() / stm->input_stream_params.channels;
+ if (!input_frames) {
return true;
}
- LOGV("Input callback: input frames: %zu", stm->linear_input_buffer.length());
+ ALOGV("Input callback: input frames: %Iu", input_frames);
- long read = refill(stm,
- stm->linear_input_buffer.data(),
- stm->linear_input_buffer.length(),
- nullptr,
- 0);
+ long read =
+ refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
- consumed_all_buffer = read == stm->linear_input_buffer.length();
+ XASSERT(read >= 0);
- stm->linear_input_buffer.clear();
+ stm->linear_input_buffer->clear();
- return consumed_all_buffer;
+ return !stm->draining;
}
bool
@@ -745,7 +1198,7 @@ refill_callback_output(cubeb_stream * stm)
{
bool rv;
HRESULT hr;
- float * output_buffer = nullptr;
+ void * output_buffer = nullptr;
size_t output_frames = 0;
XASSERT(!has_input(stm) && has_output(stm));
@@ -759,62 +1212,57 @@ refill_callback_output(cubeb_stream * stm)
return true;
}
- long got = refill(stm,
- nullptr,
- 0,
- output_buffer,
- output_frames);
+ long got = refill(stm, nullptr, 0, output_buffer, output_frames);
- LOGV("Output callback: output frames requested: %zu, got %ld",
- output_frames, got);
+ ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
+ got);
XASSERT(got >= 0);
- XASSERT(got == output_frames || stm->draining);
+ XASSERT(size_t(got) == output_frames || stm->draining);
hr = stm->render_client->ReleaseBuffer(got, 0);
if (FAILED(hr)) {
- LOG("failed to release buffer: %x", hr);
+ LOG("failed to release buffer: %lx", hr);
return false;
}
- return got == output_frames || stm->draining;
+ return size_t(got) == output_frames || stm->draining;
}
-static unsigned int __stdcall
-wasapi_stream_render_loop(LPVOID stream)
+static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
+ // Signal wasapi_stream_start that we've copied emergency_bailout.
+ BOOL ok = SetEvent(stm->thread_ready_event);
+ if (!ok) {
+ LOG("thread_ready SetEvent failed: %lx", GetLastError());
+ return 0;
+ }
+
bool is_playing = true;
- HANDLE wait_array[4] = {
- stm->shutdown_event,
- stm->reconfigure_event,
- stm->refill_event,
- stm->input_available_event
- };
+ HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
+ stm->refill_event, stm->input_available_event};
HANDLE mmcss_handle = NULL;
HRESULT hr = 0;
DWORD mmcss_task_index = 0;
- auto_com com;
- if (!com.ok()) {
- LOG("COM initialization failed on render_loop thread.");
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return 0;
- }
+ struct auto_com {
+ auto_com()
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ XASSERT(SUCCEEDED(hr));
+ }
+ ~auto_com() { CoUninitialize(); }
+ } com;
/* We could consider using "Pro Audio" here for WebAudio and
maybe WebRTC. */
- mmcss_handle =
- stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
+ mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
if (!mmcss_handle) {
/* This is not fatal, but we might glitch under heavy load. */
- LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
- }
-
- // This has already been nulled out, simply exit.
- if (!emergency_bailout) {
- is_playing = false;
+ LOG("Unable to use mmcss to bump the render thread priority: %lx",
+ GetLastError());
}
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
@@ -822,19 +1270,18 @@ wasapi_stream_render_loop(LPVOID stream)
the timeout error handling only when the timeout_limit is reached, which is
reset on each successful loop. */
unsigned timeout_count = 0;
- const unsigned timeout_limit = 5;
+ const unsigned timeout_limit = 3;
while (is_playing) {
// We want to check the emergency bailout variable before a
- // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects
- // is going to wait on might have been closed already.
+ // and after the WaitForMultipleObject, because the handles
+ // WaitForMultipleObjects is going to wait on might have been closed
+ // already.
if (*emergency_bailout) {
delete emergency_bailout;
return 0;
}
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
- wait_array,
- FALSE,
- 1000);
+ wait_array, FALSE, 1000);
if (*emergency_bailout) {
delete emergency_bailout;
return 0;
@@ -868,8 +1315,8 @@ wasapi_stream_render_loop(LPVOID stream)
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
LOG("Stream closed.");
- /* Reopen a stream and start it immediately. This will automatically pick the
- new default device for this role. */
+ /* Reopen a stream and start it immediately. This will automatically
+ pick the new default device for this role. */
int r = setup_wasapi_stream(stm);
if (r != CUBEB_OK) {
LOG("Error setting up the stream during reconfigure.");
@@ -883,24 +1330,42 @@ wasapi_stream_render_loop(LPVOID stream)
}
XASSERT(stm->output_client || stm->input_client);
if (stm->output_client) {
- stm->output_client->Start();
+ hr = stm->output_client->Start();
+ if (FAILED(hr)) {
+ LOG("Error starting output after reconfigure, error: %lx", hr);
+ is_playing = false;
+ continue;
+ }
LOG("Output started after reconfigure.");
}
if (stm->input_client) {
- stm->input_client->Start();
+ hr = stm->input_client->Start();
+ if (FAILED(hr)) {
+ LOG("Error starting input after reconfiguring, error: %lx", hr);
+ is_playing = false;
+ continue;
+ }
LOG("Input started after reconfigure.");
}
break;
}
- case WAIT_OBJECT_0 + 2: /* refill */
- XASSERT(has_input(stm) && has_output(stm) ||
- !has_input(stm) && has_output(stm));
+ case WAIT_OBJECT_0 + 2: /* refill */
+ XASSERT((has_input(stm) && has_output(stm)) ||
+ (!has_input(stm) && has_output(stm)));
is_playing = stm->refill_callback(stm);
break;
- case WAIT_OBJECT_0 + 3: /* input available */
- if (has_input(stm) && has_output(stm)) { continue; }
- is_playing = stm->refill_callback(stm);
+ case WAIT_OBJECT_0 + 3: { /* input available */
+ HRESULT rv = get_input_buffer(stm);
+ if (FAILED(rv)) {
+ return rv;
+ }
+
+ if (!has_output(stm)) {
+ is_playing = stm->refill_callback(stm);
+ }
+
break;
+ }
case WAIT_TIMEOUT:
XASSERT(stm->shutdown_event == wait_array[0]);
if (++timeout_count >= timeout_limit) {
@@ -910,7 +1375,7 @@ wasapi_stream_render_loop(LPVOID stream)
}
break;
default:
- LOG("case %d not handled in render loop.", waitResult);
+ LOG("case %lu not handled in render loop.", waitResult);
abort();
}
}
@@ -919,111 +1384,132 @@ wasapi_stream_render_loop(LPVOID stream)
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
- stm->context->revert_mm_thread_characteristics(mmcss_handle);
+ if (mmcss_handle) {
+ AvRevertMmThreadCharacteristics(mmcss_handle);
+ }
return 0;
}
-void wasapi_destroy(cubeb * context);
-
-HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index)
-{
- return (HANDLE)1;
-}
-
-BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle)
-{
- return true;
-}
+void
+wasapi_destroy(cubeb * context);
-HRESULT register_notification_client(cubeb_stream * stm)
+HRESULT
+register_notification_client(cubeb_stream * stm)
{
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER,
- IID_PPV_ARGS(&stm->device_enumerator));
- if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
- return hr;
- }
+ XASSERT(stm->device_enumerator);
- stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event);
+ stm->notification_client.reset(new wasapi_endpoint_notification_client(
+ stm->reconfigure_event, stm->role));
- hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client);
+ HRESULT hr = stm->device_enumerator->RegisterEndpointNotificationCallback(
+ stm->notification_client.get());
if (FAILED(hr)) {
- LOG("Could not register endpoint notification callback: %x", hr);
- SafeRelease(stm->notification_client);
+ LOG("Could not register endpoint notification callback: %lx", hr);
stm->notification_client = nullptr;
- SafeRelease(stm->device_enumerator);
- stm->device_enumerator = nullptr;
}
return hr;
}
-HRESULT unregister_notification_client(cubeb_stream * stm)
+HRESULT
+unregister_notification_client(cubeb_stream * stm)
{
- XASSERT(stm);
- HRESULT hr;
-
- if (!stm->device_enumerator) {
- return S_OK;
- }
+ XASSERT(stm->device_enumerator);
- hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client);
+ HRESULT hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(
+ stm->notification_client.get());
if (FAILED(hr)) {
// We can't really do anything here, we'll probably leak the
- // notification client, but we can at least release the enumerator.
- SafeRelease(stm->device_enumerator);
+ // notification client.
return S_OK;
}
- SafeRelease(stm->notification_client);
- SafeRelease(stm->device_enumerator);
+ stm->notification_client = nullptr;
return S_OK;
}
-HRESULT get_endpoint(IMMDevice ** device, LPCWSTR devid)
+HRESULT
+get_endpoint(com_ptr<IMMDevice> & device, LPCWSTR devid)
{
- IMMDeviceEnumerator * enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER,
- IID_PPV_ARGS(&enumerator));
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
+ LOG("Could not get device enumerator: %lx", hr);
return hr;
}
- hr = enumerator->GetDevice(devid, device);
+ hr = enumerator->GetDevice(devid, device.receive());
if (FAILED(hr)) {
- LOG("Could not get device: %x", hr);
- SafeRelease(enumerator);
+ LOG("Could not get device: %lx", hr);
return hr;
}
- SafeRelease(enumerator);
-
return S_OK;
}
-HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction)
+HRESULT
+register_collection_notification_client(cubeb * context)
{
- IMMDeviceEnumerator * enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER,
- IID_PPV_ARGS(&enumerator));
+ HRESULT hr = CoCreateInstance(
+ __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(context->device_collection_enumerator.receive()));
if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
+ LOG("Could not get device enumerator: %lx", hr);
return hr;
}
- hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device);
+
+ context->collection_notification_client.reset(
+ new wasapi_collection_notification_client(context));
+
+ hr = context->device_collection_enumerator
+ ->RegisterEndpointNotificationCallback(
+ context->collection_notification_client.get());
+ if (FAILED(hr)) {
+ LOG("Could not register endpoint notification callback: %lx", hr);
+ context->collection_notification_client.reset();
+ context->device_collection_enumerator.reset();
+ }
+
+ return hr;
+}
+
+HRESULT
+unregister_collection_notification_client(cubeb * context)
+{
+ HRESULT hr = context->device_collection_enumerator
+ ->UnregisterEndpointNotificationCallback(
+ context->collection_notification_client.get());
if (FAILED(hr)) {
- LOG("Could not get default audio endpoint: %x", hr);
- SafeRelease(enumerator);
return hr;
}
- SafeRelease(enumerator);
+ context->collection_notification_client = nullptr;
+ context->device_collection_enumerator = nullptr;
+
+ return hr;
+}
+
+HRESULT
+get_default_endpoint(com_ptr<IMMDevice> & device, EDataFlow direction,
+ ERole role)
+{
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+ hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive());
+ if (FAILED(hr)) {
+ LOG("Could not get default audio endpoint: %lx", hr);
+ return hr;
+ }
return ERROR_SUCCESS;
}
@@ -1043,25 +1529,26 @@ current_stream_delay(cubeb_stream * stm)
UINT64 freq;
HRESULT hr = stm->audio_clock->GetFrequency(&freq);
if (FAILED(hr)) {
- LOG("GetFrequency failed: %x", hr);
+ LOG("GetFrequency failed: %lx", hr);
return 0;
}
UINT64 pos;
hr = stm->audio_clock->GetPosition(&pos, NULL);
if (FAILED(hr)) {
- LOG("GetPosition failed: %x", hr);
+ LOG("GetPosition failed: %lx", hr);
return 0;
}
double cur_pos = static_cast<double>(pos) / freq;
- double max_pos = static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
- double delay = max_pos - cur_pos;
- XASSERT(delay >= 0);
+ double max_pos =
+ static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
+ double delay = std::max(max_pos - cur_pos, 0.0);
return delay;
}
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
int
stream_set_volume(cubeb_stream * stm, float volume)
{
@@ -1073,8 +1560,8 @@ stream_set_volume(cubeb_stream * stm, float volume)
uint32_t channels;
HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
- if (hr != S_OK) {
- LOG("could not get the channel count: %x", hr);
+ if (FAILED(hr)) {
+ LOG("could not get the channel count: %lx", hr);
return CUBEB_ERROR;
}
@@ -1088,62 +1575,51 @@ stream_set_volume(cubeb_stream * stm, float volume)
volumes[i] = volume;
}
- hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
- if (hr != S_OK) {
- LOG("could not set the channels volume: %x", hr);
+ hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
+ if (FAILED(hr)) {
+ LOG("could not set the channels volume: %lx", hr);
return CUBEB_ERROR;
}
return CUBEB_OK;
}
-} // namespace anonymous
+#endif
+} // namespace
extern "C" {
-int wasapi_init(cubeb ** context, char const * context_name)
+int
+wasapi_init(cubeb ** context, char const * context_name)
{
- HRESULT hr;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
/* We don't use the device yet, but need to make sure we can initialize one
so that this backend is not incorrectly enabled on platforms that don't
support WASAPI. */
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
if (FAILED(hr)) {
- LOG("Could not get device: %x", hr);
- return CUBEB_ERROR;
+ XASSERT(hr != CO_E_NOTINITIALIZED);
+ LOG("It wasn't able to find a default rendering device: %lx", hr);
+ hr = get_default_endpoint(device, eCapture, eConsole);
+ if (FAILED(hr)) {
+ LOG("It wasn't able to find a default capture device: %lx", hr);
+ return CUBEB_ERROR;
+ }
}
- SafeRelease(device);
- cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
- if (!ctx) {
- return CUBEB_ERROR;
- }
+ cubeb * ctx = new cubeb();
ctx->ops = &wasapi_ops;
+ if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
+ delete ctx;
+ return CUBEB_ERROR;
+ }
- ctx->mmcss_module = LoadLibraryA("Avrt.dll");
-
- if (ctx->mmcss_module) {
- ctx->set_mm_thread_characteristics =
- (set_mm_thread_characteristics_function) GetProcAddress(
- ctx->mmcss_module, "AvSetMmThreadCharacteristicsA");
- ctx->revert_mm_thread_characteristics =
- (revert_mm_thread_characteristics_function) GetProcAddress(
- ctx->mmcss_module, "AvRevertMmThreadCharacteristics");
- if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) {
- LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError());
- FreeLibrary(ctx->mmcss_module);
- }
+ LARGE_INTEGER frequency;
+ if (QueryPerformanceFrequency(&frequency)) {
+ ctx->performance_counter_frequency = frequency.QuadPart;
} else {
- // This is not a fatal error, but we might end up glitching when
- // the system is under high load.
- LOG("Could not load Avrt.dll");
- ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop;
- ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop;
+ LOG("Failed getting performance counter frequency, latency reporting will "
+ "be inacurate");
+ ctx->performance_counter_frequency = 0;
}
*context = ctx;
@@ -1153,7 +1629,8 @@ int wasapi_init(cubeb ** context, char const * context_name)
}
namespace {
-bool stop_and_join_render_thread(cubeb_stream * stm)
+bool
+stop_and_join_render_thread(cubeb_stream * stm)
{
bool rv = true;
LOG("Stop and join render thread.");
@@ -1170,31 +1647,23 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
BOOL ok = SetEvent(stm->shutdown_event);
if (!ok) {
- LOG("Destroy SetEvent failed: %d", GetLastError());
+ LOG("Destroy SetEvent failed: %lx", GetLastError());
}
/* Wait five seconds for the rendering thread to return. It's supposed to
* check its event loop very often, five seconds is rather conservative. */
DWORD r = WaitForSingleObject(stm->thread, 5000);
- if (r == WAIT_TIMEOUT) {
+ if (r != WAIT_OBJECT_0) {
/* Something weird happened, leak the thread and continue the shutdown
* process. */
*(stm->emergency_bailout) = true;
// We give the ownership to the rendering thread.
stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread timed out,"
- " leaking the thread: %d", GetLastError());
- rv = false;
- }
- if (r == WAIT_FAILED) {
- *(stm->emergency_bailout) = true;
- // We give the ownership to the rendering thread.
- stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
+ LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r,
+ GetLastError());
rv = false;
}
-
// Only attempts to close and null out the thread and event if the
// WaitForSingleObject above succeeded, so that calling this function again
// attemps to clean up the thread and event each time.
@@ -1210,15 +1679,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
return rv;
}
-void wasapi_destroy(cubeb * context)
+void
+wasapi_destroy(cubeb * context)
{
- if (context->mmcss_module) {
- FreeLibrary(context->mmcss_module);
+ if (context->device_ids) {
+ cubeb_strings_destroy(context->device_ids);
}
- free(context);
+
+ delete context;
}
-char const * wasapi_get_backend_id(cubeb * context)
+char const *
+wasapi_get_backend_id(cubeb * context)
{
return "wasapi";
}
@@ -1226,228 +1698,349 @@ char const * wasapi_get_backend_id(cubeb * context)
int
wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
- HRESULT hr;
- IAudioClient * client;
- WAVEFORMATEX * mix_format;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
XASSERT(ctx && max_channels);
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL, (void **)&client);
- SafeRelease(device);
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = client->GetMixFormat(&mix_format);
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
if (FAILED(hr)) {
- SafeRelease(client);
return CUBEB_ERROR;
}
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
*max_channels = mix_format->nChannels;
- CoTaskMemFree(mix_format);
- SafeRelease(client);
-
return CUBEB_OK;
}
int
-wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
{
- HRESULT hr;
- IAudioClient * client;
- REFERENCE_TIME default_period;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
- if (params.format != CUBEB_SAMPLE_FLOAT32NE) {
+ if (params.format != CUBEB_SAMPLE_FLOAT32NE &&
+ params.format != CUBEB_SAMPLE_S16NE) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ ERole role = pref_to_role(params.prefs);
+
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, role);
if (FAILED(hr)) {
- LOG("Could not get default endpoint: %x", hr);
+ LOG("Could not get default endpoint: %lx", hr);
return CUBEB_ERROR;
}
- hr = device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL, (void **)&client);
- SafeRelease(device);
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not activate device for latency: %x", hr);
+ LOG("Could not activate device for latency: %lx", hr);
return CUBEB_ERROR;
}
- /* The second parameter is for exclusive mode, that we don't use. */
- hr = client->GetDevicePeriod(&default_period, NULL);
+ REFERENCE_TIME minimum_period;
+ REFERENCE_TIME default_period;
+ hr = client->GetDevicePeriod(&default_period, &minimum_period);
if (FAILED(hr)) {
- SafeRelease(client);
- LOG("Could not get device period: %x", hr);
+ LOG("Could not get device period: %lx", hr);
return CUBEB_ERROR;
}
- LOG("default device period: %lld", default_period);
+ LOG("default device period: %I64d, minimum device period: %I64d",
+ default_period, minimum_period);
- /* According to the docs, the best latency we can achieve is by synchronizing
- the stream and the engine.
- http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */
+ /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency.
+ Otherwise, according to the docs, the best latency we can achieve is by
+ synchronizing the stream and the engine.
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx
+ */
+ // #ifdef _WIN32_WINNT_WIN10
+#if 0
+ *latency_frames = hns_to_frames(params.rate, minimum_period);
+#else
*latency_frames = hns_to_frames(params.rate, default_period);
+#endif
LOG("Minimum latency in frames: %u", *latency_frames);
- SafeRelease(client);
-
return CUBEB_OK;
}
int
wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
- HRESULT hr;
- IAudioClient * client;
- WAVEFORMATEX * mix_format;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL, (void **)&client);
- SafeRelease(device);
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = client->GetMixFormat(&mix_format);
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
if (FAILED(hr)) {
- SafeRelease(client);
return CUBEB_ERROR;
}
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
*rate = mix_format->nSamplesPerSec;
LOG("Preferred sample rate for output: %u", *rate);
- CoTaskMemFree(mix_format);
- SafeRelease(client);
-
return CUBEB_OK;
}
-void wasapi_stream_destroy(cubeb_stream * stm);
+void
+wasapi_stream_destroy(cubeb_stream * stm);
-/* Based on the mix format and the stream format, try to find a way to play
- what the user requested. */
static void
-handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
+waveformatex_update_derived_properties(WAVEFORMATEX * format)
{
- /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
- handled in the callback. */
- if ((*mix_format)->nChannels <= 2) {
- return;
+ format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8;
+ format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
+ if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(format);
+ format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample;
}
+}
+/* Based on the mix format and the stream format, try to find a way to play
+ what the user requested. */
+static void
+handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
+ com_heap_ptr<WAVEFORMATEX> & mix_format,
+ const cubeb_stream_params * stream_params)
+{
+ com_ptr<IAudioClient> & audio_client =
+ (direction == eRender) ? stm->output_client : stm->input_client;
+ XASSERT(audio_client);
/* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
so the reinterpret_cast below should be safe. In practice, this is not
true, and we just want to bail out and let the rest of the code find a good
conversion path instead of trying to make WASAPI do it by itself.
- [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
- if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
+ [1]:
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
+ if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
return;
}
- WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format);
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
- /* Stash a copy of the original mix format in case we need to restore it later. */
+ /* Stash a copy of the original mix format in case we need to restore it
+ * later. */
WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
- /* The hardware is in surround mode, we want to only use front left and front
- right. Try that, and check if it works. */
- switch (stream_params->channels) {
- case 1: /* Mono */
- format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
- break;
- case 2: /* Stereo */
- format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
- break;
- default:
- XASSERT(false && "Channel layout not supported.");
- break;
- }
- (*mix_format)->nChannels = stream_params->channels;
- (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8;
- (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign;
- format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- (*mix_format)->wBitsPerSample = 32;
- format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample;
+ /* Get the channel mask by the channel layout.
+ If the layout is not supported, we will get a closest settings below. */
+ format_pcm->dwChannelMask = stream_params->layout;
+ mix_format->nChannels = stream_params->channels;
+ waveformatex_update_derived_properties(mix_format.get());
/* Check if wasapi will accept our channel layout request. */
- WAVEFORMATEX * closest;
- HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
- *mix_format,
- &closest);
+ WAVEFORMATEX * tmp = nullptr;
+ HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
+ mix_format.get(), &tmp);
+ com_heap_ptr<WAVEFORMATEX> closest(tmp);
if (hr == S_FALSE) {
- /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
- eventual upmix/downmix ourselves */
+ /* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
+ and handle the eventual upmix/downmix ourselves. Ignore the subformat of
+ the suggestion, since it seems to always be IEEE_FLOAT. */
LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
- WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
- XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat);
- CoTaskMemFree(*mix_format);
- *mix_format = closest;
+ XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+ WAVEFORMATEXTENSIBLE * closest_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest.get());
+ format_pcm->dwChannelMask = closest_pcm->dwChannelMask;
+ mix_format->nChannels = closest->nChannels;
+ waveformatex_update_derived_properties(mix_format.get());
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
/* Not supported, no suggestion. This should not happen, but it does in the
field with some sound cards. We restore the mix format, and let the rest
of the code figure out the right conversion path. */
- *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format) = hw_mix_format;
+ XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+ *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()) = hw_mix_format;
} else if (hr == S_OK) {
LOG("Requested format accepted by WASAPI.");
} else {
- LOG("IsFormatSupported unhandled error: %x", hr);
+ LOG("IsFormatSupported unhandled error: %lx", hr);
+ }
+}
+
+static bool
+initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
+{
+ com_ptr<IAudioClient2> audio_client2;
+ audio_client->QueryInterface<IAudioClient2>(audio_client2.receive());
+ if (!audio_client2) {
+ LOG("Could not get IAudioClient2 interface, not setting "
+ "AUDCLNT_STREAMOPTIONS_RAW.");
+ return CUBEB_OK;
+ }
+ AudioClientProperties properties = {0};
+ properties.cbSize = sizeof(AudioClientProperties);
+#ifndef __MINGW32__
+ properties.Options |= AUDCLNT_STREAMOPTIONS_RAW;
+#endif
+ HRESULT hr = audio_client2->SetClientProperties(&properties);
+ if (FAILED(hr)) {
+ LOG("IAudioClient2::SetClientProperties error: %lx", GetLastError());
+ return CUBEB_ERROR;
}
+ return CUBEB_OK;
+}
+
+// Not static to suppress a warning.
+/* static */ bool
+initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
+ cubeb_stream * stm,
+ const com_heap_ptr<WAVEFORMATEX> & mix_format,
+ DWORD flags, EDataFlow direction)
+{
+ com_ptr<IAudioClient3> audio_client3;
+ audio_client->QueryInterface<IAudioClient3>(audio_client3.receive());
+ if (!audio_client3) {
+ LOG("Could not get IAudioClient3 interface");
+ return false;
+ }
+
+ if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) {
+ // IAudioClient3 doesn't work with loopback streams, and will return error
+ // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG
+ LOG("Audio stream is loopback, not using IAudioClient3");
+ return false;
+ }
+
+ // Some people have reported glitches with capture streams:
+ // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html
+ if (direction == eCapture) {
+ LOG("Audio stream is capture, not using IAudioClient3");
+ return false;
+ }
+
+ // Possibly initialize a shared-mode stream using IAudioClient3. Initializing
+ // a stream this way lets you request lower latencies, but also locks the
+ // global WASAPI engine at that latency.
+ // - If we request a shared-mode stream, streams created with IAudioClient
+ // will
+ // have their latency adjusted to match. When the shared-mode stream is
+ // closed, they'll go back to normal.
+ // - If there's already a shared-mode stream running, then we cannot request
+ // the engine change to a different latency - we have to match it.
+ // - It's antisocial to lock the WASAPI engine at its default latency. If we
+ // would do this, then stop and use IAudioClient instead.
+
+ HRESULT hr;
+ uint32_t default_period = 0, fundamental_period = 0, min_period = 0,
+ max_period = 0;
+ hr = audio_client3->GetSharedModeEnginePeriod(
+ mix_format.get(), &default_period, &fundamental_period, &min_period,
+ &max_period);
+ if (FAILED(hr)) {
+ LOG("Could not get shared mode engine period: error: %lx", hr);
+ return false;
+ }
+ uint32_t requested_latency = stm->latency;
+ if (requested_latency >= default_period) {
+ LOG("Requested latency %i greater than default latency %i, not using "
+ "IAudioClient3",
+ requested_latency, default_period);
+ return false;
+ }
+ LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i",
+ default_period, fundamental_period, min_period, max_period);
+ // Snap requested latency to a valid value
+ uint32_t old_requested_latency = requested_latency;
+ if (requested_latency < min_period) {
+ requested_latency = min_period;
+ }
+ requested_latency -= (requested_latency - min_period) % fundamental_period;
+ if (requested_latency != old_requested_latency) {
+ LOG("Requested latency %i was adjusted to %i", old_requested_latency,
+ requested_latency);
+ }
+
+ hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency,
+ mix_format.get(), NULL);
+ if (SUCCEEDED(hr)) {
+ return true;
+ } else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) {
+ LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request");
+ } else {
+ LOG("Could not initialize shared stream with IAudioClient3: error: %lx",
+ hr);
+ return false;
+ }
+
+ uint32_t current_period = 0;
+ WAVEFORMATEX * current_format = nullptr;
+ // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise
+ // GetCurrentSharedModeEnginePeriod will return E_POINTER
+ hr = audio_client3->GetCurrentSharedModeEnginePeriod(&current_format,
+ &current_period);
+ CoTaskMemFree(current_format);
+ if (FAILED(hr)) {
+ LOG("Could not get current shared mode engine period: error: %lx", hr);
+ return false;
+ }
+
+ if (current_period >= default_period) {
+ LOG("Current shared mode engine period %i too high, not using IAudioClient",
+ current_period);
+ return false;
+ }
+
+ hr = audio_client3->InitializeSharedAudioStream(flags, current_period,
+ mix_format.get(), NULL);
+ if (SUCCEEDED(hr)) {
+ LOG("Current shared mode engine period is %i instead of requested %i",
+ current_period, requested_latency);
+ return true;
+ }
+
+ LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr);
+ return false;
}
#define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
-template<typename T>
-int setup_wasapi_stream_one_side(cubeb_stream * stm,
- cubeb_stream_params * stream_params,
- cubeb_devid devid,
- EDataFlow direction,
- REFIID riid,
- IAudioClient ** audio_client,
- uint32_t * buffer_frame_count,
- HANDLE & event,
- T ** render_or_capture_client,
- cubeb_stream_params * mix_params)
+template <typename T>
+int
+setup_wasapi_stream_one_side(cubeb_stream * stm,
+ cubeb_stream_params * stream_params,
+ wchar_t const * devid, EDataFlow direction,
+ REFIID riid, com_ptr<IAudioClient> & audio_client,
+ uint32_t * buffer_frame_count, HANDLE & event,
+ T & render_or_capture_client,
+ cubeb_stream_params * mix_params,
+ com_ptr<IMMDevice> & device)
{
- IMMDevice * device;
- WAVEFORMATEX * mix_format;
HRESULT hr;
+ bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
+ if (is_loopback && direction != eCapture) {
+ LOG("Loopback pref can only be used with capture streams!\n");
+ return CUBEB_ERROR;
+ }
stm->stream_reset_lock.assert_current_thread_owns();
bool try_again = false;
@@ -1455,35 +2048,54 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
// possibilities.
do {
if (devid) {
- std::unique_ptr<const wchar_t[]> id(utf8_to_wstr(reinterpret_cast<char*>(devid)));
- hr = get_endpoint(&device, id.get());
+ hr = get_endpoint(device, devid);
if (FAILED(hr)) {
- LOG("Could not get %s endpoint, error: %x\n", DIRECTION_NAME, hr);
+ LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
- }
- else {
- hr = get_default_endpoint(&device, direction);
+ } else {
+ // If caller has requested loopback but not specified a device, look for
+ // the default render device. Otherwise look for the default device
+ // appropriate to the direction.
+ hr = get_default_endpoint(device, is_loopback ? eRender : direction,
+ pref_to_role(stream_params->prefs));
if (FAILED(hr)) {
- LOG("Could not get default %s endpoint, error: %x\n", DIRECTION_NAME, hr);
+ if (is_loopback) {
+ LOG("Could not get default render endpoint for loopback, error: "
+ "%lx\n",
+ hr);
+ } else {
+ LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME,
+ hr);
+ }
return CUBEB_ERROR;
}
}
/* Get a client. We will get all other interfaces we need from
* this pointer. */
- hr = device->Activate(__uuidof(IAudioClient),
+#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
+ hr = device->Activate(__uuidof(IAudioClient3),
CLSCTX_INPROC_SERVER,
- NULL, (void **)audio_client);
- SafeRelease(device);
+ NULL, audio_client.receive_vpp());
+ if (hr == E_NOINTERFACE) {
+#endif
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ audio_client.receive_vpp());
+#if 0
+ }
+#endif
+
if (FAILED(hr)) {
LOG("Could not activate the device to get an audio"
- " client for %s: error: %x\n", DIRECTION_NAME, hr);
+ " client for %s: error: %lx\n",
+ DIRECTION_NAME, hr);
// A particular device can't be activated because it has been
// unplugged, try fall back to the default audio device.
if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) {
LOG("Trying again with the default %s audio device.", DIRECTION_NAME);
devid = nullptr;
+ device = nullptr;
try_again = true;
} else {
return CUBEB_ERROR;
@@ -1495,62 +2107,154 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
/* We have to distinguish between the format the mixer uses,
* and the format the stream we want to play uses. */
- hr = (*audio_client)->GetMixFormat(&mix_format);
+ WAVEFORMATEX * tmp = nullptr;
+ hr = audio_client->GetMixFormat(&tmp);
if (FAILED(hr)) {
LOG("Could not fetch current mix format from the audio"
- " client for %s: error: %x", DIRECTION_NAME, hr);
+ " client for %s: error: %lx",
+ DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+ mix_format->wBitsPerSample = stm->bytes_per_sample * 8;
+ if (mix_format->wFormatTag == WAVE_FORMAT_PCM ||
+ mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ switch (mix_format->wBitsPerSample) {
+ case 8:
+ case 16:
+ mix_format->wFormatTag = WAVE_FORMAT_PCM;
+ break;
+ case 32:
+ mix_format->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ break;
+ default:
+ LOG("%u bits per sample is incompatible with PCM wave formats",
+ mix_format->wBitsPerSample);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+ format_pcm->SubFormat = stm->waveformatextensible_sub_format;
+ }
+ waveformatex_update_derived_properties(mix_format.get());
- handle_channel_layout(stm, &mix_format, stream_params);
+ /* Set channel layout only when there're more than two channels. Otherwise,
+ * use the default setting retrieved from the stream format of the audio
+ * engine's internal processing by GetMixFormat. */
+ if (mix_format->nChannels > 2) {
+ handle_channel_layout(stm, direction, mix_format, stream_params);
+ }
- /* Shared mode WASAPI always supports float32 sample format, so this
- * is safe. */
- mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
+ mix_params->format = stream_params->format;
mix_params->rate = mix_format->nSamplesPerSec;
mix_params->channels = mix_format->nChannels;
- LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
+ mix_params->layout = mask_to_channel_layout(mix_format.get());
+
+ LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
stream_params->format, stream_params->rate, stream_params->channels,
- mix_params->format, mix_params->rate, mix_params->channels);
-
- hr = (*audio_client)->Initialize(AUDCLNT_SHAREMODE_SHARED,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_NOPERSIST,
- frames_to_hns(stm, stm->latency),
- 0,
- mix_format,
- NULL);
+ stream_params->layout, mix_params->format, mix_params->rate,
+ mix_params->channels, mix_params->layout);
+
+ DWORD flags = 0;
+
+ // Check if a loopback device should be requested. Note that event callbacks
+ // do not work with loopback devices, so only request these if not looping.
+ if (is_loopback) {
+ flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+ } else {
+ flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ }
+
+ // Sanity check the latency, it may be that the device doesn't support it.
+ REFERENCE_TIME minimum_period;
+ REFERENCE_TIME default_period;
+ hr = audio_client->GetDevicePeriod(&default_period, &minimum_period);
if (FAILED(hr)) {
- LOG("Unable to initialize audio client for %s: %x.", DIRECTION_NAME, hr);
+ LOG("Could not get device period: %lx", hr);
return CUBEB_ERROR;
}
- CoTaskMemFree(mix_format);
+ REFERENCE_TIME latency_hns;
- hr = (*audio_client)->GetBufferSize(buffer_frame_count);
- if (FAILED(hr)) {
- LOG("Could not get the buffer size from the client"
- " for %s %x.", DIRECTION_NAME, hr);
- return CUBEB_ERROR;
+ uint32_t latency_frames = stm->latency;
+ cubeb_device_info device_info;
+ int rv = wasapi_create_device(stm->context, device_info,
+ stm->device_enumerator.get(), device.get());
+ if (rv == CUBEB_OK) {
+ const char * HANDSFREE_TAG = "BTHHFENUM";
+ size_t len = sizeof(HANDSFREE_TAG);
+ if (direction == eCapture) {
+ uint32_t default_period_frames =
+ hns_to_frames(device_info.default_rate, default_period);
+ if (strlen(device_info.group_id) >= len &&
+ strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
+ stm->input_bluetooth_handsfree = true;
+ } else {
+ stm->input_bluetooth_handsfree = false;
+ }
+ // This multiplicator has been found empirically.
+ latency_frames = default_period_frames * 8;
+ LOG("Input: latency increased to %u frames from a default of %u",
+ latency_frames, default_period_frames);
+ }
+ latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
+
+ wasapi_destroy_device(&device_info);
+ } else {
+ stm->input_bluetooth_handsfree = false;
+ latency_hns = frames_to_hns(mix_params->rate, latency_frames);
+ LOG("Could not get cubeb_device_info.");
}
- // Input is up/down mixed when depacketized in get_input_buffer.
- if (has_output(stm) &&
- (should_upmix(*stream_params, *mix_params) ||
- should_downmix(*stream_params, *mix_params))) {
- stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, *buffer_frame_count));
+ if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
+ if (initialize_iaudioclient2(audio_client) != CUBEB_OK) {
+ LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
+ // This is not fatal.
+ }
+ }
+
+#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
+ if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
+ LOG("Initialized with IAudioClient3");
+ } else {
+#endif
+ hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0,
+ mix_format.get(), NULL);
+#if 0
+ }
+#endif
+ if (FAILED(hr)) {
+ LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
}
- hr = (*audio_client)->SetEventHandle(event);
+ hr = audio_client->GetBufferSize(buffer_frame_count);
if (FAILED(hr)) {
- LOG("Could set the event handle for the %s client %x.",
+ LOG("Could not get the buffer size from the client"
+ " for %s %lx.",
DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
- hr = (*audio_client)->GetService(riid, (void **)render_or_capture_client);
+ LOG("Buffer size is: %d for %s\n", *buffer_frame_count, DIRECTION_NAME);
+
+ // Events are used if not looping back
+ if (!is_loopback) {
+ hr = audio_client->SetEventHandle(event);
+ if (FAILED(hr)) {
+ LOG("Could set the event handle for the %s client %lx.", DIRECTION_NAME,
+ hr);
+ return CUBEB_ERROR;
+ }
+ }
+
+ hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not get the %s client %x.", DIRECTION_NAME, hr);
+ LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
@@ -1559,83 +2263,175 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
#undef DIRECTION_NAME
-int setup_wasapi_stream(cubeb_stream * stm)
+void
+wasapi_find_matching_output_device(cubeb_stream * stm)
{
HRESULT hr;
- int rv;
+ cubeb_device_info * input_device = nullptr;
+ cubeb_device_collection collection;
- stm->stream_reset_lock.assert_current_thread_owns();
+ // Only try to match to an output device if the input device is a bluetooth
+ // device that is using the handsfree protocol
+ if (!stm->input_bluetooth_handsfree) {
+ return;
+ }
- auto_com com;
- if (!com.ok()) {
- LOG("Failure to initialize COM.");
- return CUBEB_ERROR;
+ wchar_t * tmp = nullptr;
+ hr = stm->input_device->GetId(&tmp);
+ if (FAILED(hr)) {
+ LOG("Couldn't get input device id in wasapi_find_matching_output_device");
+ return;
+ }
+ com_heap_ptr<wchar_t> device_id(tmp);
+ cubeb_devid input_device_id = intern_device_id(stm->context, device_id.get());
+ if (!input_device_id) {
+ return;
}
- XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first.");
+ int rv = wasapi_enumerate_devices(
+ stm->context,
+ (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
+ &collection);
+ if (rv != CUBEB_OK) {
+ return;
+ }
+
+ // Find the input device, and then find the output device with the same group
+ // id and the same rate.
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].devid == input_device_id) {
+ input_device = &collection.device[i];
+ break;
+ }
+ }
+
+ for (uint32_t i = 0; i < collection.count; i++) {
+ cubeb_device_info & dev = collection.device[i];
+ if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id && input_device &&
+ !strcmp(dev.group_id, input_device->group_id) &&
+ dev.default_rate == input_device->default_rate) {
+ LOG("Found matching device for %s: %s", input_device->friendly_name,
+ dev.friendly_name);
+ stm->output_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(dev.devid));
+ }
+ }
+
+ wasapi_device_collection_destroy(stm->context, &collection);
+}
+
+int
+setup_wasapi_stream(cubeb_stream * stm)
+{
+ int rv;
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ XASSERT((!stm->output_client || !stm->input_client) &&
+ "WASAPI stream already setup, close it first.");
if (has_input(stm)) {
- LOG("Setup capture: device=%x", (int)stm->input_device);
- rv = setup_wasapi_stream_one_side(stm,
- &stm->input_stream_params,
- stm->input_device,
- eCapture,
- __uuidof(IAudioCaptureClient),
- &stm->input_client,
- &stm->input_buffer_frame_count,
- stm->input_available_event,
- &stm->capture_client,
- &stm->input_mix_params);
+ LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get());
+ rv = setup_wasapi_stream_one_side(
+ stm, &stm->input_stream_params, stm->input_device_id.get(), eCapture,
+ __uuidof(IAudioCaptureClient), stm->input_client,
+ &stm->input_buffer_frame_count, stm->input_available_event,
+ stm->capture_client, &stm->input_mix_params, stm->input_device);
if (rv != CUBEB_OK) {
LOG("Failure to open the input side.");
return rv;
}
+
+ // We initializing an input stream, buffer ahead two buffers worth of
+ // silence. This delays the input side slightly, but allow to not glitch
+ // when no input is available when calling into the resampler to call the
+ // callback: the input refill event will be set shortly after to compensate
+ // for this lack of data. In debug, four buffers are used, to avoid tripping
+ // up assertions down the line.
+#if !defined(DEBUG)
+ const int silent_buffer_count = 2;
+#else
+ const int silent_buffer_count = 6;
+#endif
+ stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
+ stm->input_stream_params.channels *
+ silent_buffer_count);
+
+ // If this is a bluetooth device, and the output device is the default
+ // device, and the default device is the same bluetooth device, pick the
+ // right output device, running at the same rate and with the same protocol
+ // as the input.
+ if (!stm->output_device_id) {
+ wasapi_find_matching_output_device(stm);
+ }
+ }
+
+ // If we don't have an output device but are requesting a loopback device,
+ // we attempt to open that same device in output mode in order to drive the
+ // loopback via the output events.
+ stm->has_dummy_output = false;
+ if (!has_output(stm) &&
+ stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ stm->output_stream_params.rate = stm->input_stream_params.rate;
+ stm->output_stream_params.channels = stm->input_stream_params.channels;
+ stm->output_stream_params.layout = stm->input_stream_params.layout;
+ if (stm->input_device_id) {
+ size_t len = wcslen(stm->input_device_id.get());
+ std::unique_ptr<wchar_t[]> tmp(new wchar_t[len + 1]);
+ if (wcsncpy_s(tmp.get(), len + 1, stm->input_device_id.get(), len) != 0) {
+ LOG("Failed to copy device identifier while copying input stream"
+ " configuration to output stream configuration to drive loopback.");
+ return CUBEB_ERROR;
+ }
+ stm->output_device_id = move(tmp);
+ }
+ stm->has_dummy_output = true;
}
if (has_output(stm)) {
- LOG("Setup render: device=%x", (int)stm->output_device);
- rv = setup_wasapi_stream_one_side(stm,
- &stm->output_stream_params,
- stm->output_device,
- eRender,
- __uuidof(IAudioRenderClient),
- &stm->output_client,
- &stm->output_buffer_frame_count,
- stm->refill_event,
- &stm->render_client,
- &stm->output_mix_params);
+ LOG("(%p) Setup render: device=%p", stm, stm->output_device_id.get());
+ rv = setup_wasapi_stream_one_side(
+ stm, &stm->output_stream_params, stm->output_device_id.get(), eRender,
+ __uuidof(IAudioRenderClient), stm->output_client,
+ &stm->output_buffer_frame_count, stm->refill_event, stm->render_client,
+ &stm->output_mix_params, stm->output_device);
if (rv != CUBEB_OK) {
LOG("Failure to open the output side.");
return rv;
}
+ HRESULT hr = 0;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
- (void **)&stm->audio_stream_volume);
+ stm->audio_stream_volume.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not get the IAudioStreamVolume: %x", hr);
+ LOG("Could not get the IAudioStreamVolume: %lx", hr);
return CUBEB_ERROR;
}
+#endif
XASSERT(stm->frames_written == 0);
hr = stm->output_client->GetService(__uuidof(IAudioClock),
- (void **)&stm->audio_clock);
+ stm->audio_clock.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not get the IAudioClock: %x", hr);
+ LOG("Could not get the IAudioClock: %lx", hr);
return CUBEB_ERROR;
}
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
/* Restore the stream volume over a device change. */
if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
LOG("Could not set the volume.");
return CUBEB_ERROR;
}
+#endif
}
/* If we have both input and output, we resample to
* the highest sample rate available. */
int32_t target_sample_rate;
if (has_input(stm) && has_output(stm)) {
- assert(stm->input_stream_params.rate == stm->output_stream_params.rate);
+ XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate);
target_sample_rate = stm->input_stream_params.rate;
} else if (has_input(stm)) {
target_sample_rate = stm->input_stream_params.rate;
@@ -1655,14 +2451,12 @@ int setup_wasapi_stream(cubeb_stream * stm)
cubeb_stream_params output_params = stm->output_mix_params;
output_params.channels = stm->output_stream_params.channels;
- stm->resampler =
- cubeb_resampler_create(stm,
- has_input(stm) ? &input_params : nullptr,
- has_output(stm) ? &output_params : nullptr,
- target_sample_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler.reset(cubeb_resampler_create(
+ stm, has_input(stm) ? &input_params : nullptr,
+ has_output(stm) ? &output_params : nullptr, target_sample_rate,
+ stm->data_callback, stm->user_ptr,
+ stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
+ : CUBEB_RESAMPLER_QUALITY_DESKTOP));
if (!stm->resampler) {
LOG("Could not get a resampler");
return CUBEB_ERROR;
@@ -1678,189 +2472,264 @@ int setup_wasapi_stream(cubeb_stream * stm)
stm->refill_callback = refill_callback_output;
}
+ // Create input mixer.
+ if (has_input(stm) &&
+ ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED &&
+ stm->input_mix_params.layout != stm->input_stream_params.layout) ||
+ (stm->input_mix_params.channels != stm->input_stream_params.channels))) {
+ if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Input stream using undefined layout! Any mixing may be "
+ "unpredictable!\n");
+ }
+ stm->input_mixer.reset(cubeb_mixer_create(
+ stm->input_stream_params.format, stm->input_mix_params.channels,
+ stm->input_mix_params.layout, stm->input_stream_params.channels,
+ stm->input_stream_params.layout));
+ assert(stm->input_mixer);
+ }
+
+ // Create output mixer.
+ if (has_output(stm) &&
+ stm->output_mix_params.layout != stm->output_stream_params.layout) {
+ if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Output stream using undefined layout! Any mixing may be "
+ "unpredictable!\n");
+ }
+ stm->output_mixer.reset(cubeb_mixer_create(
+ stm->output_stream_params.format, stm->output_stream_params.channels,
+ stm->output_stream_params.layout, stm->output_mix_params.channels,
+ stm->output_mix_params.layout));
+ assert(stm->output_mixer);
+ // Input is up/down mixed when depacketized in get_input_buffer.
+ stm->mix_buffer.resize(
+ frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
+ }
+
return CUBEB_OK;
}
+ERole
+pref_to_role(cubeb_stream_prefs prefs)
+{
+ if (prefs & CUBEB_STREAM_PREF_VOICE) {
+ return eCommunications;
+ }
+
+ return eConsole;
+}
+
int
wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
- unsigned int latency_frames, cubeb_data_callback data_callback,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
cubeb_state_callback state_callback, void * user_ptr)
{
- HRESULT hr;
int rv;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
XASSERT(context && stream && (input_stream_params || output_stream_params));
- if (output_stream_params && output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE ||
- input_stream_params && input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) {
- LOG("Invalid format, %p %p %d %d",
- output_stream_params, input_stream_params,
- output_stream_params && output_stream_params->format,
- input_stream_params && input_stream_params->format);
+ if (output_stream_params && input_stream_params &&
+ output_stream_params->format != input_stream_params->format) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
-
- XASSERT(stm);
+ std::unique_ptr<cubeb_stream, decltype(&wasapi_stream_destroy)> stm(
+ new cubeb_stream(), wasapi_stream_destroy);
stm->context = context;
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
- stm->draining = false;
+ stm->role = eConsole;
+ stm->input_bluetooth_handsfree = false;
+
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(stm->device_enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
- stm->input_device = input_device;
+ stm->input_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(input_device));
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
- stm->output_device = output_device;
+ stm->output_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(output_device));
}
- stm->latency = latency_frames;
- stm->volume = 1.0;
+ if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE ||
+ stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) {
+ stm->voice = true;
+ } else {
+ stm->voice = false;
+ }
+
+ switch (output_stream_params ? output_stream_params->format
+ : input_stream_params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ stm->bytes_per_sample = sizeof(short);
+ stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
+ stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ stm->bytes_per_sample = sizeof(float);
+ stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ stm->linear_input_buffer.reset(new auto_array_wrapper_impl<float>);
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
- // Placement new to call ctor.
- new (&stm->stream_reset_lock) owned_critical_section();
+ stm->latency = latency_frames;
stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->reconfigure_event) {
- LOG("Can't create the reconfigure event, error: %x", GetLastError());
- wasapi_stream_destroy(stm);
+ LOG("Can't create the reconfigure event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
/* Unconditionally create the two events so that the wait logic is simpler. */
stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->refill_event) {
- LOG("Can't create the refill event, error: %x", GetLastError());
- wasapi_stream_destroy(stm);
+ LOG("Can't create the refill event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
stm->input_available_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->input_available_event) {
- LOG("Can't create the input available event , error: %x", GetLastError());
- wasapi_stream_destroy(stm);
+ LOG("Can't create the input available event , error: %lx", GetLastError());
return CUBEB_ERROR;
}
-
{
/* Locking here is not strictly necessary, because we don't have a
notification client that can reset the stream yet, but it lets us
assert that the lock is held in the function. */
auto_lock lock(stm->stream_reset_lock);
- rv = setup_wasapi_stream(stm);
+ rv = setup_wasapi_stream(stm.get());
}
if (rv != CUBEB_OK) {
- wasapi_stream_destroy(stm);
return rv;
}
- hr = register_notification_client(stm);
- if (FAILED(hr)) {
- /* this is not fatal, we can still play audio, but we won't be able
- to keep using the default audio endpoint if it changes. */
- LOG("failed to register notification client, %x", hr);
+ if (!((input_stream_params ? (input_stream_params->prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)
+ : 0) ||
+ (output_stream_params ? (output_stream_params->prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)
+ : 0))) {
+ HRESULT hr = register_notification_client(stm.get());
+ if (FAILED(hr)) {
+ /* this is not fatal, we can still play audio, but we won't be able
+ to keep using the default audio endpoint if it changes. */
+ LOG("failed to register notification client, %lx", hr);
+ }
}
- *stream = stm;
+ *stream = stm.release();
+ LOG("Stream init succesfull (%p)", *stream);
return CUBEB_OK;
}
-void close_wasapi_stream(cubeb_stream * stm)
+void
+close_wasapi_stream(cubeb_stream * stm)
{
XASSERT(stm);
stm->stream_reset_lock.assert_current_thread_owns();
- SafeRelease(stm->output_client);
- stm->output_client = NULL;
- SafeRelease(stm->input_client);
- stm->input_client = NULL;
+ stm->output_client = nullptr;
+ stm->render_client = nullptr;
- SafeRelease(stm->render_client);
- stm->render_client = NULL;
+ stm->input_client = nullptr;
+ stm->capture_client = nullptr;
- SafeRelease(stm->capture_client);
- stm->capture_client = NULL;
+ stm->output_device = nullptr;
+ stm->input_device = nullptr;
- SafeRelease(stm->audio_stream_volume);
- stm->audio_stream_volume = NULL;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ stm->audio_stream_volume = nullptr;
+#endif
- SafeRelease(stm->audio_clock);
- stm->audio_clock = NULL;
- stm->total_frames_written += static_cast<UINT64>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params)));
+ stm->audio_clock = nullptr;
+ stm->total_frames_written += static_cast<UINT64>(
+ round(stm->frames_written *
+ stream_to_mix_samplerate_ratio(stm->output_stream_params,
+ stm->output_mix_params)));
stm->frames_written = 0;
- if (stm->resampler) {
- cubeb_resampler_destroy(stm->resampler);
- stm->resampler = NULL;
+ stm->resampler.reset();
+ stm->output_mixer.reset();
+ stm->input_mixer.reset();
+ stm->mix_buffer.clear();
+ if (stm->linear_input_buffer) {
+ stm->linear_input_buffer->clear();
}
-
- free(stm->mix_buffer);
- stm->mix_buffer = NULL;
}
-void wasapi_stream_destroy(cubeb_stream * stm)
+void
+wasapi_stream_destroy(cubeb_stream * stm)
{
XASSERT(stm);
+ LOG("Stream destroy (%p)", stm);
- // Only free stm->emergency_bailout if we could not join the thread.
- // If we could not join the thread, stm->emergency_bailout is true
+ // Only free stm->emergency_bailout if we could join the thread.
+ // If we could not join the thread, stm->emergency_bailout is true
// and is still alive until the thread wakes up and exits cleanly.
if (stop_and_join_render_thread(stm)) {
delete stm->emergency_bailout.load();
stm->emergency_bailout = nullptr;
}
- unregister_notification_client(stm);
+ if (stm->notification_client) {
+ unregister_notification_client(stm);
+ }
- SafeRelease(stm->reconfigure_event);
- SafeRelease(stm->refill_event);
- SafeRelease(stm->input_available_event);
+ CloseHandle(stm->reconfigure_event);
+ CloseHandle(stm->refill_event);
+ CloseHandle(stm->input_available_event);
+
+ // The variables intialized in wasapi_stream_init,
+ // must be destroyed in wasapi_stream_destroy.
+ stm->linear_input_buffer.reset();
+
+ stm->device_enumerator = nullptr;
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
}
- // Need to call dtor to free the resource in owned_critical_section.
- stm->stream_reset_lock.~owned_critical_section();
-
- free(stm);
+ delete stm;
}
-enum StreamDirection {
- OUTPUT,
- INPUT
-};
+enum StreamDirection { OUTPUT, INPUT };
-int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
+int
+stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
{
XASSERT((dir == OUTPUT && stm->output_client) ||
(dir == INPUT && stm->input_client));
- HRESULT hr = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
+ HRESULT hr =
+ dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
LOG("audioclient invalidated for %s device, reconfiguring",
dir == OUTPUT ? "output" : "input");
BOOL ok = ResetEvent(stm->reconfigure_event);
if (!ok) {
- LOG("resetting reconfig event failed for %s stream: %x",
+ LOG("resetting reconfig event failed for %s stream: %lx",
dir == OUTPUT ? "output" : "input", GetLastError());
}
@@ -1871,14 +2740,15 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
return r;
}
- HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
+ HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start()
+ : stm->input_client->Start();
if (FAILED(hr2)) {
- LOG("could not start the %s stream after reconfig: %x",
+ LOG("could not start the %s stream after reconfig: %lx",
dir == OUTPUT ? "output" : "input", hr);
return CUBEB_ERROR;
}
} else if (FAILED(hr)) {
- LOG("could not start the %s stream: %x.",
+ LOG("could not start the %s stream: %lx.",
dir == OUTPUT ? "output" : "input", hr);
return CUBEB_ERROR;
}
@@ -1886,7 +2756,8 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
return CUBEB_OK;
}
-int wasapi_stream_start(cubeb_stream * stm)
+int
+wasapi_stream_start(cubeb_stream * stm)
{
auto_lock lock(stm->stream_reset_lock);
@@ -1911,22 +2782,40 @@ int wasapi_stream_start(cubeb_stream * stm)
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->shutdown_event) {
- LOG("Can't create the shutdown event, error: %x", GetLastError());
+ LOG("Can't create the shutdown event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
- stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->thread_ready_event) {
+ LOG("Can't create the thread_ready event, error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ cubeb_async_log_reset_threads();
+ stm->thread =
+ (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (stm->thread == NULL) {
LOG("could not create WASAPI render thread.");
return CUBEB_ERROR;
}
+ // Wait for wasapi_stream_render_loop to signal that emergency_bailout has
+ // been read, avoiding a bailout situation where we could free `stm`
+ // before wasapi_stream_render_loop had a chance to run.
+ HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
+ XASSERT(hr == WAIT_OBJECT_0);
+ CloseHandle(stm->thread_ready_event);
+ stm->thread_ready_event = 0;
+
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
-int wasapi_stream_stop(cubeb_stream * stm)
+int
+wasapi_stream_stop(cubeb_stream * stm)
{
XASSERT(stm);
HRESULT hr;
@@ -1950,22 +2839,23 @@ int wasapi_stream_stop(cubeb_stream * stm)
}
}
-
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
}
if (stop_and_join_render_thread(stm)) {
- // This is null if we've given the pointer to the other thread
- if (stm->emergency_bailout.load()) {
- delete stm->emergency_bailout.load();
- stm->emergency_bailout = nullptr;
- }
+ delete stm->emergency_bailout.load();
+ stm->emergency_bailout = nullptr;
+ } else {
+ // If we could not join the thread, put the stream in error.
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return CUBEB_ERROR;
}
return CUBEB_OK;
}
-int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
+int
+wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
XASSERT(stm && position);
auto_lock lock(stm->stream_reset_lock);
@@ -1975,11 +2865,16 @@ int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
}
/* Calculate how far behind the current stream head the playback cursor is. */
- uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) * stm->output_stream_params.rate);
+ uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) *
+ stm->output_stream_params.rate);
/* Calculate the logical stream head in frames at the stream sample rate. */
- uint64_t max_pos = stm->total_frames_written +
- static_cast<uint64_t>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params)));
+ uint64_t max_pos =
+ stm->total_frames_written +
+ static_cast<uint64_t>(
+ round(stm->frames_written *
+ stream_to_mix_samplerate_ratio(stm->output_stream_params,
+ stm->output_mix_params)));
*position = max_pos;
if (stream_delay <= *position) {
@@ -1994,7 +2889,8 @@ int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
return CUBEB_OK;
}
-int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+int
+wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
XASSERT(stm && latency);
@@ -2007,20 +2903,55 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
/* The GetStreamLatency method only works if the
AudioClient has been initialized. */
if (!stm->output_client) {
+ LOG("get_latency: No output_client.");
return CUBEB_ERROR;
}
REFERENCE_TIME latency_hns;
HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
if (FAILED(hr)) {
+ LOG("GetStreamLatency failed %lx.", hr);
return CUBEB_ERROR;
}
- *latency = hns_to_frames(stm, latency_hns);
+ // This happens on windows 10: no error, but always 0 for latency.
+ if (latency_hns == 0) {
+ LOG("GetStreamLatency returned 0, using workaround.");
+ double delay_s = current_stream_delay(stm);
+ // convert to sample-frames
+ *latency = delay_s * stm->output_stream_params.rate;
+ } else {
+ *latency = hns_to_frames(stm, latency_hns);
+ }
+
+ LOG("Output latency %u frames.", *latency);
return CUBEB_OK;
}
-int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
+int
+wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ XASSERT(stm && latency);
+
+ if (!has_input(stm)) {
+ LOG("Input latency queried on an output-only stream.");
+ return CUBEB_ERROR;
+ }
+
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) {
+ LOG("Input latency not available yet.");
+ return CUBEB_ERROR;
+ }
+
+ *latency = hns_to_frames(stm, stm->input_latency_hns);
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_set_volume(cubeb_stream * stm, float volume)
{
auto_lock lock(stm->stream_reset_lock);
@@ -2028,284 +2959,435 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_ERROR;
}
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
if (stream_set_volume(stm, volume) != CUBEB_OK) {
return CUBEB_ERROR;
}
+#endif
stm->volume = volume;
return CUBEB_OK;
}
-static char *
+static char const *
wstr_to_utf8(LPCWSTR str)
{
- char * ret = NULL;
- int size;
-
- size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, 0, NULL, NULL);
- if (size > 0) {
- ret = static_cast<char *>(malloc(size));
- ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
+ int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL);
+ if (size <= 0) {
+ return nullptr;
}
+ char * ret = static_cast<char *>(malloc(size));
+ ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
return ret;
}
-static std::unique_ptr<const wchar_t[]>
-utf8_to_wstr(char* str)
-{
- std::unique_ptr<wchar_t[]> ret;
- int size;
-
- size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
- if (size > 0) {
- ret.reset(new wchar_t[size]);
- ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
+static std::unique_ptr<wchar_t const []>
+utf8_to_wstr(char const * str) {
+ int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
+ if (size <= 0) {
+ return nullptr;
}
- return std::move(ret);
+ std::unique_ptr<wchar_t[]> ret(new wchar_t[size]);
+ ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
+ return ret;
}
-static IMMDevice *
-wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
+static com_ptr<IMMDevice> wasapi_get_device_node(
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
- IMMDevice * ret = NULL;
- IDeviceTopology * devtopo = NULL;
- IConnector * connector = NULL;
-
- if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&devtopo)) &&
- SUCCEEDED(devtopo->GetConnector(0, &connector))) {
- LPWSTR filterid;
- if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&filterid))) {
- if (FAILED(enumerator->GetDevice(filterid, &ret)))
+ com_ptr<IMMDevice> ret;
+ com_ptr<IDeviceTopology> devtopo;
+ com_ptr<IConnector> connector;
+
+ if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
+ devtopo.receive_vpp())) &&
+ SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) {
+ wchar_t * tmp = nullptr;
+ if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) {
+ com_heap_ptr<wchar_t> filterid(tmp);
+ if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive())))
ret = NULL;
- CoTaskMemFree(filterid);
}
}
- SafeRelease(connector);
- SafeRelease(devtopo);
return ret;
}
static BOOL
wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id,
- IMMDeviceEnumerator * enumerator)
+ IMMDeviceEnumerator * enumerator)
{
BOOL ret = FALSE;
- IMMDevice * dev;
+ com_ptr<IMMDevice> dev;
HRESULT hr;
- hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev);
+ hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive());
if (SUCCEEDED(hr)) {
- LPWSTR defdevid = NULL;
- if (SUCCEEDED(dev->GetId(&defdevid)))
- ret = (wcscmp(defdevid, device_id) == 0);
- if (defdevid != NULL)
- CoTaskMemFree(defdevid);
- SafeRelease(dev);
+ wchar_t * tmp = nullptr;
+ if (SUCCEEDED(dev->GetId(&tmp))) {
+ com_heap_ptr<wchar_t> defdevid(tmp);
+ ret = (wcscmp(defdevid.get(), device_id) == 0);
+ }
}
return ret;
}
-static cubeb_device_info *
-wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
+/* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value
+ * of this function is `CUBEB_OK`. */
+int
+wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
- IMMEndpoint * endpoint = NULL;
- IMMDevice * devnode = NULL;
- IAudioClient * client = NULL;
- cubeb_device_info * ret = NULL;
+ com_ptr<IMMEndpoint> endpoint;
+ com_ptr<IMMDevice> devnode;
+ com_ptr<IAudioClient> client;
EDataFlow flow;
- LPWSTR device_id = NULL;
DWORD state = DEVICE_STATE_NOTPRESENT;
- IPropertyStore * propstore = NULL;
- PROPVARIANT propvar;
+ com_ptr<IPropertyStore> propstore;
REFERENCE_TIME def_period, min_period;
HRESULT hr;
- PropVariantInit(&propvar);
+ // zero-out to be able to safely delete the pointers to friendly_name and
+ // group_id at all time in this function.
+ PodZero(&ret, 1);
- hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint));
- if (FAILED(hr)) goto done;
+ struct prop_variant : public PROPVARIANT {
+ prop_variant() { PropVariantInit(this); }
+ ~prop_variant() { PropVariantClear(this); }
+ prop_variant(prop_variant const &) = delete;
+ prop_variant & operator=(prop_variant const &) = delete;
+ };
+
+ hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
hr = endpoint->GetDataFlow(&flow);
- if (FAILED(hr)) goto done;
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ wchar_t * tmp = nullptr;
+ hr = dev->GetId(&tmp);
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<wchar_t> device_id(tmp);
- hr = dev->GetId(&device_id);
- if (FAILED(hr)) goto done;
+ char const * device_id_intern = intern_device_id(ctx, device_id.get());
+ if (!device_id_intern) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
- hr = dev->OpenPropertyStore(STGM_READ, &propstore);
- if (FAILED(hr)) goto done;
+ hr = dev->OpenPropertyStore(STGM_READ, propstore.receive());
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
hr = dev->GetState(&state);
- if (FAILED(hr)) goto done;
-
- ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
- ret->devid = ret->device_id = wstr_to_utf8(device_id);
- hr = propstore->GetValue(PKEY_Device_FriendlyName, &propvar);
- if (SUCCEEDED(hr))
- ret->friendly_name = wstr_to_utf8(propvar.pwszVal);
+ ret.device_id = device_id_intern;
+ ret.devid = reinterpret_cast<cubeb_devid>(ret.device_id);
+ prop_variant namevar;
+ hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar);
+ if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) {
+ ret.friendly_name = wstr_to_utf8(namevar.pwszVal);
+ }
+ if (!ret.friendly_name) {
+ // This is not fatal, but a valid string is expected in all cases.
+ char * empty = new char[1];
+ empty[0] = '\0';
+ ret.friendly_name = empty;
+ }
devnode = wasapi_get_device_node(enumerator, dev);
- if (devnode != NULL) {
- IPropertyStore * ps = NULL;
- hr = devnode->OpenPropertyStore(STGM_READ, &ps);
- if (FAILED(hr)) goto done;
+ if (devnode) {
+ com_ptr<IPropertyStore> ps;
+ hr = devnode->OpenPropertyStore(STGM_READ, ps.receive());
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
- PropVariantClear(&propvar);
- hr = ps->GetValue(PKEY_Device_InstanceId, &propvar);
- if (SUCCEEDED(hr)) {
- ret->group_id = wstr_to_utf8(propvar.pwszVal);
+ prop_variant instancevar;
+ hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar);
+ if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) {
+ ret.group_id = wstr_to_utf8(instancevar.pwszVal);
}
- SafeRelease(ps);
}
- ret->preferred = CUBEB_DEVICE_PREF_NONE;
- if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
- ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
- if (wasapi_is_default_device(flow, eCommunications, device_id, enumerator))
- ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE);
- if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
- ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION);
+ if (!ret.group_id) {
+ // This is not fatal, but a valid string is expected in all cases.
+ char * empty = new char[1];
+ empty[0] = '\0';
+ ret.group_id = empty;
+ }
- if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT;
- switch (state) {
- case DEVICE_STATE_ACTIVE:
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- break;
- case DEVICE_STATE_UNPLUGGED:
- ret->state = CUBEB_DEVICE_STATE_UNPLUGGED;
- break;
- default:
- ret->state = CUBEB_DEVICE_STATE_DISABLED;
- break;
- };
+ ret.preferred = CUBEB_DEVICE_PREF_NONE;
+ if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
+ }
+ if (wasapi_is_default_device(flow, eCommunications, device_id.get(),
+ enumerator)) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE);
+ }
+ if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION);
+ }
- ret->format = CUBEB_DEVICE_FMT_F32NE; /* cubeb only supports 32bit float at the moment */
- ret->default_format = CUBEB_DEVICE_FMT_F32NE;
- PropVariantClear(&propvar);
- hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &propvar);
- if (SUCCEEDED(hr) && propvar.vt == VT_BLOB) {
- if (propvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
- const PCMWAVEFORMAT * pcm = reinterpret_cast<const PCMWAVEFORMAT *>(propvar.blob.pBlobData);
+ if (flow == eRender) {
+ ret.type = CUBEB_DEVICE_TYPE_OUTPUT;
+ } else if (flow == eCapture) {
+ ret.type = CUBEB_DEVICE_TYPE_INPUT;
+ }
- ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec;
- ret->max_channels = pcm->wf.nChannels;
- } else if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
- WAVEFORMATEX* wfx = reinterpret_cast<WAVEFORMATEX*>(propvar.blob.pBlobData);
+ switch (state) {
+ case DEVICE_STATE_ACTIVE:
+ ret.state = CUBEB_DEVICE_STATE_ENABLED;
+ break;
+ case DEVICE_STATE_UNPLUGGED:
+ ret.state = CUBEB_DEVICE_STATE_UNPLUGGED;
+ break;
+ default:
+ ret.state = CUBEB_DEVICE_STATE_DISABLED;
+ break;
+ };
- if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
+ ret.format = static_cast<cubeb_device_fmt>(CUBEB_DEVICE_FMT_F32NE |
+ CUBEB_DEVICE_FMT_S16NE);
+ ret.default_format = CUBEB_DEVICE_FMT_F32NE;
+ prop_variant fmtvar;
+ hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar);
+ if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) {
+ if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
+ const PCMWAVEFORMAT * pcm =
+ reinterpret_cast<const PCMWAVEFORMAT *>(fmtvar.blob.pBlobData);
+
+ ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec;
+ ret.max_channels = pcm->wf.nChannels;
+ } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
+ WAVEFORMATEX * wfx =
+ reinterpret_cast<WAVEFORMATEX *>(fmtvar.blob.pBlobData);
+
+ if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
wfx->wFormatTag == WAVE_FORMAT_PCM) {
- ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec;
- ret->max_channels = wfx->nChannels;
+ ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec;
+ ret.max_channels = wfx->nChannels;
}
}
}
- if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) &&
+ if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
+ NULL, client.receive_vpp())) &&
SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
- ret->latency_lo = hns_to_frames(ret->default_rate, min_period);
- ret->latency_hi = hns_to_frames(ret->default_rate, def_period);
+ ret.latency_lo = hns_to_frames(ret.default_rate, min_period);
+ ret.latency_hi = hns_to_frames(ret.default_rate, def_period);
} else {
- ret->latency_lo = 0;
- ret->latency_hi = 0;
- }
- SafeRelease(client);
-
-done:
- SafeRelease(devnode);
- SafeRelease(endpoint);
- SafeRelease(propstore);
- if (device_id != NULL)
- CoTaskMemFree(device_id);
- PropVariantClear(&propvar);
- return ret;
+ ret.latency_lo = 0;
+ ret.latency_hi = 0;
+ }
+
+ XASSERT(ret.friendly_name && ret.group_id);
+
+ return CUBEB_OK;
+}
+
+void
+wasapi_destroy_device(cubeb_device_info * device)
+{
+ delete[] device->friendly_name;
+ delete[] device->group_id;
}
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** out)
+ cubeb_device_collection * out)
{
- auto_com com;
- IMMDeviceEnumerator * enumerator;
- IMMDeviceCollection * collection;
- IMMDevice * dev;
- cubeb_device_info * cur;
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ com_ptr<IMMDeviceCollection> collection;
HRESULT hr;
UINT cc, i;
EDataFlow flow;
- *out = NULL;
-
- if (!com.ok())
- return CUBEB_ERROR;
-
- hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
- CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
+ hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
+ LOG("Could not get device enumerator: %lx", hr);
return CUBEB_ERROR;
}
- if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender;
- else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture;
- else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_INPUT)) flow = eAll;
- else return CUBEB_ERROR;
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT)
+ flow = eRender;
+ else if (type == CUBEB_DEVICE_TYPE_INPUT)
+ flow = eCapture;
+ else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT))
+ flow = eAll;
+ else
+ return CUBEB_ERROR;
- hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection);
+ hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL,
+ collection.receive());
if (FAILED(hr)) {
- LOG("Could not enumerate audio endpoints: %x", hr);
+ LOG("Could not enumerate audio endpoints: %lx", hr);
return CUBEB_ERROR;
}
hr = collection->GetCount(&cc);
if (FAILED(hr)) {
- LOG("IMMDeviceCollection::GetCount() failed: %x", hr);
+ LOG("IMMDeviceCollection::GetCount() failed: %lx", hr);
return CUBEB_ERROR;
}
- *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0));
- if (!*out) {
+ cubeb_device_info * devices = new cubeb_device_info[cc];
+ if (!devices)
return CUBEB_ERROR;
- }
- (*out)->count = 0;
+
+ PodZero(devices, cc);
+ out->count = 0;
for (i = 0; i < cc; i++) {
- hr = collection->Item(i, &dev);
+ com_ptr<IMMDevice> dev;
+ hr = collection->Item(i, dev.receive());
if (FAILED(hr)) {
- LOG("IMMDeviceCollection::Item(%u) failed: %x", i-1, hr);
- } else if ((cur = wasapi_create_device(enumerator, dev)) != NULL) {
- (*out)->device[(*out)->count++] = cur;
+ LOG("IMMDeviceCollection::Item(%u) failed: %lx", i - 1, hr);
+ continue;
+ }
+ if (wasapi_create_device(context, devices[out->count], enumerator.get(),
+ dev.get()) == CUBEB_OK) {
+ out->count += 1;
+ }
+ }
+
+ out->device = devices;
+ return CUBEB_OK;
+}
+
+static int
+wasapi_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+
+ for (size_t n = 0; n < collection->count; n++) {
+ cubeb_device_info & dev = collection->device[n];
+ wasapi_destroy_device(&dev);
+ }
+
+ delete[] collection->device;
+ return CUBEB_OK;
+}
+
+static int
+wasapi_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (collection_changed_callback) {
+ // Make sure it has been unregistered first.
+ XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
+ !context->input_collection_changed_callback) ||
+ ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
+ !context->output_collection_changed_callback));
+
+ // Stop the notification client. Notifications arrive on
+ // a separate thread. We stop them here to avoid
+ // synchronization issues during the update.
+ if (context->device_collection_enumerator.get()) {
+ HRESULT hr = unregister_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+
+ HRESULT hr = register_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ } else {
+ if (!context->device_collection_enumerator.get()) {
+ // Already unregistered, ignore it.
+ return CUBEB_OK;
+ }
+
+ HRESULT hr = unregister_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ }
+
+ // If after the updates we still have registered
+ // callbacks restart the notification client.
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ hr = register_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
}
}
- SafeRelease(collection);
- SafeRelease(enumerator);
return CUBEB_OK;
}
cubeb_ops const wasapi_ops = {
- /*.init =*/ wasapi_init,
- /*.get_backend_id =*/ wasapi_get_backend_id,
- /*.get_max_channel_count =*/ wasapi_get_max_channel_count,
- /*.get_min_latency =*/ wasapi_get_min_latency,
- /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
- /*.enumerate_devices =*/ wasapi_enumerate_devices,
- /*.destroy =*/ wasapi_destroy,
- /*.stream_init =*/ wasapi_stream_init,
- /*.stream_destroy =*/ wasapi_stream_destroy,
- /*.stream_start =*/ wasapi_stream_start,
- /*.stream_stop =*/ wasapi_stream_stop,
- /*.stream_get_position =*/ wasapi_stream_get_position,
- /*.stream_get_latency =*/ wasapi_stream_get_latency,
- /*.stream_set_volume =*/ wasapi_stream_set_volume,
- /*.stream_set_panning =*/ NULL,
- /*.stream_get_current_device =*/ NULL,
- /*.stream_device_destroy =*/ NULL,
- /*.stream_register_device_changed_callback =*/ NULL,
- /*.register_device_collection_changed =*/ NULL
+ /*.init =*/wasapi_init,
+ /*.get_backend_id =*/wasapi_get_backend_id,
+ /*.get_max_channel_count =*/wasapi_get_max_channel_count,
+ /*.get_min_latency =*/wasapi_get_min_latency,
+ /*.get_preferred_sample_rate =*/wasapi_get_preferred_sample_rate,
+ /*.enumerate_devices =*/wasapi_enumerate_devices,
+ /*.device_collection_destroy =*/wasapi_device_collection_destroy,
+ /*.destroy =*/wasapi_destroy,
+ /*.stream_init =*/wasapi_stream_init,
+ /*.stream_destroy =*/wasapi_stream_destroy,
+ /*.stream_start =*/wasapi_stream_start,
+ /*.stream_stop =*/wasapi_stream_stop,
+ /*.stream_get_position =*/wasapi_stream_get_position,
+ /*.stream_get_latency =*/wasapi_stream_get_latency,
+ /*.stream_get_input_latency =*/wasapi_stream_get_input_latency,
+ /*.stream_set_volume =*/wasapi_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback =*/NULL,
+ /*.register_device_collection_changed =*/
+ wasapi_register_device_collection_changed,
};
-} // namespace anonymous
+} // namespace
diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c
deleted file mode 100644
index 585d11e89d..0000000000
--- a/media/libcubeb/src/cubeb_winmm.c
+++ /dev/null
@@ -1,1067 +0,0 @@
-/*
- * Copyright © 2011 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-#define __MSVCRT_VERSION__ 0x0700
-#undef WINVER
-#define WINVER 0x0501
-#undef WIN32_LEAN_AND_MEAN
-
-#include <malloc.h>
-#include <windows.h>
-#include <mmreg.h>
-#include <mmsystem.h>
-#include <process.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-
-/* This is missing from the MinGW headers. Use a safe fallback. */
-#if !defined(MEMORY_ALLOCATION_ALIGNMENT)
-#define MEMORY_ALLOCATION_ALIGNMENT 16
-#endif
-
-/**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/
-#ifndef WAVE_FORMAT_48M08
-#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_48M16
-#define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */
-#endif
-#ifndef WAVE_FORMAT_48S08
-#define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_48S16
-#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
-#endif
-#ifndef WAVE_FORMAT_96M08
-#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_96M16
-#define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */
-#endif
-#ifndef WAVE_FORMAT_96S08
-#define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_96S16
-#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
-#endif
-
-/**Taken from winbase.h, also not in MinGW.*/
-#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
-#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
-#endif
-
-#ifndef DRVM_MAPPER
-#define DRVM_MAPPER (0x2000)
-#endif
-#ifndef DRVM_MAPPER_PREFERRED_GET
-#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21)
-#endif
-#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
-#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23)
-#endif
-
-#define CUBEB_STREAM_MAX 32
-#define NBUFS 4
-
-const GUID KSDATAFORMAT_SUBTYPE_PCM =
-{ 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
-const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
-{ 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
-
-struct cubeb_stream_item {
- SLIST_ENTRY head;
- cubeb_stream * stream;
-};
-
-static struct cubeb_ops const winmm_ops;
-
-struct cubeb {
- struct cubeb_ops const * ops;
- HANDLE event;
- HANDLE thread;
- int shutdown;
- PSLIST_HEADER work;
- CRITICAL_SECTION lock;
- unsigned int active_streams;
- unsigned int minimum_latency_ms;
-};
-
-struct cubeb_stream {
- cubeb * context;
- cubeb_stream_params params;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * user_ptr;
- WAVEHDR buffers[NBUFS];
- size_t buffer_size;
- int next_buffer;
- int free_buffers;
- int shutdown;
- int draining;
- HANDLE event;
- HWAVEOUT waveout;
- CRITICAL_SECTION lock;
- uint64_t written;
- float soft_volume;
-};
-
-static size_t
-bytes_per_frame(cubeb_stream_params params)
-{
- size_t bytes;
-
- switch (params.format) {
- case CUBEB_SAMPLE_S16LE:
- bytes = sizeof(signed short);
- break;
- case CUBEB_SAMPLE_FLOAT32LE:
- bytes = sizeof(float);
- break;
- default:
- XASSERT(0);
- }
-
- return bytes * params.channels;
-}
-
-static WAVEHDR *
-winmm_get_next_buffer(cubeb_stream * stm)
-{
- WAVEHDR * hdr = NULL;
-
- XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
- hdr = &stm->buffers[stm->next_buffer];
- XASSERT(hdr->dwFlags & WHDR_PREPARED ||
- (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
- stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
- stm->free_buffers -= 1;
-
- return hdr;
-}
-
-static void
-winmm_refill_stream(cubeb_stream * stm)
-{
- WAVEHDR * hdr;
- long got;
- long wanted;
- MMRESULT r;
-
- EnterCriticalSection(&stm->lock);
- stm->free_buffers += 1;
- XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
-
- if (stm->draining) {
- LeaveCriticalSection(&stm->lock);
- if (stm->free_buffers == NBUFS) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- }
- SetEvent(stm->event);
- return;
- }
-
- if (stm->shutdown) {
- LeaveCriticalSection(&stm->lock);
- SetEvent(stm->event);
- return;
- }
-
- hdr = winmm_get_next_buffer(stm);
-
- wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
-
- /* It is assumed that the caller is holding this lock. It must be dropped
- during the callback to avoid deadlocks. */
- LeaveCriticalSection(&stm->lock);
- got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
- EnterCriticalSection(&stm->lock);
- if (got < 0) {
- LeaveCriticalSection(&stm->lock);
- /* XXX handle this case */
- XASSERT(0);
- return;
- } else if (got < wanted) {
- stm->draining = 1;
- }
- stm->written += got;
-
- XASSERT(hdr->dwFlags & WHDR_PREPARED);
-
- hdr->dwBufferLength = got * bytes_per_frame(stm->params);
- XASSERT(hdr->dwBufferLength <= stm->buffer_size);
-
- if (stm->soft_volume != -1.0) {
- if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
- float * b = (float *) hdr->lpData;
- uint32_t i;
- for (i = 0; i < got * stm->params.channels; i++) {
- b[i] *= stm->soft_volume;
- }
- } else {
- short * b = (short *) hdr->lpData;
- uint32_t i;
- for (i = 0; i < got * stm->params.channels; i++) {
- b[i] = (short) (b[i] * stm->soft_volume);
- }
- }
- }
-
- r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
- if (r != MMSYSERR_NOERROR) {
- LeaveCriticalSection(&stm->lock);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return;
- }
-
- LeaveCriticalSection(&stm->lock);
-}
-
-static unsigned __stdcall
-winmm_buffer_thread(void * user_ptr)
-{
- cubeb * ctx = (cubeb *) user_ptr;
- XASSERT(ctx);
-
- for (;;) {
- DWORD r;
- PSLIST_ENTRY item;
-
- r = WaitForSingleObject(ctx->event, INFINITE);
- XASSERT(r == WAIT_OBJECT_0);
-
- /* Process work items in batches so that a single stream can't
- starve the others by continuously adding new work to the top of
- the work item stack. */
- item = InterlockedFlushSList(ctx->work);
- while (item != NULL) {
- PSLIST_ENTRY tmp = item;
- winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream);
- item = item->Next;
- _aligned_free(tmp);
- }
-
- if (ctx->shutdown) {
- break;
- }
- }
-
- return 0;
-}
-
-static void CALLBACK
-winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2)
-{
- cubeb_stream * stm = (cubeb_stream *) user_ptr;
- struct cubeb_stream_item * item;
-
- if (msg != WOM_DONE) {
- return;
- }
-
- item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT);
- XASSERT(item);
- item->stream = stm;
- InterlockedPushEntrySList(stm->context->work, &item->head);
-
- SetEvent(stm->context->event);
-}
-
-static unsigned int
-calculate_minimum_latency(void)
-{
- OSVERSIONINFOEX osvi;
- DWORDLONG mask;
-
- /* Running under Terminal Services results in underruns with low latency. */
- if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
- return 500;
- }
-
- /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */
- memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
- osvi.dwMajorVersion = 6;
- osvi.dwMinorVersion = 0;
-
- mask = 0;
- VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
- VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
-
- if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
- return 200;
- }
-
- return 100;
-}
-
-static void winmm_destroy(cubeb * ctx);
-
-/*static*/ int
-winmm_init(cubeb ** context, char const * context_name)
-{
- cubeb * ctx;
-
- XASSERT(context);
- *context = NULL;
-
- /* Don't initialize a context if there are no devices available. */
- if (waveOutGetNumDevs() == 0) {
- return CUBEB_ERROR;
- }
-
- ctx = calloc(1, sizeof(*ctx));
- XASSERT(ctx);
-
- ctx->ops = &winmm_ops;
-
- ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
- XASSERT(ctx->work);
- InitializeSListHead(ctx->work);
-
- ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!ctx->event) {
- winmm_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- ctx->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
- if (!ctx->thread) {
- winmm_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
-
- InitializeCriticalSection(&ctx->lock);
- ctx->active_streams = 0;
-
- ctx->minimum_latency_ms = calculate_minimum_latency();
-
- *context = ctx;
-
- return CUBEB_OK;
-}
-
-static char const *
-winmm_get_backend_id(cubeb * ctx)
-{
- return "winmm";
-}
-
-static void
-winmm_destroy(cubeb * ctx)
-{
- DWORD r;
-
- XASSERT(ctx->active_streams == 0);
- XASSERT(!InterlockedPopEntrySList(ctx->work));
-
- DeleteCriticalSection(&ctx->lock);
-
- if (ctx->thread) {
- ctx->shutdown = 1;
- SetEvent(ctx->event);
- r = WaitForSingleObject(ctx->thread, INFINITE);
- XASSERT(r == WAIT_OBJECT_0);
- CloseHandle(ctx->thread);
- }
-
- if (ctx->event) {
- CloseHandle(ctx->event);
- }
-
- _aligned_free(ctx->work);
-
- free(ctx);
-}
-
-static void winmm_stream_destroy(cubeb_stream * stm);
-
-static int
-winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
-{
- MMRESULT r;
- WAVEFORMATEXTENSIBLE wfx;
- cubeb_stream * stm;
- int i;
- size_t bufsz;
-
- XASSERT(context);
- XASSERT(stream);
-
- if (input_stream_params) {
- /* Capture support not yet implemented. */
- return CUBEB_ERROR_NOT_SUPPORTED;
- }
-
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
-
- *stream = NULL;
-
- memset(&wfx, 0, sizeof(wfx));
- if (output_stream_params->channels > 2) {
- wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
- } else {
- wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
- wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
- }
- wfx.Format.cbSize = 0;
- }
- wfx.Format.nChannels = output_stream_params->channels;
- wfx.Format.nSamplesPerSec = output_stream_params->rate;
-
- /* XXX fix channel mappings */
- wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
-
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16LE:
- wfx.Format.wBitsPerSample = 16;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case CUBEB_SAMPLE_FLOAT32LE:
- wfx.Format.wBitsPerSample = 32;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
- default:
- return CUBEB_ERROR_INVALID_FORMAT;
- }
-
- wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
- wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
- wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
-
- EnterCriticalSection(&context->lock);
- /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
- many streams are active at once, a subset of them will not consume (via
- playback) or release (via waveOutReset) their buffers. */
- if (context->active_streams >= CUBEB_STREAM_MAX) {
- LeaveCriticalSection(&context->lock);
- return CUBEB_ERROR;
- }
- context->active_streams += 1;
- LeaveCriticalSection(&context->lock);
-
- stm = calloc(1, sizeof(*stm));
- XASSERT(stm);
-
- stm->context = context;
-
- stm->params = *output_stream_params;
-
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->written = 0;
-
- uint32_t latency_ms = latency_frames * 1000 / output_stream_params->rate;
-
- if (latency_ms < context->minimum_latency_ms) {
- latency_ms = context->minimum_latency_ms;
- }
-
- bufsz = (size_t) (stm->params.rate / 1000.0 * latency_ms * bytes_per_frame(stm->params) / NBUFS);
- if (bufsz % bytes_per_frame(stm->params) != 0) {
- bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
- }
- XASSERT(bufsz % bytes_per_frame(stm->params) == 0);
-
- stm->buffer_size = bufsz;
-
- InitializeCriticalSection(&stm->lock);
-
- stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!stm->event) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- stm->soft_volume = -1.0;
-
- /* winmm_buffer_callback will be called during waveOutOpen, so all
- other initialization must be complete before calling it. */
- r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
- (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm,
- CALLBACK_FUNCTION);
- if (r != MMSYSERR_NOERROR) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- r = waveOutPause(stm->waveout);
- if (r != MMSYSERR_NOERROR) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
-
- for (i = 0; i < NBUFS; ++i) {
- WAVEHDR * hdr = &stm->buffers[i];
-
- hdr->lpData = calloc(1, bufsz);
- XASSERT(hdr->lpData);
- hdr->dwBufferLength = bufsz;
- hdr->dwFlags = 0;
-
- r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
- if (r != MMSYSERR_NOERROR) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- winmm_refill_stream(stm);
- }
-
- *stream = stm;
-
- return CUBEB_OK;
-}
-
-static void
-winmm_stream_destroy(cubeb_stream * stm)
-{
- int i;
-
- if (stm->waveout) {
- MMTIME time;
- MMRESULT r;
- int device_valid;
- int enqueued;
-
- EnterCriticalSection(&stm->lock);
- stm->shutdown = 1;
-
- waveOutReset(stm->waveout);
-
- /* Don't need this value, we just want the result to detect invalid
- handle/no device errors than waveOutReset doesn't seem to report. */
- time.wType = TIME_SAMPLES;
- r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
- device_valid = !(r == MMSYSERR_INVALHANDLE || r == MMSYSERR_NODRIVER);
-
- enqueued = NBUFS - stm->free_buffers;
- LeaveCriticalSection(&stm->lock);
-
- /* Wait for all blocks to complete. */
- while (device_valid && enqueued > 0) {
- DWORD rv = WaitForSingleObject(stm->event, INFINITE);
- XASSERT(rv == WAIT_OBJECT_0);
-
- EnterCriticalSection(&stm->lock);
- enqueued = NBUFS - stm->free_buffers;
- LeaveCriticalSection(&stm->lock);
- }
-
- EnterCriticalSection(&stm->lock);
-
- for (i = 0; i < NBUFS; ++i) {
- if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
- waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
- }
- }
-
- waveOutClose(stm->waveout);
-
- LeaveCriticalSection(&stm->lock);
- }
-
- if (stm->event) {
- CloseHandle(stm->event);
- }
-
- DeleteCriticalSection(&stm->lock);
-
- for (i = 0; i < NBUFS; ++i) {
- free(stm->buffers[i].lpData);
- }
-
- EnterCriticalSection(&stm->context->lock);
- XASSERT(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
- LeaveCriticalSection(&stm->context->lock);
-
- free(stm);
-}
-
-static int
-winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
-{
- XASSERT(ctx && max_channels);
-
- /* We don't support more than two channels in this backend. */
- *max_channels = 2;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
-{
- // 100ms minimum, if we are not in a bizarre configuration.
- *latency = ctx->minimum_latency_ms * params.rate / 1000;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- WAVEOUTCAPS woc;
- MMRESULT r;
-
- r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
-
- /* Check if we support 48kHz, but not 44.1kHz. */
- if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
- woc.dwFormats & WAVE_FORMAT_48S16) {
- *rate = 48000;
- return CUBEB_OK;
- }
- /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
- *rate = 44100;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_start(cubeb_stream * stm)
-{
- MMRESULT r;
-
- EnterCriticalSection(&stm->lock);
- r = waveOutRestart(stm->waveout);
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_stop(cubeb_stream * stm)
-{
- MMRESULT r;
-
- EnterCriticalSection(&stm->lock);
- r = waveOutPause(stm->waveout);
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
-{
- MMRESULT r;
- MMTIME time;
-
- EnterCriticalSection(&stm->lock);
- time.wType = TIME_SAMPLES;
- r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
- return CUBEB_ERROR;
- }
-
- *position = time.u.sample;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
-{
- MMRESULT r;
- MMTIME time;
- uint64_t written;
-
- EnterCriticalSection(&stm->lock);
- time.wType = TIME_SAMPLES;
- r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
- written = stm->written;
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
- return CUBEB_ERROR;
- }
-
- XASSERT(written - time.u.sample <= UINT32_MAX);
- *latency = (uint32_t) (written - time.u.sample);
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_set_volume(cubeb_stream * stm, float volume)
-{
- EnterCriticalSection(&stm->lock);
- stm->soft_volume = volume;
- LeaveCriticalSection(&stm->lock);
- return CUBEB_OK;
-}
-
-#define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
-#define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
-#define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
-#define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16)
-#define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16)
-static void
-winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
-{
- if (formats & MM_11025HZ_MASK) {
- info->min_rate = 11025;
- info->default_rate = 11025;
- info->max_rate = 11025;
- }
- if (formats & MM_22050HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 22050;
- info->max_rate = 22050;
- info->default_rate = 22050;
- }
- if (formats & MM_44100HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 44100;
- info->max_rate = 44100;
- info->default_rate = 44100;
- }
- if (formats & MM_48000HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 48000;
- info->max_rate = 48000;
- info->default_rate = 48000;
- }
- if (formats & MM_96000HZ_MASK) {
- if (info->min_rate == 0) {
- info->min_rate = 96000;
- info->default_rate = 96000;
- }
- info->max_rate = 96000;
- }
-}
-
-
-#define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \
- WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
-static int
-winmm_query_supported_formats(UINT devid, DWORD formats,
- cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt)
-{
- WAVEFORMATEXTENSIBLE wfx;
-
- if (formats & MM_S16_MASK)
- *deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE;
- else
- *deffmt = *supfmt = 0;
-
- ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
- wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfx.Format.nChannels = 2;
- wfx.Format.nSamplesPerSec = 44100;
- wfx.Format.wBitsPerSample = 32;
- wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
- wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
- wfx.Format.cbSize = 22;
- wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
- wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
- *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
-
- return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
-}
-
-static char *
-guid_to_cstr(LPGUID guid)
-{
- char * ret = malloc(sizeof(char) * 40);
- if (!ret) {
- return NULL;
- }
- _snprintf_s(ret, sizeof(char) * 40, _TRUNCATE,
- "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
- guid->Data1, guid->Data2, guid->Data3,
- guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
- guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
- return ret;
-}
-
-static cubeb_device_pref
-winmm_query_preferred_out_device(UINT devid)
-{
- DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
- cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
-
- if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
- (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == mmpref)
- ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
-
- if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
- (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == compref)
- ret |= CUBEB_DEVICE_PREF_VOICE;
-
- return ret;
-}
-
-static char *
-device_id_idx(UINT devid)
-{
- char * ret = (char *)malloc(sizeof(char)*16);
- if (!ret) {
- return NULL;
- }
- _snprintf_s(ret, 16, _TRUNCATE, "%u", devid);
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = guid_to_cstr(&caps->ProductGuid);
- ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
-
- ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_out_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = NULL;
- ret->vendor_name = NULL;
-
- ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_out_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static cubeb_device_pref
-winmm_query_preferred_in_device(UINT devid)
-{
- DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
- cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
-
- if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
- (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == mmpref)
- ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
-
- if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
- (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == compref)
- ret |= CUBEB_DEVICE_PREF_VOICE;
-
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = guid_to_cstr(&caps->ProductGuid);
- ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
-
- ret->type = CUBEB_DEVICE_TYPE_INPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_in_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = NULL;
- ret->vendor_name = NULL;
-
- ret->type = CUBEB_DEVICE_TYPE_INPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_in_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static int
-winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
-{
- UINT i, incount, outcount, total;
- cubeb_device_info * cur;
-
- outcount = waveOutGetNumDevs();
- incount = waveInGetNumDevs();
- total = outcount + incount;
- if (total > 0) {
- total -= 1;
- }
- *collection = malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * total);
- (*collection)->count = 0;
-
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- WAVEOUTCAPSA woc;
- WAVEOUTCAPS2A woc2;
-
- ZeroMemory(&woc, sizeof(woc));
- ZeroMemory(&woc2, sizeof(woc2));
-
- for (i = 0; i < outcount; i++) {
- if ((waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_outcaps2(&woc2, i)) != NULL) ||
- (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_outcaps(&woc, i)) != NULL)
- ) {
- (*collection)->device[(*collection)->count++] = cur;
- }
- }
- }
-
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- WAVEINCAPSA wic;
- WAVEINCAPS2A wic2;
-
- ZeroMemory(&wic, sizeof(wic));
- ZeroMemory(&wic2, sizeof(wic2));
-
- for (i = 0; i < incount; i++) {
- if ((waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_incaps2(&wic2, i)) != NULL) ||
- (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_incaps(&wic, i)) != NULL)
- ) {
- (*collection)->device[(*collection)->count++] = cur;
- }
- }
- }
-
- return CUBEB_OK;
-}
-
-static struct cubeb_ops const winmm_ops = {
- /*.init =*/ winmm_init,
- /*.get_backend_id =*/ winmm_get_backend_id,
- /*.get_max_channel_count=*/ winmm_get_max_channel_count,
- /*.get_min_latency=*/ winmm_get_min_latency,
- /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
- /*.enumerate_devices =*/ winmm_enumerate_devices,
- /*.destroy =*/ winmm_destroy,
- /*.stream_init =*/ winmm_stream_init,
- /*.stream_destroy =*/ winmm_stream_destroy,
- /*.stream_start =*/ winmm_stream_start,
- /*.stream_stop =*/ winmm_stream_stop,
- /*.stream_get_position =*/ winmm_stream_get_position,
- /*.stream_get_latency = */ winmm_stream_get_latency,
- /*.stream_set_volume =*/ winmm_stream_set_volume,
- /*.stream_set_panning =*/ NULL,
- /*.stream_get_current_device =*/ NULL,
- /*.stream_device_destroy =*/ NULL,
- /*.stream_register_device_changed_callback=*/ NULL,
- /*.register_device_collection_changed =*/ NULL
-};
diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build
index c21cc873db..a67098d8dd 100644
--- a/media/libcubeb/src/moz.build
+++ b/media/libcubeb/src/moz.build
@@ -9,7 +9,11 @@ Library('cubeb')
SOURCES += [
'cubeb.c',
- 'cubeb_panner.cpp'
+ 'cubeb_log.cpp',
+ 'cubeb_mixer.cpp',
+ 'cubeb_panner.cpp',
+ 'cubeb_strings.c',
+ 'cubeb_utils.cpp'
]
if CONFIG['MOZ_ALSA']:
@@ -23,12 +27,6 @@ if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['MOZ_JACK']:
'cubeb_resampler.cpp',
]
-if CONFIG['MOZ_PULSEAUDIO']:
- SOURCES += [
- 'cubeb_pulse.c',
- ]
- DEFINES['USE_PULSE'] = True
-
if CONFIG['MOZ_JACK']:
SOURCES += [
'cubeb_jack.cpp',
@@ -38,51 +36,32 @@ if CONFIG['MOZ_JACK']:
]
DEFINES['USE_JACK'] = True
-if CONFIG['MOZ_SNDIO']:
+if CONFIG['OS_ARCH'] in ('DragonFly', 'FreeBSD', 'SunOS'):
SOURCES += [
- 'cubeb_sndio.c',
+ 'cubeb_oss.c',
]
- DEFINES['USE_SNDIO'] = True
-
-if CONFIG['OS_ARCH'] == 'SunOS':
- SOURCES += [
- 'cubeb_sun.c',
- ]
- DEFINES['USE_SUN'] = True
-
-if CONFIG['OS_TARGET'] == 'Darwin':
- SOURCES += [
- 'cubeb_audiounit.cpp',
- 'cubeb_resampler.cpp'
- ]
- DEFINES['USE_AUDIOUNIT'] = True
+ DEFINES['USE_OSS'] = True
if CONFIG['OS_TARGET'] == 'WINNT':
SOURCES += [
'cubeb_resampler.cpp',
'cubeb_wasapi.cpp',
- 'cubeb_winmm.c',
]
- DEFINES['USE_WINMM'] = True
+ DEFINES['UNICODE'] = True
DEFINES['USE_WASAPI'] = True
+ OS_LIBS += [
+ "avrt",
+ ]
if CONFIG['_MSC_VER']:
CXXFLAGS += ['-wd4005'] # C4005: '_USE_MATH_DEFINES' : macro redefinition
-if CONFIG['OS_TARGET'] == 'Android':
- SOURCES += ['cubeb_opensl.c']
- SOURCES += ['cubeb_resampler.cpp']
- DEFINES['USE_OPENSL'] = True
- SOURCES += [
- 'cubeb_audiotrack.c',
- ]
- DEFINES['USE_AUDIOTRACK'] = True
-
if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
NO_VISIBILITY_FLAGS = True
FINAL_LIBRARY = 'gkmedias'
CFLAGS += CONFIG['MOZ_ALSA_CFLAGS']
+CFLAGS += CONFIG['MOZ_JACK_CFLAGS']
CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS']
# We allow warnings for third-party code that can be updated from upstream.
diff --git a/media/libcubeb/tests/common.h b/media/libcubeb/tests/common.h
deleted file mode 100644
index 051f3c42a2..0000000000
--- a/media/libcubeb/tests/common.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright © 2013 Sebastien Alaiwan
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-#if defined( _WIN32)
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <windows.h>
-#else
-#include <unistd.h>
-#endif
-
-void delay(unsigned int ms)
-{
-#if defined(_WIN32)
- Sleep(ms);
-#else
- sleep(ms / 1000);
- usleep(ms % 1000 * 1000);
-#endif
-}
-
-#if !defined(M_PI)
-#define M_PI 3.14159265358979323846
-#endif
-
-int has_available_input_device(cubeb * ctx)
-{
- cubeb_device_collection * devices;
- int input_device_available = 0;
- int r;
- /* Bail out early if the host does not have input devices. */
- r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
- if (r != CUBEB_OK) {
- fprintf(stderr, "error enumerating devices.");
- return 0;
- }
-
- if (devices->count == 0) {
- fprintf(stderr, "no input device available, skipping test.\n");
- return 0;
- }
-
- for (uint32_t i = 0; i < devices->count; i++) {
- input_device_available |= (devices->device[i]->state ==
- CUBEB_DEVICE_STATE_ENABLED);
- }
-
- if (!input_device_available) {
- fprintf(stderr, "there are input devices, but they are not "
- "available, skipping\n");
- return 0;
- }
-
- return 1;
-}
-
diff --git a/media/libcubeb/tests/test_audio.cpp b/media/libcubeb/tests/test_audio.cpp
deleted file mode 100644
index 4943223e68..0000000000
--- a/media/libcubeb/tests/test_audio.cpp
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb api/function exhaustive test. Plays a series of tones in different
- * conditions. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#define _XOPEN_SOURCE 600
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <assert.h>
-#include <string.h>
-
-#include "cubeb/cubeb.h"
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define MAX_NUM_CHANNELS 32
-
-#if !defined(M_PI)
-#define M_PI 3.14159265358979323846
-#endif
-
-#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
-#define VOLUME 0.2
-
-float get_frequency(int channel_index)
-{
- return 220.0f * (channel_index+1);
-}
-
-/* store the phase of the generated waveform */
-typedef struct {
- int num_channels;
- float phase[MAX_NUM_CHANNELS];
- float sample_rate;
-} synth_state;
-
-synth_state* synth_create(int num_channels, float sample_rate)
-{
- synth_state* synth = (synth_state *) malloc(sizeof(synth_state));
- if (!synth)
- return NULL;
- for(int i=0;i < MAX_NUM_CHANNELS;++i)
- synth->phase[i] = 0.0f;
- synth->num_channels = num_channels;
- synth->sample_rate = sample_rate;
- return synth;
-}
-
-void synth_destroy(synth_state* synth)
-{
- free(synth);
-}
-
-void synth_run_float(synth_state* synth, float* audiobuffer, long nframes)
-{
- for(int c=0;c < synth->num_channels;++c) {
- float freq = get_frequency(c);
- float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
- for(long n=0;n < nframes;++n) {
- audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME;
- synth->phase[c] += phase_inc;
- }
- }
-}
-
-long data_cb_float(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
-{
- synth_state *synth = (synth_state *)user;
- synth_run_float(synth, (float*)outputbuffer, nframes);
- return nframes;
-}
-
-void synth_run_16bit(synth_state* synth, short* audiobuffer, long nframes)
-{
- for(int c=0;c < synth->num_channels;++c) {
- float freq = get_frequency(c);
- float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
- for(long n=0;n < nframes;++n) {
- audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME * 32767.0f;
- synth->phase[c] += phase_inc;
- }
- }
-}
-
-long data_cb_short(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
-{
- synth_state *synth = (synth_state *)user;
- synth_run_16bit(synth, (short*)outputbuffer, nframes);
- return nframes;
-}
-
-void state_cb(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
-{
-}
-
-/* Our android backends don't support float, only int16. */
-int supports_float32(const char* backend_id)
-{
- return (strcmp(backend_id, "opensl") != 0 &&
- strcmp(backend_id, "audiotrack") != 0);
-}
-
-/* The WASAPI backend only supports float. */
-int supports_int16(const char* backend_id)
-{
- return strcmp(backend_id, "wasapi") != 0;
-}
-
-/* Some backends don't have code to deal with more than mono or stereo. */
-int supports_channel_count(const char* backend_id, int nchannels)
-{
- return nchannels <= 2 ||
- (strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
-}
-
-int run_test(int num_channels, int sampling_rate, int is_float)
-{
- int r = CUBEB_OK;
-
- cubeb *ctx = NULL;
- synth_state* synth = NULL;
- cubeb_stream *stream = NULL;
- const char * backend_id = NULL;
-
- r = cubeb_init(&ctx, "Cubeb audio test: channels");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- goto cleanup;
- }
-
- backend_id = cubeb_get_backend_id(ctx);
-
- if ((is_float && !supports_float32(backend_id)) ||
- (!is_float && !supports_int16(backend_id)) ||
- !supports_channel_count(backend_id, num_channels)) {
- /* don't treat this as a test failure. */
- goto cleanup;
- }
-
- fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
-
- cubeb_stream_params params;
- params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
- params.rate = sampling_rate;
- params.channels = num_channels;
-
- synth = synth_create(params.channels, params.rate);
- if (synth == NULL) {
- fprintf(stderr, "Out of memory\n");
- goto cleanup;
- }
-
- r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
- 4096, is_float ? data_cb_float : data_cb_short, state_cb, synth);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
- goto cleanup;
- }
-
- cubeb_stream_start(stream);
- delay(200);
- cubeb_stream_stop(stream);
-
-cleanup:
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
- synth_destroy(synth);
-
- return r;
-}
-
-int run_panning_volume_test(int is_float)
-{
- int r = CUBEB_OK;
-
- cubeb *ctx = NULL;
- synth_state* synth = NULL;
- cubeb_stream *stream = NULL;
- const char * backend_id = NULL;
-
- r = cubeb_init(&ctx, "Cubeb audio test");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- goto cleanup;
- }
- backend_id = cubeb_get_backend_id(ctx);
-
- if ((is_float && !supports_float32(backend_id)) ||
- (!is_float && !supports_int16(backend_id))) {
- /* don't treat this as a test failure. */
- goto cleanup;
- }
-
- cubeb_stream_params params;
- params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
- params.rate = 44100;
- params.channels = 2;
-
- synth = synth_create(params.channels, params.rate);
- if (synth == NULL) {
- fprintf(stderr, "Out of memory\n");
- goto cleanup;
- }
-
- r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
- 4096, is_float ? data_cb_float : data_cb_short,
- state_cb, synth);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
- goto cleanup;
- }
-
- fprintf(stderr, "Testing: volume\n");
- for(int i=0;i <= 4; ++i)
- {
- fprintf(stderr, "Volume: %d%%\n", i*25);
-
- cubeb_stream_set_volume(stream, i/4.0f);
- cubeb_stream_start(stream);
- delay(400);
- cubeb_stream_stop(stream);
- delay(100);
- }
-
- fprintf(stderr, "Testing: panning\n");
- for(int i=-4;i <= 4; ++i)
- {
- fprintf(stderr, "Panning: %.2f%%\n", i/4.0f);
-
- cubeb_stream_set_panning(stream, i/4.0f);
- cubeb_stream_start(stream);
- delay(400);
- cubeb_stream_stop(stream);
- delay(100);
- }
-
-cleanup:
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
- synth_destroy(synth);
-
- return r;
-}
-
-void run_channel_rate_test()
-{
- int channel_values[] = {
- 1,
- 2,
- 3,
- 4,
- 6,
- };
-
- int freq_values[] = {
- 16000,
- 24000,
- 44100,
- 48000,
- };
-
- for(int j = 0; j < NELEMS(channel_values); ++j) {
- for(int i = 0; i < NELEMS(freq_values); ++i) {
- assert(channel_values[j] < MAX_NUM_CHANNELS);
- fprintf(stderr, "--------------------------\n");
- assert(run_test(channel_values[j], freq_values[i], 0) == CUBEB_OK);
- assert(run_test(channel_values[j], freq_values[i], 1) == CUBEB_OK);
- }
- }
-}
-
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_audio");
-#endif
-
- assert(run_panning_volume_test(0) == CUBEB_OK);
- assert(run_panning_volume_test(1) == CUBEB_OK);
- run_channel_rate_test();
-
- return CUBEB_OK;
-}
diff --git a/media/libcubeb/tests/test_devices.cpp b/media/libcubeb/tests/test_devices.cpp
deleted file mode 100644
index 9c502c0afd..0000000000
--- a/media/libcubeb/tests/test_devices.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb enumerate device test/example.
- * Prints out a list of devices enumerated. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "cubeb/cubeb.h"
-
-
-static void
-print_device_info(cubeb_device_info * info, FILE * f)
-{
- char devfmts[64] = "";
- const char * devtype, * devstate, * devdeffmt;
-
- switch (info->type) {
- case CUBEB_DEVICE_TYPE_INPUT:
- devtype = "input";
- break;
- case CUBEB_DEVICE_TYPE_OUTPUT:
- devtype = "output";
- break;
- case CUBEB_DEVICE_TYPE_UNKNOWN:
- default:
- devtype = "unknown?";
- break;
- };
-
- switch (info->state) {
- case CUBEB_DEVICE_STATE_DISABLED:
- devstate = "disabled";
- break;
- case CUBEB_DEVICE_STATE_UNPLUGGED:
- devstate = "unplugged";
- break;
- case CUBEB_DEVICE_STATE_ENABLED:
- devstate = "enabled";
- break;
- default:
- devstate = "unknown?";
- break;
- };
-
- switch (info->default_format) {
- case CUBEB_DEVICE_FMT_S16LE:
- devdeffmt = "S16LE";
- break;
- case CUBEB_DEVICE_FMT_S16BE:
- devdeffmt = "S16BE";
- break;
- case CUBEB_DEVICE_FMT_F32LE:
- devdeffmt = "F32LE";
- break;
- case CUBEB_DEVICE_FMT_F32BE:
- devdeffmt = "F32BE";
- break;
- default:
- devdeffmt = "unknown?";
- break;
- };
-
- if (info->format & CUBEB_DEVICE_FMT_S16LE)
- strcat(devfmts, " S16LE");
- if (info->format & CUBEB_DEVICE_FMT_S16BE)
- strcat(devfmts, " S16BE");
- if (info->format & CUBEB_DEVICE_FMT_F32LE)
- strcat(devfmts, " F32LE");
- if (info->format & CUBEB_DEVICE_FMT_F32BE)
- strcat(devfmts, " F32BE");
-
- fprintf(f,
- "dev: \"%s\"%s\n"
- "\tName: \"%s\"\n"
- "\tGroup: \"%s\"\n"
- "\tVendor: \"%s\"\n"
- "\tType: %s\n"
- "\tState: %s\n"
- "\tCh: %u\n"
- "\tFormat: %s (0x%x) (default: %s)\n"
- "\tRate: %u - %u (default: %u)\n"
- "\tLatency: lo %ums, hi %ums\n",
- info->device_id, info->preferred ? " (PREFERRED)" : "",
- info->friendly_name, info->group_id, info->vendor_name,
- devtype, devstate, info->max_channels,
- (devfmts[0] == ' ') ? &devfmts[1] : devfmts,
- (unsigned int)info->format, devdeffmt,
- info->min_rate, info->max_rate, info->default_rate,
- info->latency_lo_ms, info->latency_hi_ms);
-}
-
-static void
-print_device_collection(cubeb_device_collection * collection, FILE * f)
-{
- uint32_t i;
-
- for (i = 0; i < collection->count; i++)
- print_device_info(collection->device[i], f);
-}
-
-static int
-run_enumerate_devices(void)
-{
- int r = CUBEB_OK;
- cubeb * ctx = NULL;
- cubeb_device_collection * collection = NULL;
-
- r = cubeb_init(&ctx, "Cubeb audio test");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
-
- fprintf(stdout, "Enumerating input devices for backend %s\n",
- cubeb_get_backend_id(ctx));
-
- r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error enumerating devices %d\n", r);
- goto cleanup;
- }
-
- fprintf(stdout, "Found %u input devices\n", collection->count);
- print_device_collection(collection, stdout);
- cubeb_device_collection_destroy(collection);
-
- fprintf(stdout, "Enumerating output devices for backend %s\n",
- cubeb_get_backend_id(ctx));
-
- r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error enumerating devices %d\n", r);
- goto cleanup;
- }
-
- fprintf(stdout, "Found %u output devices\n", collection->count);
- print_device_collection(collection, stdout);
- cubeb_device_collection_destroy(collection);
-
-cleanup:
- cubeb_destroy(ctx);
- return r;
-}
-
-int main(int argc, char *argv[])
-{
- int ret;
-
- ret = run_enumerate_devices();
-
- return ret;
-}
diff --git a/media/libcubeb/tests/test_duplex.cpp b/media/libcubeb/tests/test_duplex.cpp
deleted file mode 100644
index c5d02d782c..0000000000
--- a/media/libcubeb/tests/test_duplex.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright © 2016 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb api/function test. Loops input back to output and check audio
- * is flowing. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#define _XOPEN_SOURCE 600
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <assert.h>
-
-#include "cubeb/cubeb.h"
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define SAMPLE_FREQUENCY 48000
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#define SILENT_SAMPLE 0.0f
-#else
-#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#define SILENT_SAMPLE 0
-#endif
-
-struct user_state
-{
- bool seen_noise;
-};
-
-
-
-long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
-{
- user_state * u = reinterpret_cast<user_state*>(user);
-#if (defined(_WIN32) || defined(__WIN32__))
- float *ib = (float *)inputbuffer;
- float *ob = (float *)outputbuffer;
-#else
- short *ib = (short *)inputbuffer;
- short *ob = (short *)outputbuffer;
-#endif
- bool seen_noise = false;
-
- if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
- return CUBEB_ERROR;
- }
-
- // Loop back: upmix the single input channel to the two output channels,
- // checking if there is noise in the process.
- long output_index = 0;
- for (long i = 0; i < nframes; i++) {
- if (ib[i] != SILENT_SAMPLE) {
- seen_noise = true;
- }
- ob[output_index] = ob[output_index + 1] = ib[i];
- output_index += 2;
- }
-
- u->seen_noise |= seen_noise;
-
- return nframes;
-}
-
-void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
-{
- if (stream == NULL)
- return;
-
- switch (state) {
- case CUBEB_STATE_STARTED:
- printf("stream started\n"); break;
- case CUBEB_STATE_STOPPED:
- printf("stream stopped\n"); break;
- case CUBEB_STATE_DRAINED:
- printf("stream drained\n"); break;
- default:
- printf("unknown stream state %d\n", state);
- }
-
- return;
-}
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_duplex");
-#endif
-
- cubeb *ctx;
- cubeb_stream *stream;
- cubeb_stream_params input_params;
- cubeb_stream_params output_params;
- int r;
- user_state stream_state = { false };
- uint32_t latency_frames = 0;
-
- r = cubeb_init(&ctx, "Cubeb duplex example");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
-
- /* This test needs an available input device, skip it if this host does not
- * have one. */
- if (!has_available_input_device(ctx)) {
- return 0;
- }
-
- /* typical user-case: mono input, stereo output, low latency. */
- input_params.format = STREAM_FORMAT;
- input_params.rate = 48000;
- input_params.channels = 1;
- output_params.format = STREAM_FORMAT;
- output_params.rate = 48000;
- output_params.channels = 2;
-
- r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
-
- if (r != CUBEB_OK) {
- fprintf(stderr, "Could not get minimal latency\n");
- return r;
- }
-
- r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
- NULL, &input_params, NULL, &output_params,
- latency_frames, data_cb, state_cb, &stream_state);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream\n");
- return r;
- }
-
- cubeb_stream_start(stream);
- delay(500);
- cubeb_stream_stop(stream);
-
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
-
- assert(stream_state.seen_noise);
-
- return CUBEB_OK;
-}
diff --git a/media/libcubeb/tests/test_latency.cpp b/media/libcubeb/tests/test_latency.cpp
deleted file mode 100644
index 0586b1a367..0000000000
--- a/media/libcubeb/tests/test_latency.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#include <stdlib.h>
-#include "cubeb/cubeb.h"
-#include <assert.h>
-#include <stdio.h>
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define LOG(msg) fprintf(stderr, "%s\n", msg);
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_latency");
-#endif
-
- cubeb * ctx = NULL;
- int r;
- uint32_t max_channels;
- uint32_t preferred_rate;
- uint32_t latency_frames;
-
- LOG("latency_test start");
- r = cubeb_init(&ctx, "Cubeb audio test");
- assert(r == CUBEB_OK && "Cubeb init failed.");
- LOG("cubeb_init ok");
-
- r = cubeb_get_max_channel_count(ctx, &max_channels);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
- if (r == CUBEB_OK) {
- assert(max_channels > 0 && "Invalid max channel count.");
- LOG("cubeb_get_max_channel_count ok");
- }
-
- r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
- if (r == CUBEB_OK) {
- assert(preferred_rate > 0 && "Invalid preferred sample rate.");
- LOG("cubeb_get_preferred_sample_rate ok");
- }
-
- cubeb_stream_params params = {
- CUBEB_SAMPLE_FLOAT32NE,
- preferred_rate,
- max_channels
- };
- r = cubeb_get_min_latency(ctx, params, &latency_frames);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
- if (r == CUBEB_OK) {
- assert(latency_frames > 0 && "Invalid minimal latency.");
- LOG("cubeb_get_min_latency ok");
- }
-
- cubeb_destroy(ctx);
- LOG("cubeb_destroy ok");
- return EXIT_SUCCESS;
-}
diff --git a/media/libcubeb/tests/test_record.cpp b/media/libcubeb/tests/test_record.cpp
deleted file mode 100644
index 681e1641e2..0000000000
--- a/media/libcubeb/tests/test_record.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright © 2016 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb api/function test. Record the mic and check there is sound. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#define _XOPEN_SOURCE 600
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <assert.h>
-
-#include "cubeb/cubeb.h"
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define SAMPLE_FREQUENCY 48000
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#else
-#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#endif
-
-struct user_state
-{
- bool seen_noise;
-};
-
-long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
-{
- user_state * u = reinterpret_cast<user_state*>(user);
-#if STREAM_FORMAT != CUBEB_SAMPLE_FLOAT32LE
- short *b = (short *)inputbuffer;
-#else
- float *b = (float *)inputbuffer;
-#endif
-
- if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
- return CUBEB_ERROR;
- }
-
- bool seen_noise = false;
- for (long i = 0; i < nframes; i++) {
- if (b[i] != 0.0) {
- seen_noise = true;
- }
- }
-
- u->seen_noise |= seen_noise;
-
- return nframes;
-}
-
-void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
-{
- if (stream == NULL)
- return;
-
- switch (state) {
- case CUBEB_STATE_STARTED:
- printf("stream started\n"); break;
- case CUBEB_STATE_STOPPED:
- printf("stream stopped\n"); break;
- case CUBEB_STATE_DRAINED:
- printf("stream drained\n"); break;
- default:
- printf("unknown stream state %d\n", state);
- }
-
- return;
-}
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_record");
-#endif
-
- cubeb *ctx;
- cubeb_stream *stream;
- cubeb_stream_params params;
- int r;
- user_state stream_state = { false };
-
- r = cubeb_init(&ctx, "Cubeb record example");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
-
- /* This test needs an available input device, skip it if this host does not
- * have one. */
- if (!has_available_input_device(ctx)) {
- return 0;
- }
-
- params.format = STREAM_FORMAT;
- params.rate = SAMPLE_FREQUENCY;
- params.channels = 1;
-
- r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
- 4096, data_cb, state_cb, &stream_state);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream\n");
- return r;
- }
-
- cubeb_stream_start(stream);
- delay(500);
- cubeb_stream_stop(stream);
-
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
-
- assert(stream_state.seen_noise);
-
- return CUBEB_OK;
-}
diff --git a/media/libcubeb/tests/test_resampler.cpp b/media/libcubeb/tests/test_resampler.cpp
deleted file mode 100644
index 7e62a35721..0000000000
--- a/media/libcubeb/tests/test_resampler.cpp
+++ /dev/null
@@ -1,554 +0,0 @@
-/*
- * Copyright © 2016 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif // NOMINMAX
-
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#include "cubeb_resampler_internal.h"
-#include <assert.h>
-#include <stdio.h>
-#include <algorithm>
-#include <iostream>
-
-/* Windows cmath USE_MATH_DEFINE thing... */
-const float PI = 3.14159265359f;
-
-/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
- * only part of the test suite is ran. */
-#ifdef THOROUGH_TESTING
-/* Some standard sample rates we're testing with. */
-const uint32_t sample_rates[] = {
- 8000,
- 16000,
- 32000,
- 44100,
- 48000,
- 88200,
- 96000,
- 192000
-};
-/* The maximum number of channels we're resampling. */
-const uint32_t max_channels = 2;
-/* The minimum an maximum number of milliseconds we're resampling for. This is
- * used to simulate the fact that the audio stream is resampled in chunks,
- * because audio is delivered using callbacks. */
-const uint32_t min_chunks = 10; /* ms */
-const uint32_t max_chunks = 30; /* ms */
-const uint32_t chunk_increment = 1;
-
-#else
-
-const uint32_t sample_rates[] = {
- 8000,
- 44100,
- 48000,
-};
-const uint32_t max_channels = 2;
-const uint32_t min_chunks = 10; /* ms */
-const uint32_t max_chunks = 30; /* ms */
-const uint32_t chunk_increment = 10;
-#endif
-
-#define DUMP_ARRAYS
-#ifdef DUMP_ARRAYS
-/**
- * Files produced by dump(...) can be converted to .wave files using:
- *
- * sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav
- *
- * for floating-point audio, or:
- *
- * sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav
- *
- * for 16bit integer audio.
- */
-
-/* Use the correct implementation of fopen, depending on the platform. */
-void fopen_portable(FILE ** f, const char * name, const char * mode)
-{
-#ifdef WIN32
- fopen_s(f, name, mode);
-#else
- *f = fopen(name, mode);
-#endif
-}
-
-template<typename T>
-void dump(const char * name, T * frames, size_t count)
-{
- FILE * file;
- fopen_portable(&file, name, "wb");
-
- if (!file) {
- fprintf(stderr, "error opening %s\n", name);
- return;
- }
-
- if (count != fwrite(frames, sizeof(T), count, file)) {
- fprintf(stderr, "error writing to %s\n", name);
- }
- fclose(file);
-}
-#else
-template<typename T>
-void dump(const char * name, T * frames, size_t count)
-{ }
-#endif
-
-// The more the ratio is far from 1, the more we accept a big error.
-float epsilon_tweak_ratio(float ratio)
-{
- return ratio >= 1 ? ratio : 1 / ratio;
-}
-
-// Epsilon values for comparing resampled data to expected data.
-// The bigger the resampling ratio is, the more lax we are about errors.
-template<typename T>
-T epsilon(float ratio);
-
-template<>
-float epsilon(float ratio) {
- return 0.08f * epsilon_tweak_ratio(ratio);
-}
-
-template<>
-int16_t epsilon(float ratio) {
- return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
-}
-
-void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
-{
- const size_t length_s = 2;
- const size_t rate = 44100;
- const size_t length_frames = rate * length_s;
- delay_line<float> delay(delay_frames, channels);
- auto_array<float> input;
- auto_array<float> output;
- uint32_t chunk_length = channels * chunk_ms * rate / 1000;
- uint32_t output_offset = 0;
- uint32_t channel = 0;
-
- /** Generate diracs every 100 frames, and check they are delayed. */
- input.push_silence(length_frames * channels);
- for (uint32_t i = 0; i < input.length() - 1; i+=100) {
- input.data()[i + channel] = 0.5;
- channel = (channel + 1) % channels;
- }
- dump("input.raw", input.data(), input.length());
- while(input.length()) {
- uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels);
- float * in = delay.input_buffer(to_pop / channels);
- input.pop(in, to_pop);
- delay.written(to_pop / channels);
- output.push_silence(to_pop);
- delay.output(output.data() + output_offset, to_pop / channels);
- output_offset += to_pop;
- }
-
- // Check the diracs have been shifted by `delay_frames` frames.
- for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) {
- assert(output.data()[i + channel + delay_frames * channels] == 0.5);
- channel = (channel + 1) % channels;
- }
-
- dump("output.raw", output.data(), output.length());
-}
-/**
- * This takes sine waves with a certain `channels` count, `source_rate`, and
- * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
- * Then a sample-wise comparison is performed against a sine wave generated at
- * the correct rate.
- */
-template<typename T>
-void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration)
-{
- size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
- float resampling_ratio = static_cast<float>(source_rate) / target_rate;
- cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3);
- auto_array<T> source(channels * source_rate * 10);
- auto_array<T> destination(channels * target_rate * 10);
- auto_array<T> expected(channels * target_rate * 10);
- uint32_t phase_index = 0;
- uint32_t offset = 0;
- const uint32_t buf_len = 2; /* seconds */
-
- // generate a sine wave in each channel, at the source sample rate
- source.push_silence(channels * source_rate * buf_len);
- while(offset != source.length()) {
- float p = phase_index++ / static_cast<float>(source_rate);
- for (uint32_t j = 0; j < channels; j++) {
- source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
- }
- }
-
- dump("input.raw", source.data(), source.length());
-
- expected.push_silence(channels * target_rate * buf_len);
- // generate a sine wave in each channel, at the target sample rate.
- // Insert silent samples at the beginning to account for the resampler latency.
- offset = resampler.latency() * channels;
- for (uint32_t i = 0; i < offset; i++) {
- expected.data()[i] = 0.0f;
- }
- phase_index = 0;
- while (offset != expected.length()) {
- float p = phase_index++ / static_cast<float>(target_rate);
- for (uint32_t j = 0; j < channels; j++) {
- expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
- }
- }
-
- dump("expected.raw", expected.data(), expected.length());
-
- // resample by chunk
- uint32_t write_offset = 0;
- destination.push_silence(channels * target_rate * buf_len);
- while (write_offset < destination.length())
- {
- size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio));
- uint32_t input_frames = resampler.input_needed_for_output(output_frames);
- resampler.input(source.data(), input_frames);
- source.pop(nullptr, input_frames * channels);
- resampler.output(destination.data() + write_offset,
- std::min(output_frames, (destination.length() - write_offset) / channels));
- write_offset += output_frames * channels;
- }
-
- dump("output.raw", destination.data(), expected.length());
-
- // compare, taking the latency into account
- bool fuzzy_equal = true;
- for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
- float diff = fabs(expected.data()[i] - destination.data()[i]);
- if (diff > epsilon<T>(resampling_ratio)) {
- fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff);
- fuzzy_equal = false;
- }
- }
- assert(fuzzy_equal);
-}
-
-template<typename T>
-cubeb_sample_format cubeb_format();
-
-template<>
-cubeb_sample_format cubeb_format<float>()
-{
- return CUBEB_SAMPLE_FLOAT32NE;
-}
-
-template<>
-cubeb_sample_format cubeb_format<short>()
-{
- return CUBEB_SAMPLE_S16NE;
-}
-
-struct osc_state {
- osc_state()
- : input_phase_index(0)
- , output_phase_index(0)
- , output_offset(0)
- , input_channels(0)
- , output_channels(0)
- {}
- uint32_t input_phase_index;
- uint32_t max_output_phase_index;
- uint32_t output_phase_index;
- uint32_t output_offset;
- uint32_t input_channels;
- uint32_t output_channels;
- uint32_t output_rate;
- uint32_t target_rate;
- auto_array<float> input;
- auto_array<float> output;
-};
-
-uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels,
- uint32_t frames, uint32_t initial_phase)
-{
- uint32_t offset = 0;
- for (uint32_t i = 0; i < frames; i++) {
- float p = initial_phase++ / static_cast<float>(rate);
- for (uint32_t j = 0; j < channels; j++) {
- buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
- }
- }
- return initial_phase;
-}
-
-long data_cb(cubeb_stream * /*stm*/, void * user_ptr,
- const void * input_buffer, void * output_buffer, long frame_count)
-{
- osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
- const float * in = reinterpret_cast<const float*>(input_buffer);
- float * out = reinterpret_cast<float*>(output_buffer);
-
-
- state->input.push(in, frame_count * state->input_channels);
-
- /* Check how much output frames we need to write */
- uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
- uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
- state->output_phase_index = fill_with_sine(out,
- state->target_rate,
- state->output_channels,
- to_write,
- state->output_phase_index);
-
- return to_write;
-}
-
-template<typename T>
-bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi)
-{
- uint32_t len = std::min(lhs.length(), rhs.length());
-
- for (uint32_t i = 0; i < len; i++) {
- if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
- std::cout << "not fuzzy equal at index: " << i
- << " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
- << " delta: " << fabs(lhs.at(i) - rhs.at(i))
- << " epsilon: "<< epsi << std::endl;
- return false;
- }
- }
- return true;
-}
-
-template<typename T>
-void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
- uint32_t input_rate, uint32_t output_rate,
- uint32_t target_rate, float chunk_duration)
-{
- cubeb_stream_params input_params;
- cubeb_stream_params output_params;
- osc_state state;
-
- input_params.format = output_params.format = cubeb_format<T>();
- state.input_channels = input_params.channels = input_channels;
- state.output_channels = output_params.channels = output_channels;
- input_params.rate = input_rate;
- state.output_rate = output_params.rate = output_rate;
- state.target_rate = target_rate;
- long got;
-
- cubeb_resampler * resampler =
- cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
- data_cb, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
-
- long latency = cubeb_resampler_latency(resampler);
-
- const uint32_t duration_s = 2;
- int32_t duration_frames = duration_s * target_rate;
- uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2;
- uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
- auto_array<float> input_buffer(input_channels * input_array_frame_count);
- auto_array<float> output_buffer(output_channels * output_array_frame_count);
- auto_array<float> expected_resampled_input(input_channels * duration_frames);
- auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s);
-
- state.max_output_phase_index = duration_s * target_rate;
-
- expected_resampled_input.push_silence(input_channels * duration_frames);
- expected_resampled_output.push_silence(output_channels * output_rate * duration_s);
-
- /* expected output is a 440Hz sine wave at 16kHz */
- fill_with_sine(expected_resampled_input.data() + latency,
- target_rate, input_channels, duration_frames - latency, 0);
- /* expected output is a 440Hz sine wave at 32kHz */
- fill_with_sine(expected_resampled_output.data() + latency,
- output_rate, output_channels, output_rate * duration_s - latency, 0);
-
-
- while (state.output_phase_index != state.max_output_phase_index) {
- uint32_t leftover_samples = input_buffer.length() * input_channels;
- input_buffer.reserve(input_array_frame_count);
- state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
- input_rate,
- input_channels,
- input_array_frame_count - leftover_samples,
- state.input_phase_index);
- long input_consumed = input_array_frame_count;
- input_buffer.set_length(input_array_frame_count);
-
- got = cubeb_resampler_fill(resampler,
- input_buffer.data(), &input_consumed,
- output_buffer.data(), output_array_frame_count);
-
- /* handle leftover input */
- if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
- input_buffer.pop(nullptr, input_consumed * input_channels);
- } else {
- input_buffer.clear();
- }
-
- state.output.push(output_buffer.data(), got * state.output_channels);
- }
-
- dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
- dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
- dump("input.raw", state.input.data(), state.input.length());
- dump("output.raw", state.output.data(), state.output.length());
-
- assert(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
- assert(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
-
- cubeb_resampler_destroy(resampler);
-}
-
-#define array_size(x) (sizeof(x) / sizeof(x[0]))
-
-void test_resamplers_one_way()
-{
- /* Test one way resamplers */
- for (uint32_t channels = 1; channels <= max_channels; channels++) {
- for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) {
- for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
- for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
- printf("one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n",
- channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration);
- test_resampler_one_way<float>(channels, sample_rates[source_rate],
- sample_rates[dest_rate], chunk_duration);
- }
- }
- }
- }
-}
-
-void test_resamplers_duplex()
-{
- /* Test duplex resamplers */
- for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
- for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
- for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
- for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
- for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
- for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
- printf("input channels:%d output_channels:%d input_rate:%d "
- "output_rate:%d target_rate:%d chunk_ms:%d\n",
- input_channels, output_channels,
- sample_rates[source_rate_input],
- sample_rates[source_rate_output],
- sample_rates[dest_rate],
- chunk_duration);
- test_resampler_duplex<float>(input_channels, output_channels,
- sample_rates[source_rate_input],
- sample_rates[source_rate_output],
- sample_rates[dest_rate],
- chunk_duration);
- }
- }
- }
- }
- }
- }
-}
-
-void test_delay_line()
-{
- for (uint32_t channel = 1; channel <= 2; channel++) {
- for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) {
- for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
- printf("channel: %d, delay_frames: %d, chunk_size: %d\n",
- channel, delay_frames, chunk_size);
- test_delay_lines(delay_frames, channel, chunk_size);
- }
- }
- }
-}
-
-long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
- const void * input_buffer,
- void * output_buffer, long frame_count)
-{
- assert(output_buffer);
- assert(!input_buffer);
- return frame_count;
-}
-
-void test_output_only_noop()
-{
- cubeb_stream_params output_params;
- int target_rate;
-
- output_params.rate = 44100;
- output_params.channels = 1;
- output_params.format = CUBEB_SAMPLE_FLOAT32NE;
- target_rate = output_params.rate;
-
- cubeb_resampler * resampler =
- cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
- test_output_only_noop_data_cb, nullptr,
- CUBEB_RESAMPLER_QUALITY_VOIP);
-
- const long out_frames = 128;
- float out_buffer[out_frames];
- long got;
-
- got = cubeb_resampler_fill(resampler, nullptr, nullptr,
- out_buffer, out_frames);
-
- assert(got == out_frames);
-
- cubeb_resampler_destroy(resampler);
-}
-
-long test_drain_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
- const void * input_buffer,
- void * output_buffer, long frame_count)
-{
- assert(output_buffer);
- assert(!input_buffer);
- return frame_count - 10;
-}
-
-void test_resampler_drain()
-{
- cubeb_stream_params output_params;
- int target_rate;
-
- output_params.rate = 44100;
- output_params.channels = 1;
- output_params.format = CUBEB_SAMPLE_FLOAT32NE;
- target_rate = 48000;
-
- cubeb_resampler * resampler =
- cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
- test_drain_data_cb, nullptr,
- CUBEB_RESAMPLER_QUALITY_VOIP);
-
- const long out_frames = 128;
- float out_buffer[out_frames];
- long got;
-
- do {
- got = cubeb_resampler_fill(resampler, nullptr, nullptr,
- out_buffer, out_frames);
- } while (got == out_frames);
-
- /* If the above is not an infinite loop, the drain was a success, just mark
- * this test as such. */
- assert(true);
-
- cubeb_resampler_destroy(resampler);
-}
-
-int main()
-{
- test_resamplers_one_way();
- test_delay_line();
- // This is disabled because the latency estimation in the resampler code is
- // slightly off so we can generate expected vectors.
- // test_resamplers_duplex();
- test_output_only_noop();
- test_resampler_drain();
-
- return 0;
-}
diff --git a/media/libcubeb/tests/test_utils.cpp b/media/libcubeb/tests/test_utils.cpp
deleted file mode 100644
index f52cd31963..0000000000
--- a/media/libcubeb/tests/test_utils.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-#include <cassert>
-#include "cubeb_utils.h"
-
-int test_auto_array()
-{
- auto_array<uint32_t> array;
- auto_array<uint32_t> array2(10);
- uint32_t a[10];
-
- assert(array2.length() == 0);
- assert(array2.capacity() == 10);
-
-
- for (uint32_t i = 0; i < 10; i++) {
- a[i] = i;
- }
-
- assert(array.capacity() == 0);
- assert(array.length() == 0);
-
- array.push(a, 10);
-
- assert(!array.reserve(9));
-
- for (uint32_t i = 0; i < 10; i++) {
- assert(array.data()[i] == i);
- }
-
- assert(array.capacity() == 10);
- assert(array.length() == 10);
-
- uint32_t b[10];
-
- array.pop(b, 5);
-
- assert(array.capacity() == 10);
- assert(array.length() == 5);
- for (uint32_t i = 0; i < 5; i++) {
- assert(b[i] == i);
- assert(array.data()[i] == 5 + i);
- }
- uint32_t* bb = b + 5;
- array.pop(bb, 5);
-
- assert(array.capacity() == 10);
- assert(array.length() == 0);
- for (uint32_t i = 0; i < 5; i++) {
- assert(bb[i] == 5 + i);
- }
-
- assert(!array.pop(nullptr, 1));
-
- array.push(a, 10);
- array.push(a, 10);
-
- for (uint32_t j = 0; j < 2; j++) {
- for (uint32_t i = 0; i < 10; i++) {
- assert(array.data()[10 * j + i] == i);
- }
- }
- assert(array.length() == 20);
- assert(array.capacity() == 20);
- array.pop(nullptr, 5);
-
- for (uint32_t i = 0; i < 5; i++) {
- assert(array.data()[i] == 5 + i);
- }
-
- assert(array.length() == 15);
- assert(array.capacity() == 20);
-
- return 0;
-}
-
-
-int main()
-{
- test_auto_array();
- return 0;
-}
diff --git a/media/libcubeb/unresampled-frames.patch b/media/libcubeb/unresampled-frames.patch
deleted file mode 100644
index 714f3d4bae..0000000000
--- a/media/libcubeb/unresampled-frames.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 46d12e9ae6fa9c233bc32812b13185ee7df8d3fd Mon Sep 17 00:00:00 2001
-From: Paul Adenot <paul@paul.cx>
-Date: Thu, 10 Nov 2016 06:20:16 +0100
-Subject: [PATCH] Prevent underflowing the number of input frames needed in
- input when resampling. (#188)
-
----
- src/cubeb_resampler_internal.h | 12 +++++++++---
- 1 file changed, 9 insertions(+), 3 deletions(-)
-
-diff --git a/src/cubeb_resampler_internal.h b/src/cubeb_resampler_internal.h
-index e165cc2..3c37a04 100644
---- a/src/cubeb_resampler_internal.h
-+++ b/src/cubeb_resampler_internal.h
-@@ -263,9 +263,15 @@ public:
- * number of output frames will be exactly equal. */
- uint32_t input_needed_for_output(uint32_t output_frame_count)
- {
-- return (uint32_t)ceilf((output_frame_count - samples_to_frames(resampling_out_buffer.length()))
-- * resampling_ratio);
--
-+ int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
-+ int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
-+ float input_frames_needed =
-+ (output_frame_count - unresampled_frames_left) * resampling_ratio
-+ - resampled_frames_left;
-+ if (input_frames_needed < 0) {
-+ return 0;
-+ }
-+ return (uint32_t)ceilf(input_frames_needed);
- }
-
- /** Returns a pointer to the input buffer, that contains empty space for at
---
-2.7.4
-
diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh
index 235b963e24..4229524b5e 100755
--- a/media/libcubeb/update.sh
+++ b/media/libcubeb/update.sh
@@ -5,41 +5,55 @@ cp $1/AUTHORS .
cp $1/LICENSE .
cp $1/README.md .
cp $1/include/cubeb/cubeb.h include
-cp $1/src/android/audiotrack_definitions.h src/android
cp $1/src/android/sles_definitions.h src/android
cp $1/src/cubeb-internal.h src
cp $1/src/cubeb-speex-resampler.h src
cp $1/src/cubeb.c src
+cp $1/src/cubeb_aaudio.cpp src
cp $1/src/cubeb_alsa.c src
-cp $1/src/cubeb_log.h src
-cp $1/src/cubeb_audiotrack.c src
+cp $1/src/cubeb_array_queue.h src
+cp $1/src/cubeb_assert.h src
cp $1/src/cubeb_audiounit.cpp src
-cp $1/src/cubeb_osx_run_loop.h src
cp $1/src/cubeb_jack.cpp src
+cp $1/src/cubeb_log.cpp src
+cp $1/src/cubeb_log.h src
+cp $1/src/cubeb_mixer.cpp src
+cp $1/src/cubeb_mixer.h src
cp $1/src/cubeb_opensl.c src
-cp $1/src/cubeb_panner.cpp src
-cp $1/src/cubeb_panner.h src
-cp $1/src/cubeb_pulse.c src
+cp $1/src/cubeb_android.h src
+cp $1/src/cubeb-jni.cpp src
+cp $1/src/cubeb-jni.h src
+cp $1/src/android/cubeb-output-latency.h src/android
+cp $1/src/android/cubeb_media_library.h src/android
+cp $1/src/cubeb_oss.c src
+cp $1/src/cubeb_osx_run_loop.h src
cp $1/src/cubeb_resampler.cpp src
cp $1/src/cubeb_resampler.h src
cp $1/src/cubeb_resampler_internal.h src
cp $1/src/cubeb_ring_array.h src
+cp $1/src/cubeb_ringbuffer.h src
cp $1/src/cubeb_sndio.c src
+cp $1/src/cubeb_strings.c src
+cp $1/src/cubeb_strings.h src
+cp $1/src/cubeb_sun.c src
cp $1/src/cubeb_utils.h src
+cp $1/src/cubeb_utils.cpp src
cp $1/src/cubeb_utils_unix.h src
cp $1/src/cubeb_utils_win.h src
cp $1/src/cubeb_wasapi.cpp src
-cp $1/src/cubeb_winmm.c src
-cp $1/test/common.h tests/common.h
-cp $1/test/test_audio.cpp tests/test_audio.cpp
-#cp $1/test/test_devices.c tests/test_devices.cpp
-cp $1/test/test_duplex.cpp tests/test_duplex.cpp
-cp $1/test/test_latency.cpp tests/test_latency.cpp
-cp $1/test/test_record.cpp tests/test_record.cpp
-cp $1/test/test_resampler.cpp tests/test_resampler.cpp
-cp $1/test/test_sanity.cpp tests/test_sanity.cpp
-cp $1/test/test_tone.cpp tests/test_tone.cpp
-cp $1/test/test_utils.cpp tests/test_utils.cpp
+cp $1/test/common.h gtest
+cp $1/test/test_audio.cpp gtest
+cp $1/test/test_devices.cpp gtest
+cp $1/test/test_duplex.cpp gtest
+cp $1/test/test_latency.cpp gtest
+cp $1/test/test_loopback.cpp gtest
+cp $1/test/test_overload_callback.cpp gtest
+cp $1/test/test_record.cpp gtest
+cp $1/test/test_resampler.cpp gtest
+cp $1/test/test_ring_array.cpp gtest
+cp $1/test/test_sanity.cpp gtest
+cp $1/test/test_tone.cpp gtest
+cp $1/test/test_utils.cpp gtest
if [ -d $1/.git ]; then
rev=$(cd $1 && git rev-parse --verify HEAD)
@@ -57,33 +71,3 @@ if [ -n "$rev" ]; then
else
echo "Remember to update README_MOZILLA with the version details."
fi
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./unresampled-frames.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./bug1302231_emergency_bailout.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./osx-linearize-operations.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./prevent-double-free.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./bug1292803_pulse_assert.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./uplift-wasapi-part-to-beta.patch
-
-echo "Applying a patch on top of $version"
-patch -p3 < ./fix-crashes.patch
-
-echo "Applying a patch on top of $version"
-patch -p3 < ./uplift-part-of-f07ee6d-esr52.patch
-
-echo "Applying a patch on top of $version"
-patch -p3 < ./uplift-system-listener-patch.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./uplift-patch-7a4c711.patch
diff --git a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch b/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch
deleted file mode 100644
index 0eb1aca82b..0000000000
--- a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch
+++ /dev/null
@@ -1,167 +0,0 @@
-# HG changeset patch
-# User Alex Chronopoulos <achronop@gmail.com>
-# Parent 00c051cd38c7a6cb3178fd0890d52056f83abfdc
-Bug 1345049 - Uplift part of cubeb upstream f07ee6d to esr52. r=padenot. a=xxxxxx
-
-diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
---- a/media/libcubeb/src/cubeb_audiounit.cpp
-+++ b/media/libcubeb/src/cubeb_audiounit.cpp
-@@ -590,33 +590,43 @@ audiounit_get_input_device_id(AudioDevic
- device_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
- }
-
-+static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
-+static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-+
- static int
- audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
- {
-+ auto_lock context_lock(stm->context->mutex);
- if (is_started) {
- audiounit_stream_stop_internal(stm);
- }
-
- {
- auto_lock lock(stm->mutex);
-+ float volume = 0.0;
-+ int vol_rv = audiounit_stream_get_volume(stm, &volume);
-
- audiounit_close_stream(stm);
-
- if (audiounit_setup_stream(stm) != CUBEB_OK) {
- LOG("(%p) Stream reinit failed.", stm);
- return CUBEB_ERROR;
- }
-
-+ if (vol_rv == CUBEB_OK) {
-+ audiounit_stream_set_volume(stm, volume);
-+ }
-+
- // Reset input frames to force new stream pre-buffer
- // silence if needed, check `is_extra_input_needed()`
- stm->frames_read = 0;
-
- // If the stream was running, start it again.
- if (is_started) {
- audiounit_stream_start_internal(stm);
- }
-@@ -1007,20 +1017,22 @@ audiounit_get_preferred_sample_rate(cube
- static OSStatus audiounit_remove_device_listener(cubeb * context);
-
- static void
- audiounit_destroy(cubeb * ctx)
- {
- // Disabling this assert for bug 1083664 -- we seem to leak a stream
- // assert(ctx->active_streams == 0);
-
-- /* Unregister the callback if necessary. */
-- if(ctx->collection_changed_callback) {
-+ {
- auto_lock lock(ctx->mutex);
-- audiounit_remove_device_listener(ctx);
-+ /* Unregister the callback if necessary. */
-+ if(ctx->collection_changed_callback) {
-+ audiounit_remove_device_listener(ctx);
-+ }
- }
-
- ctx->~cubeb();
- free(ctx);
- }
-
- static void audiounit_stream_destroy(cubeb_stream * stm);
-
-@@ -1861,17 +1873,17 @@ audiounit_close_stream(cubeb_stream *stm
- cubeb_resampler_destroy(stm->resampler);
- }
-
- static void
- audiounit_stream_destroy(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-- auto_lock context_locl(stm->context->mutex);
-+ auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
- }
-
- #if !TARGET_OS_IPHONE
-@@ -1905,17 +1917,17 @@ audiounit_stream_start_internal(cubeb_st
- }
-
- static int
- audiounit_stream_start(cubeb_stream * stm)
- {
- stm->shutdown = false;
- stm->draining = false;
-
-- auto_lock context_locl(stm->context->mutex);
-+ auto_lock context_lock(stm->context->mutex);
- audiounit_stream_start_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- LOG("Cubeb stream (%p) started successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -1933,17 +1945,17 @@ audiounit_stream_stop_internal(cubeb_str
- }
- }
-
- static int
- audiounit_stream_stop(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-- auto_lock context_locl(stm->context->mutex);
-+ auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- LOG("Cubeb stream (%p) stopped successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -2030,16 +2042,31 @@ audiounit_stream_get_latency(cubeb_strea
- }
-
- *latency = stm->hw_latency_frames + stm->current_latency_frames;
-
- return CUBEB_OK;
- #endif
- }
-
-+static int
-+audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
-+{
-+ assert(stm->output_unit);
-+ OSStatus r = AudioUnitGetParameter(stm->output_unit,
-+ kHALOutputParam_Volume,
-+ kAudioUnitScope_Global,
-+ 0, volume);
-+ if (r != noErr) {
-+ LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);
-+ return CUBEB_ERROR;
-+ }
-+ return CUBEB_OK;
-+}
-+
- int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
- {
- OSStatus r;
-
- r = AudioUnitSetParameter(stm->output_unit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0, volume, 0);
diff --git a/media/libcubeb/uplift-patch-7a4c711.patch b/media/libcubeb/uplift-patch-7a4c711.patch
deleted file mode 100644
index 188bdf8b2f..0000000000
--- a/media/libcubeb/uplift-patch-7a4c711.patch
+++ /dev/null
@@ -1,69 +0,0 @@
-From 7a4c711d6e998b451326a0a87dd2e9dab5a257ef Mon Sep 17 00:00:00 2001
-From: Alex Chronopoulos <achronop@gmail.com>
-Date: Mon, 15 May 2017 16:47:26 +0300
-Subject: [PATCH] audiounit: synchronize destroy stream and reinit (Bug
- 1361657)
-
----
- src/cubeb_audiounit.cpp | 22 +++++++++++++++-------
- 1 file changed, 15 insertions(+), 7 deletions(-)
-
-diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
-index 8aa40d54..331bc735 100644
---- a/src/cubeb_audiounit.cpp
-+++ b/src/cubeb_audiounit.cpp
-@@ -603,6 +603,7 @@ audiounit_get_input_device_id(AudioDeviceID * device_id)
-
- static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
- static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-+static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
-
- static int
- audiounit_reinit_stream(cubeb_stream * stm)
-@@ -612,6 +613,11 @@ audiounit_reinit_stream(cubeb_stream * stm)
- audiounit_stream_stop_internal(stm);
- }
-
-+ int r = audiounit_uninstall_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- {
- auto_lock lock(stm->mutex);
- float volume = 0.0;
-@@ -2516,11 +2522,6 @@ audiounit_close_stream(cubeb_stream *stm)
- {
- stm->mutex.assert_current_thread_owns();
-
-- int r = audiounit_uninstall_device_changed_callback(stm);
-- if (r != CUBEB_OK) {
-- LOG("(%p) Could not uninstall the device changed callback", stm);
-- }
--
- if (stm->input_unit) {
- AudioUnitUninitialize(stm->input_unit);
- AudioComponentInstanceDispose(stm->input_unit);
-@@ -2554,13 +2555,20 @@ audiounit_stream_destroy(cubeb_stream * stm)
- LOG("(%p) Could not uninstall the device changed callback", stm);
- }
-
-+ r = audiounit_uninstall_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
-- {
-+ // Execute close in serial queue to avoid collision
-+ // with reinit when un/plug devices
-+ dispatch_sync(stm->context->serial_queue, ^() {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
-- }
-+ });
-
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
diff --git a/media/libcubeb/uplift-system-listener-patch.patch b/media/libcubeb/uplift-system-listener-patch.patch
deleted file mode 100644
index 5064d7fb35..0000000000
--- a/media/libcubeb/uplift-system-listener-patch.patch
+++ /dev/null
@@ -1,402 +0,0 @@
-diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
---- a/media/libcubeb/src/cubeb_audiounit.cpp
-+++ b/media/libcubeb/src/cubeb_audiounit.cpp
-@@ -594,20 +594,20 @@ audiounit_get_input_device_id(AudioDevic
-
- return CUBEB_OK;
- }
-
- static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
- static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-
- static int
--audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
-+audiounit_reinit_stream(cubeb_stream * stm)
- {
- auto_lock context_lock(stm->context->mutex);
-- if (is_started) {
-+ if (!stm->shutdown) {
- audiounit_stream_stop_internal(stm);
- }
-
- {
- auto_lock lock(stm->mutex);
- float volume = 0.0;
- int vol_rv = audiounit_stream_get_volume(stm, &volume);
-
-@@ -622,32 +622,30 @@ audiounit_reinit_stream(cubeb_stream * s
- audiounit_stream_set_volume(stm, volume);
- }
-
- // Reset input frames to force new stream pre-buffer
- // silence if needed, check `is_extra_input_needed()`
- stm->frames_read = 0;
-
- // If the stream was running, start it again.
-- if (is_started) {
-+ if (!stm->shutdown) {
- audiounit_stream_start_internal(stm);
- }
- }
- return CUBEB_OK;
- }
-
- static OSStatus
- audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
- const AudioObjectPropertyAddress * addresses,
- void * user)
- {
- cubeb_stream * stm = (cubeb_stream*) user;
- stm->switching_device = true;
-- // Note if the stream was running or not
-- bool was_running = !stm->shutdown;
-
- LOG("(%p) Audio device changed, %d events.", stm, address_count);
- for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
- // Allow restart to choose the new default
- stm->output_device = nullptr;
-@@ -666,19 +664,20 @@ audiounit_property_listener_callback(Aud
- if (stm->is_default_input) {
- LOG("It's the default input device, ignore the event");
- return noErr;
- }
- // Allow restart to choose the new default. Event register only for input.
- stm->input_device = nullptr;
- }
- break;
-- case kAudioDevicePropertyDataSource:
-- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
-- break;
-+ case kAudioDevicePropertyDataSource: {
-+ LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
-+ return noErr;
-+ }
- }
- }
-
- for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice:
- case kAudioHardwarePropertyDefaultInputDevice:
- case kAudioDevicePropertyDeviceIsAlive:
-@@ -691,17 +690,17 @@ audiounit_property_listener_callback(Aud
- break;
- }
- }
- }
-
- // Use a new thread, through the queue, to avoid deadlock when calling
- // Get/SetProperties method from inside notify callback
- dispatch_async(stm->context->serial_queue, ^() {
-- if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
-+ if (audiounit_reinit_stream(stm) != CUBEB_OK) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- LOG("(%p) Could not reopen the stream after switching.", stm);
- }
- stm->switching_device = false;
- });
-
- return noErr;
- }
-@@ -752,27 +751,16 @@ audiounit_install_device_changed_callbac
- }
-
- r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
- }
--
-- /* This event will notify us when the default audio device changes,
-- * for example when the user plugs in a USB headset and the system chooses it
-- * automatically as the default, or when another device is chosen in the
-- * dropdown list. */
-- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice", r);
-- return CUBEB_ERROR;
-- }
- }
-
- if (stm->input_unit) {
- /* This event will notify us when the data source on the input device changes. */
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
-@@ -780,78 +768,112 @@ audiounit_install_device_changed_callbac
-
- r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
- }
-
-- /* This event will notify us when the default input device changes. */
-- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice", r);
-- return CUBEB_ERROR;
-- }
--
- /* Event to notify when the input is going away. */
- AudioDeviceID dev = stm->input_device ? reinterpret_cast<intptr_t>(stm->input_device) :
- audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
- r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
- return CUBEB_ERROR;
- }
- }
-
- return CUBEB_OK;
- }
-
- static int
-+audiounit_install_system_changed_callback(cubeb_stream * stm)
-+{
-+ OSStatus r;
-+
-+ if (stm->output_unit) {
-+ /* This event will notify us when the default audio device changes,
-+ * for example when the user plugs in a USB headset and the system chooses it
-+ * automatically as the default, or when another device is chosen in the
-+ * dropdown list. */
-+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ if (r != noErr) {
-+ LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r);
-+ return CUBEB_ERROR;
-+ }
-+ }
-+
-+ if (stm->input_unit) {
-+ /* This event will notify us when the default input device changes. */
-+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ if (r != noErr) {
-+ LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r);
-+ return CUBEB_ERROR;
-+ }
-+ }
-+
-+ return CUBEB_OK;
-+}
-+
-+static int
- audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
- {
- OSStatus r;
-
- if (stm->output_unit) {
- AudioDeviceID output_dev_id;
- r = audiounit_get_output_device_id(&output_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
--
-- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-- if (r != noErr) {
-- return CUBEB_ERROR;
-- }
- }
-
- if (stm->input_unit) {
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
--
-+ }
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
-+{
-+ OSStatus r;
-+
-+ if (stm->output_unit) {
-+ r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ if (r != noErr) {
-+ return CUBEB_ERROR;
-+ }
-+ }
-+
-+ if (stm->input_unit) {
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
- }
- return CUBEB_OK;
- }
-
- /* Get the acceptable buffer size (in frames) that this device can work with. */
-@@ -1764,16 +1786,22 @@ audiounit_setup_stream(cubeb_stream * st
-
- if (stm->input_unit && stm->output_unit) {
- // According to the I/O hardware rate it is expected a specific pattern of callbacks
- // for example is input is 44100 and output is 48000 we expected no more than 2
- // out callback in a row.
- stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
- }
-
-+ r = audiounit_install_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not install the device change callback.", stm);
-+ return r;
-+ }
-+
- return CUBEB_OK;
- }
-
- static int
- audiounit_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * /* stream_name */,
- cubeb_devid input_device,
-@@ -1838,31 +1866,37 @@ audiounit_stream_init(cubeb * context,
- }
-
- if (r != CUBEB_OK) {
- LOG("(%p) Could not setup the audiounit stream.", stm);
- audiounit_stream_destroy(stm);
- return r;
- }
-
-- r = audiounit_install_device_changed_callback(stm);
-+ r = audiounit_install_system_changed_callback(stm);
- if (r != CUBEB_OK) {
- LOG("(%p) Could not install the device change callback.", stm);
- return r;
- }
-
- *stream = stm;
- LOG("Cubeb stream (%p) init successful.", stm);
- return CUBEB_OK;
- }
-
- static void
- audiounit_close_stream(cubeb_stream *stm)
- {
- stm->mutex.assert_current_thread_owns();
-+
-+ int r = audiounit_uninstall_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- if (stm->input_unit) {
- AudioUnitUninitialize(stm->input_unit);
- AudioComponentInstanceDispose(stm->input_unit);
- }
-
- audiounit_destroy_input_linear_buffer(stm);
-
- if (stm->output_unit) {
-@@ -1873,31 +1907,29 @@ audiounit_close_stream(cubeb_stream *stm
- cubeb_resampler_destroy(stm->resampler);
- }
-
- static void
- audiounit_stream_destroy(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-+ int r = audiounit_uninstall_system_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
- }
-
--#if !TARGET_OS_IPHONE
-- int r = audiounit_uninstall_device_changed_callback(stm);
-- if (r != CUBEB_OK) {
-- LOG("(%p) Could not uninstall the device changed callback", stm);
-- }
--#endif
--
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
-
- LOG("Cubeb stream (%p) destroyed successful.", stm);
-
- stm->~cubeb_stream();
- free(stm);
- }
-@@ -1914,20 +1946,20 @@ audiounit_stream_start_internal(cubeb_st
- r = AudioOutputUnitStart(stm->output_unit);
- assert(r == 0);
- }
- }
-
- static int
- audiounit_stream_start(cubeb_stream * stm)
- {
-+ auto_lock context_lock(stm->context->mutex);
- stm->shutdown = false;
- stm->draining = false;
-
-- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_start_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- LOG("Cubeb stream (%p) started successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -1943,19 +1975,19 @@ audiounit_stream_stop_internal(cubeb_str
- r = AudioOutputUnitStop(stm->output_unit);
- assert(r == 0);
- }
- }
-
- static int
- audiounit_stream_stop(cubeb_stream * stm)
- {
-+ auto_lock context_lock(stm->context->mutex);
- stm->shutdown = true;
-
-- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- LOG("Cubeb stream (%p) stopped successfully.", stm);
- return CUBEB_OK;
- }
-
diff --git a/media/libcubeb/uplift-wasapi-part-to-beta.patch b/media/libcubeb/uplift-wasapi-part-to-beta.patch
deleted file mode 100644
index 90e827830e..0000000000
--- a/media/libcubeb/uplift-wasapi-part-to-beta.patch
+++ /dev/null
@@ -1,118 +0,0 @@
-# HG changeset patch
-# User Alex Chronopoulos <achronop@gmail.com>
-# Parent b7bb31e5a851d6f8e142c39dc077e3774719eced
-Bug 1342363 - Uplift wasapi fixes in Beta. r?kinetik
-
-diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
---- a/media/libcubeb/src/cubeb_wasapi.cpp
-+++ b/media/libcubeb/src/cubeb_wasapi.cpp
-@@ -807,16 +807,20 @@ wasapi_stream_render_loop(LPVOID stream)
- maybe WebRTC. */
- mmcss_handle =
- stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
- if (!mmcss_handle) {
- /* This is not fatal, but we might glitch under heavy load. */
- LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
- }
-
-+ // This has already been nulled out, simply exit.
-+ if (!emergency_bailout) {
-+ is_playing = false;
-+ }
-
- /* WaitForMultipleObjects timeout can trigger in cases where we don't want to
- treat it as a timeout, such as across a system sleep/wake cycle. Trigger
- the timeout error handling only when the timeout_limit is reached, which is
- reset on each successful loop. */
- unsigned timeout_count = 0;
- const unsigned timeout_limit = 5;
- while (is_playing) {
-@@ -1158,22 +1162,26 @@ bool stop_and_join_render_thread(cubeb_s
-
- /* Wait five seconds for the rendering thread to return. It's supposed to
- * check its event loop very often, five seconds is rather conservative. */
- DWORD r = WaitForSingleObject(stm->thread, 5000);
- if (r == WAIT_TIMEOUT) {
- /* Something weird happened, leak the thread and continue the shutdown
- * process. */
- *(stm->emergency_bailout) = true;
-+ // We give the ownership to the rendering thread.
-+ stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread timed out,"
- " leaking the thread: %d", GetLastError());
- rv = false;
- }
- if (r == WAIT_FAILED) {
- *(stm->emergency_bailout) = true;
-+ // We give the ownership to the rendering thread.
-+ stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
- rv = false;
- }
-
-
- // Only attempts to close and null out the thread and event if the
- // WaitForSingleObject above succeeded, so that calling this function again
- // attemps to clean up the thread and event each time.
-@@ -1798,19 +1806,16 @@ void wasapi_stream_destroy(cubeb_stream
- XASSERT(stm);
-
- // Only free stm->emergency_bailout if we could not join the thread.
- // If we could not join the thread, stm->emergency_bailout is true
- // and is still alive until the thread wakes up and exits cleanly.
- if (stop_and_join_render_thread(stm)) {
- delete stm->emergency_bailout.load();
- stm->emergency_bailout = nullptr;
-- } else {
-- // If we're leaking, it must be that this is true.
-- assert(*(stm->emergency_bailout));
- }
-
- unregister_notification_client(stm);
-
- SafeRelease(stm->reconfigure_event);
- SafeRelease(stm->refill_event);
- SafeRelease(stm->input_available_event);
-
-@@ -1865,21 +1870,21 @@ int stream_start_one_side(cubeb_stream *
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
- }
-
- int wasapi_stream_start(cubeb_stream * stm)
- {
-+ auto_lock lock(stm->stream_reset_lock);
-+
- XASSERT(stm && !stm->thread && !stm->shutdown_event);
- XASSERT(stm->output_client || stm->input_client);
-
-- auto_lock lock(stm->stream_reset_lock);
--
- stm->emergency_bailout = new std::atomic<bool>(false);
-
- if (stm->output_client) {
- int rv = stream_start_one_side(stm, OUTPUT);
- if (rv != CUBEB_OK) {
- return rv;
- }
- }
-@@ -1932,16 +1937,17 @@ int wasapi_stream_stop(cubeb_stream * st
- }
- }
-
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- }
-
- if (stop_and_join_render_thread(stm)) {
-+ // This is null if we've given the pointer to the other thread
- if (stm->emergency_bailout.load()) {
- delete stm->emergency_bailout.load();
- stm->emergency_bailout = nullptr;
- }
- }
-
- return CUBEB_OK;
- }