summaryrefslogtreecommitdiff
path: root/media/libcubeb
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /media/libcubeb
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'media/libcubeb')
-rw-r--r--media/libcubeb/AUTHORS15
-rw-r--r--media/libcubeb/LICENSE13
-rw-r--r--media/libcubeb/README.md6
-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/include/cubeb-stdint.h5
-rw-r--r--media/libcubeb/include/cubeb.h581
-rw-r--r--media/libcubeb/include/cubeb_export.h5
-rw-r--r--media/libcubeb/include/moz.build11
-rw-r--r--media/libcubeb/moz.build9
-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/sles_definitions.h77
-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-sles.h26
-rw-r--r--media/libcubeb/src/cubeb-speex-resampler.h1
-rw-r--r--media/libcubeb/src/cubeb.c568
-rw-r--r--media/libcubeb/src/cubeb_alsa.c1149
-rw-r--r--media/libcubeb/src/cubeb_audiotrack.c438
-rw-r--r--media/libcubeb/src/cubeb_audiounit.cpp2734
-rw-r--r--media/libcubeb/src/cubeb_jack.cpp1047
-rw-r--r--media/libcubeb/src/cubeb_log.h37
-rw-r--r--media/libcubeb/src/cubeb_opensl.c889
-rw-r--r--media/libcubeb/src/cubeb_osx_run_loop.c11
-rw-r--r--media/libcubeb/src/cubeb_osx_run_loop.h22
-rw-r--r--media/libcubeb/src/cubeb_panner.cpp60
-rw-r--r--media/libcubeb/src/cubeb_panner.h28
-rw-r--r--media/libcubeb/src/cubeb_pulse.c1385
-rw-r--r--media/libcubeb/src/cubeb_resampler.cpp299
-rw-r--r--media/libcubeb/src/cubeb_resampler.h78
-rw-r--r--media/libcubeb/src/cubeb_resampler_internal.h551
-rw-r--r--media/libcubeb/src/cubeb_ring_array.h159
-rw-r--r--media/libcubeb/src/cubeb_sndio.c383
-rw-r--r--media/libcubeb/src/cubeb_utils.h215
-rw-r--r--media/libcubeb/src/cubeb_utils_unix.h89
-rw-r--r--media/libcubeb/src/cubeb_utils_win.h71
-rw-r--r--media/libcubeb/src/cubeb_wasapi.cpp2311
-rw-r--r--media/libcubeb/src/cubeb_winmm.c1067
-rw-r--r--media/libcubeb/src/moz.build98
-rw-r--r--media/libcubeb/tests/common.h61
-rw-r--r--media/libcubeb/tests/moz.build80
-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_sanity.cpp676
-rw-r--r--media/libcubeb/tests/test_tone.cpp149
-rw-r--r--media/libcubeb/tests/test_utils.cpp80
-rw-r--r--media/libcubeb/unresampled-frames.patch36
-rwxr-xr-xmedia/libcubeb/update.sh89
-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
60 files changed, 19229 insertions, 0 deletions
diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS
new file mode 100644
index 0000000000..0fde65baad
--- /dev/null
+++ b/media/libcubeb/AUTHORS
@@ -0,0 +1,15 @@
+Matthew Gregan <kinetik@flim.org>
+Alexandre Ratchov <alex@caoua.org>
+Michael Wu <mwu@mozilla.com>
+Paul Adenot <paul@paul.cx>
+David Richards <drichards@mozilla.com>
+Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
+KO Myung-Hun <komh@chollian.net>
+Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+Alex Chronopoulos <achronop@gmail.com>
+Jan Beich <jbeich@FreeBSD.org>
+Vito Caputo <vito.caputo@coreos.com>
+Landry Breuil <landry@openbsd.org>
+Jacek Caban <jacek@codeweavers.com>
+Paul Hancock <Paul.Hancock.17041993@live.com>
+Ted Mielczarek <ted@mielczarek.org>
diff --git a/media/libcubeb/LICENSE b/media/libcubeb/LICENSE
new file mode 100644
index 0000000000..fffc9dc405
--- /dev/null
+++ b/media/libcubeb/LICENSE
@@ -0,0 +1,13 @@
+Copyright © 2011 Mozilla Foundation
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/media/libcubeb/README.md b/media/libcubeb/README.md
new file mode 100644
index 0000000000..d26b3b645b
--- /dev/null
+++ b/media/libcubeb/README.md
@@ -0,0 +1,6 @@
+[![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)
+
+See INSTALL.md for build instructions.
+
+Licensed under an ISC-style license. See LICENSE for details.
diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA
new file mode 100644
index 0000000000..c7f5e9c638
--- /dev/null
+++ b/media/libcubeb/README_MOZILLA
@@ -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
+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
new file mode 100644
index 0000000000..8dee88777b
--- /dev/null
+++ b/media/libcubeb/bug1292803_pulse_assert.patch
@@ -0,0 +1,46 @@
+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
new file mode 100644
index 0000000000..82152c23f6
--- /dev/null
+++ b/media/libcubeb/bug1302231_emergency_bailout.patch
@@ -0,0 +1,140 @@
+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
new file mode 100644
index 0000000000..b23501fcfb
--- /dev/null
+++ b/media/libcubeb/fix-crashes.patch
@@ -0,0 +1,71 @@
+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/include/cubeb-stdint.h b/media/libcubeb/include/cubeb-stdint.h
new file mode 100644
index 0000000000..4622320729
--- /dev/null
+++ b/media/libcubeb/include/cubeb-stdint.h
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdint.h>
diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h
new file mode 100644
index 0000000000..449b39c55e
--- /dev/null
+++ b/media/libcubeb/include/cubeb.h
@@ -0,0 +1,581 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
+#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
+
+#include <stdint.h>
+#include "cubeb_export.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** @mainpage
+
+ @section intro Introduction
+
+ This is the documentation for the <tt>libcubeb</tt> C API.
+ <tt>libcubeb</tt> is a callback-based audio API library allowing the
+ authoring of portable multiplatform audio playback and recording.
+
+ @section example Example code
+
+ This example shows how to create a duplex stream that pipes the microphone
+ to the speakers, with minimal latency and the proper sample-rate for the
+ platform.
+
+ @code
+ cubeb * app_ctx;
+ cubeb_init(&app_ctx, "Example Application");
+ int rv;
+ int rate;
+ int 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);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get preferred sample-rate");
+ return rv;
+ }
+
+ cubeb_stream_params output_params;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = rate;
+ output_params.channels = 2;
+
+ cubeb_stream_params input_params;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = rate;
+ output_params.channels = 1;
+
+ cubeb_stream * stm;
+ rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
+ NULL, input_params,
+ NULL, output_params,
+ latency_frames,
+ data_cb, state_cb,
+ NULL);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not open the stream");
+ return rv;
+ }
+
+ rv = cubeb_stream_start(stm);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not start the stream");
+ return rv;
+ }
+ for (;;) {
+ cubeb_stream_get_position(stm, &ts);
+ printf("time=%llu\n", ts);
+ sleep(1);
+ }
+ rv = cubeb_stream_stop(stm);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not stop the stream");
+ return rv;
+ }
+
+ cubeb_stream_destroy(stm);
+ cubeb_destroy(app_ctx);
+ @endcode
+
+ @code
+ long data_cb(cubeb_stream * stm, void * user,
+ void * input_buffer, void * output_buffer, long nframes)
+ {
+ 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];
+ }
+ }
+ return nframes;
+ }
+ @endcode
+
+ @code
+ void state_cb(cubeb_stream * stm, void * user, cubeb_state state)
+ {
+ printf("state=%d\n", state);
+ }
+ @endcode
+*/
+
+/** @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. */
+
+/** Sample format enumeration. */
+typedef enum {
+ /**< Little endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16LE,
+ /**< Big endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16BE,
+ /**< Little endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32LE,
+ /**< Big endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32BE,
+#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
+ /**< Native endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16NE = CUBEB_SAMPLE_S16BE,
+ /**< Native endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32NE = CUBEB_SAMPLE_FLOAT32BE
+#else
+ /**< Native endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16NE = CUBEB_SAMPLE_S16LE,
+ /**< Native endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32NE = CUBEB_SAMPLE_FLOAT32LE
+#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;
+
+/** 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_level;
+
+/** 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
+} 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 */
+} cubeb_device;
+
+/** Stream states signaled via state_callback. */
+typedef enum {
+ CUBEB_STATE_STARTED, /**< Stream started. */
+ CUBEB_STATE_STOPPED, /**< Stream stopped. */
+ CUBEB_STATE_DRAINED, /**< Stream drained. */
+ CUBEB_STATE_ERROR /**< Stream disabled due to error. */
+} cubeb_state;
+
+/** Result code enumeration. */
+enum {
+ 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. */
+};
+
+/**
+ * Whether a particular device is an input device (e.g. a microphone), or an
+ * output device (e.g. headphones). */
+typedef enum {
+ CUBEB_DEVICE_TYPE_UNKNOWN,
+ CUBEB_DEVICE_TYPE_INPUT,
+ CUBEB_DEVICE_TYPE_OUTPUT
+} cubeb_device_type;
+
+/**
+ * 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;
+
+/**
+ * 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;
+
+#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
+#else
+/** 16-bit integers, native endianess, when on a Little Endian environment. */
+#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
+#endif
+/** All the 16-bit integers types. */
+#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)
+/** All the device formats types. */
+#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;
+
+/** 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`. */
+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_device_info;
+
+/** Device collection. */
+typedef struct {
+ uint32_t count; /**< Device count in collection. */
+ cubeb_device_info * device[1]; /**< Array of pointers to device info. */
+} cubeb_device_collection;
+
+/** User supplied data callback.
+ - Calling other cubeb functions from this callback is unsafe.
+ - The code in the callback should be non-blocking.
+ - Returning less than the number of frames this callback asks for or
+ provides puts the stream in drain mode. This callback will not be called
+ again, and the state callback will be called with CUBEB_STATE_DRAINED when
+ all the frames have been output.
+ @param stream The stream for which this callback fired.
+ @param user_ptr The pointer passed to cubeb_stream_init.
+ @param input_buffer A pointer containing the input data, or nullptr
+ if this is an output-only stream.
+ @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 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);
+
+/** 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/** User supplied callback called when a message needs logging. */
+typedef void (* cubeb_log_callback)(const char * fmt, ...);
+
+/** Initialize an application context. This will perform any library or
+ application scoped initialization.
+ @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.
+ @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);
+
+/** 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);
+
+/** Get the maximum possible number of channels.
+ @param context A pointer to the cubeb context.
+ @param max_channels The maximum number of channels.
+ @retval CUBEB_OK
+ @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);
+
+/** 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.
+ @param context A pointer to the cubeb context.
+ @param params On some backends, the minimum achievable latency depends on
+ the characteristics of the stream.
+ @param latency_frames The latency value, in frames, to pass to
+ cubeb_stream_init.
+ @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);
+
+/** Get the preferred sample rate for this backend: this is hardware and
+ platform dependant, 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);
+
+/** 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);
+
+/** Initialize a stream associated with the supplied application context.
+ @param context A pointer to the cubeb context.
+ @param stream An out parameter to be filled with the an opaque pointer to a
+ 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.
+ @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.
+ @param output_stream_params Parameters for the output side of the stream, or
+ NULL if this stream is input only.
+ @param latency_frames Stream latency in frames. Valid range
+ is [1, 96000].
+ @param data_callback Will be called to preroll data before playback is
+ started by cubeb_stream_start.
+ @param state_callback A pointer to a state callback.
+ @param user_ptr A pointer that will be passed to the callbacks. This pointer
+ must outlive the life time of the stream.
+ @retval CUBEB_OK
+ @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);
+
+/** 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);
+
+/** Start playback.
+ @param stream
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR */
+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);
+
+/** 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);
+
+/** 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
+ can hear the sound.
+ @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_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)
+ @retval CUBEB_OK
+ @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).
+ @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);
+
+/** Get the current output device for this stream.
+ @param stm the stream for which to query the current output device
+ @param device a pointer in which the current output device will be stored.
+ @retval CUBEB_OK in case of success
+ @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);
+
+/** Destroy a cubeb_device structure.
+ @param stream the stream passed in cubeb_stream_get_current_device
+ @param devices the devices to destroy
+ @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);
+
+/** Set a callback to be notified when the output device changes.
+ @param stream the stream for which to set the callback.
+ @param device_changed_callback a function called whenever the device has
+ changed. Passing NULL allow to unregister a function
+ @retval CUBEB_OK
+ @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);
+
+/** Returns enumerated devices.
+ @param context
+ @param devtype device type to include
+ @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);
+
+/** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
+ @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);
+
+/** 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 callback a function called whenever the system device list changes.
+ Passing NULL allow to unregister a function
+ @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);
+
+/** Set a callback to be called with a message.
+ @param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
+ @param log_callback A function called with a message when there is
+ something to log. Pass NULL to unregister.
+ @retval CUBEB_OK in case of success.
+ @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);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 */
diff --git a/media/libcubeb/include/cubeb_export.h b/media/libcubeb/include/cubeb_export.h
new file mode 100644
index 0000000000..a0b3543b72
--- /dev/null
+++ b/media/libcubeb/include/cubeb_export.h
@@ -0,0 +1,5 @@
+/**
+ * This defines CUBEB_EXPORT to an empty string, we don't need any annotation to
+ * build in Gecko.
+ */
+#define CUBEB_EXPORT
diff --git a/media/libcubeb/include/moz.build b/media/libcubeb/include/moz.build
new file mode 100644
index 0000000000..2278098e97
--- /dev/null
+++ b/media/libcubeb/include/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.cubeb += [
+ 'cubeb.h',
+ 'cubeb_export.h'
+]
+
diff --git a/media/libcubeb/moz.build b/media/libcubeb/moz.build
new file mode 100644
index 0000000000..ff154e13c1
--- /dev/null
+++ b/media/libcubeb/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['include', 'src']
+TEST_DIRS += ['tests']
+
diff --git a/media/libcubeb/osx-linearize-operations.patch b/media/libcubeb/osx-linearize-operations.patch
new file mode 100644
index 0000000000..9f4f31bcaf
--- /dev/null
+++ b/media/libcubeb/osx-linearize-operations.patch
@@ -0,0 +1,968 @@
+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
new file mode 100644
index 0000000000..aa5356d7f2
--- /dev/null
+++ b/media/libcubeb/prevent-double-free.patch
@@ -0,0 +1,46 @@
+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
new file mode 100644
index 0000000000..cd501533d9
--- /dev/null
+++ b/media/libcubeb/src/android/audiotrack_definitions.h
@@ -0,0 +1,81 @@
+/*
+ * 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/sles_definitions.h b/media/libcubeb/src/android/sles_definitions.h
new file mode 100644
index 0000000000..1b1ace567e
--- /dev/null
+++ b/media/libcubeb/src/android/sles_definitions.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+/**
+ * This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in
+ * the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep
+ * using a C compiler in cubeb.
+ */
+
+#ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_
+#define OPENSL_ES_ANDROIDCONFIGURATION_H_
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioRecorder configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio recording preset */
+/** Audio recording preset key */
+#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)
+/** generic recording configuration on the platform */
+#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)
+/** uses the main microphone tuned for voice recognition */
+#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")
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio playback stream type */
+/** Audio playback stream type key */
+#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)
+/* same as android.media.AudioManager.STREAM_SYSTEM */
+#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001)
+/* same as android.media.AudioManager.STREAM_RING */
+#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002)
+/* same as android.media.AudioManager.STREAM_MUSIC */
+#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003)
+/* same as android.media.AudioManager.STREAM_ALARM */
+#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)
+
+#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
diff --git a/media/libcubeb/src/audiotrack_definitions.h b/media/libcubeb/src/audiotrack_definitions.h
new file mode 100644
index 0000000000..2beeca2de7
--- /dev/null
+++ b/media/libcubeb/src/audiotrack_definitions.h
@@ -0,0 +1,72 @@
+/*
+ * 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
new file mode 100644
index 0000000000..dfcc186c57
--- /dev/null
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5)
+#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
+
+#include "cubeb/cubeb.h"
+#include "cubeb_log.h"
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __clang__
+#ifndef CLANG_ANALYZER_NORETURN
+#if __has_feature(attribute_analyzer_noreturn)
+#define CLANG_ANALYZER_NORETURN __attribute__((analyzer_noreturn))
+#else
+#define CLANG_ANALYZER_NORETURN
+#endif // ifndef CLANG_ANALYZER_NORETURN
+#endif // __has_feature(attribute_analyzer_noreturn)
+#else // __clang__
+#define CLANG_ANALYZER_NORETURN
+#endif
+
+#if defined(__cplusplus)
+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);
+};
+
+#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-sles.h b/media/libcubeb/src/cubeb-sles.h
new file mode 100644
index 0000000000..0725523528
--- /dev/null
+++ b/media/libcubeb/src/cubeb-sles.h
@@ -0,0 +1,26 @@
+#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-speex-resampler.h b/media/libcubeb/src/cubeb-speex-resampler.h
new file mode 100644
index 0000000000..9ecf747cb0
--- /dev/null
+++ b/media/libcubeb/src/cubeb-speex-resampler.h
@@ -0,0 +1 @@
+#include <speex/speex_resampler.h>
diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c
new file mode 100644
index 0000000000..e0375c394f
--- /dev/null
+++ b/media/libcubeb/src/cubeb.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright © 2013 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 <stddef.h>
+#include <stdlib.h>
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+
+#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
+
+cubeb_log_level g_log_level;
+cubeb_log_callback g_log_callback;
+
+struct cubeb {
+ struct cubeb_ops * ops;
+};
+
+struct cubeb_stream {
+ struct cubeb * context;
+};
+
+#if defined(USE_PULSE)
+int pulse_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_JACK)
+int jack_init (cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_ALSA)
+int alsa_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOUNIT)
+int audiounit_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_WINMM)
+int winmm_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_WASAPI)
+int wasapi_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_SNDIO)
+int sndio_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_OPENSL)
+int opensl_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOTRACK)
+int audiotrack_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_KAI)
+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) {
+ 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) {
+ 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 ||
+ 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;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_S16BE:
+ case CUBEB_SAMPLE_FLOAT32LE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return CUBEB_OK;
+ }
+
+ return CUBEB_ERROR_INVALID_FORMAT;
+}
+
+
+
+static int
+validate_latency(int latency)
+{
+ if (latency < 1 || latency > 96000) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ return CUBEB_OK;
+}
+
+int
+cubeb_init(cubeb ** context, char const * context_name)
+{
+ int (* init[])(cubeb **, char const *) = {
+#if defined(USE_JACK)
+ jack_init,
+#endif
+#if defined(USE_PULSE)
+ pulse_init,
+#endif
+#if defined(USE_ALSA)
+ alsa_init,
+#endif
+#if defined(USE_AUDIOUNIT)
+ audiounit_init,
+#endif
+#if defined(USE_WASAPI)
+ wasapi_init,
+#endif
+#if defined(USE_WINMM)
+ winmm_init,
+#endif
+#if defined(USE_SNDIO)
+ sndio_init,
+#endif
+#if defined(USE_OPENSL)
+ opensl_init,
+#endif
+#if defined(USE_AUDIOTRACK)
+ audiotrack_init,
+#endif
+#if defined(USE_KAI)
+ kai_init,
+#endif
+ };
+ int i;
+
+ if (!context) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < NELEMS(init); ++i) {
+ if (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);
+ OK(stream_destroy);
+ OK(stream_start);
+ OK(stream_stop);
+ OK(stream_get_position);
+ return CUBEB_OK;
+ }
+ }
+
+ return CUBEB_ERROR;
+}
+
+char const *
+cubeb_get_backend_id(cubeb * context)
+{
+ if (!context) {
+ return NULL;
+ }
+
+ return context->ops->get_backend_id(context);
+}
+
+int
+cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ if (!context || !max_channels) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_max_channel_count) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_max_channel_count(context, max_channels);
+}
+
+int
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
+{
+ if (!context || !latency_ms) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_min_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_min_latency(context, params, latency_ms);
+}
+
+int
+cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+ if (!context || !rate) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_preferred_sample_rate) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_preferred_sample_rate(context, rate);
+}
+
+void
+cubeb_destroy(cubeb * context)
+{
+ if (!context) {
+ return;
+ }
+
+ context->ops->destroy(context);
+}
+
+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,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr)
+{
+ int r;
+
+ if (!context || !stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ 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);
+}
+
+void
+cubeb_stream_destroy(cubeb_stream * stream)
+{
+ if (!stream) {
+ return;
+ }
+
+ stream->context->ops->stream_destroy(stream);
+}
+
+int
+cubeb_stream_start(cubeb_stream * stream)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ return stream->context->ops->stream_start(stream);
+}
+
+int
+cubeb_stream_stop(cubeb_stream * stream)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ return stream->context->ops->stream_stop(stream);
+}
+
+int
+cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position)
+{
+ if (!stream || !position) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ return stream->context->ops->stream_get_position(stream, position);
+}
+
+int
+cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ if (!stream || !latency) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_latency(stream, latency);
+}
+
+int
+cubeb_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ if (!stream || volume > 1.0 || volume < 0.0) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_set_volume) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_set_volume(stream, volume);
+}
+
+int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
+{
+ if (!stream || panning < -1.0 || panning > 1.0) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_set_panning) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_set_panning(stream, panning);
+}
+
+int cubeb_stream_get_current_device(cubeb_stream * stream,
+ cubeb_device ** const device)
+{
+ if (!stream || !device) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_current_device) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_current_device(stream, device);
+}
+
+int cubeb_stream_device_destroy(cubeb_stream * stream,
+ cubeb_device * device)
+{
+ if (!stream || !device) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_device_destroy) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ 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)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_register_device_changed_callback) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
+}
+
+static
+void log_device(cubeb_device_info * device_info)
+{
+ char devfmts[128] = "";
+ 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;
+ };
+
+ 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;
+ };
+
+ 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;
+ };
+
+ if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
+ strcat(devfmts, " S16LE");
+ }
+ if (device_info->format & CUBEB_DEVICE_FMT_S16BE) {
+ strcat(devfmts, " S16BE");
+ }
+ if (device_info->format & CUBEB_DEVICE_FMT_F32LE) {
+ strcat(devfmts, " F32LE");
+ }
+ if (device_info->format & CUBEB_DEVICE_FMT_F32BE) {
+ strcat(devfmts, " F32BE");
+ }
+
+ LOG("DeviceID: \"%s\"%s\n"
+ "\tName:\t\"%s\"\n"
+ "\tGroup:\t\"%s\"\n"
+ "\tVendor:\t\"%s\"\n"
+ "\tType:\t%s\n"
+ "\tState:\t%s\n"
+ "\tMaximum channels:\t%u\n"
+ "\tFormat:\t%s (0x%x) (default: %s)\n"
+ "\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);
+}
+
+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)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ if (collection == NULL)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ if (!context->ops->enumerate_devices)
+ return CUBEB_ERROR_NOT_SUPPORTED;
+
+ 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]);
+ }
+ }
+
+ return rv;
+}
+
+int cubeb_device_collection_destroy(cubeb_device_collection * collection)
+{
+ uint32_t i;
+
+ if (collection == NULL)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+
+ for (i = 0; i < collection->count; i++)
+ cubeb_device_info_destroy(collection->device[i]);
+
+ free(collection);
+ return CUBEB_OK;
+}
+
+int cubeb_device_info_destroy(cubeb_device_info * info)
+{
+ if (info == NULL) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ free(info->device_id);
+ free(info->friendly_name);
+ free(info->group_id);
+ free(info->vendor_name);
+
+ free(info);
+ return CUBEB_OK;
+}
+
+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)
+ 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);
+}
+
+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;
+ }
+
+ if (!log_callback && log_level != CUBEB_LOG_DISABLED) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (g_log_callback && log_callback) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ g_log_callback = log_callback;
+ g_log_level = log_level;
+
+ return CUBEB_OK;
+}
+
+void
+cubeb_crash()
+{
+ abort();
+ *((volatile int *) NULL) = 0;
+}
+
diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c
new file mode 100644
index 0000000000..1ea0961d08
--- /dev/null
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -0,0 +1,1149 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#define _XOPEN_SOURCE 500
+#include <pthread.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <limits.h>
+#include <poll.h>
+#include <unistd.h>
+#include <alsa/asoundlib.h>
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+
+#define CUBEB_STREAM_MAX 16
+#define CUBEB_WATCHDOG_MS 10000
+
+#define CUBEB_ALSA_PCM_NAME "default"
+
+#define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin"
+
+/* ALSA is not thread-safe. snd_pcm_t instances are individually protected
+ by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction
+ is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1),
+ so those calls must be wrapped in the following mutex. */
+static pthread_mutex_t cubeb_alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
+static int cubeb_alsa_error_handler_set = 0;
+
+static struct cubeb_ops const alsa_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+
+ pthread_t thread;
+
+ /* Mutex for streams array, must not be held while blocked in poll(2). */
+ pthread_mutex_t mutex;
+
+ /* Sparse array of streams managed by this context. */
+ cubeb_stream * streams[CUBEB_STREAM_MAX];
+
+ /* fds and nfds are only updated by alsa_run when rebuild is set. */
+ struct pollfd * fds;
+ nfds_t nfds;
+ int rebuild;
+
+ int shutdown;
+
+ /* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */
+ int control_fd_read;
+ int control_fd_write;
+
+ /* Track number of active streams. This is limited to CUBEB_STREAM_MAX
+ due to resource contraints. */
+ unsigned int active_streams;
+
+ /* Local configuration with handle_underrun workaround set for PulseAudio
+ ALSA plugin. Will be NULL if the PA ALSA plugin is not in use or the
+ workaround is not required. */
+ snd_config_t * local_config;
+ int is_pa;
+};
+
+enum stream_state {
+ INACTIVE,
+ RUNNING,
+ DRAINING,
+ PROCESSING,
+ ERROR
+};
+
+struct cubeb_stream {
+ cubeb * context;
+ 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 last_position;
+ snd_pcm_uframes_t buffer_size;
+ cubeb_stream_params params;
+
+ /* Every member after this comment is protected by the owning context's
+ mutex rather than the stream's mutex, or is only used on the context's
+ run thread. */
+ pthread_cond_t cond; /* Signaled when the stream's state is changed. */
+
+ 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. */
+ nfds_t nfds;
+
+ struct timeval drain_timeout;
+
+ /* XXX: Horrible hack -- if an active stream has been idle for
+ CUBEB_WATCHDOG_MS it will be disabled and the error callback will be
+ called. This works around a bug seen with older versions of ALSA and
+ PulseAudio where streams would stop requesting new data despite still
+ being logically active and playing. */
+ struct timeval last_activity;
+ float volume;
+};
+
+static int
+any_revents(struct pollfd * fds, nfds_t nfds)
+{
+ nfds_t i;
+
+ for (i = 0; i < nfds; ++i) {
+ if (fds[i].revents) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+cmp_timeval(struct timeval * a, struct timeval * b)
+{
+ if (a->tv_sec == b->tv_sec) {
+ if (a->tv_usec == b->tv_usec) {
+ return 0;
+ }
+ return a->tv_usec > b->tv_usec ? 1 : -1;
+ }
+ return a->tv_sec > b->tv_sec ? 1 : -1;
+}
+
+static int
+timeval_to_relative_ms(struct timeval * tv)
+{
+ struct timeval now;
+ struct timeval dt;
+ long long t;
+ int r;
+
+ gettimeofday(&now, NULL);
+ r = cmp_timeval(tv, &now);
+ if (r >= 0) {
+ timersub(tv, &now, &dt);
+ } else {
+ timersub(&now, tv, &dt);
+ }
+ t = dt.tv_sec;
+ t *= 1000;
+ t += (dt.tv_usec + 500) / 1000;
+
+ if (t > INT_MAX) {
+ t = INT_MAX;
+ } else if (t < INT_MIN) {
+ t = INT_MIN;
+ }
+
+ return r >= 0 ? t : -t;
+}
+
+static int
+ms_until(struct timeval * tv)
+{
+ return timeval_to_relative_ms(tv);
+}
+
+static int
+ms_since(struct timeval * tv)
+{
+ return -timeval_to_relative_ms(tv);
+}
+
+static void
+rebuild(cubeb * ctx)
+{
+ nfds_t nfds;
+ int i;
+ nfds_t j;
+ cubeb_stream * stm;
+
+ assert(ctx->rebuild);
+
+ /* Always count context's control pipe fd. */
+ nfds = 1;
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm) {
+ stm->fds = NULL;
+ if (stm->state == RUNNING) {
+ nfds += stm->nfds;
+ }
+ }
+ }
+
+ free(ctx->fds);
+ ctx->fds = calloc(nfds, sizeof(struct pollfd));
+ assert(ctx->fds);
+ ctx->nfds = nfds;
+
+ /* Include context's control pipe fd. */
+ ctx->fds[0].fd = ctx->control_fd_read;
+ ctx->fds[0].events = POLLIN | POLLERR;
+
+ for (i = 0, j = 1; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm && stm->state == RUNNING) {
+ memcpy(&ctx->fds[j], stm->saved_fds, stm->nfds * sizeof(struct pollfd));
+ stm->fds = &ctx->fds[j];
+ j += stm->nfds;
+ }
+ }
+
+ ctx->rebuild = 0;
+}
+
+static void
+poll_wake(cubeb * ctx)
+{
+ if (write(ctx->control_fd_write, "x", 1) < 0) {
+ /* ignore write error */
+ }
+}
+
+static void
+set_timeout(struct timeval * timeout, unsigned int ms)
+{
+ gettimeofday(timeout, NULL);
+ timeout->tv_sec += ms / 1000;
+ timeout->tv_usec += (ms % 1000) * 1000;
+}
+
+static void
+alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
+{
+ cubeb * ctx;
+ int r;
+
+ ctx = stm->context;
+ stm->state = state;
+ r = pthread_cond_broadcast(&stm->cond);
+ assert(r == 0);
+ ctx->rebuild = 1;
+ poll_wake(ctx);
+}
+
+static enum stream_state
+alsa_refill_stream(cubeb_stream * stm)
+{
+ 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);
+ }
+
+ /* Failed to recover from an xrun, 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;
+ }
+
+ /* This should never happen. */
+ 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) {
+ pthread_mutex_unlock(&stm->mutex);
+ return RUNNING;
+ }
+
+ p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail));
+ assert(p);
+
+ 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;
+ }
+ if (got > 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++) {
+ b[i] *= stm->volume;
+ }
+ } else {
+ short * b = (short *) p;
+ for (uint32_t i = 0; i < got * stm->params.channels; i++) {
+ b[i] *= stm->volume;
+ }
+ }
+ wrote = snd_pcm_writei(stm->pcm, p, got);
+ if (wrote < 0) {
+ snd_pcm_recover(stm->pcm, wrote, 1);
+ wrote = snd_pcm_writei(stm->pcm, p, got);
+ }
+ assert(wrote >= 0 && wrote == got);
+ 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);
+
+ set_timeout(&stm->drain_timeout, buffer_time * 1000);
+
+ draining = 1;
+ }
+
+ free(p);
+ pthread_mutex_unlock(&stm->mutex);
+ return draining ? DRAINING : RUNNING;
+}
+
+static int
+alsa_run(cubeb * ctx)
+{
+ int r;
+ int timeout;
+ int i;
+ char dummy;
+ cubeb_stream * stm;
+ enum stream_state state;
+
+ pthread_mutex_lock(&ctx->mutex);
+
+ if (ctx->rebuild) {
+ rebuild(ctx);
+ }
+
+ /* Wake up at least once per second for the watchdog. */
+ timeout = 1000;
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm && stm->state == DRAINING) {
+ r = ms_until(&stm->drain_timeout);
+ if (r >= 0 && timeout > r) {
+ timeout = r;
+ }
+ }
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+ r = poll(ctx->fds, ctx->nfds, timeout);
+ pthread_mutex_lock(&ctx->mutex);
+
+ if (r > 0) {
+ if (ctx->fds[0].revents & POLLIN) {
+ if (read(ctx->control_fd_read, &dummy, 1) < 0) {
+ /* ignore read error */
+ }
+
+ if (ctx->shutdown) {
+ pthread_mutex_unlock(&ctx->mutex);
+ return -1;
+ }
+ }
+
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ 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)) {
+ alsa_set_stream_state(stm, PROCESSING);
+ pthread_mutex_unlock(&ctx->mutex);
+ state = alsa_refill_stream(stm);
+ pthread_mutex_lock(&ctx->mutex);
+ alsa_set_stream_state(stm, state);
+ }
+ }
+ } else if (r == 0) {
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm) {
+ 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) {
+ alsa_set_stream_state(stm, ERROR);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+ }
+ }
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return 0;
+}
+
+static void *
+alsa_run_thread(void * context)
+{
+ cubeb * ctx = context;
+ int r;
+
+ do {
+ r = alsa_run(ctx);
+ } while (r >= 0);
+
+ return NULL;
+}
+
+static snd_config_t *
+get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
+{
+ int r;
+ snd_config_t * slave_pcm;
+ snd_config_t * slave_def;
+ snd_config_t * pcm;
+ char const * string;
+ char node_name[64];
+
+ slave_def = NULL;
+
+ r = snd_config_search(root_pcm, "slave", &slave_pcm);
+ if (r < 0) {
+ return NULL;
+ }
+
+ r = snd_config_get_string(slave_pcm, &string);
+ if (r >= 0) {
+ r = 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);
+ if (r < 0) {
+ break;
+ }
+
+ r = 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)) {
+ break;
+ }
+ r = snd_config_search(lconf, node_name, &pcm);
+ if (r < 0) {
+ break;
+ }
+
+ return pcm;
+ } while (0);
+
+ if (slave_def) {
+ snd_config_delete(slave_def);
+ }
+
+ return NULL;
+}
+
+/* Work around PulseAudio ALSA plugin bug where the PA server forces a
+ 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 */
+static snd_config_t *
+init_local_config_with_workaround(char const * pcm_name)
+{
+ int r;
+ snd_config_t * lconf;
+ snd_config_t * pcm_node;
+ snd_config_t * node;
+ char const * string;
+ char node_name[64];
+
+ lconf = NULL;
+
+ if (snd_config == NULL) {
+ return NULL;
+ }
+
+ r = snd_config_copy(&lconf, snd_config);
+ if (r < 0) {
+ return NULL;
+ }
+
+ do {
+ r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node);
+ if (r < 0) {
+ break;
+ }
+
+ r = 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)) {
+ break;
+ }
+ r = 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. */
+ 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);
+ if (r < 0) {
+ break;
+ }
+
+ r = snd_config_get_string(node, &string);
+ if (r < 0) {
+ break;
+ }
+
+ if (strcmp(string, "pulse") != 0) {
+ break;
+ }
+
+ /* 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);
+ if (r != -ENOENT) {
+ break;
+ }
+
+ /* Disable pcm_pulse's asynchronous underrun handling. */
+ r = snd_config_imake_integer(&node, "handle_underrun", 0);
+ if (r < 0) {
+ break;
+ }
+
+ r = snd_config_add(pcm_node, node);
+ if (r < 0) {
+ break;
+ }
+
+ return lconf;
+ } while (0);
+
+ 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)
+{
+ 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);
+ } else {
+ r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK);
+ }
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+
+ return r;
+}
+
+static int
+alsa_locked_pcm_close(snd_pcm_t * pcm)
+{
+ int r;
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ r = snd_pcm_close(pcm);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+
+ return r;
+}
+
+static int
+alsa_register_stream(cubeb * ctx, cubeb_stream * stm)
+{
+ int i;
+
+ pthread_mutex_lock(&ctx->mutex);
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ if (!ctx->streams[i]) {
+ ctx->streams[i] = stm;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return i == CUBEB_STREAM_MAX;
+}
+
+static void
+alsa_unregister_stream(cubeb_stream * stm)
+{
+ cubeb * ctx;
+ int i;
+
+ ctx = stm->context;
+
+ pthread_mutex_lock(&ctx->mutex);
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ if (ctx->streams[i] == stm) {
+ ctx->streams[i] = NULL;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&ctx->mutex);
+}
+
+static void
+silent_error_handler(char const * file, int line, char const * function,
+ int err, char const * fmt, ...)
+{
+ (void)file;
+ (void)line;
+ (void)function;
+ (void)err;
+ (void)fmt;
+}
+
+/*static*/ int
+alsa_init(cubeb ** context, char const * context_name)
+{
+ (void)context_name;
+ cubeb * ctx;
+ int r;
+ int i;
+ int fd[2];
+ pthread_attr_t attr;
+ snd_pcm_t * dummy;
+
+ assert(context);
+ *context = NULL;
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ if (!cubeb_alsa_error_handler_set) {
+ snd_lib_error_set_handler(silent_error_handler);
+ cubeb_alsa_error_handler_set = 1;
+ }
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+
+ ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+
+ ctx->ops = &alsa_ops;
+
+ r = pthread_mutex_init(&ctx->mutex, NULL);
+ assert(r == 0);
+
+ r = pipe(fd);
+ assert(r == 0);
+
+ for (i = 0; i < 2; ++i) {
+ fcntl(fd[i], F_SETFD, fcntl(fd[i], F_GETFD) | FD_CLOEXEC);
+ fcntl(fd[i], F_SETFL, fcntl(fd[i], F_GETFL) | O_NONBLOCK);
+ }
+
+ ctx->control_fd_read = fd[0];
+ ctx->control_fd_write = fd[1];
+
+ /* Force an early rebuild when alsa_run is first called to ensure fds and
+ nfds have been initialized. */
+ ctx->rebuild = 1;
+
+ r = pthread_attr_init(&attr);
+ assert(r == 0);
+
+ r = pthread_attr_setstacksize(&attr, 256 * 1024);
+ assert(r == 0);
+
+ r = pthread_create(&ctx->thread, &attr, alsa_run_thread, ctx);
+ assert(r == 0);
+
+ r = pthread_attr_destroy(&attr);
+ assert(r == 0);
+
+ /* 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);
+ if (r >= 0) {
+ alsa_locked_pcm_close(dummy);
+ }
+ ctx->is_pa = 0;
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ ctx->local_config = init_local_config_with_workaround(CUBEB_ALSA_PCM_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);
+ /* 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);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+ ctx->local_config = NULL;
+ } else if (r >= 0) {
+ alsa_locked_pcm_close(dummy);
+ }
+ }
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+alsa_get_backend_id(cubeb * ctx)
+{
+ (void)ctx;
+ return "alsa";
+}
+
+static void
+alsa_destroy(cubeb * ctx)
+{
+ int r;
+
+ assert(ctx);
+
+ pthread_mutex_lock(&ctx->mutex);
+ ctx->shutdown = 1;
+ poll_wake(ctx);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ r = pthread_join(ctx->thread, NULL);
+ assert(r == 0);
+
+ close(ctx->control_fd_read);
+ close(ctx->control_fd_write);
+ pthread_mutex_destroy(&ctx->mutex);
+ free(ctx->fds);
+
+ if (ctx->local_config) {
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ snd_config_delete(ctx->local_config);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+ }
+
+ free(ctx);
+}
+
+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)
+{
+ (void)stream_name;
+ cubeb_stream * stm;
+ int r;
+ snd_pcm_format_t format;
+ snd_pcm_uframes_t period_size;
+ int latency_us = 0;
+
+
+ assert(ctx && 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;
+
+ switch (output_stream_params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format = SND_PCM_FORMAT_S16_LE;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format = SND_PCM_FORMAT_S16_BE;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format = SND_PCM_FORMAT_FLOAT_LE;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format = SND_PCM_FORMAT_FLOAT_BE;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ pthread_mutex_lock(&ctx->mutex);
+ if (ctx->active_streams >= CUBEB_STREAM_MAX) {
+ pthread_mutex_unlock(&ctx->mutex);
+ return CUBEB_ERROR;
+ }
+ ctx->active_streams += 1;
+ pthread_mutex_unlock(&ctx->mutex);
+
+ 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->state = INACTIVE;
+ stm->volume = 1.0;
+
+ r = pthread_mutex_init(&stm->mutex, NULL);
+ assert(r == 0);
+
+ r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ if (r < 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ r = snd_pcm_nonblock(stm->pcm, 1);
+ assert(r == 0);
+
+ latency_us = latency_frames * 1e6 / stm->params.rate;
+
+ /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't
+ possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274.
+ 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;
+ }
+
+ r = 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);
+ assert(r == 0);
+
+ stm->nfds = 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);
+
+ if (alsa_register_stream(ctx, stm) != 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+static void
+alsa_stream_destroy(cubeb_stream * stm)
+{
+ int r;
+ cubeb * ctx;
+
+ assert(stm && (stm->state == INACTIVE ||
+ stm->state == ERROR ||
+ stm->state == DRAINING));
+
+ ctx = stm->context;
+
+ pthread_mutex_lock(&stm->mutex);
+ if (stm->pcm) {
+ if (stm->state == DRAINING) {
+ snd_pcm_drain(stm->pcm);
+ }
+ alsa_locked_pcm_close(stm->pcm);
+ stm->pcm = NULL;
+ }
+ free(stm->saved_fds);
+ pthread_mutex_unlock(&stm->mutex);
+ pthread_mutex_destroy(&stm->mutex);
+
+ r = pthread_cond_destroy(&stm->cond);
+ assert(r == 0);
+
+ alsa_unregister_stream(stm);
+
+ pthread_mutex_lock(&ctx->mutex);
+ assert(ctx->active_streams >= 1);
+ ctx->active_streams -= 1;
+ pthread_mutex_unlock(&ctx->mutex);
+
+ free(stm);
+}
+
+static int
+alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ int r;
+ cubeb_stream * stm;
+ snd_pcm_hw_params_t* hw_params;
+ cubeb_stream_params params;
+ params.rate = 44100;
+ params.format = CUBEB_SAMPLE_FLOAT32NE;
+ params.channels = 2;
+
+ snd_pcm_hw_params_alloca(&hw_params);
+
+ assert(ctx);
+
+ 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);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ alsa_stream_destroy(stm);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
+ (void)ctx;
+ int r, dir;
+ snd_pcm_t * pcm;
+ snd_pcm_hw_params_t * hw_params;
+
+ snd_pcm_hw_params_alloca(&hw_params);
+
+ /* 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);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ r = snd_pcm_hw_params_any(pcm, hw_params);
+ if (r < 0) {
+ snd_pcm_close(pcm);
+ return CUBEB_ERROR;
+ }
+
+ r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir);
+ if (r >= 0) {
+ /* There is a default rate: use it. */
+ 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);
+ if (r < 0) {
+ snd_pcm_close(pcm);
+ return CUBEB_ERROR;
+ }
+
+ snd_pcm_close(pcm);
+
+ return CUBEB_OK;
+}
+
+static int
+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
+ * machine. */
+ *latency_frames = 40 * params.rate / 1000;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_start(cubeb_stream * stm)
+{
+ cubeb * ctx;
+
+ assert(stm);
+ ctx = stm->context;
+
+ pthread_mutex_lock(&stm->mutex);
+ snd_pcm_pause(stm->pcm, 0);
+ gettimeofday(&stm->last_activity, NULL);
+ pthread_mutex_unlock(&stm->mutex);
+
+ pthread_mutex_lock(&ctx->mutex);
+ if (stm->state != INACTIVE) {
+ pthread_mutex_unlock(&ctx->mutex);
+ return CUBEB_ERROR;
+ }
+ alsa_set_stream_state(stm, RUNNING);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_stop(cubeb_stream * stm)
+{
+ cubeb * ctx;
+ int r;
+
+ assert(stm);
+ ctx = stm->context;
+
+ pthread_mutex_lock(&ctx->mutex);
+ while (stm->state == PROCESSING) {
+ r = pthread_cond_wait(&stm->cond, &ctx->mutex);
+ assert(r == 0);
+ }
+
+ alsa_set_stream_state(stm, INACTIVE);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ pthread_mutex_lock(&stm->mutex);
+ snd_pcm_pause(stm->pcm, 1);
+ pthread_mutex_unlock(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ snd_pcm_sframes_t delay;
+
+ assert(stm && 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) {
+ *position = stm->last_position;
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_OK;
+ }
+
+ assert(delay >= 0);
+
+ *position = 0;
+ if (stm->write_position >= (snd_pcm_uframes_t) delay) {
+ *position = stm->write_position - delay;
+ }
+
+ stm->last_position = *position;
+
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_OK;
+}
+
+static int
+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)) {
+ return CUBEB_ERROR;
+ }
+
+ *latency = delay;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ /* setting the volume using an API call does not seem very stable/supported */
+ pthread_mutex_lock(&stm->mutex);
+ stm->volume = volume;
+ pthread_mutex_unlock(&stm->mutex);
+
+ 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
+};
diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c
new file mode 100644
index 0000000000..fe2603405e
--- /dev/null
+++ b/media/libcubeb/src/cubeb_audiotrack.c
@@ -0,0 +1,438 @@
+/*
+ * 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
new file mode 100644
index 0000000000..f24dfbff2f
--- /dev/null
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -0,0 +1,2734 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+
+#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"
+#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>
+
+#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
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
+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)
+
+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 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)
+ {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);
+ }
+
+ 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();
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+ }
+
+private:
+ auto_array<float> * float_ar;
+ auto_array<short> * short_ar;
+ owned_critical_section lock;
+};
+
+struct cubeb_stream {
+ cubeb * context;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ cubeb_device_changed_callback device_changed_callback;
+ /* 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;
+ /* Format descriptions */
+ AudioStreamBasicDescription input_desc;
+ AudioStreamBasicDescription output_desc;
+ /* I/O AudioUnits */
+ AudioUnit input_unit;
+ AudioUnit output_unit;
+ /* I/O device sample rate */
+ Float64 input_hw_rate;
+ Float64 output_hw_rate;
+ /* Expected I/O thread interleave,
+ * calculated from I/O hw rate. */
+ int expected_output_callbacks_in_a_row;
+ 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;
+ /* Frame counters */
+ uint64_t frames_played;
+ uint64_t frames_queued;
+ std::atomic<int64_t> frames_read;
+ std::atomic<bool> shutdown;
+ std::atomic<bool> draining;
+ /* 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;
+ /* 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)
+{
+ return stm->output_stream_params.rate != 0;
+}
+
+#if TARGET_OS_IPHONE
+typedef UInt32 AudioDeviceID;
+typedef UInt32 AudioObjectID;
+
+#define AudioGetCurrentHostTime mach_absolute_time
+
+uint64_t
+AudioConvertHostTimeToNanos(uint64_t host_time)
+{
+ static struct mach_timebase_info timebase_info;
+ static bool initialized = false;
+ if (!initialized) {
+ mach_timebase_info(&timebase_info);
+ initialized = true;
+ }
+
+ long double answer = host_time;
+ if (timebase_info.numer != timebase_info.denom) {
+ answer *= timebase_info.numer;
+ answer /= timebase_info.denom;
+ }
+ return (uint64_t)answer;
+}
+#endif
+
+static int64_t
+audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream)
+{
+ if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) {
+ return 0;
+ }
+
+ 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
+audiounit_render_input(cubeb_stream * stm,
+ AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp,
+ UInt32 bus,
+ UInt32 input_frames)
+{
+ /* Create the AudioBufferList to store input. */
+ AudioBufferList input_buffer_list;
+ 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.mNumberBuffers = 1;
+
+ /* Render input samples */
+ OSStatus r = AudioUnitRender(stm->input_unit,
+ flags,
+ tstamp,
+ bus,
+ input_frames,
+ &input_buffer_list);
+
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioUnitRender", r);
+ return r;
+ }
+
+ /* 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;
+
+ return noErr;
+}
+
+static OSStatus
+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);
+ 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);
+ }
+
+ OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
+ if (r != noErr) {
+ return r;
+ }
+
+ // 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);
+ // 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)
+{
+ /* 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. */
+
+ /* 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));
+}
+
+static OSStatus
+audiounit_output_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * /* flags */,
+ 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;
+
+ if (stm->shutdown) {
+ LOG("(%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 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
+ 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;
+ }
+
+ /* Call user callback through resampler. */
+ outframes = cubeb_resampler_fill(stm->resampler,
+ 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);
+ }
+
+ if (outframes < 0) {
+ stm->shutdown = true;
+ return noErr;
+ }
+
+ size_t outbpf = stm->output_desc.mBytesPerFrame;
+ stm->draining = 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);
+ }
+ /* 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);
+ }
+ }
+ return noErr;
+}
+
+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;
+
+ return CUBEB_OK;
+}
+}
+
+static char const *
+audiounit_get_backend_id(cubeb * /* ctx */)
+{
+ return "audiounit";
+}
+
+#if !TARGET_OS_IPHONE
+static int
+audiounit_get_output_device_id(AudioDeviceID * device_id)
+{
+ UInt32 size;
+ OSStatus r;
+ AudioObjectPropertyAddress output_device_address = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ size = sizeof(*device_id);
+
+ r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &output_device_address,
+ 0,
+ NULL,
+ &size,
+ device_id);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("output_device_id", r);
+ return CUBEB_ERROR;
+ }
+
+ 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);
+
+ r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &input_device_address,
+ 0,
+ NULL,
+ &size,
+ 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_uninstall_device_changed_callback(cubeb_stream * stm);
+
+static int
+audiounit_reinit_stream(cubeb_stream * stm)
+{
+ auto_lock context_lock(stm->context->mutex);
+ 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);
+ }
+
+ {
+ 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 (!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;
+
+ 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;
+ }
+ 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;
+ }
+ }
+ }
+
+ for (UInt32 i = 0; i < address_count; i++) {
+ 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;
+ }
+ }
+ }
+
+ // 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;
+ });
+
+ return noErr;
+}
+
+OSStatus
+audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
+ AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
+{
+ AudioObjectPropertyAddress address = {
+ selector,
+ scope,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectAddPropertyListener(id, &address, listener, stm);
+}
+
+OSStatus
+audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id,
+ AudioObjectPropertySelector selector,
+ AudioObjectPropertyScope scope,
+ AudioObjectPropertyListenerProc listener)
+{
+ AudioObjectPropertyAddress address = {
+ selector,
+ scope,
+ kAudioObjectPropertyElementMaster
+ };
+
+ return AudioObjectRemovePropertyListener(id, &address, listener, stm);
+}
+
+static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
+
+static int
+audiounit_install_device_changed_callback(cubeb_stream * stm)
+{
+ OSStatus r;
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ /* 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;
+ }
+ }
+
+ 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);
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+/* Get the acceptable buffer size (in frames) that this device can work with. */
+static int
+audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
+{
+ UInt32 size;
+ OSStatus r;
+ AudioDeviceID output_device_id;
+ AudioObjectPropertyAddress output_device_buffer_size_range = {
+ kAudioDevicePropertyBufferFrameSizeRange,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ LOG("Could not get default output device id.");
+ return CUBEB_ERROR;
+ }
+
+ /* Get the buffer size range this device supports */
+ size = sizeof(*latency_range);
+
+ r = AudioObjectGetPropertyData(output_device_id,
+ &output_device_buffer_size_range,
+ 0,
+ NULL,
+ &size,
+ latency_range);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+#endif /* !TARGET_OS_IPHONE */
+
+static AudioObjectID
+audiounit_get_default_device_id(cubeb_device_type type)
+{
+ AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
+ AudioDeviceID devid;
+ UInt32 size;
+
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ adr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ } else {
+ return kAudioObjectUnknown;
+ }
+
+ size = sizeof(AudioDeviceID);
+ if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) {
+ return kAudioObjectUnknown;
+ }
+
+ return devid;
+}
+
+int
+audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+#if TARGET_OS_IPHONE
+ //TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]
+ *max_channels = 2;
+#else
+ UInt32 size;
+ OSStatus r;
+ AudioDeviceID output_device_id;
+ AudioStreamBasicDescription stream_format;
+ AudioObjectPropertyAddress stream_format_address = {
+ kAudioDevicePropertyStreamFormat,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ assert(ctx && max_channels);
+
+ if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ size = sizeof(stream_format);
+
+ r = AudioObjectGetPropertyData(output_device_id,
+ &stream_format_address,
+ 0,
+ NULL,
+ &size,
+ &stream_format);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r);
+ return CUBEB_ERROR;
+ }
+
+ *max_channels = stream_format.mChannelsPerFrame;
+#endif
+ return CUBEB_OK;
+}
+
+static int
+audiounit_get_min_latency(cubeb * /* ctx */,
+ cubeb_stream_params /* params */,
+ uint32_t * latency_frames)
+{
+#if TARGET_OS_IPHONE
+ //TODO: [[AVAudioSession sharedInstance] inputLatency]
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ AudioValueRange latency_range;
+ if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {
+ LOG("Could not get acceptable latency range.");
+ return CUBEB_ERROR;
+ }
+
+ *latency_frames = std::max<uint32_t>(latency_range.mMinimum,
+ SAFE_MIN_LATENCY_FRAMES);
+#endif
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
+{
+#if TARGET_OS_IPHONE
+ //TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ UInt32 size;
+ OSStatus r;
+ Float64 fsamplerate;
+ AudioDeviceID output_device_id;
+ AudioObjectPropertyAddress samplerate_address = {
+ kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ size = sizeof(fsamplerate);
+ r = AudioObjectGetPropertyData(output_device_id,
+ &samplerate_address,
+ 0,
+ NULL,
+ &size,
+ &fsamplerate);
+
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = static_cast<uint32_t>(fsamplerate);
+#endif
+ return CUBEB_OK;
+}
+
+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);
+
+ {
+ auto_lock lock(ctx->mutex);
+ /* 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);
+
+static int
+audio_stream_desc_init(AudioStreamBasicDescription * ss,
+ const cubeb_stream_params * stream_params)
+{
+ switch (stream_params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ ss->mBitsPerChannel = 16;
+ ss->mFormatFlags = kAudioFormatFlagIsSignedInteger;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ ss->mBitsPerChannel = 16;
+ ss->mFormatFlags = kAudioFormatFlagIsSignedInteger |
+ kAudioFormatFlagIsBigEndian;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ ss->mBitsPerChannel = 32;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ ss->mBitsPerChannel = 32;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat |
+ kAudioFormatFlagIsBigEndian;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ ss->mFormatID = kAudioFormatLinearPCM;
+ ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked;
+ ss->mSampleRate = stream_params->rate;
+ ss->mChannelsPerFrame = stream_params->channels;
+
+ ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame;
+ ss->mFramesPerPacket = 1;
+ ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket;
+
+ ss->mReserved = 0;
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit,
+ bool is_input,
+ const cubeb_stream_params * /* stream_params */,
+ cubeb_devid 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) {
+ desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ } else {
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+ }
+#endif
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ comp = AudioComponentFindNext(NULL, &desc);
+ if (comp == NULL) {
+ LOG("Could not find matching audio hardware.");
+ return CUBEB_ERROR;
+ }
+
+ rv = AudioComponentInstanceNew(comp, unit);
+ if (rv != noErr) {
+ PRINT_ERROR_CODE("AudioComponentInstanceNew", rv);
+ return CUBEB_ERROR;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
+{
+ 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) );
+ }
+
+ if (!stream->input_linear_buffer) {
+ return CUBEB_ERROR;
+ }
+
+ assert(stream->input_linear_buffer->length() == 0);
+
+ // 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);
+
+ assert(stream->input_linear_buffer->length() == silence_size);
+ }
+
+ return CUBEB_OK;
+}
+
+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)
+{
+ // 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);
+ }
+
+ // If more than one stream operates in parallel
+ // allow only lower values of latency
+ int r;
+ 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);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
+ return 0;
+ }
+
+ output_buffer_size = std::max(std::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);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
+ return 0;
+ }
+
+ input_buffer_size = std::max(std::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);
+ } else if (input_buffer_size != 0) {
+ upper_latency_limit = input_buffer_size;
+ } else if (output_buffer_size != 0) {
+ upper_latency_limit = output_buffer_size;
+ } 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
+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 = 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;
+ }
+ }
+
+ if (has_output(stm)) {
+ r = audiounit_create_unit(&stm->output_unit, false,
+ &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)) {
+ r = audiounit_configure_input(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Configure audiounit input failed.", stm);
+ return r;
+ }
+ }
+
+ /* Setup Output Stream! */
+ if (has_output(stm)) {
+ r = audiounit_configure_output(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Configure audiounit output failed.", stm);
+ return r;
+ }
+ }
+
+ // 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
+ * the requested latency to this acceptable range. */
+#if !TARGET_OS_IPHONE
+ if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ if (buffer_size < (unsigned int) latency_range.mMinimum) {
+ buffer_size = (unsigned int) latency_range.mMinimum;
+ } else if (buffer_size > (unsigned int) latency_range.mMaximum) {
+ buffer_size = (unsigned int) latency_range.mMaximum;
+ }
+
+ /**
+ * Get the default buffer size. If our latency request is below the default,
+ * set it. Otherwise, use the default latency.
+ **/
+ size = sizeof(default_buffer_size);
+ if (AudioUnitGetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, 0, &default_buffer_size, &size) != 0) {
+ return CUBEB_ERROR;
+ }
+
+ if (buffer_size < default_buffer_size) {
+ /* Set the maximum number of frame that the render callback will ask for,
+ * effectively setting the latency of the stream. This is process-wide. */
+ if (AudioUnitSetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)) != 0) {
+ return CUBEB_ERROR;
+ }
+ }
+#else // TARGET_OS_IPHONE
+ //TODO: [[AVAudioSession sharedInstance] inputLatency]
+ // http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios
+#endif
+#endif
+
+ /* We use a resampler because input AudioUnit operates
+ * reliable only in the capture device sample rate.
+ * Resampler will convert it to the user sample rate
+ * and deliver it to the callback. */
+ uint32_t target_sample_rate;
+ if (has_input(stm)) {
+ target_sample_rate = stm->input_stream_params.rate;
+ } else {
+ assert(has_output(stm));
+ target_sample_rate = stm->output_stream_params.rate;
+ }
+
+ cubeb_stream_params input_unconverted_params;
+ if (has_input(stm)) {
+ input_unconverted_params = stm->input_stream_params;
+ /* Use the rate of the input device. */
+ input_unconverted_params.rate = stm->input_hw_rate;
+ }
+
+ /* 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);
+ if (!stm->resampler) {
+ LOG("(%p) Could not create resampler.", stm);
+ return CUBEB_ERROR;
+ }
+
+ if (stm->input_unit != NULL) {
+ r = AudioUnitInitialize(stm->input_unit);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioUnitInitialize/input", r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->output_unit != NULL) {
+ r = AudioUnitInitialize(stm->output_unit);
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioUnitInitialize/output", r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ 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,
+ 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;
+ 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;
+ }
+
+ 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;
+
+ 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);
+ }
+
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not setup the audiounit stream.", stm);
+ audiounit_stream_destroy(stm);
+ return r;
+ }
+
+ 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();
+
+ if (stm->input_unit) {
+ AudioUnitUninitialize(stm->input_unit);
+ AudioComponentInstanceDispose(stm->input_unit);
+ }
+
+ audiounit_destroy_input_linear_buffer(stm);
+
+ if (stm->output_unit) {
+ AudioUnitUninitialize(stm->output_unit);
+ AudioComponentInstanceDispose(stm->output_unit);
+ }
+
+ 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);
+ }
+
+ 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;
+
+ LOG("Cubeb stream (%p) destroyed successful.", stm);
+
+ stm->~cubeb_stream();
+ free(stm);
+}
+
+void
+audiounit_stream_start_internal(cubeb_stream * stm)
+{
+ OSStatus r;
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStart(stm->input_unit);
+ assert(r == 0);
+ }
+ if (stm->output_unit != NULL) {
+ 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;
+
+ 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;
+}
+
+void
+audiounit_stream_stop_internal(cubeb_stream * stm)
+{
+ OSStatus r;
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ if (stm->output_unit != NULL) {
+ 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;
+
+ 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;
+}
+
+static int
+audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ auto_lock lock(stm->mutex);
+
+ *position = stm->frames_played;
+ return CUBEB_OK;
+}
+
+int
+audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+#if TARGET_OS_IPHONE
+ //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;
+
+ 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);
+
+ if (r != noErr) {
+ PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+int audiounit_stream_set_panning(cubeb_stream * stm, float panning)
+{
+ if (stm->output_desc.mChannelsPerFrame > 2) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ stm->panning.store(panning, std::memory_order_relaxed);
+ return CUBEB_OK;
+}
+
+int audiounit_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+#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) {
+ 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);
+ if (r != noErr) {
+ size = 0;
+ data = 0;
+ }
+
+ (*device)->output_name = new char[size + 1];
+ if (!(*device)->output_name) {
+ return CUBEB_ERROR;
+ }
+
+ // 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);
+
+ memcpy((*device)->output_name, strdata, size);
+ (*device)->output_name[size] = '\0';
+
+ if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ 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;
+ }
+
+ (*device)->input_name = new char[size + 1];
+ if (!(*device)->input_name) {
+ return CUBEB_ERROR;
+ }
+
+ // 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);
+
+ memcpy((*device)->input_name, strdata, size);
+ (*device)->input_name[size] = '\0';
+
+ return CUBEB_OK;
+#endif
+}
+
+int audiounit_stream_device_destroy(cubeb_stream * /* stream */,
+ cubeb_device * device)
+{
+ 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)
+{
+ /* 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);
+
+ 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)
+{
+ CFIndex len, size;
+ char * ret;
+ if (strref == NULL) {
+ return NULL;
+ }
+
+ len = CFStringGetLength(strref);
+ size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8);
+ ret = static_cast<char *>(malloc(size));
+
+ if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {
+ free(ret);
+ ret = NULL;
+ }
+
+ return ret;
+}
+
+static uint32_t
+audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
+{
+ 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) {
+ AudioBufferList * list = static_cast<AudioBufferList *>(alloca(size));
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == noErr) {
+ for (i = 0; i < list->mNumberBuffers; i++)
+ ret += list->mBuffers[i].mNumberChannels;
+ }
+ }
+
+ return ret;
+}
+
+static void
+audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max, uint32_t * def)
+{
+ 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) {
+ *def = fvalue;
+ }
+ }
+
+ adr.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
+ UInt32 size = 0;
+ 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];
+ range.mMinimum = 9999999999.0;
+ range.mMaximum = 0.0;
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) {
+ for (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)
+{
+ AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
+ UInt32 size, dev, stream = 0, offset;
+ AudioStreamID sid[1];
+
+ adr.mSelector = kAudioDevicePropertyLatency;
+ size = sizeof(UInt32);
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr) {
+ dev = 0;
+ }
+
+ adr.mSelector = kAudioDevicePropertyStreams;
+ size = sizeof(sid);
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) {
+ adr.mSelector = kAudioStreamPropertyLatency;
+ size = sizeof(UInt32);
+ 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;
+}
+
+static cubeb_device_info *
+audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type)
+{
+ AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster };
+ UInt32 size, ch, latency;
+ cubeb_device_info * ret;
+ CFStringRef str = NULL;
+ AudioValueRange range;
+
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ adr.mScope = kAudioDevicePropertyScopeOutput;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ adr.mScope = kAudioDevicePropertyScopeInput;
+ } else {
+ return NULL;
+ }
+
+ ch = audiounit_get_channel_count(devid, adr.mScope);
+ if (ch == 0) {
+ return NULL;
+ }
+
+ ret = new cubeb_device_info;
+ PodZero(ret, 1);
+
+ 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);
+ }
+
+ 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;
+ }
+ }
+
+ ret->friendly_name = audiounit_strref_to_cstr_utf8(str);
+ CFRelease(str);
+ }
+
+ 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! */
+ /* 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);
+
+ latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+
+ 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;
+ } else {
+ ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */
+ ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */
+ }
+
+ return ret;
+}
+
+static int
+audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,
+ cubeb_device_collection ** collection)
+{
+ AudioObjectID * hwdevs = NULL;
+ uint32_t i, hwdevcount = 0;
+ OSStatus err;
+
+ if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ *collection = static_cast<cubeb_device_collection *>(malloc(sizeof(cubeb_device_collection) +
+ sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0)));
+ (*collection)->count = 0;
+
+ if (hwdevcount > 0) {
+ cubeb_device_info * cur;
+
+ 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_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;
+ }
+ }
+ }
+
+ delete [] hwdevs;
+
+ return CUBEB_OK;
+}
+
+/* qsort compare method. */
+int compare_devid(const void * a, const void * b)
+{
+ return (*(AudioObjectID*)a - *(AudioObjectID*)b);
+}
+
+static uint32_t
+audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array)
+{
+ assert(devid_array == NULL || *devid_array == NULL);
+
+ AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
+ if (ret != noErr) {
+ return 0;
+ }
+ /* 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);
+ if (ret != noErr) {
+ return 0;
+ }
+ /* Expected sorted but did not find anything in the docs. */
+ qsort(devices, count, sizeof(AudioObjectID), compare_devid);
+
+ 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;
+ }
+
+ 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) {
+ /* 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;
+ }
+ }
+
+ 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;
+}
+
+static OSStatus
+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;
+ }
+ /* 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);
+ 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)
+{
+ /* 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;
+ }
+ return ret;
+}
+
+static OSStatus
+audiounit_remove_device_listener(cubeb * context)
+{
+ AudioObjectPropertyAddress devAddr;
+ devAddr.mSelector = kAudioHardwarePropertyDevices;
+ devAddr.mScope = kAudioObjectPropertyScopeGlobal;
+ devAddr.mElement = kAudioObjectPropertyElementMaster;
+
+ /* 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;
+ }
+ }
+ return ret;
+}
+
+int audiounit_register_device_collection_changed(cubeb * context,
+ cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ OSStatus ret;
+ auto_lock lock(context->mutex);
+ if (collection_changed_callback) {
+ ret = audiounit_add_device_listener(context, devtype,
+ collection_changed_callback,
+ user_ptr);
+ } else {
+ ret = audiounit_remove_device_listener(context);
+ }
+ 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
+};
diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp
new file mode 100644
index 0000000000..8f995da661
--- /dev/null
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -0,0 +1,1047 @@
+/*
+ * Copyright © 2012 David Richards
+ * Copyright © 2013 Sebastien Alaiwan
+ * Copyright © 2016 Damien Zammit
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#define _POSIX_SOURCE
+#include <algorithm>
+#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 <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) \
+ X(jack_set_info_function)
+
+#define IMPORT_FUNC(x) static decltype(x) * api_##x;
+JACK_API_VISIT(IMPORT_FUNC);
+
+static const int MAX_STREAMS = 16;
+static const int MAX_CHANNELS = 8;
+static const int FIFO_SIZE = 4096 * sizeof(float);
+
+enum devstream {
+ NONE = 0,
+ IN_ONLY,
+ OUT_ONLY,
+ DUPLEX,
+};
+
+static void
+s16ne_to_float(float * dst, const int16_t * src, size_t n)
+{
+ for (size_t i = 0; i < n; i++)
+ *(dst++) = (float)((float)*(src++) / 32767.0f);
+}
+
+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;
+ *(dst++) = (int16_t)((int16_t)(*(src++) * 32767));
+ }
+}
+
+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 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
+};
+
+struct cubeb_stream {
+ cubeb * context;
+
+ /**< Mutex for each stream */
+ pthread_mutex_t mutex;
+
+ 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;
+
+ cubeb_resampler * resampler;
+
+ uint64_t position;
+ bool pause;
+ float ratio;
+ enum devstream devs;
+ char stream_name[256];
+ jack_port_t * output_ports[MAX_CHANNELS];
+ jack_port_t * input_ports[MAX_CHANNELS];
+ float volume;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libjack;
+
+ /**< Mutex for whole context */
+ pthread_mutex_t mutex;
+
+ /**< Audio buffers, converted to float */
+ float in_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS];
+ float out_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS];
+
+ /**< Audio buffer, at the sampling rate of the output */
+ float in_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3];
+ int16_t in_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3];
+ float out_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3];
+ int16_t out_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3];
+
+ cubeb_stream streams[MAX_STREAMS];
+ unsigned int active_streams;
+
+ cubeb_device_info * devinfo[2];
+ cubeb_device_collection_changed_callback collection_changed_callback;
+
+ bool active;
+ unsigned int jack_sample_rate;
+ unsigned int jack_latency;
+ unsigned int jack_xruns;
+ unsigned int jack_buffer_size;
+ unsigned int fragment_size;
+ unsigned int output_bytes_per_frame;
+ jack_client_t * jack_client;
+};
+
+static int
+load_jack_lib(cubeb * context)
+{
+#ifdef __APPLE__
+ 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
+#else
+ context->libjack = dlopen("libjack.so.0", 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; \
+ } \
+ }
+
+ JACK_API_VISIT(LOAD);
+#undef LOAD
+
+ return CUBEB_OK;
+}
+
+static void
+cbjack_connect_ports (cubeb_stream * stream)
+{
+ 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) {
+ 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]);
+
+ api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]);
+ }
+
+skipplayback:
+ if (*phys_out_ports == NULL) {
+ 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);
+ }
+end:
+ api_jack_free(phys_out_ports);
+ api_jack_free(phys_in_ports);
+}
+
+static int
+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;
+ return 0;
+}
+
+static int
+cbjack_graph_order_callback(void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+ int i;
+ jack_latency_range_t latency_range;
+ jack_nframes_t port_latency, max_latency = 0;
+
+ for (int j = 0; j < MAX_STREAMS; j++) {
+ cubeb_stream *stm = &ctx->streams[j];
+
+ if (!stm->in_use)
+ continue;
+ if (!stm->ports_ready)
+ continue;
+
+ for (i = 0; i < (int)stm->out_params.channels; ++i) {
+ 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;
+ }
+ /* Cap minimum latency to 128 frames */
+ if (max_latency < 128)
+ max_latency = 128;
+ }
+
+ ctx->jack_latency = max_latency;
+
+ return 0;
+}
+
+static int
+cbjack_process(jack_nframes_t nframes, void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+ int t_jack_xruns = ctx->jack_xruns;
+ int i;
+
+ 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];
+
+ 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;
+
+ if (!stm->ports_ready)
+ continue;
+
+ 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);
+ }
+ 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);
+ }
+ 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];
+ for (long f = 0; f < nframes; f++) {
+ buffer_out[f] = 0.f;
+ }
+ }
+ }
+ 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];
+ for (long f = 0; f < nframes; f++) {
+ buffer_in[f] = 0.f;
+ }
+ }
+ }
+ } else {
+
+ // 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;
+
+ // 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);
+ } 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);
+ }
+ } 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);
+ } 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);
+ }
+ } else if (stm->devs == OUT_ONLY) {
+ if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
+ 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);
+ }
+ }
+ // unlock stream mutex
+ pthread_mutex_unlock(&stm->mutex);
+
+ } else {
+ // could not lock mutex
+ // output silence
+ if (stm->devs & OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ float* buffer_out = bufs_out[c];
+ for (long f = 0; f < nframes; f++) {
+ buffer_out[f] = 0.f;
+ }
+ }
+ }
+ if (stm->devs & IN_ONLY) {
+ // capture silence
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ float* buffer_in = bufs_in[c];
+ for (long f = 0; f < nframes; f++) {
+ buffer_in[f] = 0.f;
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+static void
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes)
+{
+ float * out_interleaved_buffer = nullptr;
+
+ float * inptr = (in != NULL) ? *in : nullptr;
+ float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
+
+ long needed_frames = (bufs_out != NULL) ? nframes : 0;
+ 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);
+
+ 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];
+ for (long f = 0; f < done_frames; f++) {
+ buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ }
+ if (done_frames < needed_frames) {
+ // draining
+ for (long f = done_frames; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ if (done_frames == 0) {
+ // stop, but first zero out the existing buffer
+ for (long f = 0; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ }
+
+ if (done_frames >= 0 && done_frames < needed_frames) {
+ // set drained
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ // stop stream
+ cbjack_stream_stop(stream);
+ }
+ if (done_frames > 0 && done_frames <= needed_frames) {
+ // advance stream position
+ stream->position += done_frames * stream->ratio;
+ }
+ if (done_frames < 0 || done_frames > needed_frames) {
+ // stream error
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR);
+ }
+}
+
+static void
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes)
+{
+ float * out_interleaved_buffer = nullptr;
+
+ short * inptr = (in != NULL) ? *in : nullptr;
+ float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
+
+ long needed_frames = (bufs_out != NULL) ? nframes : 0;
+ 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);
+
+ 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;
+
+ 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];
+ for (long f = 0; f < done_frames; f++) {
+ buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ }
+ if (done_frames < needed_frames) {
+ // draining
+ for (long f = done_frames; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ if (done_frames == 0) {
+ // stop, but first zero out the existing buffer
+ for (long f = 0; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ }
+
+ if (done_frames >= 0 && done_frames < needed_frames) {
+ // set drained
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ // stop stream
+ cbjack_stream_stop(stream);
+ }
+ if (done_frames > 0 && done_frames <= needed_frames) {
+ // advance stream position
+ stream->position += done_frames * stream->ratio;
+ }
+ if (done_frames < 0 || done_frames > needed_frames) {
+ // stream error
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR);
+ }
+}
+
+static void
+cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch)
+{
+ 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;
+ }
+ }
+ if (format_mismatch) {
+ 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));
+ }
+}
+
+static void
+silent_jack_error_callback(char const * /*msg*/)
+{
+}
+
+/*static*/ int
+jack_init (cubeb ** context, char const * context_name)
+{
+ int r;
+
+ *context = NULL;
+
+ cubeb * ctx = (cubeb *)calloc(1, sizeof(*ctx));
+ if (ctx == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ r = load_jack_lib(ctx);
+ if (r != 0) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ api_jack_set_error_function(silent_jack_error_callback);
+ api_jack_set_info_function(silent_jack_error_callback);
+
+ ctx->ops = &cbjack_ops;
+
+ ctx->mutex = PTHREAD_MUTEX_INITIALIZER;
+ for (r = 0; r < MAX_STREAMS; r++) {
+ ctx->streams[r].mutex = PTHREAD_MUTEX_INITIALIZER;
+ }
+
+ const char * jack_client_name = "cubeb";
+ if (context_name)
+ jack_client_name = context_name;
+
+ ctx->jack_client = api_jack_client_open(jack_client_name,
+ JackNoStartServer,
+ NULL);
+
+ if (ctx->jack_client == NULL) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ 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);
+
+ if (api_jack_activate (ctx->jack_client)) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->jack_sample_rate = api_jack_get_sample_rate(ctx->jack_client);
+ ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate;
+
+ ctx->active = true;
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+cbjack_get_backend_id(cubeb * /*context*/)
+{
+ return "jack";
+}
+
+static int
+cbjack_get_max_channel_count(cubeb * /*ctx*/, uint32_t * max_channels)
+{
+ *max_channels = MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms)
+{
+ *latency_ms = stm->context->jack_latency;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/, uint32_t * latency_ms)
+{
+ *latency_ms = ctx->jack_latency;
+ return CUBEB_OK;
+}
+
+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);
+ if (!testclient) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = api_jack_get_sample_rate(testclient);
+ api_jack_client_close(testclient);
+
+ } else {
+ *rate = api_jack_get_sample_rate(ctx->jack_client);
+ }
+ return CUBEB_OK;
+}
+
+static void
+cbjack_destroy(cubeb * context)
+{
+ context->active = false;
+
+ if (context->jack_client != NULL)
+ api_jack_client_close (context->jack_client);
+
+ if (context->libjack)
+ dlclose(context->libjack);
+
+ free(context);
+}
+
+static cubeb_stream *
+context_alloc_stream(cubeb * context, char const * stream_name)
+{
+ for (int i = 0; i < MAX_STREAMS; i++) {
+ if (!context->streams[i].in_use) {
+ cubeb_stream * stm = &context->streams[i];
+ stm->in_use = true;
+ snprintf(stm->stream_name, 255, "%s_%u", stream_name, i);
+ return stm;
+ }
+ }
+ return NULL;
+}
+
+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)
+{
+ 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)
+ ) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ 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)
+ return CUBEB_ERROR_NOT_SUPPORTED;
+
+ *stream = NULL;
+
+ // Find a free stream.
+ pthread_mutex_lock(&context->mutex);
+ cubeb_stream * stm = context_alloc_stream(context, stream_name);
+
+ // No free stream?
+ if (stm == NULL) {
+ pthread_mutex_unlock(&context->mutex);
+ return CUBEB_ERROR;
+ }
+
+ // unlock context mutex
+ pthread_mutex_unlock(&context->mutex);
+
+ // Lock active stream
+ pthread_mutex_lock(&stm->mutex);
+
+ stm->ports_ready = false;
+ stm->user_ptr = user_ptr;
+ stm->context = context;
+ stm->devs = NONE;
+ if (output_stream_params && !input_stream_params) {
+ stm->out_params = *output_stream_params;
+ stream_actual_rate = stm->out_params.rate;
+ stm->out_params.rate = jack_rate;
+ stm->devs = OUT_ONLY;
+ if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ }
+ }
+ if (input_stream_params && output_stream_params) {
+ stm->in_params = *input_stream_params;
+ stm->out_params = *output_stream_params;
+ stream_actual_rate = stm->out_params.rate;
+ stm->in_params.rate = jack_rate;
+ stm->out_params.rate = jack_rate;
+ stm->devs = DUPLEX;
+ if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ stm->in_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ stm->in_params.format = CUBEB_SAMPLE_S16NE;
+ }
+ } else if (input_stream_params && !output_stream_params) {
+ stm->in_params = *input_stream_params;
+ stream_actual_rate = stm->in_params.rate;
+ stm->in_params.rate = jack_rate;
+ stm->devs = IN_ONLY;
+ if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ }
+ }
+
+ stm->ratio = (float)stream_actual_rate / (float)jack_rate;
+
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->position = 0;
+ stm->volume = 1.0f;
+ context->jack_buffer_size = api_jack_get_buffer_size(context->jack_client);
+ context->fragment_size = context->jack_buffer_size;
+
+ if (stm->devs == NONE) {
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_ERROR;
+ }
+
+ 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);
+ } 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);
+ } 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);
+ }
+
+ if (!stm->resampler) {
+ stm->in_use = false;
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_ERROR;
+ }
+
+ if (stm->devs == DUPLEX || stm->devs == OUT_ONLY) {
+ 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);
+ }
+ }
+
+ if (stm->devs == DUPLEX || stm->devs == IN_ONLY) {
+ 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);
+ }
+ }
+
+ cbjack_connect_ports(stm);
+
+ *stream = stm;
+
+ stm->ports_ready = true;
+ stm->pause = true;
+ pthread_mutex_unlock(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static void
+cbjack_stream_destroy(cubeb_stream * stream)
+{
+ pthread_mutex_lock(&stream->mutex);
+ stream->ports_ready = false;
+
+ 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]);
+ stream->output_ports[c] = NULL;
+ }
+ }
+ }
+
+ 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]);
+ stream->input_ports[c] = NULL;
+ }
+ }
+ }
+
+ if (stream->resampler) {
+ cubeb_resampler_destroy(stream->resampler);
+ stream->resampler = NULL;
+ }
+ stream->in_use = false;
+ pthread_mutex_unlock(&stream->mutex);
+}
+
+static int
+cbjack_stream_start(cubeb_stream * stream)
+{
+ stream->pause = false;
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_stop(cubeb_stream * stream)
+{
+ stream->pause = true;
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position)
+{
+ *position = stream->position;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ stm->volume = volume;
+ return CUBEB_OK;
+}
+
+
+static int
+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 * empty = "";
+
+ if (stm->devs == DUPLEX) {
+ (*device)->input_name = strdup(j_in);
+ (*device)->output_name = strdup(j_out);
+ } else if (stm->devs == IN_ONLY) {
+ (*device)->input_name = strdup(j_in);
+ (*device)->output_name = strdup(empty);
+ } else if (stm->devs == OUT_ONLY) {
+ (*device)->input_name = strdup(empty);
+ (*device)->output_name = strdup(j_out);
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
+ cubeb_device * device)
+{
+ if (device->input_name)
+ free(device->input_name);
+ if (device->output_name)
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ 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";
+
+ 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++;
+ }
+
+ 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++;
+ }
+
+ *collection = (cubeb_device_collection *)
+ malloc(sizeof(cubeb_device_collection) +
+ i * sizeof(cubeb_device_info *));
+
+ (*collection)->count = i;
+
+ for (j = 0; j < i; j++) {
+ (*collection)->device[j] = context->devinfo[j];
+ }
+ return CUBEB_OK;
+}
diff --git a/media/libcubeb/src/cubeb_log.h b/media/libcubeb/src/cubeb_log.h
new file mode 100644
index 0000000000..bca98c96fe
--- /dev/null
+++ b/media/libcubeb/src/cubeb_log.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_LOG
+#define CUBEB_LOG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
+#else
+#define PRINTF_FORMAT(fmt, args)
+#endif
+
+extern cubeb_log_level g_log_level;
+extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#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)
+
+#endif // CUBEB_LOG
diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c
new file mode 100644
index 0000000000..d44a56bd7a
--- /dev/null
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright © 2012 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 <pthread.h>
+#include <SLES/OpenSLES.h>
+#include <math.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)
+#define ANDROID_VERSION_GINGERBREAD_MR1 10
+#define ANDROID_VERSION_LOLLIPOP 21
+#define ANDROID_VERSION_MARSHMALLOW 23
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include "cubeb_resampler.h"
+#include "cubeb-sles.h"
+
+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;
+#endif
+ SLInterfaceID SL_IID_VOLUME;
+ SLObjectItf engObj;
+ SLEngineItf eng;
+ SLObjectItf outmixObj;
+};
+
+#define NELEMS(A) (sizeof(A) / sizeof A[0])
+#define NBUFS 4
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+struct cubeb_stream {
+ cubeb * context;
+ pthread_mutex_t mutex;
+ SLObjectItf playerObj;
+ SLPlayItf play;
+ SLBufferQueueItf bufq;
+ SLVolumeItf volume;
+ uint8_t *queuebuf[NBUFS];
+ int queuebuf_idx;
+ long queuebuf_len;
+ long bytespersec;
+ long framesize;
+ long written;
+ int draining;
+ cubeb_stream_type stream_type;
+
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ void * user_ptr;
+
+ cubeb_resampler * resampler;
+ unsigned int inputrate;
+ unsigned int outputrate;
+ unsigned int latency;
+ int64_t lastPosition;
+ int64_t lastPositionTimeStamp;
+ int64_t lastCompensativePosition;
+};
+
+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);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ cubeb_stream * stm = user_ptr;
+ assert(stm);
+ SLBufferQueueState state;
+ SLresult res;
+
+ res = (*stm->bufq)->GetState(stm->bufq, &state);
+ assert(res == SL_RESULT_SUCCESS);
+
+ 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;
+ }
+ }
+
+ // 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);
+ }
+
+ 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);
+ // 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;
+ }
+ }
+}
+
+#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;
+ }
+}
+#endif
+
+static void opensl_destroy(cubeb * ctx);
+
+#if defined(__ANDROID__)
+
+// The bionic header file on B2G contains the required
+// declarations on all releases.
+#ifndef MOZ_WIDGET_GONK
+
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+typedef int (system_property_get)(const char*, char*);
+
+static int
+__system_property_get(const char* name, char* value)
+{
+ 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");
+ int ret = -1;
+ if (func) {
+ ret = func(name, value);
+ }
+ dlclose(libc);
+ return ret;
+}
+#endif
+#endif
+
+static int
+get_android_version(void)
+{
+ char version_string[PROP_VALUE_MAX];
+
+ memset(version_string, 0, PROP_VALUE_MAX);
+
+ int len = __system_property_get("ro.build.version.sdk", version_string);
+ if (len <= 0) {
+ LOG("Failed to get Android version!\n");
+ return len;
+ }
+
+ int version = (int)strtol(version_string, NULL, 10);
+ LOG("%d", version);
+ return version;
+}
+#endif
+
+/*static*/ int
+opensl_init(cubeb ** context, char const * context_name)
+{
+ cubeb * ctx;
+
+#if defined(__ANDROID__)
+ int android_version = get_android_version();
+ if (android_version > 0 && android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
+ // Don't even attempt to run on Gingerbread and lower
+ return CUBEB_ERROR;
+ }
+#endif
+
+ *context = NULL;
+
+ ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+
+ ctx->ops = &opensl_ops;
+
+ ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
+ ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY);
+ if (!ctx->lib || !ctx->libmedia) {
+ 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 *);
+ 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");
+ ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
+ 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");
+#endif
+ ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
+ if (!f_slCreateEngine ||
+ !SL_IID_ENGINE ||
+ !SL_IID_OUTPUTMIX ||
+ !ctx->SL_IID_BUFFERQUEUE ||
+#if defined(__ANDROID__)
+ !ctx->SL_IID_ANDROIDCONFIGURATION ||
+#endif
+ !ctx->SL_IID_PLAY) {
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}};
+
+ SLresult res;
+ res = cubeb_get_sles_engine(&ctx->engObj, 1, opt, 0, NULL, NULL);
+
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ res = cubeb_realize_sles_engine(ctx->engObj);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
+ const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
+ res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+opensl_get_backend_id(cubeb * ctx)
+{
+ return "opensl";
+}
+
+static int
+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 */
+ *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;
+ }
+
+ /* 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;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+
+ dlclose(libmedia);
+
+ /* 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;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+{
+ /* 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;
+
+ r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate);
+
+ if (r) {
+ return CUBEB_ERROR;
+ }
+
+ libmedia = dlopen("libmedia.so", RTLD_LAZY);
+ if (!libmedia) {
+ return CUBEB_ERROR;
+ }
+
+ /* 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);
+ 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) {
+ 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;
+
+ dlclose(libmedia);
+
+ return CUBEB_OK;
+}
+
+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);
+}
+
+static void opensl_stream_destroy(cubeb_stream * 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);
+ assert(!input_stream_params && "not supported");
+ if (input_device || output_device) {
+ /* Device selection not yet implemented. */
+ return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ }
+
+ *stream = NULL;
+
+ SLDataFormat_PCM format;
+
+ 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;
+ }
+
+ 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->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);
+ stm->lastPosition = -1;
+ stm->lastPositionTimeStamp = 0;
+ stm->lastCompensativePosition = -1;
+
+ int r = pthread_mutex_init(&stm->mutex, NULL);
+ assert(r == 0);
+
+ SLDataLocator_BufferQueue loc_bufq;
+ loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+ loc_bufq.numBuffers = NBUFS;
+ SLDataSource source;
+ source.pLocator = &loc_bufq;
+ source.pFormat = &format;
+
+ SLDataLocator_OutputMix loc_outmix;
+ loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ loc_outmix.outputMix = ctx->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 SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+#else
+ const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
+ const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+#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
+
+ SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
+ if (preferred_sampling_rate) {
+ res = (*ctx->eng)->CreateAudioPlayer(ctx->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_SUCCESS) {
+ opensl_stream_destroy(stm);
+ 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->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;
+ }
+
+ int i;
+ for (i = 0; i < NBUFS; i++) {
+ stm->queuebuf[i] = malloc(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));
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
+#endif
+
+ res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE,
+ &stm->bufq);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME,
+ &stm->volume);
+
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ 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);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ {
+ // Enqueue a silent frame so once the player becomes playing, the frame
+ // 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++];
+ memset(buf, 0, stm->framesize);
+ res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize);
+ assert(res == SL_RESULT_SUCCESS);
+ }
+
+ *stream = stm;
+ return CUBEB_OK;
+}
+
+static void
+opensl_stream_destroy(cubeb_stream * stm)
+{
+ if (stm->playerObj)
+ (*stm->playerObj)->Destroy(stm->playerObj);
+ int i;
+ for (i = 0; i < NBUFS; i++) {
+ free(stm->queuebuf[i]);
+ }
+ pthread_mutex_destroy(&stm->mutex);
+
+ cubeb_resampler_destroy(stm->resampler);
+
+ free(stm);
+}
+
+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;
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_stop(cubeb_stream * stm)
+{
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS)
+ return CUBEB_ERROR;
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ return CUBEB_OK;
+}
+
+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;
+
+ res = (*stm->play)->GetPosition(stm->play, &msec);
+ if (res != SL_RESULT_SUCCESS)
+ return CUBEB_ERROR;
+
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ if(stm->lastPosition == msec) {
+ compensation_msec =
+ (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000;
+ } else {
+ 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;
+ }
+
+ pthread_mutex_lock(&stm->mutex);
+ int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate;
+ pthread_mutex_unlock(&stm->mutex);
+ assert(maximum_position >= 0);
+
+ if (msec > mixer_latency) {
+ int64_t unadjusted_position;
+ if (stm->lastCompensativePosition > msec + compensation_msec) {
+ // Over compensation, use lastCompensativePosition.
+ unadjusted_position =
+ samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000;
+ } else {
+ unadjusted_position =
+ samplerate * (msec - mixer_latency + compensation_msec) / 1000;
+ stm->lastCompensativePosition = msec + compensation_msec;
+ }
+ *position = unadjusted_position < maximum_position ?
+ unadjusted_position : maximum_position;
+ } else {
+ *position = 0;
+ }
+ return CUBEB_OK;
+}
+
+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;
+ }
+
+ *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency
+ mixer_latency * stm->inputrate / 1000; // AudioFlinger latency
+
+ return CUBEB_OK;
+}
+
+int
+opensl_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ SLresult res;
+ SLmillibel max_level, millibels;
+ float unclamped_millibels;
+
+ res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level);
+
+ if (res != SL_RESULT_SUCCESS) {
+ return CUBEB_ERROR;
+ }
+
+ /* millibels are 100*dB, so the conversion from the volume's linear amplitude
+ * is 100 * 20 * log(volume). However we clamp the resulting value before
+ * passing it to lroundf() in order to prevent it from silently returning an
+ * erroneous value when the unclamped value exceeds the size of a long. */
+ unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f));
+ unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN);
+ unclamped_millibels = fminf(unclamped_millibels, max_level);
+
+ millibels = lroundf(unclamped_millibels);
+
+ res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels);
+
+ if (res != SL_RESULT_SUCCESS) {
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+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
+};
diff --git a/media/libcubeb/src/cubeb_osx_run_loop.c b/media/libcubeb/src/cubeb_osx_run_loop.c
new file mode 100644
index 0000000000..0ba9536560
--- /dev/null
+++ b/media/libcubeb/src/cubeb_osx_run_loop.c
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSXRunLoopSingleton.h"
+
+void cubeb_set_coreaudio_notification_runloop()
+{
+ mozilla_set_coreaudio_notification_runloop_if_needed();
+}
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..78cd68d09b
--- /dev/null
+++ b/media/libcubeb/src/cubeb_osx_run_loop.h
@@ -0,0 +1,22 @@
+/*
+ * 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_panner.cpp b/media/libcubeb/src/cubeb_panner.cpp
new file mode 100644
index 0000000000..bd96ed6ef0
--- /dev/null
+++ b/media/libcubeb/src/cubeb_panner.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <stdint.h>
+
+#include "cubeb_panner.h"
+
+#ifndef M_PI
+#define M_PI 3.14159263
+#endif
+
+/**
+ * We use a cos/sin law.
+ */
+
+namespace {
+template<typename T>
+void cubeb_pan_stereo_buffer(T * buf, uint32_t frames, float pan)
+{
+ if (pan == 0.0) {
+ return;
+ }
+ /* rescale in [0; 1] */
+ pan += 1;
+ pan /= 2;
+ float left_gain = float(cos(pan * M_PI * 0.5));
+ float right_gain = float(sin(pan * M_PI * 0.5));
+
+ /* In we are panning on the left, pan the right channel into the left one and
+ * vice-versa. */
+ if (pan < 0.5) {
+ for (uint32_t i = 0; i < frames * 2; i+=2) {
+ buf[i] = T(buf[i] + buf[i + 1] * left_gain);
+ buf[i + 1] = T(buf[i + 1] * right_gain);
+ }
+ } else {
+ for (uint32_t i = 0; i < frames * 2; i+=2) {
+ buf[i] = T(buf[i] * left_gain);
+ buf[i + 1] = T(buf[i + 1] + buf[i] * right_gain);
+ }
+ }
+}
+}
+
+void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan)
+{
+ cubeb_pan_stereo_buffer(buf, frames, pan);
+}
+
+void cubeb_pan_stereo_buffer_int(short * buf, uint32_t frames, float pan)
+{
+ cubeb_pan_stereo_buffer(buf, frames, pan);
+}
+
diff --git a/media/libcubeb/src/cubeb_panner.h b/media/libcubeb/src/cubeb_panner.h
new file mode 100644
index 0000000000..c61363b2bc
--- /dev/null
+++ b/media/libcubeb/src/cubeb_panner.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_PANNER)
+#define CUBEB_PANNER
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Pan an integer or an float stereo buffer according to a cos/sin pan law
+ * @param buf the buffer to pan
+ * @param frames the number of frames in `buf`
+ * @param pan a float in [-1.0; 1.0]
+ */
+void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan);
+void cubeb_pan_stereo_buffer_int(short* buf, uint32_t frames, float pan);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c
new file mode 100644
index 0000000000..4f474452d4
--- /dev/null
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -0,0 +1,1385 @@
+/*
+ * 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
new file mode 100644
index 0000000000..f6676946c0
--- /dev/null
+++ b/media/libcubeb/src/cubeb_resampler.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright © 2014 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 <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"
+
+int
+to_speex_quality(cubeb_resampler_quality q)
+{
+ switch(q) {
+ case CUBEB_RESAMPLER_QUALITY_VOIP:
+ return SPEEX_RESAMPLER_QUALITY_VOIP;
+ case CUBEB_RESAMPLER_QUALITY_DEFAULT:
+ return SPEEX_RESAMPLER_QUALITY_DEFAULT;
+ case CUBEB_RESAMPLER_QUALITY_DESKTOP:
+ return SPEEX_RESAMPLER_QUALITY_DESKTOP;
+ default:
+ assert(false);
+ return 0XFFFFFFFF;
+ }
+}
+
+long noop_resampler::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);
+ output_frames = *input_frames_count;
+ }
+
+ if (input_buffer && *input_frames_count != output_frames) {
+ assert(*input_frames_count > output_frames);
+ *input_frames_count = output_frames;
+ }
+
+ return data_callback(stream, user_ptr,
+ input_buffer, output_buffer, output_frames);
+}
+
+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) {
+ fill_internal = &cubeb_resampler_speex::fill_internal_input;
+ } 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>
+long
+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);
+}
+
+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)
+{
+ 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;
+
+
+ /* 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);
+
+ got = data_callback(stream, user_ptr,
+ nullptr, out_unprocessed,
+ output_frames_before_processing);
+
+ if (got < 0) {
+ return got;
+ }
+
+ output_processor->written(got);
+
+ /* Process the output. If not enough frames have been returned from the
+ * callback, drain the processors. */
+ return output_processor->output(output_buffer, output_frames_needed);
+}
+
+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*/)
+{
+ assert(input_buffer && input_frames_count && *input_frames_count &&
+ !output_buffer);
+
+ /* 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);
+
+ /* process the input, and present exactly `output_frames_needed` in the
+ * 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);
+
+ /* 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. */
+ return (*input_frames_count) * (got / resampled_frame_count);
+}
+
+
+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)
+{
+ /* 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;
+ /* The number of frames returned from the callback. */
+ long got = 0;
+
+ /* We need to determine how much frames to present to the consumer.
+ * - If we have a two way stream, but we're only resampling input, we resample
+ * the input to the number of output frames.
+ * - If we have a two way stream, but we're only resampling the output, we
+ * resize the input buffer of the output resampler to the number of input
+ * frames, and we resample it afterwards.
+ * - If we resample both ways, we resample the input to the number of frames
+ * we would need to pass down to the consumer (before resampling the output),
+ * get the output data, and resample it to the number of frames needed by the
+ * 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 */
+ out_unprocessed =
+ output_processor->input_buffer(output_frames_before_processing);
+
+ if (in_buffer) {
+ /* process the input, and present exactly `output_frames_needed` in the
+ * callback. */
+ input_processor->input(in_buffer, *input_frames_count);
+ resampled_input =
+ input_processor->output(output_frames_before_processing);
+ } else {
+ resampled_input = nullptr;
+ }
+
+ got = data_callback(stream, user_ptr,
+ resampled_input, out_unprocessed,
+ output_frames_before_processing);
+
+ if (got < 0) {
+ return got;
+ }
+
+ output_processor->written(got);
+
+ /* 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);
+}
+
+/* Resampler C API */
+
+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_sample_format format;
+
+ assert(input_params || output_params);
+
+ if (input_params) {
+ format = input_params->format;
+ } else {
+ 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;
+ }
+}
+
+long
+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);
+}
+
+void
+cubeb_resampler_destroy(cubeb_resampler * resampler)
+{
+ delete resampler;
+}
+
+long
+cubeb_resampler_latency(cubeb_resampler * resampler)
+{
+ return resampler->latency();
+}
diff --git a/media/libcubeb/src/cubeb_resampler.h b/media/libcubeb/src/cubeb_resampler.h
new file mode 100644
index 0000000000..020ccc17ab
--- /dev/null
+++ b/media/libcubeb/src/cubeb_resampler.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifndef CUBEB_RESAMPLER_H
+#define CUBEB_RESAMPLER_H
+
+#include "cubeb/cubeb.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct cubeb_resampler cubeb_resampler;
+
+typedef enum {
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_QUALITY_DEFAULT,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP
+} cubeb_resampler_quality;
+
+/**
+ * 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 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);
+
+/**
+ * Fill the buffer with frames acquired using the data callback. Resampling will
+ * happen if necessary.
+ * @param resampler A cubeb_resampler instance.
+ * @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.
+ * @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);
+
+/**
+ * Destroy a cubeb_resampler.
+ * @param resampler A cubeb_resampler instance.
+ */
+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);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CUBEB_RESAMPLER_H */
diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h
new file mode 100644
index 0000000000..3c37a04b9c
--- /dev/null
+++ b/media/libcubeb/src/cubeb_resampler_internal.h
@@ -0,0 +1,551 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_RESAMPLER_INTERNAL)
+#define CUBEB_RESAMPLER_INTERNAL
+
+#include <cmath>
+#include <cassert>
+#include <algorithm>
+#include <memory>
+#ifdef CUBEB_GECKO_BUILD
+#include "mozilla/UniquePtr.h"
+// In libc++, symbols such as std::unique_ptr may be defined in std::__1.
+// The _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros
+// will expand to the correct namespace.
+#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
+#define MOZ_BEGIN_STD_NAMESPACE _LIBCPP_BEGIN_NAMESPACE_STD
+#define MOZ_END_STD_NAMESPACE _LIBCPP_END_NAMESPACE_STD
+#else
+#define MOZ_BEGIN_STD_NAMESPACE namespace std {
+#define MOZ_END_STD_NAMESPACE }
+#endif
+MOZ_BEGIN_STD_NAMESPACE
+ 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_resampler.h"
+#include <stdio.h>
+
+/* This header file contains the internal C++ API of the resamplers, for testing. */
+
+int to_speex_quality(cubeb_resampler_quality q);
+
+struct cubeb_resampler {
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long frames_needed) = 0;
+ virtual long latency() = 0;
+ virtual ~cubeb_resampler() {}
+};
+
+class noop_resampler : public cubeb_resampler {
+public:
+ noop_resampler(cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr)
+ : stream(s)
+ , data_callback(cb)
+ , user_ptr(ptr)
+ {
+ }
+
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames);
+
+ virtual long latency()
+ {
+ return 0;
+ }
+
+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;
+};
+
+/** 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>
+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);
+
+ virtual ~cubeb_resampler_speex();
+
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames_needed);
+
+ virtual long latency()
+ {
+ if (input_processor && output_processor) {
+ assert(input_processor->latency() == output_processor->latency());
+ return input_processor->latency();
+ } else if (input_processor) {
+ return input_processor->latency();
+ } else {
+ return output_processor->latency();
+ }
+ }
+
+private:
+ 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);
+ long fill_internal_input(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+ long fill_internal_output(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+
+ std::unique_ptr<InputProcessing> input_processor;
+ std::unique_ptr<OutputProcessing> output_processor;
+ processing_callback fill_internal;
+ cubeb_stream * const stream;
+ const cubeb_data_callback data_callback;
+ void * const user_ptr;
+};
+
+/** 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 {
+public:
+ /** The sample type of this resampler, either 16-bit integers or 32-bit
+ * floats. */
+ typedef T sample_type;
+ /** Construct a resampler resampling from #source_rate to #target_rate, that
+ * can be arbitrary, strictly positive number.
+ * @parameter channels The number of channels this resampler will resample.
+ * @parameter source_rate The sample-rate of the audio input.
+ * @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)
+ {
+ int r;
+ speex_resampler = speex_resampler_init(channels, source_rate,
+ target_rate, quality, &r);
+ assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
+ }
+
+ /** Destructor, deallocate the resampler */
+ virtual ~cubeb_resampler_speex_one_way()
+ {
+ 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)
+ {
+ resampling_in_buffer.push(input_buffer,
+ frames_to_samples(input_frame_count));
+ }
+
+ /** Outputs exactly `output_frame_count` into `output_buffer`.
+ * `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);
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+
+ return out_len;
+ }
+
+ size_t output_for_input(uint32_t input_frames)
+ {
+ 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)
+ {
+ if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
+ resampling_out_buffer.reserve(frames_to_samples(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,
+ resampling_out_buffer.data(), &out_len);
+
+ assert(out_len == output_frame_count);
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+
+ return resampling_out_buffer.data();
+ }
+
+ /** Get the latency of the resampler, in output frames. */
+ uint32_t latency() const
+ {
+ /* The documentation of the resampler talks about "samples" here, but it
+ * 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;
+
+ assert(latency >= 0);
+
+ return latency;
+ }
+
+ /** Returns the number of frames to pass in the input of the resampler to have
+ * 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)
+ {
+ 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
+ * least `frame_count` elements. This is useful so that consumer can directly
+ * write into the input buffer of the resampler. The pointer returned is
+ * adjusted so that leftover data are not overwritten.
+ */
+ T * input_buffer(size_t frame_count)
+ {
+ leftover_samples = resampling_in_buffer.length();
+ resampling_in_buffer.reserve(leftover_samples +
+ frames_to_samples(frame_count));
+ return resampling_in_buffer.data() + leftover_samples;
+ }
+
+ /** This method works with `input_buffer`, and allows to inform the processor
+ how much frames have been written in the provided buffer. */
+ void written(size_t written_frames)
+ {
+ resampling_in_buffer.set_length(leftover_samples +
+ frames_to_samples(written_frames));
+ }
+private:
+ /** Wrapper for the speex resampling functions to have a typed
+ * interface. */
+ void speex_resample(float * input_buffer, uint32_t * input_frame_count,
+ float * output_buffer, uint32_t * output_frame_count)
+ {
+#ifndef NDEBUG
+ int rv;
+ rv =
+#endif
+ speex_resampler_process_interleaved_float(speex_resampler,
+ input_buffer,
+ input_frame_count,
+ output_buffer,
+ output_frame_count);
+ assert(rv == RESAMPLER_ERR_SUCCESS);
+ }
+
+ void speex_resample(short * input_buffer, uint32_t * input_frame_count,
+ short * output_buffer, uint32_t * output_frame_count)
+ {
+#ifndef NDEBUG
+ int rv;
+ rv =
+#endif
+ 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;
+ /** Storage for the input frames, to be resampled. Also contains
+ * any unresampled frames after resampling. */
+ auto_array<T> resampling_in_buffer;
+ /* Storage for the resampled frames, to be passed back to the caller. */
+ auto_array<T> resampling_out_buffer;
+ /** Additional latency inserted into the pipeline for synchronisation. */
+ uint32_t additional_latency;
+ /** When `input_buffer` is called, this allows tracking the number of samples
+ that were in the buffer. */
+ uint32_t leftover_samples;
+};
+
+/** This class allows delaying an audio stream by `frames` frames. */
+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)
+ {
+ /* 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. */
+ void input(T * buffer, uint32_t frame_count)
+ {
+ delay_input_buffer.push(buffer, frames_to_samples(frame_count));
+ }
+ /** Pop some frames from the internal buffer, into a internal output buffer.
+ * @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)
+ {
+ if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
+ delay_output_buffer.reserve(frames_to_samples(frames_needed));
+ }
+
+ delay_output_buffer.clear();
+ delay_output_buffer.push(delay_input_buffer.data(),
+ frames_to_samples(frames_needed));
+ delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
+
+ return delay_output_buffer.data();
+ }
+ /** Get a pointer to the first writable location in the input buffer>
+ * @parameter frames_needed the number of frames the user needs to write into
+ * the buffer.
+ * @returns a pointer to a location in the input buffer where #frames_needed
+ * can be writen. */
+ T * input_buffer(uint32_t frames_needed)
+ {
+ leftover_samples = delay_input_buffer.length();
+ 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
+ how much frames have been written in the provided buffer. */
+ void written(size_t frames_written)
+ {
+ delay_input_buffer.set_length(leftover_samples +
+ frames_to_samples(frames_written));
+ }
+ /** Drains the delay line, emptying the buffer.
+ * @parameter output_buffer the buffer in which the frames are written.
+ * @parameter frames_needed the maximum number of frames to write.
+ * @return the actual number of frames written. */
+ size_t output(T * output_buffer, uint32_t frames_needed)
+ {
+ uint32_t in_len = samples_to_frames(delay_input_buffer.length());
+ uint32_t out_len = frames_needed;
+
+ uint32_t to_pop = std::min(in_len, out_len);
+
+ delay_input_buffer.pop(output_buffer, frames_to_samples(to_pop));
+
+ return to_pop;
+ }
+ /** Returns the number of frames one needs to input into the delay line to get
+ * #frames_needed frames back.
+ * @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)
+ {
+ 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;
+ }
+ /** The number of frames this delay line delays the stream by.
+ * @returns The number of frames of delay. */
+ size_t latency()
+ {
+ return length;
+ }
+private:
+ /** The length, in frames, of this delay line */
+ uint32_t length;
+ /** When `input_buffer` is called, this allows tracking the number of samples
+ that where in the buffer. */
+ uint32_t leftover_samples;
+ /** The input buffer, where the delay is applied. */
+ auto_array<T> delay_input_buffer;
+ /** The output buffer. This is only ever used if using the ::output with a
+ * single argument. */
+ auto_array<T> delay_output_buffer;
+};
+
+/** This sits behind the C API and is more typed. */
+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_resampler_quality quality)
+{
+ std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
+ std::unique_ptr<cubeb_resampler_speex_one_way<T>> output_resampler = nullptr;
+ std::unique_ptr<delay_line<T>> input_delay = nullptr;
+ std::unique_ptr<delay_line<T>> output_delay = nullptr;
+
+ assert((input_params || output_params) &&
+ "need at least one valid parameter pointer.");
+
+ /* All the streams we have have a sample rate that matches the target
+ 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)) ||
+ (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);
+ }
+
+ /* 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)));
+ 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)));
+ if (!input_resampler) {
+ return NULL;
+ }
+ }
+
+ /* If we resample only one direction but we have a duplex stream, insert a
+ * delay line with a length equal to the resampler latency of the
+ * 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));
+ if (!output_delay) {
+ return NULL;
+ }
+ } else if (output_resampler && !input_resampler && input_params && output_params) {
+ input_delay.reset(new delay_line<T>(output_resampler->latency(),
+ input_params->channels));
+ 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);
+ } 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);
+ } 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);
+ }
+}
+
+#endif /* CUBEB_RESAMPLER_INTERNAL */
diff --git a/media/libcubeb/src/cubeb_ring_array.h b/media/libcubeb/src/cubeb_ring_array.h
new file mode 100644
index 0000000000..51b3b321a3
--- /dev/null
+++ b/media/libcubeb/src/cubeb_ring_array.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_RING_ARRAY_H
+#define CUBEB_RING_ARRAY_H
+
+#include "cubeb_utils.h"
+
+/** Ring array of pointers is used to hold buffers. In case that
+ asynchronous producer/consumer callbacks do not arrive in a
+ repeated order the ring array stores the buffers and fetch
+ 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. */
+} ring_array;
+
+static int
+single_audiobuffer_init(AudioBuffer * buffer,
+ uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame,
+ uint32_t frames)
+{
+ assert(buffer);
+ assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
+
+ size_t size = bytesPerFrame * frames;
+ buffer->mData = operator new(size);
+ if (buffer->mData == NULL) {
+ return CUBEB_ERROR;
+ }
+ PodZero(static_cast<char*>(buffer->mData), size);
+
+ buffer->mNumberChannels = channelsPerFrame;
+ buffer->mDataByteSize = size;
+
+ return CUBEB_OK;
+}
+
+/** Initialize the ring array.
+ @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)
+{
+ assert(ra);
+ if (capacity == 0 || bytesPerFrame == 0 ||
+ channelsPerFrame == 0 || framesPerBuffer == 0) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ ra->capacity = capacity;
+ ra->tail = 0;
+ ra->count = 0;
+
+ ra->buffer_array = new AudioBuffer[ra->capacity];
+ PodZero(ra->buffer_array, ra->capacity);
+ if (ra->buffer_array == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ for (unsigned int i = 0; i < ra->capacity; ++i) {
+ if (single_audiobuffer_init(&ra->buffer_array[i],
+ bytesPerFrame,
+ channelsPerFrame,
+ framesPerBuffer) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+ }
+
+ return CUBEB_OK;
+}
+
+/** Destroy the ring array.
+ @param ra The ring_array pointer.*/
+void
+ring_array_destroy(ring_array * ra)
+{
+ assert(ra);
+ if (ra->buffer_array == NULL){
+ return;
+ }
+ for (unsigned int i = 0; i < ra->capacity; ++i) {
+ if (ra->buffer_array[i].mData) {
+ operator delete(ra->buffer_array[i].mData);
+ }
+ }
+ 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. */
+AudioBuffer *
+ring_array_get_free_buffer(ring_array * ra)
+{
+ assert(ra && ra->buffer_array);
+ assert(ra->buffer_array[0].mData != NULL);
+ if (ra->count == ra->capacity) {
+ return NULL;
+ }
+
+ assert(ra->count == 0 || (ra->tail + ra->count) % ra->capacity != ra->tail);
+ AudioBuffer * ret = &ra->buffer_array[(ra->tail + ra->count) % ra->capacity];
+
+ ++ra->count;
+ assert(ra->count <= ra->capacity);
+
+ return ret;
+}
+
+/** Get the next available buffer with data.
+ @param ra The ring_array pointer.
+ @retval Pointer of the next in order data buffer or NULL if empty. */
+AudioBuffer *
+ring_array_get_data_buffer(ring_array * ra)
+{
+ assert(ra && ra->buffer_array);
+ assert(ra->buffer_array[0].mData != NULL);
+
+ if (ra->count == 0) {
+ return NULL;
+ }
+ AudioBuffer * ret = &ra->buffer_array[ra->tail];
+
+ ra->tail = (ra->tail + 1) % ra->capacity;
+ assert(ra->tail < ra->capacity);
+
+ assert(ra->count > 0);
+ --ra->count;
+
+ return ret;
+}
+
+/** When array is empty get the first allocated buffer in the array.
+ @param ra The ring_array pointer.
+ @retval If arrays is empty, pointer of the allocated space else NULL. */
+AudioBuffer *
+ring_array_get_dummy_buffer(ring_array * ra)
+{
+ assert(ra && ra->buffer_array);
+ assert(ra->capacity > 0);
+ if (ra->count > 0) {
+ return NULL;
+ }
+ return &ra->buffer_array[0];
+}
+
+#endif //CUBEB_RING_ARRAY_H
diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c
new file mode 100644
index 0000000000..7937897658
--- /dev/null
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#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"
+
+#if defined(CUBEB_SNDIO_DEBUG)
+#define DPR(...) fprintf(stderr, __VA_ARGS__);
+#else
+#define DPR(...) do {} while(0)
+#endif
+
+static struct cubeb_ops const sndio_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+};
+
+struct cubeb_stream {
+ 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 */
+};
+
+static void
+float_to_s16(void *ptr, long nsamp)
+{
+ int16_t *dst = ptr;
+ float *src = ptr;
+ int s;
+
+ while (nsamp-- > 0) {
+ s = lrintf(*(src++) * 32768);
+ if (s < -32768)
+ s = -32768;
+ else if (s > 32767)
+ s = 32767;
+ *(dst++) = s;
+ }
+}
+
+static void
+sndio_onmove(void *arg, int delta)
+{
+ cubeb_stream *s = (cubeb_stream *)arg;
+
+ s->rdpos += delta * s->bpf;
+}
+
+static void *
+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;
+ long nfr;
+
+ DPR("sndio_mainloop()\n");
+ s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
+ pthread_mutex_lock(&s->mtx);
+ if (!sio_start(s->hdl)) {
+ pthread_mutex_unlock(&s->mtx);
+ return NULL;
+ }
+ DPR("sndio_mainloop(), started\n");
+
+ start = end = s->nfr;
+ for (;;) {
+ if (!s->active) {
+ DPR("sndio_mainloop() stopped\n");
+ state = CUBEB_STATE_STOPPED;
+ break;
+ }
+ if (start == end) {
+ if (end < s->nfr) {
+ DPR("sndio_mainloop() drained\n");
+ state = CUBEB_STATE_DRAINED;
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+ nfr = s->data_cb(s, s->arg, NULL, s->buf, 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;
+ }
+ if (end == 0)
+ continue;
+ nfds = sio_pollfd(s->hdl, pfds, POLLOUT);
+ if (nfds > 0) {
+ pthread_mutex_unlock(&s->mtx);
+ n = poll(pfds, nfds, -1);
+ pthread_mutex_lock(&s->mtx);
+ if (n < 0)
+ continue;
+ }
+ revents = sio_revents(s->hdl, pfds);
+ if (revents & POLLHUP)
+ break;
+ if (revents & POLLOUT) {
+ n = sio_write(s->hdl, s->buf + start, end - start);
+ if (n == 0) {
+ DPR("sndio_mainloop() werr\n");
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ s->wrpos += n;
+ start += n;
+ }
+ }
+ sio_stop(s->hdl);
+ s->rdpos = s->wrpos;
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->arg, state);
+ return NULL;
+}
+
+/*static*/ int
+sndio_init(cubeb **context, char const *context_name)
+{
+ DPR("sndio_init(%s)\n", context_name);
+ *context = malloc(sizeof(*context));
+ (*context)->ops = &sndio_ops;
+ (void)context_name;
+ return CUBEB_OK;
+}
+
+static char const *
+sndio_get_backend_id(cubeb *context)
+{
+ return "sndio";
+}
+
+static void
+sndio_destroy(cubeb *context)
+{
+ DPR("sndio_destroy()\n");
+ free(context);
+}
+
+static int
+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_stream *s;
+ struct sio_par wpar, rpar;
+ DPR("sndio_stream_init(%s)\n", stream_name);
+ size_t size;
+
+ assert(!input_stream_params && "not supported.");
+ if (input_device || output_device) {
+ /* Device selection not yet implemented. */
+ return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ }
+
+ s = malloc(sizeof(cubeb_stream));
+ if (s == NULL)
+ return CUBEB_ERROR;
+ s->context = context;
+ s->hdl = sio_open(NULL, SIO_PLAY, 1);
+ if (s->hdl == NULL) {
+ free(s);
+ DPR("sndio_stream_init(), sio_open() failed\n");
+ return CUBEB_ERROR;
+ }
+ sio_initpar(&wpar);
+ wpar.sig = 1;
+ wpar.bits = 16;
+ switch (output_stream_params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ wpar.le = 1;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ wpar.le = 0;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ wpar.le = SIO_LE_NATIVE;
+ break;
+ default:
+ DPR("sndio_stream_init() unsupported format\n");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ wpar.rate = output_stream_params->rate;
+ 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);
+ DPR("sndio_stream_init(), sio_setpar() failed\n");
+ return CUBEB_ERROR;
+ }
+ 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);
+ DPR("sndio_stream_init() unsupported params\n");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ sio_onmove(s->hdl, sndio_onmove, s);
+ s->active = 0;
+ s->nfr = rpar.round;
+ s->bpf = rpar.bps * rpar.pchan;
+ s->pchan = rpar.pchan;
+ s->data_cb = data_callback;
+ s->state_cb = state_callback;
+ s->arg = user_ptr;
+ s->mtx = PTHREAD_MUTEX_INITIALIZER;
+ s->rdpos = s->wrpos = 0;
+ if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
+ s->conv = 1;
+ size = rpar.round * rpar.pchan * sizeof(float);
+ } else {
+ s->conv = 0;
+ size = rpar.round * rpar.pchan * rpar.bps;
+ }
+ s->buf = malloc(size);
+ if (s->buf == NULL) {
+ sio_close(s->hdl);
+ free(s);
+ return CUBEB_ERROR;
+ }
+ *stream = s;
+ DPR("sndio_stream_init() end, ok\n");
+ (void)context;
+ (void)stream_name;
+ return CUBEB_OK;
+}
+
+static int
+sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+
+ *max_channels = 8;
+
+ return CUBEB_OK;
+}
+
+static int
+sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ // XXX Not yet implemented.
+ *rate = 44100;
+
+ return CUBEB_OK;
+}
+
+static int
+sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+{
+ // XXX Not yet implemented.
+ *latency_frames = 2048;
+
+ return CUBEB_OK;
+}
+
+static void
+sndio_stream_destroy(cubeb_stream *s)
+{
+ DPR("sndio_stream_destroy()\n");
+ sio_close(s->hdl);
+ free(s);
+}
+
+static int
+sndio_stream_start(cubeb_stream *s)
+{
+ int err;
+
+ DPR("sndio_stream_start()\n");
+ s->active = 1;
+ err = pthread_create(&s->th, NULL, sndio_mainloop, s);
+ if (err) {
+ s->active = 0;
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+sndio_stream_stop(cubeb_stream *s)
+{
+ void *dummy;
+
+ DPR("sndio_stream_stop()\n");
+ if (s->active) {
+ s->active = 0;
+ pthread_join(s->th, &dummy);
+ }
+ return CUBEB_OK;
+}
+
+static int
+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;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+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);
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+int
+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;
+ 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
+};
diff --git a/media/libcubeb/src/cubeb_utils.h b/media/libcubeb/src/cubeb_utils.h
new file mode 100644
index 0000000000..d8e9928fe2
--- /dev/null
+++ b/media/libcubeb/src/cubeb_utils.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_UTILS)
+#define CUBEB_UTILS
+
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <type_traits>
+#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)
+{
+ static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ 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)
+{
+ static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ 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)
+{
+ static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ memset(destination, 0, count * sizeof(T));
+}
+
+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()
+ {
+ delete [] data_;
+ }
+
+ /** Get a constant pointer to the underlying data. */
+ T * data() const
+ {
+ return data_;
+ }
+
+ const T& at(size_t index) const
+ {
+ assert(index < length_ && "out of range");
+ return data_[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_;
+ }
+
+ /** Get how much elements this auto_array contains. */
+ size_t length() const
+ {
+ return length_;
+ }
+
+ /** Keeps the storage, but removes all the elements from the array. */
+ 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.
+ */
+ bool reserve(size_t new_capacity)
+ {
+ if (new_capacity < length_) {
+ return false;
+ }
+ T * new_data = new T[new_capacity];
+ if (data_ && length_) {
+ PodCopy(new_data, data_, length_);
+ }
+ capacity_ = new_capacity;
+ 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.
+ */
+ void push(const T * elements, size_t length)
+ {
+ if (length_ + length > capacity_) {
+ reserve(length_ + length);
+ }
+ PodCopy(data_ + length_, elements, length);
+ length_ += length;
+ }
+
+ /** Append `length` zero-ed elements to the end of the array, resizing the
+ * array if needed.
+ * @parameter length the number of elements to append to the array.
+ */
+ void push_silence(size_t length)
+ {
+ if (length_ + length > capacity_) {
+ reserve(length + length_);
+ }
+ PodZero(data_ + length_, length);
+ length_ += length;
+ }
+
+ /** Prepend `length` zero-ed elements to the end of the array, resizing the
+ * array if needed.
+ * @parameter length the number of elements to prepend to the array.
+ */
+ void push_front_silence(size_t length)
+ {
+ if (length_ + length > capacity_) {
+ reserve(length + length_);
+ }
+ PodMove(data_ + length, data_, length_);
+ PodZero(data_, length);
+ length_ += length;
+ }
+
+ /** Return the number of free elements in the array. */
+ 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. */
+ bool pop(T * elements, size_t length)
+ {
+ if (length > length_) {
+ return false;
+ }
+ if (elements) {
+ PodCopy(elements, data_, length);
+ }
+ PodMove(data_, data_ + length, length_ - length);
+
+ length_ -= length;
+
+ return true;
+ }
+
+ void set_length(size_t length)
+ {
+ assert(length <= capacity_);
+ length_ = length;
+ }
+
+private:
+ /** The underlying storage */
+ T * data_;
+ /** The size, in number of elements, of the storage. */
+ size_t capacity_;
+ /** The number of elements the array contains. */
+ size_t length_;
+};
+
+struct auto_lock {
+ explicit auto_lock(owned_critical_section & lock)
+ : lock(lock)
+ {
+ lock.enter();
+ }
+ ~auto_lock()
+ {
+ lock.leave();
+ }
+private:
+ owned_critical_section & lock;
+};
+
+#endif /* CUBEB_UTILS */
diff --git a/media/libcubeb/src/cubeb_utils_unix.h b/media/libcubeb/src/cubeb_utils_unix.h
new file mode 100644
index 0000000000..80219d58b1
--- /dev/null
+++ b/media/libcubeb/src/cubeb_utils_unix.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_UTILS_UNIX)
+#define CUBEB_UTILS_UNIX
+
+#include <pthread.h>
+#include <errno.h>
+#include <stdio.h>
+
+/* This wraps a critical section to track the owner in debug mode. */
+class owned_critical_section
+{
+public:
+ owned_critical_section()
+ {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+#ifndef NDEBUG
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+#else
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+#endif
+
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_init(&mutex, &attr);
+#ifndef NDEBUG
+ assert(r == 0);
+#endif
+
+ pthread_mutexattr_destroy(&attr);
+ }
+
+ ~owned_critical_section()
+ {
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_destroy(&mutex);
+#ifndef NDEBUG
+ assert(r == 0);
+#endif
+ }
+
+ void enter()
+ {
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_lock(&mutex);
+#ifndef NDEBUG
+ assert(r == 0 && "Deadlock");
+#endif
+ }
+
+ void leave()
+ {
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_unlock(&mutex);
+#ifndef NDEBUG
+ assert(r == 0 && "Unlocking unlocked mutex");
+#endif
+ }
+
+ void assert_current_thread_owns()
+ {
+#ifndef NDEBUG
+ int r = pthread_mutex_lock(&mutex);
+ assert(r == EDEADLK);
+#endif
+ }
+
+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&);
+};
+
+#endif /* CUBEB_UTILS_UNIX */
diff --git a/media/libcubeb/src/cubeb_utils_win.h b/media/libcubeb/src/cubeb_utils_win.h
new file mode 100644
index 0000000000..2b094cd93d
--- /dev/null
+++ b/media/libcubeb/src/cubeb_utils_win.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_UTILS_WIN)
+#define CUBEB_UTILS_WIN
+
+#include <windows.h>
+#include "cubeb-internal.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
+{
+public:
+ owned_critical_section()
+#ifndef NDEBUG
+ : owner(0)
+#endif
+ {
+ InitializeCriticalSection(&critical_section);
+ }
+
+ ~owned_critical_section()
+ {
+ DeleteCriticalSection(&critical_section);
+ }
+
+ void enter()
+ {
+ EnterCriticalSection(&critical_section);
+#ifndef NDEBUG
+ XASSERT(owner != GetCurrentThreadId() && "recursive locking");
+ owner = GetCurrentThreadId();
+#endif
+ }
+
+ void leave()
+ {
+#ifndef NDEBUG
+ /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
+ owner = 0;
+#endif
+ LeaveCriticalSection(&critical_section);
+ }
+
+ /* This is guaranteed to have the good behaviour if it succeeds. The behaviour
+ is undefined otherwise. */
+ void assert_current_thread_owns()
+ {
+#ifndef NDEBUG
+ /* This implies owner != 0, because GetCurrentThreadId cannot return 0. */
+ XASSERT(owner == GetCurrentThreadId());
+#endif
+ }
+
+private:
+ CRITICAL_SECTION critical_section;
+#ifndef NDEBUG
+ DWORD owner;
+#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&);
+};
+
+#endif /* CUBEB_UTILS_WIN */
diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
new file mode 100644
index 0000000000..e88d6becd8
--- /dev/null
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -0,0 +1,2311 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define NOMINMAX
+
+#include <initguid.h>
+#include <windows.h>
+#include <mmdeviceapi.h>
+#include <windef.h>
+#include <audioclient.h>
+#include <devicetopology.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 "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
+
+/* devicetopology.h missing in MinGW. */
+#ifndef __devicetopology_h__
+#include "cubeb_devicetopology.h"
+#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
+
+#ifndef PKEY_Device_FriendlyName
+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
+#endif
+
+namespace {
+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>
+void SafeRelease(T * ptr)
+{
+ if (ptr) {
+ ptr->Release();
+ }
+}
+
+struct auto_com {
+ auto_com() {
+ result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ }
+ ~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();
+ }
+ }
+ bool ok() {
+ return result == RPC_E_CHANGED_MODE || SUCCEEDED(result);
+ }
+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);
+
+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);
+static char * wstr_to_utf8(const wchar_t * str);
+static std::unique_ptr<const wchar_t[]> utf8_to_wstr(char* str);
+
+}
+
+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;
+};
+
+class wasapi_endpoint_notification_client;
+
+/* We have three possible callbacks we can use with a stream:
+ * - input only
+ * - output only
+ * - synchronized input and output
+ *
+ * Returns true when we should continue to play, false otherwise.
+ */
+typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
+
+struct cubeb_stream
+{
+ cubeb * context;
+ /* 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;
+ /* 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;
+ /* The input and output device, or NULL for default. */
+ cubeb_devid input_device;
+ cubeb_devid 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;
+ /* Lifetime considerations:
+ - client, render_client, audio_clock and audio_stream_volume are interface
+ pointer to the IAudioClient.
+ - The lifetime for device_enumerator and notification_client, resampler,
+ mix_buffer are the same as the cubeb_stream instance. */
+
+ /* Main handle on the WASAPI stream. */
+ IAudioClient * output_client;
+ /* Interface pointer to use the event-driven interface. */
+ IAudioRenderClient * render_client;
+ /* Interface pointer to use the volume facilities. */
+ IAudioStreamVolume * audio_stream_volume;
+ /* Interface pointer to use the stream audio clock. */
+ IAudioClock * audio_clock;
+ /* Frames written to the stream since it was opened. Reset on device
+ change. Uses mix_params.rate. */
+ UINT64 frames_written;
+ /* Frames written to the (logical) stream since it was first
+ created. Updated on device change. Uses stream_params.rate. */
+ UINT64 total_frames_written;
+ /* Last valid reported stream position. Used to ensure the position
+ reported by stream_get_position increases monotonically. */
+ UINT64 prev_position;
+ /* Device enumerator to be able to be notified when the default
+ device change. */
+ 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;
+ /* Main andle to the WASAPI capture stream. */
+ IAudioClient * input_client;
+ /* Interface to use the event driven capture interface */
+ 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;
+ /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
+ The reconfiguration is handled by the render loop thread. */
+ HANDLE reconfigure_event;
+ /* This is set by WASAPI when we should refill the stream. */
+ HANDLE refill_event;
+ /* 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;
+ /* Each cubeb_stream has its own thread. */
+ HANDLE thread;
+ /* 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;
+ /* Maximum number of frames that can be requested in a callback. */
+ uint32_t output_buffer_frame_count;
+ /* Resampler instance. Resampling will only happen if necessary. */
+ cubeb_resampler * resampler;
+ /* A buffer for up/down mixing multi-channel audio. */
+ float * 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;
+ /* Stream volume. Set via stream_set_volume and used to reset volume on
+ device changes. */
+ 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:
+ /* 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) {
+ delete this;
+ }
+ return ulRef;
+ }
+
+ 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_endpoint_notification_client(HANDLE event)
+ : ref_count(1)
+ , reconfigure_event(event)
+ { }
+
+ virtual ~wasapi_endpoint_notification_client()
+ { }
+
+ HRESULT STDMETHODCALLTYPE
+ OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
+ {
+ LOG("Audio device default changed.");
+
+ /* we only support a single stream type for now. */
+ if (flow != eRender && role != eConsole) {
+ return S_OK;
+ }
+
+ BOOL ok = SetEvent(reconfigure_event);
+ if (!ok) {
+ LOG("SetEvent on reconfigure_event failed: %x", GetLastError());
+ }
+
+ 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("Audio device added.");
+ return S_OK;
+ };
+
+ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
+ {
+ LOG("Audio device removed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE
+ OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
+ {
+ LOG("Audio device state changed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE
+ OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
+ {
+ LOG("Audio device property value changed.");
+ return S_OK;
+ }
+private:
+ /* refcount for this instance, necessary to implement MSCOM semantics. */
+ LONG ref_count;
+ HANDLE reconfigure_event;
+};
+
+namespace {
+bool has_input(cubeb_stream * stm)
+{
+ return stm->input_stream_params.rate != 0;
+}
+
+bool has_output(cubeb_stream * stm)
+{
+ return stm->output_stream_params.rate != 0;
+}
+
+bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+{
+ return mixer.channels > stream.channels;
+}
+
+bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+{
+ return mixer.channels < stream.channels;
+}
+
+double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+{
+ return double(stream.rate) / mixer.rate;
+}
+
+
+uint32_t
+get_rate(cubeb_stream * stm)
+{
+ return has_input(stm) ? stm->input_stream_params.rate
+ : stm->output_stream_params.rate;
+}
+
+uint32_t
+ms_to_hns(uint32_t ms)
+{
+ return ms * 10000;
+}
+
+uint32_t
+hns_to_ms(REFERENCE_TIME hns)
+{
+ return static_cast<uint32_t>(hns / 10000);
+}
+
+double
+hns_to_s(REFERENCE_TIME hns)
+{
+ return static_cast<double>(hns) / 10000000;
+}
+
+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;
+}
+
+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)
+{
+ 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;
+ }
+}
+
+/* This returns the size of a frame in the stream, before the eventual upmix
+ occurs. */
+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 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)
+{
+ /* 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;
+ } else {
+ dest = output_buffer;
+ }
+ }
+
+ long out_frames = cubeb_resampler_fill(stm->resampler,
+ input_buffer,
+ &input_frames_count,
+ dest,
+ output_frames_needed);
+ /* TODO: Report out_frames < 0 as an error via the API. */
+ XASSERT(out_frames >= 0);
+
+ {
+ auto_lock lock(stm->stream_reset_lock);
+ stm->frames_written += out_frames;
+ }
+
+ /* Go in draining mode if we got fewer frames than requested. */
+ if (out_frames < output_frames_needed) {
+ 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));
+
+ 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);
+ }
+ }
+
+ 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)
+{
+ HRESULT hr;
+ UINT32 padding_in;
+
+ 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;
+
+ BYTE * input_packet = NULL;
+ DWORD flags;
+ UINT64 dev_pos;
+ 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 (FAILED(hr)) {
+ LOG("cannot get next packet size: %x", 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);
+ if (FAILED(hr)) {
+ LOG("GetBuffer failed for capture: %x", hr);
+ return false;
+ }
+ XASSERT(packet_size == next);
+ 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);
+ } 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);
+ } else {
+ stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
+ packet_size * stm->input_stream_params.channels);
+ }
+ }
+ hr = stm->capture_client->ReleaseBuffer(packet_size);
+ if (FAILED(hr)) {
+ LOG("FAILED to release intput buffer");
+ return false;
+ }
+ offset += packet_size;
+ }
+
+ assert(stm->linear_input_buffer.length() >= total_available_input &&
+ offset == total_available_input);
+
+ 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)
+{
+ UINT32 padding_out;
+ HRESULT hr;
+
+ XASSERT(has_output(stm));
+
+ hr = stm->output_client->GetCurrentPadding(&padding_out);
+ if (FAILED(hr)) {
+ LOG("Failed to get padding: %x", hr);
+ return false;
+ }
+ XASSERT(padding_out <= stm->output_buffer_frame_count);
+
+ if (stm->draining) {
+ if (padding_out == 0) {
+ LOG("Draining finished.");
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ return false;
+ }
+ LOG("Draining.");
+ return true;
+ }
+
+ frame_count = stm->output_buffer_frame_count - padding_out;
+ BYTE * output_buffer;
+
+ hr = stm->render_client->GetBuffer(frame_count, &output_buffer);
+ if (FAILED(hr)) {
+ LOG("cannot get render buffer");
+ return false;
+ }
+
+ buffer = reinterpret_cast<float*>(output_buffer);
+
+ return true;
+}
+
+/**
+ * This function gets input data from a input device, and pass it along with an
+ * output buffer to the resamplers. */
+bool
+refill_callback_duplex(cubeb_stream * stm)
+{
+ HRESULT hr;
+ float * 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;
+ }
+
+ input_frames = stm->linear_input_buffer.length() / stm->input_stream_params.channels;
+ if (!input_frames) {
+ return true;
+ }
+
+ rv = get_output_buffer(stm, output_buffer, output_frames);
+ if (!rv) {
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ return rv;
+ }
+
+ /* This can only happen when debugging, and having breakpoints set in the
+ * callback in a way that it makes the stream underrun. */
+ if (output_frames == 0) {
+ 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);
+ }
+
+ LOGV("Duplex callback: input frames: %zu, output frames: %zu",
+ stm->linear_input_buffer.length(), output_frames);
+
+ refill(stm,
+ stm->linear_input_buffer.data(),
+ stm->linear_input_buffer.length(),
+ output_buffer,
+ output_frames);
+
+ stm->linear_input_buffer.clear();
+
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ if (FAILED(hr)) {
+ LOG("failed to release buffer: %x", hr);
+ return false;
+ }
+ return true;
+}
+
+bool
+refill_callback_input(cubeb_stream * stm)
+{
+ bool rv, consumed_all_buffer;
+
+ XASSERT(has_input(stm) && !has_output(stm));
+
+ rv = get_input_buffer(stm);
+ if (!rv) {
+ return rv;
+ }
+
+ // This can happen at the very beginning of the stream.
+ if (!stm->linear_input_buffer.length()) {
+ return true;
+ }
+
+ LOGV("Input callback: input frames: %zu", stm->linear_input_buffer.length());
+
+ long read = refill(stm,
+ stm->linear_input_buffer.data(),
+ stm->linear_input_buffer.length(),
+ nullptr,
+ 0);
+
+ consumed_all_buffer = read == stm->linear_input_buffer.length();
+
+ stm->linear_input_buffer.clear();
+
+ return consumed_all_buffer;
+}
+
+bool
+refill_callback_output(cubeb_stream * stm)
+{
+ bool rv;
+ HRESULT hr;
+ float * output_buffer = nullptr;
+ size_t output_frames = 0;
+
+ XASSERT(!has_input(stm) && has_output(stm));
+
+ rv = get_output_buffer(stm, output_buffer, output_frames);
+ if (!rv) {
+ return rv;
+ }
+
+ if (stm->draining || output_frames == 0) {
+ return true;
+ }
+
+ long got = refill(stm,
+ nullptr,
+ 0,
+ output_buffer,
+ output_frames);
+
+ LOGV("Output callback: output frames requested: %zu, got %ld",
+ output_frames, got);
+
+ XASSERT(got >= 0);
+ XASSERT(got == output_frames || stm->draining);
+
+ hr = stm->render_client->ReleaseBuffer(got, 0);
+ if (FAILED(hr)) {
+ LOG("failed to release buffer: %x", hr);
+ return false;
+ }
+
+ return got == output_frames || stm->draining;
+}
+
+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] = {
+ 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;
+ }
+
+ /* We could consider using "Pro Audio" here for WebAudio and
+ 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) {
+ // 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;
+ }
+ if (waitResult != WAIT_TIMEOUT) {
+ timeout_count = 0;
+ }
+ switch (waitResult) {
+ case WAIT_OBJECT_0: { /* shutdown */
+ is_playing = false;
+ /* We don't check if the drain is actually finished here, we just want to
+ shutdown. */
+ if (stm->draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ continue;
+ }
+ case WAIT_OBJECT_0 + 1: { /* reconfigure */
+ XASSERT(stm->output_client || stm->input_client);
+ LOG("Reconfiguring the stream");
+ /* Close the stream */
+ if (stm->output_client) {
+ stm->output_client->Stop();
+ LOG("Output stopped.");
+ }
+ if (stm->input_client) {
+ stm->input_client->Stop();
+ LOG("Input stopped.");
+ }
+ {
+ 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. */
+ int r = setup_wasapi_stream(stm);
+ if (r != CUBEB_OK) {
+ LOG("Error setting up the stream during reconfigure.");
+ /* Don't destroy the stream here, since we expect the caller to do
+ so after the error has propagated via the state callback. */
+ is_playing = false;
+ hr = E_FAIL;
+ continue;
+ }
+ LOG("Stream setup successfuly.");
+ }
+ XASSERT(stm->output_client || stm->input_client);
+ if (stm->output_client) {
+ stm->output_client->Start();
+ LOG("Output started after reconfigure.");
+ }
+ if (stm->input_client) {
+ stm->input_client->Start();
+ 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));
+ 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);
+ break;
+ case WAIT_TIMEOUT:
+ XASSERT(stm->shutdown_event == wait_array[0]);
+ if (++timeout_count >= timeout_limit) {
+ LOG("Render loop reached the timeout limit.");
+ is_playing = false;
+ hr = E_FAIL;
+ }
+ break;
+ default:
+ LOG("case %d not handled in render loop.", waitResult);
+ abort();
+ }
+ }
+
+ if (FAILED(hr)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+
+ stm->context->revert_mm_thread_characteristics(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;
+}
+
+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;
+ }
+
+ stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event);
+
+ hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client);
+ if (FAILED(hr)) {
+ LOG("Could not register endpoint notification callback: %x", hr);
+ SafeRelease(stm->notification_client);
+ stm->notification_client = nullptr;
+ SafeRelease(stm->device_enumerator);
+ stm->device_enumerator = nullptr;
+ }
+
+ return hr;
+}
+
+HRESULT unregister_notification_client(cubeb_stream * stm)
+{
+ XASSERT(stm);
+ HRESULT hr;
+
+ if (!stm->device_enumerator) {
+ return S_OK;
+ }
+
+ hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client);
+ 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);
+ return S_OK;
+ }
+
+ SafeRelease(stm->notification_client);
+ SafeRelease(stm->device_enumerator);
+
+ return S_OK;
+}
+
+HRESULT get_endpoint(IMMDevice ** device, LPCWSTR devid)
+{
+ IMMDeviceEnumerator * enumerator;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
+ NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&enumerator));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %x", hr);
+ return hr;
+ }
+
+ hr = enumerator->GetDevice(devid, device);
+ if (FAILED(hr)) {
+ LOG("Could not get device: %x", hr);
+ SafeRelease(enumerator);
+ return hr;
+ }
+
+ SafeRelease(enumerator);
+
+ return S_OK;
+}
+
+HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction)
+{
+ IMMDeviceEnumerator * enumerator;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
+ NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&enumerator));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %x", hr);
+ return hr;
+ }
+ hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device);
+ if (FAILED(hr)) {
+ LOG("Could not get default audio endpoint: %x", hr);
+ SafeRelease(enumerator);
+ return hr;
+ }
+
+ SafeRelease(enumerator);
+
+ return ERROR_SUCCESS;
+}
+
+double
+current_stream_delay(cubeb_stream * stm)
+{
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ /* If the default audio endpoint went away during playback and we weren't
+ able to configure a new one, it's possible the caller may call this
+ before the error callback has propogated back. */
+ if (!stm->audio_clock) {
+ return 0;
+ }
+
+ UINT64 freq;
+ HRESULT hr = stm->audio_clock->GetFrequency(&freq);
+ if (FAILED(hr)) {
+ LOG("GetFrequency failed: %x", hr);
+ return 0;
+ }
+
+ UINT64 pos;
+ hr = stm->audio_clock->GetPosition(&pos, NULL);
+ if (FAILED(hr)) {
+ LOG("GetPosition failed: %x", 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);
+
+ return delay;
+}
+
+int
+stream_set_volume(cubeb_stream * stm, float volume)
+{
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ if (!stm->audio_stream_volume) {
+ return CUBEB_ERROR;
+ }
+
+ uint32_t channels;
+ HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
+ if (hr != S_OK) {
+ LOG("could not get the channel count: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ /* up to 9.1 for now */
+ if (channels > 10) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ float volumes[10];
+ for (uint32_t i = 0; i < channels; i++) {
+ volumes[i] = volume;
+ }
+
+ hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
+ if (hr != S_OK) {
+ LOG("could not set the channels volume: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+} // namespace anonymous
+
+extern "C" {
+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);
+ if (FAILED(hr)) {
+ LOG("Could not get device: %x", hr);
+ return CUBEB_ERROR;
+ }
+ SafeRelease(device);
+
+ cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
+ if (!ctx) {
+ return CUBEB_ERROR;
+ }
+
+ ctx->ops = &wasapi_ops;
+
+ 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);
+ }
+ } 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;
+ }
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+}
+
+namespace {
+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 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: %d", 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) {
+ /* 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.
+ if (rv) {
+ LOG("Closing thread.");
+ CloseHandle(stm->thread);
+ stm->thread = NULL;
+
+ CloseHandle(stm->shutdown_event);
+ stm->shutdown_event = 0;
+ }
+
+ return rv;
+}
+
+void wasapi_destroy(cubeb * context)
+{
+ if (context->mmcss_module) {
+ FreeLibrary(context->mmcss_module);
+ }
+ free(context);
+}
+
+char const * wasapi_get_backend_id(cubeb * context)
+{
+ return "wasapi";
+}
+
+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);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ hr = device->Activate(__uuidof(IAudioClient),
+ CLSCTX_INPROC_SERVER,
+ NULL, (void **)&client);
+ SafeRelease(device);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ hr = client->GetMixFormat(&mix_format);
+ if (FAILED(hr)) {
+ SafeRelease(client);
+ return CUBEB_ERROR;
+ }
+
+ *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)
+{
+ HRESULT hr;
+ IAudioClient * client;
+ REFERENCE_TIME default_period;
+ auto_com com;
+ if (!com.ok()) {
+ return CUBEB_ERROR;
+ }
+
+ if (params.format != CUBEB_SAMPLE_FLOAT32NE) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ IMMDevice * device;
+ hr = get_default_endpoint(&device, eRender);
+ if (FAILED(hr)) {
+ LOG("Could not get default endpoint: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ hr = device->Activate(__uuidof(IAudioClient),
+ CLSCTX_INPROC_SERVER,
+ NULL, (void **)&client);
+ SafeRelease(device);
+ if (FAILED(hr)) {
+ LOG("Could not activate device for latency: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ /* The second parameter is for exclusive mode, that we don't use. */
+ hr = client->GetDevicePeriod(&default_period, NULL);
+ if (FAILED(hr)) {
+ SafeRelease(client);
+ LOG("Could not get device period: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ LOG("default device period: %lld", default_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 */
+
+ *latency_frames = hns_to_frames(params.rate, default_period);
+
+ 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);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ hr = device->Activate(__uuidof(IAudioClient),
+ CLSCTX_INPROC_SERVER,
+ NULL, (void **)&client);
+ SafeRelease(device);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ hr = client->GetMixFormat(&mix_format);
+ if (FAILED(hr)) {
+ SafeRelease(client);
+ return CUBEB_ERROR;
+ }
+
+ *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);
+
+/* 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)
+{
+ /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
+ handled in the callback. */
+ if ((*mix_format)->nChannels <= 2) {
+ return;
+ }
+
+ /* 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) {
+ return;
+ }
+
+ WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format);
+
+ /* 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;
+
+ /* Check if wasapi will accept our channel layout request. */
+ WAVEFORMATEX * closest;
+ HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
+ *mix_format,
+ &closest);
+ if (hr == S_FALSE) {
+ /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
+ eventual upmix/downmix ourselves */
+ 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;
+ } 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;
+ } else if (hr == S_OK) {
+ LOG("Requested format accepted by WASAPI.");
+ } else {
+ LOG("IsFormatSupported unhandled error: %x", hr);
+ }
+}
+
+#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)
+{
+ IMMDevice * device;
+ WAVEFORMATEX * mix_format;
+ HRESULT hr;
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+ bool try_again = false;
+ // This loops until we find a device that works, or we've exhausted all
+ // possibilities.
+ do {
+ if (devid) {
+ std::unique_ptr<const wchar_t[]> id(utf8_to_wstr(reinterpret_cast<char*>(devid)));
+ hr = get_endpoint(&device, id.get());
+ if (FAILED(hr)) {
+ LOG("Could not get %s endpoint, error: %x\n", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+ }
+ else {
+ hr = get_default_endpoint(&device, direction);
+ if (FAILED(hr)) {
+ LOG("Could not get default %s endpoint, error: %x\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),
+ CLSCTX_INPROC_SERVER,
+ NULL, (void **)audio_client);
+ SafeRelease(device);
+ if (FAILED(hr)) {
+ LOG("Could not activate the device to get an audio"
+ " client for %s: error: %x\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;
+ try_again = true;
+ } else {
+ return CUBEB_ERROR;
+ }
+ } else {
+ try_again = false;
+ }
+ } while (try_again);
+
+ /* 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);
+ if (FAILED(hr)) {
+ LOG("Could not fetch current mix format from the audio"
+ " client for %s: error: %x", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ handle_channel_layout(stm, &mix_format, stream_params);
+
+ /* Shared mode WASAPI always supports float32 sample format, so this
+ * is safe. */
+ mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
+ 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]",
+ 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);
+ if (FAILED(hr)) {
+ LOG("Unable to initialize audio client for %s: %x.", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ CoTaskMemFree(mix_format);
+
+ 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;
+ }
+
+ // 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));
+ }
+
+ hr = (*audio_client)->SetEventHandle(event);
+ if (FAILED(hr)) {
+ LOG("Could set the event handle for the %s client %x.",
+ DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ hr = (*audio_client)->GetService(riid, (void **)render_or_capture_client);
+ if (FAILED(hr)) {
+ LOG("Could not get the %s client %x.", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+#undef DIRECTION_NAME
+
+int setup_wasapi_stream(cubeb_stream * stm)
+{
+ HRESULT hr;
+ int rv;
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ auto_com com;
+ if (!com.ok()) {
+ LOG("Failure to initialize COM.");
+ return CUBEB_ERROR;
+ }
+
+ 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);
+ if (rv != CUBEB_OK) {
+ LOG("Failure to open the input side.");
+ return rv;
+ }
+ }
+
+ 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);
+ if (rv != CUBEB_OK) {
+ LOG("Failure to open the output side.");
+ return rv;
+ }
+
+ hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
+ (void **)&stm->audio_stream_volume);
+ if (FAILED(hr)) {
+ LOG("Could not get the IAudioStreamVolume: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ XASSERT(stm->frames_written == 0);
+ hr = stm->output_client->GetService(__uuidof(IAudioClock),
+ (void **)&stm->audio_clock);
+ if (FAILED(hr)) {
+ LOG("Could not get the IAudioClock: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ /* 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;
+ }
+ }
+
+ /* 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);
+ target_sample_rate = stm->input_stream_params.rate;
+ } else if (has_input(stm)) {
+ target_sample_rate = stm->input_stream_params.rate;
+ } else {
+ XASSERT(has_output(stm));
+ target_sample_rate = stm->output_stream_params.rate;
+ }
+
+ LOG("Target sample rate: %d", target_sample_rate);
+
+ /* If we are playing/capturing a mono stream, we only resample one channel,
+ and copy it over, so we are always resampling the number
+ of channels of the stream, not the number of channels
+ that WASAPI wants. */
+ cubeb_stream_params input_params = stm->input_mix_params;
+ input_params.channels = stm->input_stream_params.channels;
+ 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);
+ if (!stm->resampler) {
+ LOG("Could not get a resampler");
+ return CUBEB_ERROR;
+ }
+
+ XASSERT(has_input(stm) || has_output(stm));
+
+ if (has_input(stm) && has_output(stm)) {
+ stm->refill_callback = refill_callback_duplex;
+ } else if (has_input(stm)) {
+ stm->refill_callback = refill_callback_input;
+ } else if (has_output(stm)) {
+ stm->refill_callback = refill_callback_output;
+ }
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_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)
+{
+ 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);
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
+
+ XASSERT(stm);
+
+ stm->context = context;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->draining = false;
+ if (input_stream_params) {
+ stm->input_stream_params = *input_stream_params;
+ stm->input_device = input_device;
+ }
+ if (output_stream_params) {
+ stm->output_stream_params = *output_stream_params;
+ stm->output_device = output_device;
+ }
+
+ stm->latency = latency_frames;
+ stm->volume = 1.0;
+
+ // Placement new to call ctor.
+ new (&stm->stream_reset_lock) owned_critical_section();
+
+ 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);
+ 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);
+ 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);
+ 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);
+ }
+ 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);
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+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;
+
+ SafeRelease(stm->render_client);
+ stm->render_client = NULL;
+
+ SafeRelease(stm->capture_client);
+ stm->capture_client = NULL;
+
+ SafeRelease(stm->audio_stream_volume);
+ stm->audio_stream_volume = NULL;
+
+ 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->frames_written = 0;
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ stm->resampler = NULL;
+ }
+
+ free(stm->mix_buffer);
+ stm->mix_buffer = NULL;
+}
+
+void wasapi_stream_destroy(cubeb_stream * stm)
+{
+ 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;
+ }
+
+ unregister_notification_client(stm);
+
+ SafeRelease(stm->reconfigure_event);
+ SafeRelease(stm->refill_event);
+ SafeRelease(stm->input_available_event);
+
+ {
+ 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);
+}
+
+enum StreamDirection {
+ OUTPUT,
+ INPUT
+};
+
+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();
+ 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",
+ dir == OUTPUT ? "output" : "input", GetLastError());
+ }
+
+ close_wasapi_stream(stm);
+ int r = setup_wasapi_stream(stm);
+ if (r != CUBEB_OK) {
+ LOG("reconfigure failed");
+ return r;
+ }
+
+ 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",
+ dir == OUTPUT ? "output" : "input", hr);
+ return CUBEB_ERROR;
+ }
+ } else if (FAILED(hr)) {
+ LOG("could not start the %s stream: %x.",
+ dir == OUTPUT ? "output" : "input", hr);
+ 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);
+
+ 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;
+ }
+ }
+
+ if (stm->input_client) {
+ int rv = stream_start_one_side(stm, INPUT);
+ if (rv != CUBEB_OK) {
+ return rv;
+ }
+ }
+
+ stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->shutdown_event) {
+ LOG("Can't create the shutdown event, error: %x", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ 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;
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ return CUBEB_OK;
+}
+
+int wasapi_stream_stop(cubeb_stream * stm)
+{
+ XASSERT(stm);
+ HRESULT hr;
+
+ {
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (stm->output_client) {
+ hr = stm->output_client->Stop();
+ if (FAILED(hr)) {
+ LOG("could not stop AudioClient (output)");
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->input_client) {
+ hr = stm->input_client->Stop();
+ if (FAILED(hr)) {
+ LOG("could not stop AudioClient (input)");
+ return CUBEB_ERROR;
+ }
+ }
+
+
+ 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;
+}
+
+int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ XASSERT(stm && position);
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (!has_output(stm)) {
+ return CUBEB_ERROR;
+ }
+
+ /* 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);
+
+ /* 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)));
+
+ *position = max_pos;
+ if (stream_delay <= *position) {
+ *position -= stream_delay;
+ }
+
+ if (*position < stm->prev_position) {
+ *position = stm->prev_position;
+ }
+ stm->prev_position = *position;
+
+ return CUBEB_OK;
+}
+
+int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ XASSERT(stm && latency);
+
+ if (!has_output(stm)) {
+ return CUBEB_ERROR;
+ }
+
+ auto_lock lock(stm->stream_reset_lock);
+
+ /* The GetStreamLatency method only works if the
+ AudioClient has been initialized. */
+ if (!stm->output_client) {
+ return CUBEB_ERROR;
+ }
+
+ REFERENCE_TIME latency_hns;
+ HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ *latency = hns_to_frames(stm, latency_hns);
+
+ return CUBEB_OK;
+}
+
+int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (!has_output(stm)) {
+ return CUBEB_ERROR;
+ }
+
+ if (stream_set_volume(stm, volume) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ stm->volume = volume;
+
+ return CUBEB_OK;
+}
+
+static char *
+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);
+ }
+
+ 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);
+ }
+
+ return std::move(ret);
+}
+
+static 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)))
+ 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)
+{
+ BOOL ret = FALSE;
+ IMMDevice * dev;
+ HRESULT hr;
+
+ hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev);
+ if (SUCCEEDED(hr)) {
+ LPWSTR defdevid = NULL;
+ if (SUCCEEDED(dev->GetId(&defdevid)))
+ ret = (wcscmp(defdevid, device_id) == 0);
+ if (defdevid != NULL)
+ CoTaskMemFree(defdevid);
+ SafeRelease(dev);
+ }
+
+ return ret;
+}
+
+static cubeb_device_info *
+wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
+{
+ IMMEndpoint * endpoint = NULL;
+ IMMDevice * devnode = NULL;
+ IAudioClient * client = NULL;
+ cubeb_device_info * ret = NULL;
+ EDataFlow flow;
+ LPWSTR device_id = NULL;
+ DWORD state = DEVICE_STATE_NOTPRESENT;
+ IPropertyStore * propstore = NULL;
+ PROPVARIANT propvar;
+ REFERENCE_TIME def_period, min_period;
+ HRESULT hr;
+
+ PropVariantInit(&propvar);
+
+ hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint));
+ if (FAILED(hr)) goto done;
+
+ hr = endpoint->GetDataFlow(&flow);
+ if (FAILED(hr)) goto done;
+
+ hr = dev->GetId(&device_id);
+ if (FAILED(hr)) goto done;
+
+ hr = dev->OpenPropertyStore(STGM_READ, &propstore);
+ if (FAILED(hr)) goto done;
+
+ hr = dev->GetState(&state);
+ if (FAILED(hr)) goto done;
+
+ ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
+
+ 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);
+
+ devnode = wasapi_get_device_node(enumerator, dev);
+ if (devnode != NULL) {
+ IPropertyStore * ps = NULL;
+ hr = devnode->OpenPropertyStore(STGM_READ, &ps);
+ if (FAILED(hr)) goto done;
+
+ PropVariantClear(&propvar);
+ hr = ps->GetValue(PKEY_Device_InstanceId, &propvar);
+ if (SUCCEEDED(hr)) {
+ ret->group_id = wstr_to_utf8(propvar.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 (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->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);
+
+ 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);
+
+ if (propvar.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;
+ }
+ }
+ }
+
+ if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) &&
+ 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);
+ } 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;
+}
+
+static int
+wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection ** out)
+{
+ auto_com com;
+ IMMDeviceEnumerator * enumerator;
+ IMMDeviceCollection * collection;
+ IMMDevice * dev;
+ cubeb_device_info * cur;
+ 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));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %x", 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;
+
+ hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection);
+ if (FAILED(hr)) {
+ LOG("Could not enumerate audio endpoints: %x", hr);
+ return CUBEB_ERROR;
+ }
+
+ hr = collection->GetCount(&cc);
+ if (FAILED(hr)) {
+ LOG("IMMDeviceCollection::GetCount() failed: %x", hr);
+ return CUBEB_ERROR;
+ }
+ *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) +
+ sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0));
+ if (!*out) {
+ return CUBEB_ERROR;
+ }
+ (*out)->count = 0;
+ for (i = 0; i < cc; i++) {
+ hr = collection->Item(i, &dev);
+ 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;
+ }
+ }
+
+ 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
+};
+} // namespace anonymous
diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c
new file mode 100644
index 0000000000..585d11e89d
--- /dev/null
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -0,0 +1,1067 @@
+/*
+ * 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
new file mode 100644
index 0000000000..7812146206
--- /dev/null
+++ b/media/libcubeb/src/moz.build
@@ -0,0 +1,98 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['CUBEB_GECKO_BUILD'] = True
+
+Library('cubeb')
+
+SOURCES += [
+ 'cubeb.c',
+ 'cubeb_panner.cpp'
+]
+
+if CONFIG['MOZ_ALSA']:
+ SOURCES += [
+ 'cubeb_alsa.c',
+ ]
+ DEFINES['USE_ALSA'] = True
+
+if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['MOZ_JACK']:
+ SOURCES += [
+ 'cubeb_resampler.cpp',
+ ]
+
+if CONFIG['MOZ_PULSEAUDIO']:
+ SOURCES += [
+ 'cubeb_pulse.c',
+ ]
+ DEFINES['USE_PULSE'] = True
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ DEFINES['DISABLE_LIBPULSE_DLOPEN'] = True
+
+if CONFIG['MOZ_JACK']:
+ SOURCES += [
+ 'cubeb_jack.cpp',
+ ]
+ USE_LIBS += [
+ 'speex',
+ ]
+ DEFINES['USE_JACK'] = True
+
+if CONFIG['OS_ARCH'] == 'OpenBSD':
+ SOURCES += [
+ 'cubeb_sndio.c',
+ ]
+ DEFINES['USE_SNDIO'] = True
+
+if CONFIG['OS_TARGET'] == 'Darwin':
+ SOURCES += [
+ 'cubeb_audiounit.cpp',
+ 'cubeb_resampler.cpp'
+ ]
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'cubeb_osx_run_loop.c',
+ ]
+ DEFINES['USE_AUDIOUNIT'] = True
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ SOURCES += [
+ 'cubeb_resampler.cpp',
+ 'cubeb_wasapi.cpp',
+ 'cubeb_winmm.c',
+ ]
+ DEFINES['USE_WINMM'] = True
+ DEFINES['USE_WASAPI'] = True
+ 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
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
+ SOURCES += [
+ 'cubeb_audiotrack.c',
+ ]
+ DEFINES['USE_AUDIOTRACK'] = True
+
+FINAL_LIBRARY = 'gkmedias'
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ if CONFIG['ANDROID_VERSION'] >= '17':
+ LOCAL_INCLUDES += [
+ '%' + '%s/frameworks/wilhelm/include' % CONFIG['ANDROID_SOURCE'],
+ ]
+ else:
+ LOCAL_INCLUDES += [
+ '%' + '%s/system/media/wilhelm/include' % CONFIG['ANDROID_SOURCE'],
+ ]
+
+CFLAGS += CONFIG['MOZ_ALSA_CFLAGS']
+CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS']
+
+# We allow warnings for third-party code that can be updated from upstream.
+ALLOW_COMPILER_WARNINGS = True
diff --git a/media/libcubeb/tests/common.h b/media/libcubeb/tests/common.h
new file mode 100644
index 0000000000..051f3c42a2
--- /dev/null
+++ b/media/libcubeb/tests/common.h
@@ -0,0 +1,61 @@
+/*
+ * 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/moz.build b/media/libcubeb/tests/moz.build
new file mode 100644
index 0000000000..1b17c7b1cb
--- /dev/null
+++ b/media/libcubeb/tests/moz.build
@@ -0,0 +1,80 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['CUBEB_GECKO_BUILD'] = True
+
+GeckoCppUnitTests([
+ 'test_duplex',
+ 'test_record',
+ 'test_tone',
+ 'test_utils'
+])
+
+if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['OS_TARGET'] in ('Darwin', 'WINNT', 'Android'):
+ GeckoCppUnitTests([
+ 'test_resampler',
+ ])
+
+if CONFIG['OS_TARGET'] != 'Android':
+ GeckoCppUnitTests([
+ 'test_audio',
+ 'test_latency',
+ 'test_sanity'
+ ])
+
+LOCAL_INCLUDES += [
+ '../include',
+ '../src'
+]
+
+USE_LIBS += [
+ 'cubeb',
+ 'speex'
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ # On windows, the WASAPI backend needs the resampler we have in
+ # /media/libspeex_resampler, so we can't get away with just linking cubeb's
+ # .o
+ USE_LIBS += [
+ 'cubeb',
+ 'speex',
+ ]
+ OS_LIBS += [
+ 'ole32'
+ ]
+else:
+ # Otherwise, we can just grab all the compiled .o and compile against that,
+ # linking the appriopriate libraries.
+ USE_LIBS += [
+ 'cubeb',
+ ]
+ # Don't link gkmedias for it introduces dependencies on Android.
+ if CONFIG['OS_TARGET'] == 'Android':
+ USE_LIBS += [
+ 'speex',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ OS_LIBS += [
+ '-framework AudioUnit',
+ '-framework CoreAudio',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
+ OS_LIBS += [
+ '-framework CoreFoundation',
+ '-framework AudioToolbox',
+ ]
+elif CONFIG['OS_TARGET'] == 'OpenBSD':
+ OS_LIBS += [
+ 'sndio',
+ ]
+else:
+ OS_LIBS += CONFIG['MOZ_ALSA_LIBS']
+ OS_LIBS += CONFIG['MOZ_PULSEAUDIO_LIBS']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/media/libcubeb/tests/test_audio.cpp b/media/libcubeb/tests/test_audio.cpp
new file mode 100644
index 0000000000..4943223e68
--- /dev/null
+++ b/media/libcubeb/tests/test_audio.cpp
@@ -0,0 +1,294 @@
+/*
+ * 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
new file mode 100644
index 0000000000..9c502c0afd
--- /dev/null
+++ b/media/libcubeb/tests/test_devices.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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
new file mode 100644
index 0000000000..c5d02d782c
--- /dev/null
+++ b/media/libcubeb/tests/test_duplex.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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
new file mode 100644
index 0000000000..0586b1a367
--- /dev/null
+++ b/media/libcubeb/tests/test_latency.cpp
@@ -0,0 +1,60 @@
+#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
new file mode 100644
index 0000000000..681e1641e2
--- /dev/null
+++ b/media/libcubeb/tests/test_record.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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
new file mode 100644
index 0000000000..7e62a35721
--- /dev/null
+++ b/media/libcubeb/tests/test_resampler.cpp
@@ -0,0 +1,554 @@
+/*
+ * 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_sanity.cpp b/media/libcubeb/tests/test_sanity.cpp
new file mode 100644
index 0000000000..77973ff150
--- /dev/null
+++ b/media/libcubeb/tests/test_sanity.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+#define _XOPEN_SOURCE 600
+#include "cubeb/cubeb.h"
+#include <assert.h>
+#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 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_FORMAT CUBEB_SAMPLE_S16LE
+#endif
+
+template<typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T(&)[N])
+{
+ return N;
+}
+
+static int dummy;
+static 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
+ memset(outputbuffer, 0, nframes * sizeof(short));
+#endif
+
+ total_frames_written += nframes;
+ if (delay_callback) {
+ delay(10);
+ }
+ return nframes;
+}
+
+void
+test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state /*state*/)
+{
+}
+
+static void
+test_init_destroy_context(void)
+{
+ int r;
+ cubeb * ctx;
+ char const* backend_id;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+
+ backend_id = cubeb_get_backend_id(ctx);
+ assert(backend_id);
+
+ fprintf(stderr, "Backend: %s\n", backend_id);
+
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static void
+test_init_destroy_multiple_contexts(void)
+{
+ size_t i;
+ int r;
+ cubeb * ctx[4];
+ int order[4] = {2, 0, 3, 1};
+ assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order));
+
+ BEGIN_TEST;
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ r = cubeb_init(&ctx[i], NULL);
+ assert(r == 0 && ctx[i]);
+ }
+
+ /* 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)
+{
+ int r;
+ cubeb * ctx;
+ uint32_t value;
+ cubeb_stream_params params;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_context_variables");
+ assert(r == 0 && ctx);
+
+ 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);
+ if (r == CUBEB_OK) {
+ assert(value > 0);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &value);
+ assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ assert(value > 0);
+ }
+
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static void
+test_init_destroy_stream(void)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ assert(r == 0 && stream);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static void
+test_init_destroy_multiple_streams(void)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream[8];
+ cubeb_stream_params params;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ 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]);
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ cubeb_stream_destroy(stream[i]);
+ }
+
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static void
+test_configure_stream(void)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2; // panning
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ assert(r == 0 && stream);
+
+ r = cubeb_stream_set_volume(stream, 1.0f);
+ assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ r = cubeb_stream_set_panning(stream, 0.0f);
+ assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+ END_TEST;
+}
+
+static void
+test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream[8];
+ cubeb_stream_params params;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ 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]);
+ if (early) {
+ r = cubeb_stream_start(stream[i]);
+ assert(r == 0);
+ }
+ }
+
+
+ if (!early) {
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_start(stream[i]);
+ assert(r == 0);
+ }
+ }
+
+ if (delay_ms) {
+ delay(delay_ms);
+ }
+
+ if (!early) {
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_stop(stream[i]);
+ assert(r == 0);
+ }
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ if (early) {
+ r = cubeb_stream_stop(stream[i]);
+ assert(r == 0);
+ }
+ cubeb_stream_destroy(stream[i]);
+ }
+
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static void
+test_init_destroy_multiple_contexts_and_streams(void)
+{
+ size_t i, j;
+ int r;
+ cubeb * ctx[2];
+ 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));
+
+ BEGIN_TEST;
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ r = cubeb_init(&ctx[i], "test_sanity");
+ assert(r == 0 && ctx[i]);
+
+ 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]);
+ }
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ for (j = 0; j < streams_per_ctx; ++j) {
+ cubeb_stream_destroy(stream[i * streams_per_ctx + j]);
+ }
+ cubeb_destroy(ctx[i]);
+ }
+
+ END_TEST;
+}
+
+static void
+test_basic_stream_operations(void)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position;
+
+ BEGIN_TEST;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ assert(r == 0 && stream);
+
+ /* position and volume before stream has started */
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0 && position == 0);
+
+ r = cubeb_stream_start(stream);
+ assert(r == 0);
+
+ /* position and volume after while stream running */
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+
+ r = cubeb_stream_stop(stream);
+ assert(r == 0);
+
+ /* position and volume after stream has stopped */
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static void
+test_stream_position(void)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ 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);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ assert(r == 0 && stream);
+
+ /* stream position should not advance before starting playback */
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0 && position == 0);
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0 && position == 0);
+
+ /* stream position should advance during playback */
+ r = cubeb_stream_start(stream);
+ assert(r == 0);
+
+ /* XXX let start happen */
+ delay(500);
+
+ /* stream should have prefilled */
+ assert(total_frames_written > 0);
+
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ last_position = position;
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ assert(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);
+ last_position = position;
+ delay(500);
+ }
+
+ /* test that the position is valid even when starting and
+ * stopping the stream. */
+ for (i = 0; i < 5; ++i) {
+ r = cubeb_stream_stop(stream);
+ assert(r == 0);
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ assert(last_position < position);
+ last_position = position;
+ delay(500);
+ r = cubeb_stream_start(stream);
+ assert(r == 0);
+ delay(500);
+ }
+
+ assert(last_position != 0);
+
+ /* stream position should not advance after stopping playback */
+ r = cubeb_stream_stop(stream);
+ assert(r == 0);
+
+ /* XXX allow stream to settle */
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ last_position = position;
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ assert(position == last_position);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+static int do_drain;
+static 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);
+ 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
+ memset(outputbuffer, 0, nframes * sizeof(short));
+#endif
+ total_frames_written += nframes;
+ return nframes;
+}
+
+void
+test_drain_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state state)
+{
+ if (state == CUBEB_STATE_DRAINED) {
+ assert(!got_drain);
+ got_drain = 1;
+ }
+}
+
+static void
+test_drain(void)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position;
+
+ BEGIN_TEST;
+
+ total_frames_written = 0;
+
+ r = cubeb_init(&ctx, "test_sanity");
+ assert(r == 0 && ctx);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+#if defined(__ANDROID__)
+ params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+#endif
+
+ 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);
+
+ r = cubeb_stream_start(stream);
+ assert(r == 0);
+
+ delay(500);
+
+ do_drain = 1;
+
+ for (;;) {
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ if (got_drain) {
+ break;
+ } else {
+ assert(position <= total_frames_written);
+ }
+ delay(500);
+ }
+
+ r = cubeb_stream_get_position(stream, &position);
+ assert(r == 0);
+ assert(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);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ END_TEST;
+}
+
+int is_windows_7()
+{
+#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;
+
+ 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
+}
+
+int
+main(int /*argc*/, char * /*argv*/[])
+{
+#ifdef CUBEB_GECKO_BUILD
+ ScopedXPCOM xpcom("test_sanity");
+#endif
+
+ 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();
+
+ /* 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();
+
+ 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);
+ }
+ delay_callback = 0;
+ test_drain();
+/*
+ to implement:
+ test_eos_during_prefill();
+ test_stream_destroy_pending_drain();
+*/
+ printf("\n");
+
+ return 0;
+}
diff --git a/media/libcubeb/tests/test_tone.cpp b/media/libcubeb/tests/test_tone.cpp
new file mode 100644
index 0000000000..3c6e0ec548
--- /dev/null
+++ b/media/libcubeb/tests/test_tone.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Plays a simple tone. */
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+#define _XOPEN_SOURCE 600
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <limits.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
+
+/* store the phase of the generated waveform */
+struct cb_user_data {
+ long position;
+};
+
+long data_cb(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;
+
+ if (stream == NULL || u == NULL)
+ return CUBEB_ERROR;
+
+ /* generate our test tone on the fly */
+ for (i = 0; i < nframes; i++) {
+ /* 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 */
+ /* we'll still click if position overflows */
+ u->position += nframes;
+
+ return nframes;
+}
+
+void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
+{
+ struct cb_user_data *u = (struct cb_user_data *)user;
+
+ if (stream == NULL || u == 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_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;
+ }
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+
+ 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;
+ }
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ assert(user_data->position);
+
+ free(user_data);
+
+ return CUBEB_OK;
+}
diff --git a/media/libcubeb/tests/test_utils.cpp b/media/libcubeb/tests/test_utils.cpp
new file mode 100644
index 0000000000..f52cd31963
--- /dev/null
+++ b/media/libcubeb/tests/test_utils.cpp
@@ -0,0 +1,80 @@
+#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
new file mode 100644
index 0000000000..714f3d4bae
--- /dev/null
+++ b/media/libcubeb/unresampled-frames.patch
@@ -0,0 +1,36 @@
+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
new file mode 100755
index 0000000000..235b963e24
--- /dev/null
+++ b/media/libcubeb/update.sh
@@ -0,0 +1,89 @@
+# Usage: sh update.sh <upstream_src_directory>
+set -e
+
+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_alsa.c src
+cp $1/src/cubeb_log.h src
+cp $1/src/cubeb_audiotrack.c 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_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_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_sndio.c src
+cp $1/src/cubeb_utils.h 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
+
+if [ -d $1/.git ]; then
+ rev=$(cd $1 && git rev-parse --verify HEAD)
+ dirty=$(cd $1 && git diff-index --name-only HEAD)
+fi
+
+if [ -n "$rev" ]; then
+ version=$rev
+ if [ -n "$dirty" ]; then
+ version=$version-dirty
+ echo "WARNING: updating from a dirty git repository."
+ fi
+ sed -i.bak -e "/The git commit ID used was/ s/[0-9a-f]\{40\}\(-dirty\)\{0,1\}\./$version./" README_MOZILLA
+ rm README_MOZILLA.bak
+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
new file mode 100644
index 0000000000..0eb1aca82b
--- /dev/null
+++ b/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch
@@ -0,0 +1,167 @@
+# 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
new file mode 100644
index 0000000000..188bdf8b2f
--- /dev/null
+++ b/media/libcubeb/uplift-patch-7a4c711.patch
@@ -0,0 +1,69 @@
+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
new file mode 100644
index 0000000000..5064d7fb35
--- /dev/null
+++ b/media/libcubeb/uplift-system-listener-patch.patch
@@ -0,0 +1,402 @@
+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
new file mode 100644
index 0000000000..90e827830e
--- /dev/null
+++ b/media/libcubeb/uplift-wasapi-part-to-beta.patch
@@ -0,0 +1,118 @@
+# 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;
+ }