diff --git a/CMakeLists.txt b/CMakeLists.txt
index 69ea61b..0199412 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,26 +19,33 @@ add_subdirectory("${PROJECT_SOURCE_DIR}/devices/")
add_subdirectory("${PROJECT_SOURCE_DIR}/machines/")
add_subdirectory("${PROJECT_SOURCE_DIR}/thirdparty/loguru/")
-set(BUILD_EXAMPLE_PROGRAMS OFF CACHE BOOL "Build libsoundio example programs")
-set(BUILD_TESTS OFF CACHE BOOL "Build libsoundio tests")
-set(BUILD_DYNAMIC_LIBS ON CACHE BOOL "Build libsoundio dynamic libs")
-set(BUILD_STATIC_LIBS ON CACHE BOOL "Build libsoundio static libs")
+#set(BUILD_EXAMPLE_PROGRAMS OFF CACHE BOOL "Build libsoundio example programs")
+#set(BUILD_TESTS OFF CACHE BOOL "Build libsoundio tests")
+#set(BUILD_DYNAMIC_LIBS ON CACHE BOOL "Build libsoundio dynamic libs")
+#set(BUILD_STATIC_LIBS ON CACHE BOOL "Build libsoundio static libs")
-add_subdirectory("${PROJECT_SOURCE_DIR}/thirdparty/libsoundio")
+#add_subdirectory("${PROJECT_SOURCE_DIR}/thirdparty/libsoundio")
+
+set(BUILD_TESTS OFF CACHE BOOL "Build Cubeb tests")
+set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries")
+set(BUILD_TOOLS OFF CACHE BOOL "Build Cubeb tools")
+
+add_subdirectory(thirdparty/cubeb EXCLUDE_FROM_ALL)
include_directories("${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}/devices"
"${PROJECT_SOURCE_DIR}/cpu/ppc"
"${PROJECT_SOURCE_DIR}/debugger"
"${PROJECT_SOURCE_DIR}/thirdparty/loguru/"
- "${PROJECT_SOURCE_DIR}/thirdparty/SDL2/"
- ${LIBSOUNDIO_HEADERS})
+ "${PROJECT_SOURCE_DIR}/thirdparty/SDL2/")
+# "${PROJECT_SOURCE_DIR}/thirdparty/cubeb/include")
+# ${LIBSOUNDIO_HEADERS})
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/*.cpp"
"${PROJECT_SOURCE_DIR}/*.c"
"${PROJECT_SOURCE_DIR}/*.hpp"
"${PROJECT_SOURCE_DIR}/*.h")
-get_directory_property(LIBSOUNDIO_LIBS DIRECTORY thirdparty/libsoundio DEFINITION LIBSOUNDIO_LIBS)
+#get_directory_property(LIBSOUNDIO_LIBS DIRECTORY thirdparty/libsoundio DEFINITION LIBSOUNDIO_LIBS)
file(GLOB TEST_SOURCES "${PROJECT_SOURCE_DIR}/cpu/ppc/test/*.cpp")
@@ -53,7 +60,8 @@ target_link_libraries(dingusppc "${PROJECT_SOURCE_DIR}/thirdparty/SDL2/lib/x64/S
"${PROJECT_SOURCE_DIR}/thirdparty/SDL2/lib/x64/SDL2main.lib"
libsoundio_static ${LIBSOUNDIO_LIBS})
else()
-target_link_libraries(dingusppc libsoundio_static ${LIBSOUNDIO_LIBS} ${SDL2_LIBRARIES} ${CMAKE_DL_LIBS})
+#target_link_libraries(dingusppc libsoundio_static ${LIBSOUNDIO_LIBS} ${SDL2_LIBRARIES})
+target_link_libraries(dingusppc cubeb ${SDL2_LIBRARIES})
endif()
@@ -68,7 +76,8 @@ target_link_libraries(testppc "${PROJECT_SOURCE_DIR}/thirdparty/SDL2/lib/x64/SDL
"${PROJECT_SOURCE_DIR}/thirdparty/SDL2/lib/x64/SDL2main.lib"
libsoundio_static ${LIBSOUNDIO_LIBS})
else()
-target_link_libraries(testppc libsoundio_static ${LIBSOUNDIO_LIBS} ${SDL2_LIBRARIES} ${CMAKE_DL_LIBS})
+#target_link_libraries(testppc libsoundio_static ${LIBSOUNDIO_LIBS} ${SDL2_LIBRARIES})
+target_link_libraries(testppc cubeb ${SDL2_LIBRARIES})
endif()
add_custom_command(
diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt
index b2169e3..21193b6 100644
--- a/devices/CMakeLists.txt
+++ b/devices/CMakeLists.txt
@@ -3,3 +3,4 @@ include_directories("${PROJECT_SOURCE_DIR}")
file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_library(devices OBJECT ${SOURCES})
+target_link_libraries(devices PRIVATE cubeb)
diff --git a/devices/awacs.cpp b/devices/awacs.cpp
index 437fabe..88145c5 100644
--- a/devices/awacs.cpp
+++ b/devices/awacs.cpp
@@ -30,7 +30,7 @@ along with this program. If not, see .
#include "dbdma.h"
#include "machines/machinebase.h"
#include "soundserver.h"
-#include
+//#include
static int awac_freqs[8] = {44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350};
@@ -43,14 +43,18 @@ AWACDevice::AWACDevice()
I2CBus *i2c_bus = dynamic_cast(gMachineObj->get_comp_by_type(HWCompType::I2C_HOST));
i2c_bus->register_device(0x45, this->audio_proc);
- SoundServer *snd_server = dynamic_cast
+ this->snd_server = dynamic_cast
(gMachineObj->get_comp_by_name("SoundServer"));
- this->out_device = snd_server->get_out_device();
+ //this->out_device = snd_server->get_out_device();
this->out_stream_ready = false;
}
AWACDevice::~AWACDevice()
{
+ if (this->out_stream_ready) {
+ snd_server->close_out_stream();
+ }
+
delete this->audio_proc;
if (this->snd_buf)
@@ -103,6 +107,7 @@ void AWACDevice::snd_ctrl_write(uint32_t offset, uint32_t value, int size)
}
}
+#if 0
static void convert_data(const uint8_t *in, SoundIoChannelArea *out_buf, uint32_t frame_count)
{
uint16_t *p_in = (uint16_t *)in;
@@ -134,7 +139,9 @@ static void insert_silence(SoundIoChannelArea *out_buf, uint32_t frame_count)
out_buf[1].ptr += out_buf[1].step;
}
}
+#endif
+#if 0
static void sound_out_callback(struct SoundIoOutStream *outstream,
int frame_count_min, int frame_count_max)
{
@@ -144,13 +151,13 @@ static void sound_out_callback(struct SoundIoOutStream *outstream,
struct SoundIoChannelArea *areas;
DMAChannel *dma_ch = (DMAChannel *)outstream->userdata; /* C API baby! */
int n_channels = outstream->layout.channel_count;
- //bool stop = false;
+ bool stop = false;
- if (!dma_ch->is_active()) {
- LOG_F(INFO, "pausing result: %s",
- soundio_strerror(soundio_outstream_pause(outstream, true)));
- return;
- }
+ //if (!dma_ch->is_active()) {
+ // soundio_outstream_clear_buffer(outstream);
+ // soundio_outstream_pause(outstream, true);
+ // return;
+ //}
if (frame_count_max > 512) {
frame_count = 512;
@@ -184,7 +191,7 @@ static void sound_out_callback(struct SoundIoOutStream *outstream,
/* fill the buffer with silence */
//LOG_F(ERROR, "rem_len=%d", rem_len);
insert_silence(areas, rem_len >> 2);
- //stop = true;
+ stop = true;
break;
}
}
@@ -194,16 +201,74 @@ static void sound_out_callback(struct SoundIoOutStream *outstream,
return;
}
- //if (stop) {
- // LOG_F(INFO, "pausing result: %s",
- // soundio_strerror(soundio_outstream_pause(outstream, true)));
- //}
+ if (stop) {
+ LOG_F(INFO, "pausing result: %s",
+ soundio_strerror(soundio_outstream_pause(outstream, true)));
+ }
+}
+#endif
+
+static void convert_data(const uint8_t *in, uint8_t *out, uint32_t len)
+{
+ uint16_t *p_in, *p_out;
+
+ if (len & 7) {
+ LOG_F(WARNING, "AWAC sound buffer len not a multiply of 8, %d", len);
+ }
+
+ p_in = (uint16_t *)in;
+ p_out = (uint16_t *)out;
+ len >>= 1;
+
+ LOG_F(INFO, "Converting %d samples", len);
+
+ /* AWAC data comes as LLRR -> convert it to LRLR */
+ for (int i = 0; i < len; i += 4) {
+ p_out[i+0] = BYTESWAP_16(p_in[i+0]);
+ p_out[i+1] = BYTESWAP_16(p_in[i+2]);
+ p_out[i+2] = BYTESWAP_16(p_in[i+1]);
+ p_out[i+3] = BYTESWAP_16(p_in[i+3]);
+ }
+}
+
+long sound_out_callback(cubeb_stream *stream, void *user_data,
+ void const *input_buffer, void *output_buffer,
+ long nframes)
+{
+ uint8_t *p_in, *buf;
+ uint32_t buf_len, rem_len, got_len;
+ DMAChannel *dma_ch = static_cast(user_data); /* C API baby! */
+
+ //LOG_F(INFO, "Cubeb data callback fired, nframes = %ld", nframes);
+
+ buf = (uint8_t *)output_buffer;
+ buf_len = nframes * 2 * sizeof(int16_t);
+
+ for (rem_len = buf_len; rem_len > 0; rem_len -= got_len, buf += got_len) {
+ if (!dma_ch->get_data(rem_len, &got_len, &p_in)) {
+ convert_data(p_in, buf, got_len);
+ LOG_F(INFO, "Converted sound data, len = %d", got_len);
+ } else { /* no more data */
+ //memset(buf, 0, rem_len); /* fill the buffer with silence */
+ //LOG_F(INFO, "Inserted silence, len = %d", rem_len);
+ LOG_F(INFO, "AWAC: return %d samples", (buf_len - rem_len) >> 2);
+ return (buf_len - rem_len) >> 2;
+ }
+ }
+
+ return nframes;
+}
+
+void status_callback(cubeb_stream *stream, void *user_data, cubeb_state state)
+{
+ LOG_F(INFO, "Cubeb status callback fired, status = %d", state);
}
void AWACDevice::open_stream(int sample_rate)
{
int err;
+#if 0
this->out_stream = soundio_outstream_create(this->out_device);
this->out_stream->write_callback = sound_out_callback;
this->out_stream->format = SoundIoFormatS16LE;
@@ -217,6 +282,16 @@ void AWACDevice::open_stream(int sample_rate)
this->out_sample_rate = sample_rate;
this->out_stream_ready = true;
}
+#endif
+
+ if ((err = this->snd_server->open_out_stream(sample_rate, sound_out_callback,
+ status_callback, (void *)this->dma_out_ch))) {
+ LOG_F(ERROR, "AWAC: unable to open sound output stream: %d", err);
+ this->out_stream_ready = false;
+ } else {
+ this->out_sample_rate = sample_rate;
+ this->out_stream_ready = true;
+ }
}
void AWACDevice::dma_start()
@@ -226,12 +301,14 @@ void AWACDevice::dma_start()
if (!this->out_stream_ready) {
this->open_stream(awac_freqs[(this->snd_ctrl_reg >> 8) & 7]);
} else if (this->out_sample_rate != awac_freqs[(this->snd_ctrl_reg >> 8) & 7]) {
- soundio_outstream_destroy(this->out_stream);
+ //soundio_outstream_destroy(this->out_stream);
+ snd_server->close_out_stream();
this->open_stream(awac_freqs[(this->snd_ctrl_reg >> 8) & 7]);
} else {
+ LOG_F(ERROR, "AWAC: unpausing attempted!");
//soundio_outstream_clear_buffer(this->out_stream);
- LOG_F(INFO, "AWAC: unpausing result: %s",
- soundio_strerror(soundio_outstream_pause(this->out_stream, false)));
+ //LOG_F(INFO, "AWAC: unpausing result: %s",
+ // soundio_strerror(soundio_outstream_pause(this->out_stream, false)));
return;
}
@@ -239,9 +316,13 @@ void AWACDevice::dma_start()
return;
}
- if ((err = soundio_outstream_start(this->out_stream))) {
- LOG_F(ERROR, "AWAC: unable to start stream: %s\n", soundio_strerror(err));
- return;
+ //if ((err = soundio_outstream_start(this->out_stream))) {
+ // LOG_F(ERROR, "AWAC: unable to start stream: %s\n", soundio_strerror(err));
+ // return;
+ //}
+
+ if ((err = snd_server->start_out_stream())) {
+ LOG_F(ERROR, "Could not start sound output stream");
}
}
diff --git a/devices/awacs.h b/devices/awacs.h
index 9e90321..f541974 100644
--- a/devices/awacs.h
+++ b/devices/awacs.h
@@ -33,13 +33,14 @@ along with this program. If not, see .
#include
#include "i2c.h"
#include "dbdma.h"
-#ifdef SDL
-#include
+#include "soundserver.h"
+//#ifdef SDL
+//#include
//#else
//#include "thirdparty/portaudio/include/portaudio.h"
-#endif
+//#endif
//#include "libsoundio/soundio/soundio.h"
-#include
+//#include
/** AWAC registers offsets. */
enum {
@@ -131,8 +132,10 @@ private:
uint8_t is_busy = 0;
AudioProcessor *audio_proc;
- SoundIoDevice *out_device;
- SoundIoOutStream *out_stream;
+ SoundServer *snd_server;
+
+ //SoundIoDevice *out_device;
+ //SoundIoOutStream *out_stream;
bool out_stream_ready;
int out_sample_rate;
diff --git a/devices/soundserver.cpp b/devices/soundserver.cpp
index bd17568..61f0c39 100644
--- a/devices/soundserver.cpp
+++ b/devices/soundserver.cpp
@@ -20,9 +20,11 @@ along with this program. If not, see .
*/
#include "soundserver.h"
-#include
+//#include
#include
+#include
+#if 0
int SoundServer::start()
{
int err;
@@ -79,3 +81,85 @@ void SoundServer::shutdown()
LOG_F(INFO, "Sound Server shut down.");
}
+#endif
+
+int SoundServer::start()
+{
+ int res;
+
+ this->status = SND_SERVER_DOWN;
+
+ res = cubeb_init(&this->cubeb_ctx, "Dingus sound server", NULL);
+ if (res != CUBEB_OK) {
+ LOG_F(ERROR, "Could not initialize Cubeb library");
+ return -1;
+ }
+
+ LOG_F(INFO, "Connected to backend: %s", cubeb_get_backend_id(this->cubeb_ctx));
+
+ this->status = SND_API_READY;
+
+ return 0;
+}
+
+void SoundServer::shutdown()
+{
+ switch (this->status) {
+ case SND_SERVER_UP:
+ //soundio_device_unref(this->out_device);
+ /* fall through */
+ case SND_API_READY:
+ cubeb_destroy(this->cubeb_ctx);
+ }
+
+ this->status = SND_SERVER_DOWN;
+
+ LOG_F(INFO, "Sound Server shut down.");
+}
+
+
+int SoundServer::open_out_stream(uint32_t sample_rate, cubeb_data_callback data_cb,
+ cubeb_state_callback status_cb, void *user_data)
+{
+ int res;
+ uint32_t latency_frames;
+ cubeb_stream_params params;
+
+ params.format = CUBEB_SAMPLE_S16NE;
+ params.rate = sample_rate;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ res = cubeb_get_min_latency(this->cubeb_ctx, ¶ms, &latency_frames);
+ if (res != CUBEB_OK) {
+ LOG_F(ERROR, "Could not get minimum latency, error: %d", res);
+ return -1;
+ } else {
+ LOG_F(INFO, "Minimum sound latency: %d frames", latency_frames);
+ }
+
+ res = cubeb_stream_init(this->cubeb_ctx, &this->out_stream, "SndOut stream",
+ NULL, NULL, NULL, ¶ms, latency_frames,
+ data_cb, status_cb, user_data);
+ if (res != CUBEB_OK) {
+ LOG_F(ERROR, "Could not open sound output stream, error: %d", res);
+ return -1;
+ }
+
+ LOG_F(INFO, "Sound output stream opened.");
+
+ return 0;
+}
+
+int SoundServer::start_out_stream()
+{
+ return cubeb_stream_start(this->out_stream);
+}
+
+void SoundServer::close_out_stream()
+{
+ cubeb_stream_stop(this->out_stream);
+ cubeb_stream_destroy(this->out_stream);
+ LOG_F(INFO, "Sound output stream closed.");
+}
diff --git a/devices/soundserver.h b/devices/soundserver.h
index ede64d3..b0eccaa 100644
--- a/devices/soundserver.h
+++ b/devices/soundserver.h
@@ -35,7 +35,8 @@ along with this program. If not, see .
#define SOUND_SERVER_H
#include "hwcomponent.h"
-#include
+//#include
+#include
enum {
SND_SERVER_DOWN = 0,
@@ -51,9 +52,14 @@ public:
int start();
void shutdown();
+ int open_out_stream(uint32_t sample_rate, cubeb_data_callback data_cb,
+ cubeb_state_callback status_cb, void *user_data);
+ int start_out_stream();
+ void close_out_stream();
bool supports_type(HWCompType type) { return type == HWCompType::SND_SERVER; };
+#if 0
SoundIoDevice *get_out_device() {
if (this->status == SND_SERVER_UP) {
return this->out_device;
@@ -61,12 +67,16 @@ public:
return NULL;
}
};
+#endif
private:
int status; /* server status */
- SoundIo *soundio;
- int out_dev_index; /* current output device index */
- SoundIoDevice *out_device; /* current output device instance */
+ cubeb *cubeb_ctx;
+
+ cubeb_stream *out_stream;
+ //SoundIo *soundio;
+ //int out_dev_index; /* current output device index */
+ //SoundIoDevice *out_device; /* current output device instance */
//SoundIoOutStream *outstream;
};
diff --git a/machines/CMakeLists.txt b/machines/CMakeLists.txt
index fd9bd5c..c72473c 100644
--- a/machines/CMakeLists.txt
+++ b/machines/CMakeLists.txt
@@ -5,3 +5,4 @@ include_directories("${PROJECT_SOURCE_DIR}")
file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_library(machines OBJECT ${SOURCES})
+target_link_libraries(machines PRIVATE cubeb)
diff --git a/thirdparty/cubeb/.gitignore b/thirdparty/cubeb/.gitignore
new file mode 100644
index 0000000..e524d79
--- /dev/null
+++ b/thirdparty/cubeb/.gitignore
@@ -0,0 +1,2 @@
+.vscode/
+build/
diff --git a/thirdparty/cubeb/.gitmodules b/thirdparty/cubeb/.gitmodules
new file mode 100644
index 0000000..a819201
--- /dev/null
+++ b/thirdparty/cubeb/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "googletest"]
+ path = googletest
+ url = https://github.com/google/googletest
+[submodule "cmake/sanitizers-cmake"]
+ path = cmake/sanitizers-cmake
+ url = https://github.com/arsenm/sanitizers-cmake
diff --git a/thirdparty/cubeb/.travis.yml b/thirdparty/cubeb/.travis.yml
new file mode 100644
index 0000000..70ed094
--- /dev/null
+++ b/thirdparty/cubeb/.travis.yml
@@ -0,0 +1,68 @@
+language: cpp
+dist: xenial
+sudo: required
+addons:
+ apt:
+ packages:
+ - clang-3.8
+ - g++-5
+ - libpulse-dev
+ - libasound2-dev
+ - pulseaudio
+ - doxygen
+ - mingw-w64
+ - mingw-w64-tools
+env:
+ global:
+ - CMAKE_USE_ASAN=OFF
+matrix:
+ include:
+ - env: C_COMPILER=clang-3.8 CXX_COMPILER=clang++-3.8
+ - env: C_COMPILER=clang-3.8 CXX_COMPILER=clang++-3.8 CMAKE_USE_ASAN=ON
+ - env: C_COMPILER=gcc-5 CXX_COMPILER=g++-5
+ - env: SCAN_BUILD=true
+ - env: CROSS=mingw
+ - env: CROSS=android API_LEVEL=android-16
+ - os: osx
+ - os: osx
+ env: SCAN_BUILD=true
+ - os: windows
+before_install:
+ - if [[ -n $SCAN_BUILD ]]; then
+ if [[ $TRAVIS_OS_NAME = "osx" ]]; then
+ . scan-build-install.sh;
+ else
+ export SCAN_BUILD_PATH=/usr/share/clang/scan-build-3.8/bin/scan-build;
+ fi;
+ export SCAN_BUILD_PATH="$SCAN_BUILD_PATH -o scan_results";
+ fi
+ - if [[ $CROSS = "android" ]]; then
+ NDK=android-ndk-r19c;
+ curl -LO https://dl.google.com/android/repository/$NDK-linux-x86_64.zip;
+ unzip -q $NDK-linux-x86_64.zip;
+ rm $NDK-linux-x86_64.zip;
+ export ANDROID_NDK=$(pwd)/$NDK;
+ fi
+before_script:
+ - if [[ -n $C_COMPILER ]]; then
+ export CC=$C_COMPILER;
+ fi
+ - if [[ -n $CXX_COMPILER ]]; then
+ export CXX=$CXX_COMPILER;
+ fi
+ - if [[ -n $CROSS ]]; then
+ export TOOLCHAIN=-DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-cross-$CROSS.cmake;
+ fi
+ - if [[ $CROSS = "android" ]]; then
+ export TOOLCHAIN="-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=$API_LEVEL";
+ fi
+ - mkdir build && cd build
+ - $SCAN_BUILD_PATH cmake -DCMAKE_BUILD_TYPE=Debug -DSANITIZE_ADDRESS=$CMAKE_USE_ASAN $TOOLCHAIN ..;
+script:
+ - $SCAN_BUILD_PATH cmake --build .
+ - if [[ $TRAVIS_OS_NAME = "linux" && -z $CROSS ]]; then
+ ctest -V;
+ fi
+ - if [[ -n $SCAN_BUILD ]]; then
+ rmdir scan_results || ( echo "scan-build detected bugs!" && exit 1 );
+ fi
diff --git a/thirdparty/cubeb/AUTHORS b/thirdparty/cubeb/AUTHORS
new file mode 100644
index 0000000..f0f9595
--- /dev/null
+++ b/thirdparty/cubeb/AUTHORS
@@ -0,0 +1,16 @@
+Matthew Gregan
+Alexandre Ratchov
+Michael Wu
+Paul Adenot
+David Richards
+Sebastien Alaiwan
+KO Myung-Hun
+Haakon Sporsheim
+Alex Chronopoulos
+Jan Beich
+Vito Caputo
+Landry Breuil
+Jacek Caban
+Paul Hancock
+Ted Mielczarek
+Chun-Min Chang
diff --git a/thirdparty/cubeb/CMakeLists.txt b/thirdparty/cubeb/CMakeLists.txt
new file mode 100644
index 0000000..5fc134d
--- /dev/null
+++ b/thirdparty/cubeb/CMakeLists.txt
@@ -0,0 +1,334 @@
+# TODO
+# - backend selection via command line, rather than simply detecting headers.
+
+cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
+project(cubeb
+ VERSION 0.0.0)
+
+option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
+option(BUILD_TESTS "Build tests" ON)
+option(BUILD_RUST_LIBS "Build rust backends" OFF)
+option(BUILD_TOOLS "Build tools" ON)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
+ "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+endif()
+
+if(POLICY CMP0063)
+ cmake_policy(SET CMP0063 NEW)
+endif()
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+if(NOT COMMAND add_sanitizers)
+ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake")
+ find_package(Sanitizers)
+ if(NOT COMMAND add_sanitizers)
+ message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout")
+ endif()
+endif()
+
+if(BUILD_TESTS)
+ if(NOT TARGET gtest_main)
+ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/googletest/CMakeLists.txt")
+ message(FATAL_ERROR "Could not find googletest: run\n\tgit submodule update --init --recursive\nin base git checkout")
+ endif()
+ add_definitions(-DGTEST_HAS_TR1_TUPLE=0)
+ set(gtest_force_shared_crt ON CACHE BOOL "")
+ add_subdirectory(googletest)
+ endif()
+endif()
+
+if (BUILD_RUST_LIBS)
+ if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs")
+ set(USE_PULSE_RUST 1)
+ endif()
+ if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs")
+ set(USE_AUDIOUNIT_RUST 1)
+ endif()
+endif()
+
+# On OS/2, visibility attribute is not supported.
+if(NOT OS2)
+ set(CMAKE_C_VISIBILITY_PRESET hidden)
+ set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+ set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
+endif()
+
+set(CMAKE_CXX_WARNING_LEVEL 4)
+if(NOT MSVC)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter")
+endif()
+
+add_library(cubeb
+ src/cubeb.c
+ src/cubeb_mixer.cpp
+ src/cubeb_resampler.cpp
+ src/cubeb_log.cpp
+ src/cubeb_strings.c
+ src/cubeb_utils.cpp
+ $)
+target_include_directories(cubeb
+ PUBLIC $ $
+)
+target_include_directories(cubeb PRIVATE src)
+target_compile_definitions(cubeb PRIVATE OUTSIDE_SPEEX)
+target_compile_definitions(cubeb PRIVATE FLOATING_POINT)
+target_compile_definitions(cubeb PRIVATE EXPORT=)
+target_compile_definitions(cubeb PRIVATE RANDOM_PREFIX=speex)
+
+add_sanitizers(cubeb)
+
+include(GenerateExportHeader)
+generate_export_header(cubeb EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/cubeb_export.h)
+target_include_directories(cubeb
+ PUBLIC $
+)
+
+if(UNIX)
+ include(GNUInstallDirs)
+else()
+ set(CMAKE_INSTALL_LIBDIR "lib")
+ set(CMAKE_INSTALL_BINDIR "bin")
+ set(CMAKE_INSTALL_DATADIR "share")
+ set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATADIR}/doc")
+ set(CMAKE_INSTALL_INCLUDEDIR "include")
+endif()
+
+install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(DIRECTORY ${CMAKE_BINARY_DIR}/exports/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
+ COMPATIBILITY SameMajorVersion
+)
+
+configure_package_config_file(
+ "Config.cmake.in"
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+ INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+
+install(TARGETS cubeb
+ EXPORT "${PROJECT_NAME}Targets"
+ DESTINATION ${CMAKE_INSTALL_PREFIX}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
+)
+install(
+ FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+install(
+ EXPORT "${PROJECT_NAME}Targets"
+ NAMESPACE "${PROJECT_NAME}::"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+
+add_library(speex OBJECT
+ src/speex/resample.c)
+set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+target_compile_definitions(speex PRIVATE OUTSIDE_SPEEX)
+target_compile_definitions(speex PRIVATE FLOATING_POINT)
+target_compile_definitions(speex PRIVATE EXPORT=)
+target_compile_definitions(speex PRIVATE RANDOM_PREFIX=speex)
+
+include(CheckIncludeFiles)
+
+check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
+if(USE_AUDIOUNIT)
+ target_sources(cubeb PRIVATE
+ src/cubeb_audiounit.cpp
+ src/cubeb_osx_run_loop.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT)
+ target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
+endif()
+
+check_include_files(pulse/pulseaudio.h USE_PULSE)
+if(USE_PULSE)
+ target_sources(cubeb PRIVATE
+ src/cubeb_pulse.c)
+ target_compile_definitions(cubeb PRIVATE USE_PULSE)
+ target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
+endif()
+
+check_include_files(alsa/asoundlib.h USE_ALSA)
+if(USE_ALSA)
+ target_sources(cubeb PRIVATE
+ src/cubeb_alsa.c)
+ target_compile_definitions(cubeb PRIVATE USE_ALSA)
+ target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
+endif()
+
+check_include_files(jack/jack.h USE_JACK)
+if(USE_JACK)
+ target_sources(cubeb PRIVATE
+ src/cubeb_jack.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_JACK)
+ target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
+endif()
+
+check_include_files(audioclient.h USE_WASAPI)
+if(USE_WASAPI)
+ target_sources(cubeb PRIVATE
+ src/cubeb_wasapi.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_WASAPI)
+ target_link_libraries(cubeb PRIVATE avrt ole32)
+endif()
+
+check_include_files("windows.h;mmsystem.h" USE_WINMM)
+if(USE_WINMM)
+ target_sources(cubeb PRIVATE
+ src/cubeb_winmm.c)
+ target_compile_definitions(cubeb PRIVATE USE_WINMM)
+ target_link_libraries(cubeb PRIVATE winmm)
+endif()
+
+check_include_files(SLES/OpenSLES.h USE_OPENSL)
+if(USE_OPENSL)
+ target_sources(cubeb PRIVATE
+ src/cubeb_opensl.c
+ src/cubeb-jni.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_OPENSL)
+ target_link_libraries(cubeb PRIVATE OpenSLES)
+endif()
+
+check_include_files(android/log.h USE_AUDIOTRACK)
+if(USE_AUDIOTRACK)
+ target_sources(cubeb PRIVATE
+ src/cubeb_audiotrack.c)
+ target_compile_definitions(cubeb PRIVATE USE_AUDIOTRACK)
+ target_link_libraries(cubeb PRIVATE log)
+endif()
+
+check_include_files(sndio.h USE_SNDIO)
+if(USE_SNDIO)
+ target_sources(cubeb PRIVATE
+ src/cubeb_sndio.c)
+ target_compile_definitions(cubeb PRIVATE USE_SNDIO)
+ target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
+endif()
+
+check_include_files(sys/audioio.h USE_SUN)
+if(USE_SUN)
+ target_sources(cubeb PRIVATE
+ src/cubeb_sun.c)
+ target_compile_definitions(cubeb PRIVATE USE_SUN)
+ target_link_libraries(cubeb PRIVATE pthread)
+endif()
+
+check_include_files(kai.h USE_KAI)
+if(USE_KAI)
+ target_sources(cubeb PRIVATE
+ src/cubeb_kai.c)
+ target_compile_definitions(cubeb PRIVATE USE_KAI)
+ target_link_libraries(cubeb PRIVATE kai)
+endif()
+
+if(USE_PULSE_RUST)
+ include(ExternalProject)
+ set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
+ ExternalProject_Add(
+ cubeb_pulse_rs
+ DOWNLOAD_COMMAND ""
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND cargo build COMMAND cargo build --release
+ BUILD_ALWAYS ON
+ BINARY_DIR "${CMAKE_SOURCE_DIR}/src/cubeb-pulse-rs"
+ INSTALL_COMMAND ""
+ LOG_BUILD ON)
+ add_dependencies(cubeb cubeb_pulse_rs)
+ target_compile_definitions(cubeb PRIVATE USE_PULSE_RUST)
+ target_link_libraries(cubeb PRIVATE
+ debug "${CMAKE_SOURCE_DIR}/src/cubeb-pulse-rs/target/debug/libcubeb_pulse.a"
+ optimized "${CMAKE_SOURCE_DIR}/src/cubeb-pulse-rs/target/release/libcubeb_pulse.a" pulse)
+endif()
+
+if(USE_AUDIOUNIT_RUST)
+ include(ExternalProject)
+ set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
+ ExternalProject_Add(
+ cubeb_coreaudio_rs
+ DOWNLOAD_COMMAND ""
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND cargo build COMMAND cargo build --release
+ BUILD_ALWAYS ON
+ BINARY_DIR "${CMAKE_SOURCE_DIR}/src/cubeb-coreaudio-rs"
+ INSTALL_COMMAND ""
+ LOG_BUILD ON)
+ add_dependencies(cubeb cubeb_coreaudio_rs)
+ target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT_RUST)
+ target_link_libraries(cubeb PRIVATE
+ debug "${CMAKE_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/debug/libcubeb_coreaudio.a"
+ optimized "${CMAKE_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/release/libcubeb_coreaudio.a")
+endif()
+
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile @ONLY)
+ add_custom_target(doc ALL
+ ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs
+ COMMENT "Generating API documentation with Doxygen" VERBATIM)
+endif()
+
+if(BUILD_TESTS)
+ enable_testing()
+
+ macro(cubeb_add_test NAME)
+ add_executable(test_${NAME} test/test_${NAME}.cpp)
+ target_include_directories(test_${NAME} PRIVATE ${gtest_SOURCE_DIR}/include)
+ target_include_directories(test_${NAME} PRIVATE src)
+ target_link_libraries(test_${NAME} PRIVATE cubeb gtest_main)
+ add_test(${NAME} test_${NAME})
+ add_sanitizers(test_${NAME})
+ install(TARGETS test_${NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
+ endmacro(cubeb_add_test)
+
+ cubeb_add_test(sanity)
+ cubeb_add_test(tone)
+ cubeb_add_test(audio)
+ cubeb_add_test(record)
+ cubeb_add_test(devices)
+ cubeb_add_test(callback_ret)
+
+ add_executable(test_resampler test/test_resampler.cpp src/cubeb_resampler.cpp $)
+ target_include_directories(test_resampler PRIVATE ${gtest_SOURCE_DIR}/include)
+ target_include_directories(test_resampler PRIVATE src)
+ target_compile_definitions(test_resampler PRIVATE OUTSIDE_SPEEX)
+ target_compile_definitions(test_resampler PRIVATE FLOATING_POINT)
+ target_compile_definitions(test_resampler PRIVATE EXPORT=)
+ target_compile_definitions(test_resampler PRIVATE RANDOM_PREFIX=speex)
+ target_link_libraries(test_resampler PRIVATE cubeb gtest_main)
+ add_test(resampler test_resampler)
+ add_sanitizers(test_resampler)
+ install(TARGETS test_resampler DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
+
+ cubeb_add_test(duplex)
+
+ if (USE_WASAPI)
+ cubeb_add_test(overload_callback)
+ cubeb_add_test(loopback)
+ endif()
+
+ cubeb_add_test(latency test_latency)
+ cubeb_add_test(ring_array)
+
+ cubeb_add_test(utils)
+ cubeb_add_test(ring_buffer)
+ cubeb_add_test(device_changed_callback)
+endif()
+
+if(BUILD_TOOLS)
+ add_executable(cubeb-test tools/cubeb-test.cpp)
+ target_include_directories(cubeb-test PRIVATE src)
+ target_link_libraries(cubeb-test PRIVATE cubeb)
+ add_sanitizers(cubeb-test)
+ install(TARGETS cubeb-test DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
+endif()
diff --git a/thirdparty/cubeb/Config.cmake.in b/thirdparty/cubeb/Config.cmake.in
new file mode 100644
index 0000000..c5326ef
--- /dev/null
+++ b/thirdparty/cubeb/Config.cmake.in
@@ -0,0 +1,4 @@
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake")
+check_required_components(cubeb)
\ No newline at end of file
diff --git a/thirdparty/cubeb/INSTALL.md b/thirdparty/cubeb/INSTALL.md
new file mode 100644
index 0000000..f849224
--- /dev/null
+++ b/thirdparty/cubeb/INSTALL.md
@@ -0,0 +1,46 @@
+# Build instructions for libcubeb
+
+You must have CMake v3.1 or later installed.
+
+1. `git clone --recursive https://github.com/kinetiknz/cubeb.git`
+2. `mkdir cubeb-build`
+3. `cd cubeb-build`
+3. `cmake ../cubeb`
+4. `cmake --build .`
+5. `ctest`
+
+# Windows build notes
+
+Windows builds can use Microsoft Visual Studio 2015, Microsoft Visual Studio
+2017, or MinGW-w64 with Win32 threads (by passing `cmake -G` to generate the
+appropriate build configuration).
+
+## Microsoft Visual Studio 2015 or 2017 Command Line
+
+CMake can be used from the command line by following the build steps at the top
+of this file. CMake will select a default generator based on the environment,
+or one can be specified with the `-G` argument.
+
+## Microsoft Visual Studio 2017 IDE
+
+Visual Studio 2017 adds in built support for CMake. CMake can be used from
+within the IDE via the following steps:
+
+- Navigate to `File -> Open -> Cmake...`
+- Open `CMakeLists.txt` file in the root of the project.
+
+Note, to generate the build in the cubeb dir CMake settings need to be updated
+via: `CMake -> Change CMake Settings -> CMakeLists.txt`. The default
+configuration used by Visual Studio will place the build in a different location
+than the steps detailed at the top of this file.
+
+## MinGW-w64
+
+To build with MinGW-w64, install the following items:
+
+- Download and install MinGW-w64 with Win32 threads.
+- Download and install CMake.
+- Run MinGW-w64 Terminal from the Start Menu.
+- Follow the build steps at the top of this file, but at step 3 run:
+ `cmake -G "MinGW Makefiles" ..`
+- Continue the build steps at the top of this file.
diff --git a/thirdparty/cubeb/LICENSE b/thirdparty/cubeb/LICENSE
new file mode 100644
index 0000000..fffc9dc
--- /dev/null
+++ b/thirdparty/cubeb/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/thirdparty/cubeb/README.md b/thirdparty/cubeb/README.md
new file mode 100644
index 0000000..9954c37
--- /dev/null
+++ b/thirdparty/cubeb/README.md
@@ -0,0 +1,8 @@
+[![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.
+
+See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
+
+Licensed under an ISC-style license. See LICENSE for details.
diff --git a/thirdparty/cubeb/appveyor.yml b/thirdparty/cubeb/appveyor.yml
new file mode 100644
index 0000000..3a3a17a
--- /dev/null
+++ b/thirdparty/cubeb/appveyor.yml
@@ -0,0 +1,9 @@
+version: 1.0.{build}
+install:
+- cmd: git submodule update --init --recursive
+build_script:
+- cmd: |-
+ cd c:\projects\cubeb
+ cmake .
+ cmake --build . --config Debug
+
diff --git a/thirdparty/cubeb/cmake/sanitizers-cmake b/thirdparty/cubeb/cmake/sanitizers-cmake
new file mode 160000
index 0000000..aab6948
--- /dev/null
+++ b/thirdparty/cubeb/cmake/sanitizers-cmake
@@ -0,0 +1 @@
+Subproject commit aab6948fa863bc1cbe5d0850bc46b9ef02ed4c1a
diff --git a/thirdparty/cubeb/cmake/toolchain-cross-mingw.cmake b/thirdparty/cubeb/cmake/toolchain-cross-mingw.cmake
new file mode 100644
index 0000000..6d29fe2
--- /dev/null
+++ b/thirdparty/cubeb/cmake/toolchain-cross-mingw.cmake
@@ -0,0 +1,14 @@
+SET(CMAKE_SYSTEM_NAME Windows)
+
+set(COMPILER_PREFIX "i686-w64-mingw32")
+
+find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres)
+find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc-posix)
+find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++-posix)
+
+SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX})
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
diff --git a/thirdparty/cubeb/cubeb.supp b/thirdparty/cubeb/cubeb.supp
new file mode 100644
index 0000000..0012ea5
--- /dev/null
+++ b/thirdparty/cubeb/cubeb.supp
@@ -0,0 +1,36 @@
+{
+ snd_config_update-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:snd_config_update_r
+}
+{
+ snd1_dlobj_cache_get-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:snd1_dlobj_cache_get
+}
+{
+ parse_defs-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:parse_defs
+}
+{
+ parse_defs-calloc
+ Memcheck:Leak
+ fun:calloc
+ ...
+ fun:parse_defs
+}
+{
+ pa_client_conf_from_x11-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:pa_client_conf_from_x11
+}
+
diff --git a/thirdparty/cubeb/docs/Doxyfile.in b/thirdparty/cubeb/docs/Doxyfile.in
new file mode 100644
index 0000000..2b52bce
--- /dev/null
+++ b/thirdparty/cubeb/docs/Doxyfile.in
@@ -0,0 +1,12 @@
+PROJECT_NAME = @PACKAGE@
+PROJECT_NUMBER = @VERSION@
+OUTPUT_DIRECTORY = .
+JAVADOC_AUTOBRIEF = YES
+OPTIMIZE_OUTPUT_FOR_C = YES
+CASE_SENSE_NAMES = NO
+SORT_MEMBER_DOCS = NO
+QUIET = YES
+WARN_NO_PARAMDOC = YES
+INPUT = @CMAKE_CURRENT_SOURCE_DIR@/include/cubeb
+GENERATE_HTML = YES
+GENERATE_LATEX = NO
diff --git a/thirdparty/cubeb/googletest b/thirdparty/cubeb/googletest
new file mode 160000
index 0000000..800f542
--- /dev/null
+++ b/thirdparty/cubeb/googletest
@@ -0,0 +1 @@
+Subproject commit 800f5422ac9d9e0ad59cd860a2ef3a679588acb4
diff --git a/thirdparty/cubeb/include/cubeb/cubeb.h b/thirdparty/cubeb/include/cubeb/cubeb.h
new file mode 100644
index 0000000..f372831
--- /dev/null
+++ b/thirdparty/cubeb/include/cubeb/cubeb.h
@@ -0,0 +1,668 @@
+/*
+ * 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
+#include
+#include "cubeb_export.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** @mainpage
+
+ @section intro Introduction
+
+ This is the documentation for the libcubeb C API.
+ libcubeb 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", NULL);
+ int rv;
+ uint32_t rate;
+ uint32_t latency_frames;
+ uint64_t ts;
+
+ rv = cubeb_get_preferred_sample_rate(app_ctx, &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;
+ output_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get minimum latency");
+ return rv;
+ }
+
+ cubeb_stream_params input_params;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = rate;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ cubeb_stream * stm;
+ rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
+ NULL, &input_params,
+ NULL, &output_params,
+ 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,
+ const void * input_buffer, void * output_buffer, long nframes)
+ {
+ const float * in = input_buffer;
+ float * out = output_buffer;
+
+ for (int i = 0; i < nframes; ++i) {
+ for (int c = 0; c < 2; ++c) {
+ out[2 * 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 libcubeb 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;
+
+/** An opaque handle used to refer a particular input or output device
+ * across calls. */
+typedef void const * cubeb_devid;
+
+/** Level (verbosity) of logging for a particular cubeb context. */
+typedef enum {
+ CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
+ CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */
+ CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
+} cubeb_log_level;
+
+typedef enum {
+ CHANNEL_UNKNOWN = 0,
+ CHANNEL_FRONT_LEFT = 1 << 0,
+ CHANNEL_FRONT_RIGHT = 1 << 1,
+ CHANNEL_FRONT_CENTER = 1 << 2,
+ CHANNEL_LOW_FREQUENCY = 1 << 3,
+ CHANNEL_BACK_LEFT = 1 << 4,
+ CHANNEL_BACK_RIGHT = 1 << 5,
+ CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
+ CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
+ CHANNEL_BACK_CENTER = 1 << 8,
+ CHANNEL_SIDE_LEFT = 1 << 9,
+ CHANNEL_SIDE_RIGHT = 1 << 10,
+ CHANNEL_TOP_CENTER = 1 << 11,
+ CHANNEL_TOP_FRONT_LEFT = 1 << 12,
+ CHANNEL_TOP_FRONT_CENTER = 1 << 13,
+ CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
+ CHANNEL_TOP_BACK_LEFT = 1 << 15,
+ CHANNEL_TOP_BACK_CENTER = 1 << 16,
+ CHANNEL_TOP_BACK_RIGHT = 1 << 17
+} cubeb_channel;
+
+typedef uint32_t cubeb_channel_layout;
+// Some common layout definitions.
+enum {
+ CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
+ CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
+ CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F1 =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
+ CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+};
+
+/** Miscellaneous stream preferences. */
+typedef enum {
+ CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
+ CUBEB_STREAM_PREF_LOOPBACK = 0x01, /**< Request a loopback stream. Should be
+ specified on the input params and an
+ output device to loopback from should
+ be passed in place of an input device. */
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
+ default device on OS
+ changes. */
+ CUBEB_STREAM_PREF_VOICE = 0x04 /**< This stream is going to transport voice data.
+ Depending on the backend and platform, this can
+ change the audio input or output devices
+ selected, as well as the quality of the stream,
+ for example to accomodate bluetooth SCO modes on
+ bluetooth devices. */
+} cubeb_stream_prefs;
+
+/** Stream format initialization parameters. */
+typedef struct {
+ cubeb_sample_format format; /**< Requested sample format. One of
+ #cubeb_sample_format. */
+ uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
+ uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
+ cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */
+ cubeb_stream_prefs prefs; /**< Requested preferences. */
+} cubeb_stream_params;
+
+/** Audio device description */
+typedef struct {
+ char * output_name; /**< The name of the output device */
+ char * input_name; /**< The name of the input device */
+} 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 is obtained using
+ * `cubeb_enumerate_devices`, which returns these structures via
+ * `cubeb_device_collection` and must be destroyed via
+ * `cubeb_device_collection_destroy`. */
+typedef struct {
+ cubeb_devid devid; /**< Device identifier handle. */
+ char const * device_id; /**< Device identifier which might be presented in a UI. */
+ char const * friendly_name; /**< Friendly device name which might be presented in a UI. */
+ char const * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
+ char const * vendor_name; /**< Optional vendor name, may be NULL. */
+
+ cubeb_device_type type; /**< Type of device (Input/Output). */
+ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
+ cubeb_device_pref preferred;/**< Preferred device. */
+
+ cubeb_device_fmt format; /**< Sample format supported. */
+ cubeb_device_fmt default_format; /**< The default sample format for this device. */
+ uint32_t max_channels; /**< Channels. */
+ uint32_t default_rate; /**< Default/Preferred sample rate. */
+ uint32_t max_rate; /**< Maximum sample rate supported. */
+ uint32_t min_rate; /**< Minimum sample rate supported. */
+
+ uint32_t latency_lo; /**< Lowest possible latency in frames. */
+ uint32_t latency_hi; /**< Higest possible latency in frames. */
+} cubeb_device_info;
+
+/** Device collection.
+ * Returned by `cubeb_enumerate_devices` and destroyed by
+ * `cubeb_device_collection_destroy`. */
+typedef struct {
+ cubeb_device_info * device; /**< Array of pointers to device info. */
+ size_t count; /**< Device count in collection. */
+} 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 If the stream has output, this is the number of frames written to
+ the output buffer. In this case, if this number is less than
+ nframes then the stream will start to drain. If the stream is
+ input only, then returning nframes indicates data has been read.
+ In this case, a value less than nframes will result in the stream
+ being stopped.
+ @retval CUBEB_ERROR on error, in which case the data callback will stop
+ and the stream will enter a shutdown state. */
+typedef long (* cubeb_data_callback)(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes);
+
+/** User supplied state callback.
+ @param stream The stream for this this callback fired.
+ @param user_ptr The pointer passed to cubeb_stream_init.
+ @param state The new state of the stream. */
+typedef void (* cubeb_state_callback)(cubeb_stream * stream,
+ void * user_ptr,
+ cubeb_state state);
+
+/**
+ * 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_register_device_collection_changed. */
+typedef void (* cubeb_device_collection_changed_callback)(cubeb * context,
+ void * user_ptr);
+
+/** User supplied callback called when a message needs logging. */
+typedef void (* cubeb_log_callback)(char const * fmt, ...);
+
+/** Initialize an application context. This will perform any library or
+ application scoped initialization.
+
+ Note: On Windows platforms, COM must be initialized in MTA mode on
+ any thread that will call the cubeb API.
+
+ @param context A out param where an opaque pointer to the application
+ context will be returned.
+ @param context_name A name for the context. Depending on the platform this
+ can appear in different locations.
+ @param backend_name The name of the cubeb backend user desires to select.
+ Accepted values self-documented in cubeb.c: init_oneshot
+ If NULL, a default ordering is used for backend choice.
+ A valid choice overrides all other possible backends,
+ so long as the backend was included at compile time.
+ @retval CUBEB_OK in case of success.
+ @retval CUBEB_ERROR in case of error, for example because the host
+ has no audio hardware. */
+CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name);
+
+/** Get a read-only string identifying this context's current backend.
+ @param context A pointer to the cubeb context.
+ @retval Read-only string identifying current backend. */
+CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
+
+/** 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 dependent.
+ @param context A pointer to the cubeb context.
+ @param params On some backends, the minimum achievable latency depends on
+ the characteristics of the stream.
+ @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 dependent, and can avoid resampling, and/or trigger fastpaths.
+ @param context A pointer to the cubeb context.
+ @param rate The samplerate (in Hz) the current configuration prefers.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
+
+/** 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,
+ uint32_t latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr);
+
+/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
+ stream.
+ @param stream The stream to destroy. */
+CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream);
+
+/** 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);
+
+/** Reset stream to the default device.
+ @param stream
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int cubeb_stream_reset_default_device(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);
+
+/** Get the input latency for this stream, in frames. This is the number of
+ frames between the time the audio input devices records the data, and they
+ are available in the data callback.
+ This returns CUBEB_ERROR when the stream is output-only.
+ @param stream
+ @param latency Current approximate stream latency in frames.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
+/** Set the volume for a stream.
+ @param stream the stream for which to adjust the volume.
+ @param volume a float between 0.0 (muted) and 1.0 (maximum volume)
+ @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);
+
+/** 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);
+
+/** Return the user data pointer registered with the stream with cubeb_stream_init.
+ @param stream the stream for which to retrieve user data pointer.
+ @retval user data pointer */
+CUBEB_EXPORT void * cubeb_stream_user_ptr(cubeb_stream * stream);
+
+/** Returns enumerated devices.
+ @param context
+ @param devtype device type to include
+ @param collection output collection. Must be destroyed with cubeb_device_collection_destroy
+ @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 context
+ @param collection collection to destroy
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
+CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
+
+/** Registers a callback which is called when the system detects
+ a new device or a device is removed.
+ @param context
+ @param devtype device type to include. Different callbacks and user pointers
+ can be registered for each devtype. The hybrid devtype
+ `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
+ and will register the provided callback and user pointer in both sides.
+ @param callback a function called whenever the system device list changes.
+ Passing NULL allow to unregister a function. You have to unregister
+ first before you register a new callback.
+ @param user_ptr pointer to user specified data which will be present in
+ subsequent callbacks.
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
+ cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback,
+ void * user_ptr);
+
+/** 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/thirdparty/cubeb/scan-build-install.sh b/thirdparty/cubeb/scan-build-install.sh
new file mode 100755
index 0000000..3e1b496
--- /dev/null
+++ b/thirdparty/cubeb/scan-build-install.sh
@@ -0,0 +1,15 @@
+#!/bin/sh -x
+
+CLANG_CHECKER_NAME=checker-278
+
+cd ~
+
+if [ ! -d ~/$CLANG_CHECKER_NAME ]
+then
+ curl http://clang-analyzer.llvm.org/downloads/$CLANG_CHECKER_NAME.tar.bz2 -o ~/$CLANG_CHECKER_NAME.tar.bz2
+ tar -xf ~/$CLANG_CHECKER_NAME.tar.bz2
+fi
+
+export SCAN_BUILD_PATH=~/$CLANG_CHECKER_NAME/bin/scan-build
+
+cd -
diff --git a/thirdparty/cubeb/src/android/audiotrack_definitions.h b/thirdparty/cubeb/src/android/audiotrack_definitions.h
new file mode 100644
index 0000000..cd50153
--- /dev/null
+++ b/thirdparty/cubeb/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
+
+/*
+ * 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/thirdparty/cubeb/src/android/cubeb-output-latency.h b/thirdparty/cubeb/src/android/cubeb-output-latency.h
new file mode 100644
index 0000000..2128cd1
--- /dev/null
+++ b/thirdparty/cubeb/src/android/cubeb-output-latency.h
@@ -0,0 +1,76 @@
+#ifndef _CUBEB_OUTPUT_LATENCY_H_
+#define _CUBEB_OUTPUT_LATENCY_H_
+
+#include
+#include "cubeb_media_library.h"
+#include "../cubeb-jni.h"
+
+struct output_latency_function {
+ media_lib * from_lib;
+ cubeb_jni * from_jni;
+ int version;
+};
+
+typedef struct output_latency_function output_latency_function;
+
+const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
+
+output_latency_function *
+cubeb_output_latency_load_method(int version)
+{
+ output_latency_function * ol = NULL;
+ ol = calloc(1, sizeof(output_latency_function));
+
+ ol->version = version;
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){
+ ol->from_jni = cubeb_jni_init();
+ return ol;
+ }
+
+ ol->from_lib = cubeb_load_media_library();
+ return ol;
+}
+
+bool
+cubeb_output_latency_method_is_loaded(output_latency_function * ol)
+{
+ assert(ol);
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){
+ return !!ol->from_jni;
+ }
+
+ return !!ol->from_lib;
+}
+
+void
+cubeb_output_latency_unload_method(output_latency_function * ol)
+{
+ if (!ol) {
+ return;
+ }
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
+ cubeb_jni_destroy(ol->from_jni);
+ }
+
+ if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
+ cubeb_close_media_library(ol->from_lib);
+ }
+
+ free(ol);
+}
+
+uint32_t
+cubeb_get_output_latency(output_latency_function * ol)
+{
+ assert(cubeb_output_latency_method_is_loaded(ol));
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){
+ return cubeb_get_output_latency_from_jni(ol->from_jni);
+ }
+
+ return cubeb_get_output_latency_from_media_library(ol->from_lib);
+}
+
+#endif // _CUBEB_OUTPUT_LATENCY_H_
diff --git a/thirdparty/cubeb/src/android/cubeb_media_library.h b/thirdparty/cubeb/src/android/cubeb_media_library.h
new file mode 100644
index 0000000..ab21b77
--- /dev/null
+++ b/thirdparty/cubeb/src/android/cubeb_media_library.h
@@ -0,0 +1,62 @@
+#ifndef _CUBEB_MEDIA_LIBRARY_H_
+#define _CUBEB_MEDIA_LIBRARY_H_
+
+struct media_lib {
+ void * libmedia;
+ int32_t (* get_output_latency)(uint32_t * latency, int stream_type);
+};
+
+typedef struct media_lib media_lib;
+
+media_lib *
+cubeb_load_media_library()
+{
+ media_lib ml = {0};
+ ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
+ if (!ml.libmedia) {
+ return NULL;
+ }
+
+ // Get the latency, in ms, from AudioFlinger. First, try the most recent signature.
+ // status_t AudioSystem::getOutputLatency(uint32_t* latency, audio_stream_type_t streamType)
+ ml.get_output_latency =
+ dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
+ if (!ml.get_output_latency) {
+ // In case of failure, try the signature from legacy version.
+ // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
+ ml.get_output_latency =
+ dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
+ if (!ml.get_output_latency) {
+ return NULL;
+ }
+ }
+
+ media_lib * rv = NULL;
+ rv = calloc(1, sizeof(media_lib));
+ assert(rv);
+ *rv = ml;
+ return rv;
+}
+
+void
+cubeb_close_media_library(media_lib * ml)
+{
+ dlclose(ml->libmedia);
+ ml->libmedia = NULL;
+ ml->get_output_latency = NULL;
+ free(ml);
+}
+
+uint32_t
+cubeb_get_output_latency_from_media_library(media_lib * ml)
+{
+ uint32_t latency = 0;
+ const int audio_stream_type_music = 3;
+ int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
+ if (r) {
+ return 0;
+ }
+ return latency;
+}
+
+#endif // _CUBEB_MEDIA_LIBRARY_H_
diff --git a/thirdparty/cubeb/src/android/sles_definitions.h b/thirdparty/cubeb/src/android/sles_definitions.h
new file mode 100644
index 0000000..06d2e8d
--- /dev/null
+++ b/thirdparty/cubeb/src/android/sles_definitions.h
@@ -0,0 +1,102 @@
+/*
+ * 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)
+/** uses the main microphone unprocessed */
+#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005)
+
+
+/*---------------------------------------------------------------------------*/
+/* 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)
+
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer and AudioRecorder configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio Performance mode.
+ * Performance mode tells the framework how to configure the audio path
+ * for a player or recorder according to application performance and
+ * functional requirements.
+ * It affects the output or input latency based on acceptable tradeoffs on
+ * battery drain and use of pre or post processing effects.
+ * Performance mode should be set before realizing the object and should be
+ * read after realizing the object to check if the requested mode could be
+ * granted or not.
+ */
+/** Audio Performance mode key */
+#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode")
+
+/** Audio performance values */
+/* No specific performance requirement. Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32) 0x00000000)
+/* Priority given to latency. No HW or software pre/post processing.
+ * This is the default if no performance mode is specified. */
+#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32) 0x00000001)
+/* Priority given to latency while still allowing HW pre and post processing. */
+#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32) 0x00000002)
+/* Priority given to power saving if latency is not a concern.
+ * Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32) 0x00000003)
+
+#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
diff --git a/thirdparty/cubeb/src/cubeb-internal.h b/thirdparty/cubeb/src/cubeb-internal.h
new file mode 100644
index 0000000..4aee68f
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb-internal.h
@@ -0,0 +1,80 @@
+/*
+ * 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 "cubeb_assert.h"
+#include
+#include
+
+#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
+
+#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);
+ int (* device_collection_destroy)(cubeb * context,
+ cubeb_device_collection * collection);
+ void (* destroy)(cubeb * context);
+ int (* stream_init)(cubeb * context,
+ cubeb_stream ** stream,
+ char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr);
+ void (* stream_destroy)(cubeb_stream * stream);
+ int (* stream_start)(cubeb_stream * stream);
+ int (* stream_stop)(cubeb_stream * stream);
+ int (* stream_reset_default_device)(cubeb_stream * stream);
+ int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
+ int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (* stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (* stream_set_volume)(cubeb_stream * stream, float volumes);
+ int (* stream_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);
+};
+
+#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
diff --git a/thirdparty/cubeb/src/cubeb-jni-instances.h b/thirdparty/cubeb/src/cubeb-jni-instances.h
new file mode 100644
index 0000000..4fed046
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb-jni-instances.h
@@ -0,0 +1,30 @@
+#ifndef _CUBEB_JNI_INSTANCES_H_
+#define _CUBEB_JNI_INSTANCES_H_
+
+/*
+ * The methods in this file offer a way to pass in the required
+ * JNI instances in the cubeb library. By default they return NULL.
+ * In this case part of the cubeb API that depends on JNI
+ * will return CUBEB_ERROR_NOT_SUPPORTED. Currently only one
+ * method depends on that:
+ *
+ * cubeb_stream_get_position()
+ *
+ * Users that want to use that cubeb API method must "override"
+ * the methods bellow to return a valid instance of JavaVM
+ * and application's Context object.
+ * */
+
+JNIEnv *
+cubeb_get_jni_env_for_thread()
+{
+ return nullptr;
+}
+
+jobject
+cubeb_jni_get_context_instance()
+{
+ return nullptr;
+}
+
+#endif //_CUBEB_JNI_INSTANCES_H_
diff --git a/thirdparty/cubeb/src/cubeb-jni.cpp b/thirdparty/cubeb/src/cubeb-jni.cpp
new file mode 100644
index 0000000..a506696
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb-jni.cpp
@@ -0,0 +1,68 @@
+#include "jni.h"
+#include
+#include "cubeb-jni-instances.h"
+
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+struct cubeb_jni {
+ jobject s_audio_manager_obj = nullptr;
+ jclass s_audio_manager_class = nullptr;
+ jmethodID s_get_output_latency_id = nullptr;
+};
+
+extern "C"
+cubeb_jni *
+cubeb_jni_init()
+{
+ jobject ctx_obj = cubeb_jni_get_context_instance();
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ if (!jni_env || !ctx_obj) {
+ return nullptr;
+ }
+
+ cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
+ assert(cubeb_jni_ptr);
+
+ // Find the audio manager object and make it global to call it from another method
+ jclass context_class = jni_env->FindClass("android/content/Context");
+ jfieldID audio_service_field = jni_env->GetStaticFieldID(context_class, "AUDIO_SERVICE", "Ljava/lang/String;");
+ jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, audio_service_field);
+ jmethodID get_system_service_id = jni_env->GetMethodID(context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
+ jobject audio_manager_obj = jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr);
+ cubeb_jni_ptr->s_audio_manager_obj = reinterpret_cast(jni_env->NewGlobalRef(audio_manager_obj));
+
+ // Make the audio manager class a global reference in order to preserve method id
+ jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager");
+ cubeb_jni_ptr->s_audio_manager_class = reinterpret_cast(jni_env->NewGlobalRef(audio_manager_class));
+ cubeb_jni_ptr->s_get_output_latency_id = jni_env->GetMethodID (audio_manager_class, "getOutputLatency", "(I)I");
+
+ jni_env->DeleteLocalRef(ctx_obj);
+ jni_env->DeleteLocalRef(context_class);
+ jni_env->DeleteLocalRef(jstr);
+ jni_env->DeleteLocalRef(audio_manager_obj);
+ jni_env->DeleteLocalRef(audio_manager_class);
+
+ return cubeb_jni_ptr;
+}
+
+extern "C"
+int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ return jni_env->CallIntMethod(cubeb_jni_ptr->s_audio_manager_obj, cubeb_jni_ptr->s_get_output_latency_id, AUDIO_STREAM_TYPE_MUSIC); //param: AudioManager.STREAM_MUSIC
+}
+
+extern "C"
+void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ assert(jni_env);
+
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj);
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class);
+
+ delete cubeb_jni_ptr;
+}
diff --git a/thirdparty/cubeb/src/cubeb-jni.h b/thirdparty/cubeb/src/cubeb-jni.h
new file mode 100644
index 0000000..8c7ddb6
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb-jni.h
@@ -0,0 +1,10 @@
+#ifndef _CUBEB_JNI_H_
+#define _CUBEB_JNI_H_
+
+typedef struct cubeb_jni cubeb_jni;
+
+cubeb_jni * cubeb_jni_init();
+int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
+void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
+
+#endif // _CUBEB_JNI_H_
diff --git a/thirdparty/cubeb/src/cubeb-sles.h b/thirdparty/cubeb/src/cubeb-sles.h
new file mode 100644
index 0000000..ac22150
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb-sles.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef _CUBEB_SLES_H_
+#define _CUBEB_SLES_H_
+#include
+
+static SLresult
+cubeb_get_sles_engine(SLObjectItf * pEngine,
+ SLuint32 numOptions,
+ const SLEngineOption * pEngineOptions,
+ SLuint32 numInterfaces,
+ const SLInterfaceID * pInterfaceIds,
+ const SLboolean * pInterfaceRequired)
+{
+ return slCreateEngine(pEngine,
+ numOptions,
+ pEngineOptions,
+ numInterfaces,
+ pInterfaceIds,
+ pInterfaceRequired);
+}
+
+static void
+cubeb_destroy_sles_engine(SLObjectItf * self)
+{
+ if (*self != NULL) {
+ (**self)->Destroy(*self);
+ *self = NULL;
+ }
+}
+
+static SLresult
+cubeb_realize_sles_engine(SLObjectItf self)
+{
+ return (*self)->Realize(self, SL_BOOLEAN_FALSE);
+}
+
+#endif
diff --git a/thirdparty/cubeb/src/cubeb-speex-resampler.h b/thirdparty/cubeb/src/cubeb-speex-resampler.h
new file mode 100644
index 0000000..9ecf747
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb-speex-resampler.h
@@ -0,0 +1 @@
+#include
diff --git a/thirdparty/cubeb/src/cubeb.c b/thirdparty/cubeb/src/cubeb.c
new file mode 100644
index 0000000..3320fcc
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb.c
@@ -0,0 +1,679 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+
+#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
+
+struct cubeb {
+ struct cubeb_ops * ops;
+};
+
+struct cubeb_stream {
+ /*
+ * Note: All implementations of cubeb_stream must keep the following
+ * layout.
+ */
+ struct cubeb * context;
+ void * user_ptr;
+};
+
+#if defined(USE_PULSE)
+int pulse_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_PULSE_RUST)
+int pulse_rust_init(cubeb ** contet, char const * context_name);
+#endif
+#if defined(USE_JACK)
+int jack_init (cubeb ** context, char const * context_name);
+#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_AUDIOUNIT_RUST)
+int audiounit_rust_init(cubeb ** contet, char const * context_name);
+#endif
+#if defined(USE_WINMM)
+int winmm_init(cubeb ** context, char const * context_name);
+#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_SUN)
+int sun_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 > UINT8_MAX) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ }
+ if (input_stream_params) {
+ if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
+ input_stream_params->channels < 1 || input_stream_params->channels > UINT8_MAX) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ }
+ // Rate and sample format must be the same for input and output, if using a
+ // duplex stream
+ if (input_stream_params && output_stream_params) {
+ if (input_stream_params->rate != output_stream_params->rate ||
+ 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, char const * backend_name)
+{
+ int (* init_oneshot)(cubeb **, char const *) = NULL;
+
+ if (backend_name != NULL) {
+ if (!strcmp(backend_name, "pulse")) {
+#if defined(USE_PULSE)
+ init_oneshot = pulse_init;
+#endif
+ } else if (!strcmp(backend_name, "pulse-rust")) {
+#if defined(USE_PULSE_RUST)
+ init_oneshot = pulse_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "jack")) {
+#if defined(USE_JACK)
+ init_oneshot = jack_init;
+#endif
+ } else if (!strcmp(backend_name, "alsa")) {
+#if defined(USE_ALSA)
+ init_oneshot = alsa_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit")) {
+#if defined(USE_AUDIOUNIT)
+ init_oneshot = audiounit_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit-rust")) {
+#if defined(USE_AUDIOUNIT_RUST)
+ init_oneshot = audiounit_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "wasapi")) {
+#if defined(USE_WASAPI)
+ init_oneshot = wasapi_init;
+#endif
+ } else if (!strcmp(backend_name, "winmm")) {
+#if defined(USE_WINMM)
+ init_oneshot = winmm_init;
+#endif
+ } else if (!strcmp(backend_name, "sndio")) {
+#if defined(USE_SNDIO)
+ init_oneshot = sndio_init;
+#endif
+ } else if (!strcmp(backend_name, "sun")) {
+#if defined(USE_SUN)
+ init_oneshot = sun_init;
+#endif
+ } else if (!strcmp(backend_name, "opensl")) {
+#if defined(USE_OPENSL)
+ init_oneshot = opensl_init;
+#endif
+ } else if (!strcmp(backend_name, "audiotrack")) {
+#if defined(USE_AUDIOTRACK)
+ init_oneshot = audiotrack_init;
+#endif
+ } else if (!strcmp(backend_name, "kai")) {
+#if defined(USE_KAI)
+ init_oneshot = kai_init;
+#endif
+ } else {
+ /* Already set */
+ }
+ }
+
+ int (* default_init[])(cubeb **, char const *) = {
+ /*
+ * init_oneshot must be at the top to allow user
+ * to override all other choices
+ */
+ init_oneshot,
+#if defined(USE_PULSE_RUST)
+ pulse_rust_init,
+#endif
+#if defined(USE_PULSE)
+ pulse_init,
+#endif
+#if defined(USE_JACK)
+ jack_init,
+#endif
+#if defined(USE_SNDIO)
+ sndio_init,
+#endif
+#if defined(USE_ALSA)
+ alsa_init,
+#endif
+#if defined(USE_AUDIOUNIT)
+ audiounit_init,
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+ audiounit_rust_init,
+#endif
+#if defined(USE_WASAPI)
+ wasapi_init,
+#endif
+#if defined(USE_WINMM)
+ winmm_init,
+#endif
+#if defined(USE_SUN)
+ sun_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;
+ }
+
+#define OK(fn) assert((* context)->ops->fn)
+ for (i = 0; i < NELEMS(default_init); ++i) {
+ if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
+ /* Assert that the minimal API is implemented. */
+ 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 || !params || !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 || !data_callback || !state_callback) {
+ 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;
+ }
+
+ r = context->ops->stream_init(context, stream, stream_name,
+ input_device,
+ input_stream_params,
+ output_device,
+ output_stream_params,
+ latency,
+ data_callback,
+ state_callback,
+ user_ptr);
+
+ if (r == CUBEB_ERROR_INVALID_FORMAT) {
+ LOG("Invalid format, %p %p %d %d",
+ output_stream_params, input_stream_params,
+ output_stream_params && output_stream_params->format,
+ input_stream_params && input_stream_params->format);
+ }
+
+ return r;
+}
+
+void
+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_reset_default_device(cubeb_stream * stream)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_reset_default_device) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_reset_default_device(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_get_input_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ if (!stream || !latency) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_input_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_input_latency(stream, latency);
+}
+
+int
+cubeb_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ if (!stream || volume > 1.0 || volume < 0.0) {
+ 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_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);
+}
+
+void * cubeb_stream_user_ptr(cubeb_stream * stream)
+{
+ if (!stream) {
+ return NULL;
+ }
+
+ return stream->user_ptr;
+}
+
+static
+void log_device(cubeb_device_info * device_info)
+{
+ char devfmts[128] = "";
+ const char * devtype, * devstate, * devdeffmt;
+
+ 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_cubeb_log_callback) {
+ for (size_t i = 0; i < collection->count; i++) {
+ log_device(&collection->device[i]);
+ }
+ }
+
+ return rv;
+}
+
+int cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ int r;
+
+ if (context == NULL || collection == NULL)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+
+ if (!context->ops->device_collection_destroy)
+ return CUBEB_ERROR_NOT_SUPPORTED;
+
+ if (!collection->device)
+ return CUBEB_OK;
+
+ r = context->ops->device_collection_destroy(context, collection);
+ if (r == CUBEB_OK) {
+ collection->device = NULL;
+ collection->count = 0;
+ }
+
+ return r;
+}
+
+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_cubeb_log_callback && log_callback) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ g_cubeb_log_callback = log_callback;
+ g_cubeb_log_level = log_level;
+
+ // Logging a message here allows to initialize the asynchronous logger from a
+ // thread that is not the audio rendering thread, and especially to not
+ // initialize it the first time we find a verbose log, which is often in the
+ // audio rendering callback, that runs from the audio rendering thread, and
+ // that is high priority, and that we don't want to block.
+ if (log_level >= CUBEB_LOG_VERBOSE) {
+ ALOGV("Starting cubeb log");
+ }
+
+ return CUBEB_OK;
+}
+
diff --git a/thirdparty/cubeb/src/cubeb_alsa.c b/thirdparty/cubeb/src/cubeb_alsa.c
new file mode 100644
index 0000000..4b479dc
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_alsa.c
@@ -0,0 +1,1453 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+
+#ifdef DISABLE_LIBASOUND_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBASOUND_API_VISIT(X) \
+ X(snd_config) \
+ X(snd_config_add) \
+ X(snd_config_copy) \
+ X(snd_config_delete) \
+ X(snd_config_get_id) \
+ X(snd_config_get_string) \
+ X(snd_config_imake_integer) \
+ X(snd_config_search) \
+ X(snd_config_search_definition) \
+ X(snd_lib_error_set_handler) \
+ X(snd_pcm_avail_update) \
+ X(snd_pcm_close) \
+ X(snd_pcm_delay) \
+ X(snd_pcm_drain) \
+ X(snd_pcm_frames_to_bytes) \
+ X(snd_pcm_get_params) \
+ X(snd_pcm_hw_params_any) \
+ X(snd_pcm_hw_params_get_channels_max) \
+ X(snd_pcm_hw_params_get_rate) \
+ X(snd_pcm_hw_params_set_rate_near) \
+ X(snd_pcm_hw_params_sizeof) \
+ X(snd_pcm_nonblock) \
+ X(snd_pcm_open) \
+ X(snd_pcm_open_lconf) \
+ X(snd_pcm_pause) \
+ X(snd_pcm_poll_descriptors) \
+ X(snd_pcm_poll_descriptors_count) \
+ X(snd_pcm_poll_descriptors_revents) \
+ X(snd_pcm_readi) \
+ X(snd_pcm_recover) \
+ X(snd_pcm_set_params) \
+ X(snd_pcm_start) \
+ X(snd_pcm_state) \
+ X(snd_pcm_writei) \
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBASOUND_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+/* snd_pcm_hw_params_alloca is actually a macro */
+#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof
+#endif
+
+#define CUBEB_STREAM_MAX 16
+#define CUBEB_WATCHDOG_MS 10000
+
+#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;
+ void * libasound;
+
+ 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 {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ pthread_mutex_t mutex;
+ snd_pcm_t * pcm;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ snd_pcm_uframes_t stream_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;
+
+ char * buffer;
+ snd_pcm_uframes_t bufframes;
+ snd_pcm_stream_t stream_type;
+
+ struct cubeb_stream * other_stream;
+};
+
+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
+stream_buffer_decrement(cubeb_stream * stm, long count)
+{
+ char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count);
+ memmove(stm->buffer, bufremains, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count));
+ stm->bufframes -= count;
+}
+
+static void
+alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
+{
+ cubeb * ctx;
+ 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_process_stream(cubeb_stream * stm)
+{
+ unsigned short revents;
+ snd_pcm_sframes_t avail;
+ int draining;
+
+ draining = 0;
+
+ pthread_mutex_lock(&stm->mutex);
+
+ /* Call _poll_descriptors_revents() even if we don't use it
+ to let underlying plugins clear null events. Otherwise poll()
+ may wake up again and again, producing unnecessary CPU usage. */
+ WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents);
+
+ avail = WRAP(snd_pcm_avail_update)(stm->pcm);
+
+ /* Got null event? Bail and wait for another wakeup. */
+ if (avail == 0) {
+ pthread_mutex_unlock(&stm->mutex);
+ return RUNNING;
+ }
+
+ /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */
+ if ((unsigned int) avail > stm->buffer_size) {
+ avail = stm->buffer_size;
+ }
+
+ /* Capture: Read available frames */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) {
+ snd_pcm_sframes_t got;
+
+ if (avail + stm->bufframes > stm->buffer_size) {
+ /* Buffer overflow. Skip and overwrite with new data. */
+ stm->bufframes = 0;
+ // TODO: should it be marked as DRAINING?
+ }
+
+ got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+ stm->stream_position += got;
+
+ gettimeofday(&stm->last_activity, NULL);
+ }
+ }
+
+ /* Capture: Pass read frames to callback function */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 &&
+ (!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) {
+ snd_pcm_sframes_t wrote = stm->bufframes;
+ struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL;
+
+ /* Correct write size to the other stream available space */
+ if (stm->other_stream && wrote > (snd_pcm_sframes_t) (stm->other_stream->buffer_size - stm->other_stream->bufframes)) {
+ wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes;
+ }
+
+ pthread_mutex_unlock(&stm->mutex);
+ wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (wrote < 0) {
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ if (stm->other_stream) {
+ stm->other_stream->bufframes += wrote;
+ }
+ }
+ }
+
+ /* Playback: Don't have enough data? Let's ask for more. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes &&
+ (!stm->other_stream || stm->other_stream->bufframes > 0)) {
+ long got = avail - stm->bufframes;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
+ char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+
+ /* Correct read size to the other stream available frames */
+ if (stm->other_stream && got > (snd_pcm_sframes_t) stm->other_stream->bufframes) {
+ got = stm->other_stream->bufframes;
+ }
+
+ pthread_mutex_unlock(&stm->mutex);
+ got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+
+ if (stm->other_stream) {
+ stream_buffer_decrement(stm->other_stream, got);
+ }
+ }
+ }
+
+ /* Playback: Still don't have enough data? Add some silence. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes) {
+ long drain_frames = avail - stm->bufframes;
+ double drain_time = (double) drain_frames / stm->params.rate;
+
+ char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+ memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames));
+ stm->bufframes = avail;
+
+ /* Mark as draining, unless we're waiting for capture */
+ if (!stm->other_stream || stm->other_stream->bufframes > 0) {
+ set_timeout(&stm->drain_timeout, drain_time * 1000);
+
+ draining = 1;
+ }
+ }
+
+ /* Playback: Have enough data and no errors. Let's write it out. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) {
+ snd_pcm_sframes_t wrote;
+
+ if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ float * b = (float *) stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
+ b[i] *= stm->volume;
+ }
+ } else {
+ short * b = (short *) stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
+ b[i] *= stm->volume;
+ }
+ }
+
+ wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail);
+ if (wrote < 0) {
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ stm->stream_position += wrote;
+ gettimeofday(&stm->last_activity, NULL);
+ }
+ }
+
+ /* Got some error? Let's try to recover the stream. */
+ if (avail < 0) {
+ avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0);
+
+ /* Capture pcm must be started after initial setup/recover */
+ if (avail >= 0 &&
+ stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ avail = WRAP(snd_pcm_start)(stm->pcm);
+ }
+ }
+
+ /* Failed to recover, this stream must be broken. */
+ if (avail < 0) {
+ pthread_mutex_unlock(&stm->mutex);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return ERROR;
+ }
+
+ 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_process_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 = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm);
+ if (r < 0) {
+ return NULL;
+ }
+
+ r = WRAP(snd_config_get_string)(slave_pcm, &string);
+ if (r >= 0) {
+ r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, &slave_def);
+ if (r < 0) {
+ return NULL;
+ }
+ }
+
+ do {
+ r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string);
+ if (r < 0) {
+ break;
+ }
+
+ r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
+ if (r < 0 || r > (int) sizeof(node_name)) {
+ break;
+ }
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm);
+ if (r < 0) {
+ break;
+ }
+
+ return pcm;
+ } while (0);
+
+ if (slave_def) {
+ WRAP(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 (*WRAP(snd_config) == NULL) {
+ return NULL;
+ }
+
+ r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config));
+ if (r < 0) {
+ return NULL;
+ }
+
+ do {
+ r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_get_id)(pcm_node, &string);
+ if (r < 0) {
+ break;
+ }
+
+ r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
+ if (r < 0 || r > (int) sizeof(node_name)) {
+ break;
+ }
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm_node);
+ if (r < 0) {
+ break;
+ }
+
+ /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */
+ 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 = WRAP(snd_config_search)(pcm_node, "type", &node);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(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 = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node);
+ if (r != -ENOENT) {
+ break;
+ }
+
+ /* Disable pcm_pulse's asynchronous underrun handling. */
+ r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_add)(pcm_node, node);
+ if (r < 0) {
+ break;
+ }
+
+ return lconf;
+ } while (0);
+
+ WRAP(snd_config_delete)(lconf);
+
+ return NULL;
+}
+
+static int
+alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config)
+{
+ int r;
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ if (local_config) {
+ r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config);
+ } else {
+ r = WRAP(snd_pcm_open)(pcm, 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 = WRAP(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;
+ void * libasound = NULL;
+ cubeb * ctx;
+ int r;
+ int i;
+ int fd[2];
+ pthread_attr_t attr;
+ snd_pcm_t * dummy;
+
+ assert(context);
+ *context = NULL;
+
+#ifndef DISABLE_LIBASOUND_DLOPEN
+ libasound = dlopen("libasound.so.2", RTLD_LAZY);
+ if (!libasound) {
+ libasound = dlopen("libasound.so", RTLD_LAZY);
+ if (!libasound) {
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) { \
+ cubeb_##x = dlsym(libasound, #x); \
+ if (!cubeb_##x) { \
+ dlclose(libasound); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBASOUND_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ if (!cubeb_alsa_error_handler_set) {
+ WRAP(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;
+ ctx->libasound = libasound;
+
+ 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, CUBEB_ALSA_PCM_NAME, 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, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ /* If we got a local_config, we found a PA PCM. If opening a PCM with that
+ config fails with EINVAL, the PA PCM is too old for this workaround. */
+ if (r == -EINVAL) {
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ WRAP(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);
+ WRAP(snd_config_delete)(ctx->local_config);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+ }
+
+ if (ctx->libasound) {
+ dlclose(ctx->libasound);
+ }
+
+ free(ctx);
+}
+
+static void alsa_stream_destroy(cubeb_stream * stm);
+
+static int
+alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+ snd_pcm_stream_t stream_type,
+ cubeb_devid deviceid,
+ cubeb_stream_params * stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr)
+{
+ (void)stream_name;
+ cubeb_stream * stm;
+ int r;
+ snd_pcm_format_t format;
+ snd_pcm_uframes_t period_size;
+ int latency_us = 0;
+ char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME;
+
+ assert(ctx && stream);
+
+ *stream = NULL;
+
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ switch (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 = *stream_params;
+ stm->state = INACTIVE;
+ stm->volume = 1.0;
+ stm->buffer = NULL;
+ stm->bufframes = 0;
+ stm->stream_type = stream_type;
+ stm->other_stream = NULL;
+
+ r = pthread_mutex_init(&stm->mutex, NULL);
+ assert(r == 0);
+
+ r = pthread_cond_init(&stm->cond, NULL);
+ assert(r == 0);
+
+ r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config);
+ if (r < 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(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 = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
+ stm->params.channels, stm->params.rate, 1,
+ latency_us);
+ if (r < 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size);
+ assert(r == 0);
+
+ /* Double internal buffer size to have enough space when waiting for the other side of duplex connection */
+ stm->buffer_size *= 2;
+ stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size));
+ assert(stm->buffer);
+
+ stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm);
+ assert(stm->nfds > 0);
+
+ stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
+ assert(stm->saved_fds);
+ r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds);
+ assert((nfds_t) r == stm->nfds);
+
+ if (alsa_register_stream(ctx, stm) != 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback, cubeb_state_callback state_callback,
+ void * user_ptr)
+{
+ int result = CUBEB_OK;
+ cubeb_stream * instm = NULL, * outstm = NULL;
+
+ if (result == CUBEB_OK && input_stream_params) {
+ result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE,
+ input_device, input_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && output_stream_params) {
+ result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK,
+ output_device, output_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && input_stream_params && output_stream_params) {
+ instm->other_stream = outstm;
+ outstm->other_stream = instm;
+ }
+
+ if (result != CUBEB_OK && instm) {
+ alsa_stream_destroy(instm);
+ }
+
+ *stream = outstm ? outstm : instm;
+
+ return result;
+}
+
+static void
+alsa_stream_destroy(cubeb_stream * stm)
+{
+ int r;
+ cubeb * ctx;
+
+ assert(stm && (stm->state == INACTIVE ||
+ stm->state == ERROR ||
+ stm->state == DRAINING));
+
+ ctx = stm->context;
+
+ if (stm->other_stream) {
+ stm->other_stream->other_stream = NULL; // to stop infinite recursion
+ alsa_stream_destroy(stm->other_stream);
+ }
+
+ pthread_mutex_lock(&stm->mutex);
+ if (stm->pcm) {
+ if (stm->state == DRAINING) {
+ WRAP(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->buffer);
+
+ 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, ¶ms, 100, NULL, NULL, NULL);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ assert(stm);
+
+ r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(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 = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params);
+ if (r < 0) {
+ WRAP(snd_pcm_close)(pcm);
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir);
+ if (r >= 0) {
+ /* There is a default rate: use it. */
+ WRAP(snd_pcm_close)(pcm);
+ return CUBEB_OK;
+ }
+
+ /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */
+ *rate = 44100;
+
+ r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL);
+ if (r < 0) {
+ WRAP(snd_pcm_close)(pcm);
+ return CUBEB_ERROR;
+ }
+
+ WRAP(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;
+
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_start(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
+ pthread_mutex_lock(&stm->mutex);
+ /* Capture pcm must be started after initial setup/recover */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ WRAP(snd_pcm_start)(stm->pcm);
+ }
+ WRAP(snd_pcm_pause)(stm->pcm, 0);
+ gettimeofday(&stm->last_activity, NULL);
+ pthread_mutex_unlock(&stm->mutex);
+
+ 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;
+
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_stop(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
+ pthread_mutex_lock(&ctx->mutex);
+ while (stm->state == PROCESSING) {
+ r = pthread_cond_wait(&stm->cond, &ctx->mutex);
+ assert(r == 0);
+ }
+
+ alsa_set_stream_state(stm, INACTIVE);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ pthread_mutex_lock(&stm->mutex);
+ WRAP(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 (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING ||
+ WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) {
+ *position = stm->last_position;
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_OK;
+ }
+
+ assert(delay >= 0);
+
+ *position = 0;
+ if (stm->stream_position >= (snd_pcm_uframes_t) delay) {
+ *position = stm->stream_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 (WRAP(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 int
+alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info* device = NULL;
+
+ if (!context)
+ return CUBEB_ERROR;
+
+ uint32_t rate, max_channels;
+ int r;
+
+ r = alsa_get_preferred_sample_rate(context, &rate);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ r = alsa_get_max_channel_count(context, &max_channels);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ char const * a_name = "default";
+ device = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info));
+ assert(device);
+ if (!device)
+ return CUBEB_ERROR;
+
+ device->device_id = a_name;
+ device->devid = (cubeb_devid) device->device_id;
+ device->friendly_name = a_name;
+ device->group_id = a_name;
+ device->vendor_name = a_name;
+ device->type = type;
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = max_channels;
+ device->min_rate = rate;
+ device->max_rate = rate;
+ device->default_rate = rate;
+ device->latency_lo = 0;
+ device->latency_hi = 0;
+
+ collection->device = device;
+ collection->count = 1;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ assert(collection->count == 1);
+ (void) context;
+ free(collection->device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const alsa_ops = {
+ .init = alsa_init,
+ .get_backend_id = alsa_get_backend_id,
+ .get_max_channel_count = alsa_get_max_channel_count,
+ .get_min_latency = alsa_get_min_latency,
+ .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
+ .enumerate_devices = alsa_enumerate_devices,
+ .device_collection_destroy = alsa_device_collection_destroy,
+ .destroy = alsa_destroy,
+ .stream_init = alsa_stream_init,
+ .stream_destroy = alsa_stream_destroy,
+ .stream_start = alsa_stream_start,
+ .stream_stop = alsa_stream_stop,
+ .stream_reset_default_device = NULL,
+ .stream_get_position = alsa_stream_get_position,
+ .stream_get_latency = alsa_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = alsa_stream_set_volume,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL
+};
diff --git a/thirdparty/cubeb/src/cubeb_array_queue.h b/thirdparty/cubeb/src/cubeb_array_queue.h
new file mode 100644
index 0000000..a8ea4cd
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_array_queue.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ARRAY_QUEUE_H
+#define CUBEB_ARRAY_QUEUE_H
+
+#include
+#include
+#include
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct
+{
+ void ** buf;
+ size_t num;
+ size_t writePos;
+ size_t readPos;
+ pthread_mutex_t mutex;
+} array_queue;
+
+array_queue * array_queue_create(size_t num)
+{
+ assert(num != 0);
+ array_queue * new_queue = (array_queue*)calloc(1, sizeof(array_queue));
+ new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
+ new_queue->readPos = 0;
+ new_queue->writePos = 0;
+ new_queue->num = num;
+
+ pthread_mutex_init(&new_queue->mutex, NULL);
+
+ return new_queue;
+}
+
+void array_queue_destroy(array_queue * aq)
+{
+ assert(aq);
+
+ free(aq->buf);
+ pthread_mutex_destroy(&aq->mutex);
+ free(aq);
+}
+
+int array_queue_push(array_queue * aq, void * item)
+{
+ assert(item);
+
+ pthread_mutex_lock(&aq->mutex);
+ int ret = -1;
+ if(aq->buf[aq->writePos % aq->num] == NULL)
+ {
+ aq->buf[aq->writePos % aq->num] = item;
+ aq->writePos = (aq->writePos + 1) % aq->num;
+ ret = 0;
+ }
+ // else queue is full
+ pthread_mutex_unlock(&aq->mutex);
+ return ret;
+}
+
+void* array_queue_pop(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ void * value = aq->buf[aq->readPos % aq->num];
+ if(value)
+ {
+ aq->buf[aq->readPos % aq->num] = NULL;
+ aq->readPos = (aq->readPos + 1) % aq->num;
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return value;
+}
+
+size_t array_queue_get_size(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ ssize_t r = aq->writePos - aq->readPos;
+ if (r < 0) {
+ r = aq->num + r;
+ assert(r >= 0);
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return (size_t)r;
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif //CUBE_ARRAY_QUEUE_H
diff --git a/thirdparty/cubeb/src/cubeb_assert.h b/thirdparty/cubeb/src/cubeb_assert.h
new file mode 100644
index 0000000..9257a2c
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_assert.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ASSERT
+#define CUBEB_ASSERT
+
+#include
+#include
+
+/**
+ * This allow using an external release assert method. This file should only
+ * export a function or macro called XASSERT that aborts the program.
+ */
+
+#define XASSERT(expr) do { \
+ if (!(expr)) { \
+ fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
+ abort(); \
+ } \
+ } while (0)
+
+#endif
diff --git a/thirdparty/cubeb/src/cubeb_audiotrack.c b/thirdparty/cubeb/src/cubeb_audiotrack.c
new file mode 100644
index 0000000..352473d
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_audiotrack.c
@@ -0,0 +1,442 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+
+#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 {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ cubeb_stream_params params;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ void * instance;
+ /* 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, AUDIO_STREAM_TYPE_MUSIC, params->rate);
+ } else {
+ status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, 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, ¶ms, (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, AUDIO_STREAM_TYPE_MUSIC, 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,
+ .device_collection_destroy = 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_reset_default_device = NULL,
+ .stream_get_position = audiotrack_stream_get_position,
+ .stream_get_latency = audiotrack_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = audiotrack_stream_set_volume,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL
+};
diff --git a/thirdparty/cubeb/src/cubeb_audiounit.cpp b/thirdparty/cubeb/src/cubeb_audiounit.cpp
new file mode 100644
index 0000000..4b506d0
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_audiounit.cpp
@@ -0,0 +1,3630 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+
+#include
+#include
+#include
+#include
+#include
+#include
+#if !TARGET_OS_IPHONE
+#include
+#include
+#include
+#include
+#endif
+#include
+#include
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include "cubeb_mixer.h"
+#if !TARGET_OS_IPHONE
+#include "cubeb_osx_run_loop.h"
+#endif
+#include "cubeb_resampler.h"
+#include "cubeb_ring_array.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
+typedef UInt32 AudioFormatFlags;
+#endif
+
+#define AU_OUT_BUS 0
+#define AU_IN_BUS 1
+
+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
+const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice";
+
+#ifdef ALOGV
+#undef ALOGV
+#endif
+#define ALOGV(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOGV(msg, ##__VA_ARGS__);})
+
+#ifdef ALOG
+#undef ALOG
+#endif
+#define ALOG(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOG(msg, ##__VA_ARGS__);})
+
+/* Testing empirically, some headsets report a minimal latency that is very
+ * low, but this does not work in practice. Lie and say the minimum is 256
+ * frames. */
+const uint32_t SAFE_MIN_LATENCY_FRAMES = 128;
+const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
+
+const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+};
+
+const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+};
+
+const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDeviceIsAlive,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+};
+
+const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+};
+
+const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource,
+ kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster
+};
+
+const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+};
+
+typedef uint32_t device_flags_value;
+
+enum device_flags {
+ DEV_UNKNOWN = 0x00, /* Unknown */
+ DEV_INPUT = 0x01, /* Record device like mic */
+ DEV_OUTPUT = 0x02, /* Playback device like speakers */
+ DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
+ DEV_SELECTED_DEFAULT = 0x08, /* User selected to use the system default device */
+};
+
+void audiounit_stream_stop_internal(cubeb_stream * stm);
+static int audiounit_stream_start_internal(cubeb_stream * stm);
+static void audiounit_close_stream(cubeb_stream *stm);
+static int audiounit_setup_stream(cubeb_stream *stm);
+static vector
+audiounit_get_devices_of_type(cubeb_device_type devtype);
+static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope);
+
+#if !TARGET_OS_IPHONE
+static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
+static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
+static int audiounit_uninstall_system_changed_callback(cubeb_stream * stm);
+static void audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags);
+#endif
+
+extern cubeb_ops const audiounit_ops;
+
+struct cubeb {
+ cubeb_ops const * ops = &audiounit_ops;
+ owned_critical_section mutex;
+ int active_streams = 0;
+ uint32_t global_latency_frames = 0;
+ cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ // Store list of devices to detect changes
+ vector input_device_array;
+ vector output_device_array;
+ // The queue should be released when it’s no longer needed.
+ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ // Current used channel layout
+ atomic layout{ CUBEB_LAYOUT_UNDEFINED };
+ uint32_t channels = 0;
+};
+
+static unique_ptr
+make_sized_audio_channel_layout(size_t sz)
+{
+ assert(sz >= sizeof(AudioChannelLayout));
+ AudioChannelLayout * acl = reinterpret_cast(calloc(1, sz));
+ assert(acl); // Assert the allocation works.
+ return unique_ptr(acl, free);
+}
+
+enum class io_side {
+ INPUT,
+ OUTPUT,
+};
+
+static char const *
+to_string(io_side side)
+{
+ switch (side) {
+ case io_side::INPUT:
+ return "input";
+ case io_side::OUTPUT:
+ return "output";
+ }
+}
+
+struct device_info {
+ AudioDeviceID id = kAudioObjectUnknown;
+ device_flags_value flags = DEV_UNKNOWN;
+};
+
+struct property_listener {
+ AudioDeviceID device_id;
+ const AudioObjectPropertyAddress * property_address;
+ AudioObjectPropertyListenerProc callback;
+ cubeb_stream * stream;
+
+ property_listener(AudioDeviceID id,
+ const AudioObjectPropertyAddress * address,
+ AudioObjectPropertyListenerProc proc,
+ cubeb_stream * stm)
+ : device_id(id)
+ , property_address(address)
+ , callback(proc)
+ , stream(stm)
+ {}
+};
+
+struct cubeb_stream {
+ explicit cubeb_stream(cubeb * context);
+
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr = nullptr;
+ /**/
+
+ cubeb_data_callback data_callback = nullptr;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_device_changed_callback device_changed_callback = nullptr;
+ owned_critical_section device_changed_callback_lock;
+ /* Stream creation parameters */
+ cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+ cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
+ device_info input_device;
+ device_info output_device;
+ /* Format descriptions */
+ AudioStreamBasicDescription input_desc;
+ AudioStreamBasicDescription output_desc;
+ /* I/O AudioUnits */
+ AudioUnit input_unit = nullptr;
+ AudioUnit output_unit = nullptr;
+ /* I/O device sample rate */
+ Float64 input_hw_rate = 0;
+ Float64 output_hw_rate = 0;
+ /* Expected I/O thread interleave,
+ * calculated from I/O hw rate. */
+ int expected_output_callbacks_in_a_row = 0;
+ owned_critical_section mutex;
+ // Hold the input samples in every input callback iteration.
+ // Only accessed on input/output callback thread and during initial configure.
+ unique_ptr input_linear_buffer;
+ /* Frame counters */
+ atomic frames_played{ 0 };
+ uint64_t frames_queued = 0;
+ // How many frames got read from the input since the stream started (includes
+ // padded silence)
+ atomic frames_read{ 0 };
+ // How many frames got written to the output device since the stream started
+ atomic frames_written{ 0 };
+ atomic shutdown{ true };
+ atomic draining{ false };
+ atomic reinit_pending { false };
+ atomic destroy_pending{ false };
+ /* Latency requested by the user. */
+ uint32_t latency_frames = 0;
+ atomic current_latency_frames{ 0 };
+ atomic total_output_latency_frames { 0 };
+ unique_ptr resampler;
+ /* This is true if a device change callback is currently running. */
+ atomic switching_device{ false };
+ atomic buffer_size_change_state{ false };
+ AudioDeviceID aggregate_device_id = kAudioObjectUnknown; // the aggregate device id
+ AudioObjectID plugin_id = kAudioObjectUnknown; // used to create aggregate device
+ /* Mixer interface */
+ unique_ptr mixer;
+ /* Buffer where remixing/resampling will occur when upmixing is required */
+ /* Only accessed from callback thread */
+ unique_ptr temp_buffer;
+ size_t temp_buffer_size = 0; // size in bytes.
+ /* Listeners indicating what system events are monitored. */
+ unique_ptr default_input_listener;
+ unique_ptr default_output_listener;
+ unique_ptr input_alive_listener;
+ unique_ptr input_source_listener;
+ unique_ptr output_source_listener;
+};
+
+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;
+}
+
+cubeb_channel
+channel_label_to_cubeb_channel(UInt32 label)
+{
+ switch (label) {
+ case kAudioChannelLabel_Left:
+ return CHANNEL_FRONT_LEFT;
+ case kAudioChannelLabel_Right:
+ return CHANNEL_FRONT_RIGHT;
+ case kAudioChannelLabel_Center:
+ return CHANNEL_FRONT_CENTER;
+ case kAudioChannelLabel_LFEScreen:
+ return CHANNEL_LOW_FREQUENCY;
+ case kAudioChannelLabel_LeftSurround:
+ return CHANNEL_BACK_LEFT;
+ case kAudioChannelLabel_RightSurround:
+ return CHANNEL_BACK_RIGHT;
+ case kAudioChannelLabel_LeftCenter:
+ return CHANNEL_FRONT_LEFT_OF_CENTER;
+ case kAudioChannelLabel_RightCenter:
+ return CHANNEL_FRONT_RIGHT_OF_CENTER;
+ case kAudioChannelLabel_CenterSurround:
+ return CHANNEL_BACK_CENTER;
+ case kAudioChannelLabel_LeftSurroundDirect:
+ return CHANNEL_SIDE_LEFT;
+ case kAudioChannelLabel_RightSurroundDirect:
+ return CHANNEL_SIDE_RIGHT;
+ case kAudioChannelLabel_TopCenterSurround:
+ return CHANNEL_TOP_CENTER;
+ case kAudioChannelLabel_VerticalHeightLeft:
+ return CHANNEL_TOP_FRONT_LEFT;
+ case kAudioChannelLabel_VerticalHeightCenter:
+ return CHANNEL_TOP_FRONT_CENTER;
+ case kAudioChannelLabel_VerticalHeightRight:
+ return CHANNEL_TOP_FRONT_RIGHT;
+ case kAudioChannelLabel_TopBackLeft:
+ return CHANNEL_TOP_BACK_LEFT;
+ case kAudioChannelLabel_TopBackCenter:
+ return CHANNEL_TOP_BACK_CENTER;
+ case kAudioChannelLabel_TopBackRight:
+ return CHANNEL_TOP_BACK_RIGHT;
+ default:
+ return CHANNEL_UNKNOWN;
+ }
+}
+
+AudioChannelLabel
+cubeb_channel_to_channel_label(cubeb_channel channel)
+{
+ switch (channel) {
+ case CHANNEL_FRONT_LEFT:
+ return kAudioChannelLabel_Left;
+ case CHANNEL_FRONT_RIGHT:
+ return kAudioChannelLabel_Right;
+ case CHANNEL_FRONT_CENTER:
+ return kAudioChannelLabel_Center;
+ case CHANNEL_LOW_FREQUENCY:
+ return kAudioChannelLabel_LFEScreen;
+ case CHANNEL_BACK_LEFT:
+ return kAudioChannelLabel_LeftSurround;
+ case CHANNEL_BACK_RIGHT:
+ return kAudioChannelLabel_RightSurround;
+ case CHANNEL_FRONT_LEFT_OF_CENTER:
+ return kAudioChannelLabel_LeftCenter;
+ case CHANNEL_FRONT_RIGHT_OF_CENTER:
+ return kAudioChannelLabel_RightCenter;
+ case CHANNEL_BACK_CENTER:
+ return kAudioChannelLabel_CenterSurround;
+ case CHANNEL_SIDE_LEFT:
+ return kAudioChannelLabel_LeftSurroundDirect;
+ case CHANNEL_SIDE_RIGHT:
+ return kAudioChannelLabel_RightSurroundDirect;
+ case CHANNEL_TOP_CENTER:
+ return kAudioChannelLabel_TopCenterSurround;
+ case CHANNEL_TOP_FRONT_LEFT:
+ return kAudioChannelLabel_VerticalHeightLeft;
+ case CHANNEL_TOP_FRONT_CENTER:
+ return kAudioChannelLabel_VerticalHeightCenter;
+ case CHANNEL_TOP_FRONT_RIGHT:
+ return kAudioChannelLabel_VerticalHeightRight;
+ case CHANNEL_TOP_BACK_LEFT:
+ return kAudioChannelLabel_TopBackLeft;
+ case CHANNEL_TOP_BACK_CENTER:
+ return kAudioChannelLabel_TopBackCenter;
+ case CHANNEL_TOP_BACK_RIGHT:
+ return kAudioChannelLabel_TopBackRight;
+ default:
+ return kAudioChannelLabel_Unknown;
+ }
+}
+
+#if TARGET_OS_IPHONE
+typedef UInt32 AudioDeviceID;
+typedef UInt32 AudioObjectID;
+
+#define AudioGetCurrentHostTime mach_absolute_time
+
+#endif
+
+uint64_t
+ConvertHostTimeToNanos(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;
+}
+
+static void
+audiounit_increment_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams += 1;
+}
+
+static void
+audiounit_decrement_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams -= 1;
+}
+
+static int
+audiounit_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ return ctx->active_streams;
+}
+
+static void
+audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames)
+{
+ ctx->mutex.assert_current_thread_owns();
+ assert(audiounit_active_streams(ctx) == 1);
+ ctx->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) {
+ LOG("AudioUnitRender rv=%d", r);
+ if (r != kAudioUnitErr_CannotDoInCurrentContext) {
+ return r;
+ }
+ if (stm->output_unit) {
+ // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT
+ // headset and the profile is changed from A2DP to HFP/HSP. The previous
+ // output device is no longer valid and must be reset.
+ audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);
+ }
+ // For now state that no error occurred and feed silence, stream will be
+ // resumed once reinit has completed.
+ ALOGV("(%p) input: reinit pending feeding silence instead", stm);
+ stm->input_linear_buffer->push_silence(input_frames * stm->input_desc.mChannelsPerFrame);
+ } else {
+ /* Copy input data in linear buffer. */
+ stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
+ input_frames * stm->input_desc.mChannelsPerFrame);
+ }
+
+ /* Advance input frame counter. */
+ assert(input_frames > 0);
+ stm->frames_read += input_frames;
+
+ ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, total frames %lu.",
+ stm,
+ (unsigned int) input_buffer_list.mNumberBuffers,
+ (unsigned int) input_buffer_list.mBuffers[0].mDataByteSize,
+ (unsigned int) input_buffer_list.mBuffers[0].mNumberChannels,
+ (unsigned int) input_frames,
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
+
+ return noErr;
+}
+
+static OSStatus
+audiounit_input_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp,
+ UInt32 bus,
+ UInt32 input_frames,
+ AudioBufferList * /* bufs */)
+{
+ cubeb_stream * stm = static_cast(user_ptr);
+
+ assert(stm->input_unit != NULL);
+ assert(AU_IN_BUS == bus);
+
+ if (stm->shutdown) {
+ ALOG("(%p) input shutdown", stm);
+ return noErr;
+ }
+
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ // Only fire state callback in input-only stream. For duplex stream,
+ // the state callback will be fired in output callback.
+ if (stm->output_unit == NULL) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ return noErr;
+ }
+
+ OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
+ if (r != noErr) {
+ return r;
+ }
+
+ // Full Duplex. We'll call data_callback in the AudioUnit output callback.
+ if (stm->output_unit != NULL) {
+ 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;
+ long outframes = cubeb_resampler_fill(stm->resampler.get(),
+ stm->input_linear_buffer->data(),
+ &total_input_frames,
+ NULL,
+ 0);
+ stm->draining = outframes < total_input_frames;
+
+ // Reset input buffer
+ stm->input_linear_buffer->clear();
+
+ return noErr;
+}
+
+static void
+audiounit_mix_output_buffer(cubeb_stream * stm,
+ size_t output_frames,
+ void * input_buffer,
+ size_t input_buffer_size,
+ void * output_buffer,
+ size_t output_buffer_size)
+{
+ assert(input_buffer_size >=
+ cubeb_sample_size(stm->output_stream_params.format) *
+ stm->output_stream_params.channels * output_frames);
+ assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames);
+
+ int r = cubeb_mixer_mix(stm->mixer.get(),
+ output_frames,
+ input_buffer,
+ input_buffer_size,
+ output_buffer,
+ output_buffer_size);
+ if (r != 0) {
+ LOG("Remix error = %d", r);
+ }
+}
+
+// Return how many input frames (sampled at input_hw_rate) are needed to provide
+// output_frames (sampled at output_stream_params.rate)
+static int64_t
+minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)
+{
+ if (stm->input_hw_rate == stm->output_stream_params.rate) {
+ // Fast path.
+ return output_frames;
+ }
+ return ceil(stm->input_hw_rate * output_frames /
+ stm->output_stream_params.rate);
+}
+
+static OSStatus
+audiounit_output_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * /* flags */,
+ AudioTimeStamp const * tstamp,
+ UInt32 bus,
+ UInt32 output_frames,
+ AudioBufferList * outBufferList)
+{
+ assert(AU_OUT_BUS == bus);
+ assert(outBufferList->mNumberBuffers == 1);
+
+ cubeb_stream * stm = static_cast(user_ptr);
+
+ uint64_t now = ConvertHostTimeToNanos(mach_absolute_time());
+ uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime);
+ uint64_t output_latency_ns = audio_output_time - now;
+
+ const int ns2s = 1e9;
+ // The total output latency is the timestamp difference + the stream latency +
+ // the hardware latency.
+ stm->total_output_latency_frames = output_latency_ns * stm->output_hw_rate / ns2s + stm->current_latency_frames;
+
+ ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input frames %lu.",
+ stm,
+ (unsigned int) outBufferList->mNumberBuffers,
+ (unsigned int) outBufferList->mBuffers[0].mDataByteSize,
+ (unsigned int) outBufferList->mBuffers[0].mNumberChannels,
+ (unsigned int) output_frames,
+ has_input(stm) ? stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame : 0);
+
+ long input_frames = 0;
+ void * output_buffer = NULL, * input_buffer = NULL;
+
+ if (stm->shutdown) {
+ ALOG("(%p) output shutdown.", stm);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->output_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. */
+ if (stm->mixer) {
+ // If remixing needs to occur, we can't directly work in our final
+ // destination buffer as data may be overwritten or too small to start with.
+ size_t size_needed = output_frames * stm->output_stream_params.channels *
+ cubeb_sample_size(stm->output_stream_params.format);
+ if (stm->temp_buffer_size < size_needed) {
+ stm->temp_buffer.reset(new uint8_t[size_needed]);
+ stm->temp_buffer_size = size_needed;
+ }
+ output_buffer = stm->temp_buffer.get();
+ } else {
+ output_buffer = outBufferList->mBuffers[0].mData;
+ }
+
+ stm->frames_written += output_frames;
+
+ /* If Full duplex get also input buffer */
+ if (stm->input_unit != NULL) {
+ /* If the output callback came first and this is a duplex stream, we need to
+ * fill in some additional silence in the resampler.
+ * Otherwise, if we had more than expected callbacks in a row, or we're
+ * currently switching, we add some silence as well to compensate for the
+ * fact that we're lacking some input data. */
+ uint32_t input_frames_needed =
+ minimum_resampling_input_frames(stm, stm->frames_written);
+ long missing_frames = input_frames_needed - stm->frames_read;
+ if (missing_frames > 0) {
+ stm->input_linear_buffer->push_silence(missing_frames * stm->input_desc.mChannelsPerFrame);
+ stm->frames_read = input_frames_needed;
+
+ ALOG("(%p) %s pushed %ld frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
+ stm->switching_device ? "Device switching," : "Drop out,", missing_frames);
+ }
+ input_buffer = stm->input_linear_buffer->data();
+ // Number of input frames in the buffer. It will change to actually used frames
+ // inside fill
+ input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ }
+
+ /* Call user callback through resampler. */
+ long outframes = cubeb_resampler_fill(stm->resampler.get(),
+ input_buffer,
+ input_buffer ? &input_frames : NULL,
+ output_buffer,
+ output_frames);
+
+ if (input_buffer) {
+ // Pop from the buffer the frames used by the the resampler.
+ stm->input_linear_buffer->pop(input_frames * stm->input_desc.mChannelsPerFrame);
+ }
+
+ if (outframes < 0 || outframes > output_frames) {
+ stm->shutdown = true;
+ OSStatus r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ if (stm->input_unit) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+
+ stm->draining = (UInt32) outframes < output_frames;
+ stm->frames_played = stm->frames_queued;
+ stm->frames_queued += outframes;
+
+ /* Post process output samples. */
+ if (stm->draining) {
+ /* Clear missing frames (silence) */
+ size_t channels = stm->output_stream_params.channels;
+ size_t missing_samples = (output_frames - outframes) * channels;
+ size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);
+ /* number of bytes that have been filled with valid audio by the callback. */
+ size_t audio_byte_count = outframes * channels * size_sample;
+ PodZero((uint8_t*)output_buffer + audio_byte_count,
+ missing_samples * size_sample);
+ }
+
+ /* Mixing */
+ if (stm->mixer) {
+ audiounit_mix_output_buffer(stm,
+ output_frames,
+ output_buffer,
+ stm->temp_buffer_size,
+ outBufferList->mBuffers[0].mData,
+ outBufferList->mBuffers[0].mDataByteSize);
+ }
+
+ return noErr;
+}
+
+extern "C" {
+int
+audiounit_init(cubeb ** context, char const * /* context_name */)
+{
+#if !TARGET_OS_IPHONE
+ cubeb_set_coreaudio_notification_runloop();
+#endif
+
+ *context = new cubeb;
+
+ return CUBEB_OK;
+}
+}
+
+static char const *
+audiounit_get_backend_id(cubeb * /* ctx */)
+{
+ return "audiounit";
+}
+
+#if !TARGET_OS_IPHONE
+
+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_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
+{
+ assert(stm);
+
+ device_info * info = nullptr;
+ cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
+
+ if (side == io_side::INPUT) {
+ info = &stm->input_device;
+ type = CUBEB_DEVICE_TYPE_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info = &stm->output_device;
+ type = CUBEB_DEVICE_TYPE_OUTPUT;
+ }
+ memset(info, 0, sizeof(device_info));
+ info->id = id;
+
+ if (side == io_side::INPUT) {
+ info->flags |= DEV_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info->flags |= DEV_OUTPUT;
+ }
+
+ AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
+ if (default_device_id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+ if (id == kAudioObjectUnknown) {
+ info->id = default_device_id;
+ info->flags |= DEV_SELECTED_DEFAULT;
+ }
+
+ if (info->id == default_device_id) {
+ info->flags |= DEV_SYSTEM_DEFAULT;
+ }
+
+ assert(info->id);
+ assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
+ !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
+
+ return CUBEB_OK;
+}
+
+
+static int
+audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
+{
+ auto_lock context_lock(stm->context->mutex);
+ assert((flags & DEV_INPUT && stm->input_unit) ||
+ (flags & DEV_OUTPUT && stm->output_unit));
+ if (!stm->shutdown) {
+ audiounit_stream_stop_internal(stm);
+ }
+
+ int r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners.", stm);
+ }
+
+ {
+ auto_lock lock(stm->mutex);
+ float volume = 0.0;
+ int vol_rv = CUBEB_ERROR;
+ if (stm->output_unit) {
+ vol_rv = audiounit_stream_get_volume(stm, &volume);
+ }
+
+ audiounit_close_stream(stm);
+
+ /* Reinit occurs in one of the following case:
+ * - When the device is not alive any more
+ * - When the default system device change.
+ * - The bluetooth device changed from A2DP to/from HFP/HSP profile
+ * We first attempt to re-use the same device id, should that fail we will
+ * default to the (potentially new) default device. */
+ AudioDeviceID input_device = flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown;
+ if (flags & DEV_INPUT) {
+ r = audiounit_set_device_info(stm, input_device, io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set input device info failed. This can happen when last media device is unplugged", stm);
+ return CUBEB_ERROR;
+ }
+ }
+
+ /* Always use the default output on reinit. This is not correct in every
+ * case but it is sufficient for Firefox and prevent reinit from reporting
+ * failures. It will change soon when reinit mechanism will be updated. */
+ r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set output device info failed. This can happen when last media device is unplugged", stm);
+ return CUBEB_ERROR;
+ }
+
+ if (audiounit_setup_stream(stm) != CUBEB_OK) {
+ LOG("(%p) Stream reinit failed.", stm);
+ if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) {
+ // Attempt to re-use the same device-id failed, so attempt again with
+ // default input device.
+ audiounit_close_stream(stm);
+ if (audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::INPUT) != CUBEB_OK ||
+ audiounit_setup_stream(stm) != CUBEB_OK) {
+ LOG("(%p) Second stream reinit failed.", stm);
+ return CUBEB_ERROR;
+ }
+ }
+ }
+
+ if (vol_rv == CUBEB_OK) {
+ audiounit_stream_set_volume(stm, volume);
+ }
+
+ // If the stream was running, start it again.
+ if (!stm->shutdown) {
+ r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+ }
+ }
+ return CUBEB_OK;
+}
+
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)
+{
+ if (std::atomic_exchange(&stm->reinit_pending, true)) {
+ // A reinit task is already pending, nothing more to do.
+ ALOG("(%p) re-init stream task already pending, cancelling request", stm);
+ return;
+ }
+
+ // Use a new thread, through the queue, to avoid deadlock when calling
+ // Get/SetProperties method from inside notify callback
+ dispatch_async(stm->context->serial_queue, ^() {
+ if (stm->destroy_pending) {
+ ALOG("(%p) stream pending destroy, cancelling reinit task", stm);
+ return;
+ }
+
+ if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) {
+ if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
+ LOG("(%p) Could not uninstall system changed callback", stm);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ LOG("(%p) Could not reopen the stream after switching.", stm);
+ }
+ stm->switching_device = false;
+ stm->reinit_pending = false;
+ });
+}
+
+static char const *
+event_addr_to_string(AudioObjectPropertySelector selector)
+{
+ switch(selector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ return "kAudioHardwarePropertyDefaultOutputDevice";
+ case kAudioHardwarePropertyDefaultInputDevice:
+ return "kAudioHardwarePropertyDefaultInputDevice";
+ case kAudioDevicePropertyDeviceIsAlive:
+ return "kAudioDevicePropertyDeviceIsAlive";
+ case kAudioDevicePropertyDataSource:
+ return "kAudioDevicePropertyDataSource";
+ default:
+ return "Unknown";
+ }
+}
+
+static OSStatus
+audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count,
+ const AudioObjectPropertyAddress * addresses,
+ void * user)
+{
+ cubeb_stream * stm = (cubeb_stream*) user;
+ if (stm->switching_device) {
+ LOG("Switching is already taking place. Skip Event %s for id=%d", event_addr_to_string(addresses[0].mSelector), id);
+ return noErr;
+ }
+ stm->switching_device = true;
+
+ LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count);
+ for (UInt32 i = 0; i < address_count; i++) {
+ switch(addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice for id=%d", (unsigned int) i, id);
+ }
+ break;
+ case kAudioHardwarePropertyDefaultInputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id=%d", (unsigned int) i, id);
+ }
+ break;
+ case kAudioDevicePropertyDeviceIsAlive: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for id=%d", (unsigned int) i, id);
+ // If this is the default input device ignore the event,
+ // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
+ if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
+ LOG("It's the default input device, ignore the event");
+ stm->switching_device = false;
+ return noErr;
+ }
+ }
+ break;
+ case kAudioDevicePropertyDataSource: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d", (unsigned int) i, id);
+ }
+ break;
+ default:
+ LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector);
+ stm->switching_device = false;
+ return noErr;
+ }
+ }
+
+ // Allow restart to choose the new default
+ device_flags_value switch_side = DEV_UNKNOWN;
+ if (has_input(stm)) {
+ switch_side |= DEV_INPUT;
+ }
+ if (has_output(stm)) {
+ switch_side |= DEV_OUTPUT;
+ }
+
+ for (UInt32 i = 0; i < address_count; i++) {
+ switch(addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ case kAudioHardwarePropertyDefaultInputDevice:
+ case kAudioDevicePropertyDeviceIsAlive:
+ /* fall through */
+ case kAudioDevicePropertyDataSource: {
+ auto_lock dev_cb_lock(stm->device_changed_callback_lock);
+ if (stm->device_changed_callback) {
+ stm->device_changed_callback(stm->user_ptr);
+ }
+ break;
+ }
+ }
+ }
+
+ audiounit_reinit_stream_async(stm, switch_side);
+
+ return noErr;
+}
+
+OSStatus
+audiounit_add_listener(const property_listener * listener)
+{
+ assert(listener);
+ return AudioObjectAddPropertyListener(listener->device_id,
+ listener->property_address,
+ listener->callback,
+ listener->stream);
+}
+
+OSStatus
+audiounit_remove_listener(const property_listener * listener)
+{
+ assert(listener);
+ return AudioObjectRemovePropertyListener(listener->device_id,
+ listener->property_address,
+ listener->callback,
+ listener->stream);
+}
+
+static int
+audiounit_install_device_changed_callback(cubeb_stream * stm)
+{
+ OSStatus rv;
+ int r = CUBEB_OK;
+
+ if (stm->output_unit) {
+ /* This event will notify us when the data source on the same device changes,
+ * for example when the user plugs in a normal (non-usb) headset in the
+ * headphone jack. */
+ stm->output_source_listener.reset(new property_listener(
+ stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ stm->output_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id);
+ r = CUBEB_ERROR;
+ }
+ }
+
+ if (stm->input_unit) {
+ /* This event will notify us when the data source on the input device changes. */
+ stm->input_source_listener.reset(new property_listener(
+ stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ stm->input_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+
+ /* Event to notify when the input is going away. */
+ stm->input_alive_listener.reset(new property_listener(
+ stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ stm->input_alive_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d", rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+ }
+
+ return r;
+}
+
+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. */
+ stm->default_output_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_output_listener.get());
+ if (r != noErr) {
+ stm->default_output_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->input_unit) {
+ /* This event will notify us when the default input device changes. */
+ stm->default_input_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_input_listener.get());
+ if (r != noErr) {
+ stm->default_input_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
+{
+ OSStatus rv;
+ // Failing to uninstall listeners is not a fatal error.
+ int r = CUBEB_OK;
+
+ if (stm->output_source_listener) {
+ rv = audiounit_remove_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id);
+ r = CUBEB_ERROR;
+ }
+ stm->output_source_listener.reset();
+ }
+
+ if (stm->input_source_listener) {
+ rv = audiounit_remove_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+ stm->input_source_listener.reset();
+ }
+
+ if (stm->input_alive_listener) {
+ rv = audiounit_remove_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d", rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+ stm->input_alive_listener.reset();
+ }
+
+ return r;
+}
+
+static int
+audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
+{
+ OSStatus r;
+
+ if (stm->default_output_listener) {
+ r = audiounit_remove_listener(stm->default_output_listener.get());
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+ stm->default_output_listener.reset();
+ }
+
+ if (stm->default_input_listener) {
+ r = audiounit_remove_listener(stm->default_input_listener.get());
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+ stm->default_input_listener.reset();
+ }
+ return CUBEB_OK;
+}
+
+/* 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
+ };
+
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
+ LOG("Could not get default output device id.");
+ return CUBEB_ERROR;
+ }
+
+ /* 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) {
+ LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+#endif /* !TARGET_OS_IPHONE */
+
+static AudioObjectID
+audiounit_get_default_device_id(cubeb_device_type type)
+{
+ const AudioObjectPropertyAddress * adr;
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS;
+ } else {
+ return kAudioObjectUnknown;
+ }
+
+ AudioDeviceID devid;
+ UInt32 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);
+
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+
+ size = sizeof(stream_format);
+
+ r = AudioObjectGetPropertyData(output_device_id,
+ &stream_format_address,
+ 0,
+ NULL,
+ &size,
+ &stream_format);
+ if (r != noErr) {
+ LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", 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 = max(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
+ };
+
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+
+ size = sizeof(fsamplerate);
+ r = AudioObjectGetPropertyData(output_device_id,
+ &samplerate_address,
+ 0,
+ NULL,
+ &size,
+ &fsamplerate);
+
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = static_cast(fsamplerate);
+#endif
+ return CUBEB_OK;
+}
+
+static cubeb_channel_layout
+audiounit_convert_channel_layout(AudioChannelLayout * layout)
+{
+ // When having one or two channel, force mono or stereo. Some devices (namely,
+ // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for
+ // some reason.
+ if (layout->mNumberChannelDescriptions == 1) {
+ return CUBEB_LAYOUT_MONO;
+ } else if (layout->mNumberChannelDescriptions == 2) {
+ return CUBEB_LAYOUT_STEREO;
+ }
+
+ if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) {
+ // kAudioChannelLayoutTag_UseChannelBitmap
+ // kAudioChannelLayoutTag_Mono
+ // kAudioChannelLayoutTag_Stereo
+ // ....
+ LOG("Only handle UseChannelDescriptions for now.\n");
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ cubeb_channel_layout cl = 0;
+ for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {
+ cubeb_channel cc = channel_label_to_cubeb_channel(
+ layout->mChannelDescriptions[i].mChannelLabel);
+ if (cc == CHANNEL_UNKNOWN) {
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ cl |= cc;
+ }
+
+ return cl;
+}
+
+static cubeb_channel_layout
+audiounit_get_preferred_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(output_unit,
+ kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output,
+ AU_OUT_BUS,
+ &size,
+ nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout rv=%d", rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(output_unit,
+ kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output,
+ AU_OUT_BUS,
+ layout.get(),
+ &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d", rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static cubeb_channel_layout
+audiounit_get_current_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(output_unit,
+ kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output,
+ AU_OUT_BUS,
+ &size,
+ nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d", rv);
+ // This property isn't known before macOS 10.12, attempt another method.
+ return audiounit_get_preferred_channel_layout(output_unit);
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(output_unit,
+ kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output,
+ AU_OUT_BUS,
+ layout.get(),
+ &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static int audiounit_create_unit(AudioUnit * unit, device_info * device);
+
+static OSStatus audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype);
+
+static void
+audiounit_destroy(cubeb * ctx)
+{
+ {
+ auto_lock lock(ctx->mutex);
+
+ // Disabling this assert for bug 1083664 -- we seem to leak a stream
+ // assert(ctx->active_streams == 0);
+ if (audiounit_active_streams(ctx) > 0) {
+ LOG("(%p) API misuse, %d streams active when context destroyed!", ctx, audiounit_active_streams(ctx));
+ }
+
+ /* Unregister the callback if necessary. */
+ if (ctx->input_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ }
+ if (ctx->output_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
+ }
+ }
+
+ dispatch_release(ctx->serial_queue);
+
+ delete 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;
+}
+
+void
+audiounit_init_mixer(cubeb_stream * stm)
+{
+ // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio
+ // data, it silently drop the channels so we need to remix the
+ // audio data by ourselves to keep all the information.
+ stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format,
+ stm->output_stream_params.channels,
+ stm->output_stream_params.layout,
+ stm->context->channels,
+ stm->context->layout));
+ assert(stm->mixer);
+}
+
+static int
+audiounit_set_channel_layout(AudioUnit unit,
+ io_side side,
+ cubeb_channel_layout layout)
+{
+ if (side != io_side::OUTPUT) {
+ return CUBEB_ERROR;
+ }
+
+ if (layout == CUBEB_LAYOUT_UNDEFINED) {
+ // We leave everything as-is...
+ return CUBEB_OK;
+ }
+
+
+ OSStatus r;
+ uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout);
+
+ // We do not use CoreAudio standard layout for lack of documentation on what
+ // the actual channel orders are. So we set a custom layout.
+ size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]);
+ auto au_layout = make_sized_audio_channel_layout(size);
+ au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ au_layout->mNumberChannelDescriptions = nb_channels;
+
+ uint32_t channels = 0;
+ cubeb_channel_layout channelMap = layout;
+ for (uint32_t i = 0; channelMap != 0; ++i) {
+ XASSERT(channels < nb_channels);
+ uint32_t channel = (channelMap & 1) << i;
+ if (channel != 0) {
+ au_layout->mChannelDescriptions[channels].mChannelLabel =
+ cubeb_channel_to_channel_label(static_cast(channel));
+ au_layout->mChannelDescriptions[channels].mChannelFlags = kAudioChannelFlags_AllOff;
+ channels++;
+ }
+ channelMap = channelMap >> 1;
+ }
+
+ r = AudioUnitSetProperty(unit,
+ kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Input,
+ AU_OUT_BUS,
+ au_layout.get(),
+ size);
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d", to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+void
+audiounit_layout_init(cubeb_stream * stm, io_side side)
+{
+ // We currently don't support the input layout setting.
+ if (side == io_side::INPUT) {
+ return;
+ }
+
+ stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit);
+
+ audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT, stm->context->layout);
+}
+
+static vector
+audiounit_get_sub_devices(AudioDeviceID device_id)
+{
+ vector sub_devices;
+ AudioObjectPropertyAddress property_address = { kAudioAggregateDevicePropertyActiveSubDeviceList,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(device_id,
+ &property_address,
+ 0,
+ nullptr,
+ &size);
+
+ if (rv != noErr) {
+ sub_devices.push_back(device_id);
+ return sub_devices;
+ }
+
+ uint32_t count = static_cast(size / sizeof(AudioObjectID));
+ sub_devices.resize(count);
+ rv = AudioObjectGetPropertyData(device_id,
+ &property_address,
+ 0,
+ nullptr,
+ &size,
+ sub_devices.data());
+ if (rv != noErr) {
+ sub_devices.clear();
+ sub_devices.push_back(device_id);
+ } else {
+ LOG("Found %u sub-devices", count);
+ }
+ return sub_devices;
+}
+
+static int
+audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id, AudioDeviceID * aggregate_device_id)
+{
+ AudioObjectPropertyAddress address_plugin_bundle_id = { kAudioHardwarePropertyPlugInForBundleID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ UInt32 size = 0;
+ OSStatus r = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
+ &address_plugin_bundle_id,
+ 0, NULL,
+ &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioHardwarePropertyPlugInForBundleID, rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ AudioValueTranslation translation_value;
+ CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio");
+ translation_value.mInputData = &in_bundle_ref;
+ translation_value.mInputDataSize = sizeof(in_bundle_ref);
+ translation_value.mOutputData = plugin_id;
+ translation_value.mOutputDataSize = sizeof(*plugin_id);
+
+ r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &address_plugin_bundle_id,
+ 0,
+ nullptr,
+ &size,
+ &translation_value);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress create_aggregate_device_address = { kAudioPlugInCreateAggregateDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ r = AudioObjectGetPropertyDataSize(*plugin_id,
+ &create_aggregate_device_address,
+ 0,
+ nullptr,
+ &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ struct timeval timestamp;
+ gettimeofday(×tamp, NULL);
+ long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec;
+ CFStringRef aggregate_device_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceNameKey), aggregate_device_name);
+ CFRelease(aggregate_device_name);
+
+ CFStringRef aggregate_device_UID = CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceUIDKey), aggregate_device_UID);
+ CFRelease(aggregate_device_UID);
+
+ int private_value = 1;
+ CFNumberRef aggregate_device_private_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value);
+ CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsPrivateKey), aggregate_device_private_key);
+ CFRelease(aggregate_device_private_key);
+
+ int stacked_value = 0;
+ CFNumberRef aggregate_device_stacked_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value);
+ CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsStackedKey), aggregate_device_stacked_key);
+ CFRelease(aggregate_device_stacked_key);
+
+ r = AudioObjectGetPropertyData(*plugin_id,
+ &create_aggregate_device_address,
+ sizeof(aggregate_device_dict),
+ &aggregate_device_dict,
+ &size,
+ aggregate_device_id);
+ CFRelease(aggregate_device_dict);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ LOG("New aggregate device %u", *aggregate_device_id);
+
+ return CUBEB_OK;
+}
+
+// The returned CFStringRef object needs to be released (via CFRelease)
+// if it's not NULL, since the reference count of the returned CFStringRef
+// object is increased.
+static CFStringRef
+get_device_name(AudioDeviceID id)
+{
+ UInt32 size = sizeof(CFStringRef);
+ CFStringRef UIname = nullptr;
+ AudioObjectPropertyAddress address_uuid = { kAudioDevicePropertyDeviceUID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ OSStatus err = AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname);
+ return (err == noErr) ? UIname : NULL;
+}
+
+static int
+audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id,
+ AudioDeviceID input_device_id,
+ AudioDeviceID output_device_id)
+{
+ LOG("Add devices input %u and output %u into aggregate device %u",
+ input_device_id, output_device_id, aggregate_device_id);
+ const vector output_sub_devices = audiounit_get_sub_devices(output_device_id);
+ const vector input_sub_devices = audiounit_get_sub_devices(input_device_id);
+
+ CFMutableArrayRef aggregate_sub_devices_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ /* The order of the items in the array is significant and is used to determine the order of the streams
+ of the AudioAggregateDevice. */
+ for (UInt32 i = 0; i < output_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(output_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+ for (UInt32 i = 0; i < input_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(input_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+
+ AudioObjectPropertyAddress aggregate_sub_device_list = { kAudioAggregateDevicePropertyFullSubDeviceList,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ UInt32 size = sizeof(CFMutableArrayRef);
+ OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id,
+ &aggregate_sub_device_list,
+ 0,
+ nullptr,
+ size,
+ &aggregate_sub_devices_array);
+ CFRelease(aggregate_sub_devices_array);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress master_aggregate_sub_device = { kAudioAggregateDevicePropertyMasterSubDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ // Master become the 1st output sub device
+ AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ const vector output_sub_devices = audiounit_get_sub_devices(output_device_id);
+ CFStringRef master_sub_device = get_device_name(output_sub_devices[0]);
+
+ UInt32 size = sizeof(CFStringRef);
+ OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id,
+ &master_aggregate_sub_device,
+ 0,
+ NULL,
+ size,
+ &master_sub_device);
+ if (master_sub_device) {
+ CFRelease(master_sub_device);
+ }
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/kAudioAggregateDevicePropertyMasterSubDevice, rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_activate_clock_drift_compensation(const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress address_owned = { kAudioObjectPropertyOwnedObjects,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ UInt32 qualifier_data_size = sizeof(AudioObjectID);
+ AudioClassID class_id = kAudioSubDeviceClassID;
+ void * qualifier_data = &class_id;
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(aggregate_device_id,
+ &address_owned,
+ qualifier_data_size,
+ qualifier_data,
+ &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ UInt32 subdevices_num = 0;
+ subdevices_num = size / sizeof(AudioObjectID);
+ AudioObjectID sub_devices[subdevices_num];
+ size = sizeof(sub_devices);
+
+ rv = AudioObjectGetPropertyData(aggregate_device_id,
+ &address_owned,
+ qualifier_data_size,
+ qualifier_data,
+ &size,
+ sub_devices);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress address_drift = { kAudioSubDevicePropertyDriftCompensation,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ // Start from the second device since the first is the master clock
+ for (UInt32 i = 1; i < subdevices_num; ++i) {
+ UInt32 drift_compensation_value = 1;
+ rv = AudioObjectSetPropertyData(sub_devices[i],
+ &address_drift,
+ 0,
+ nullptr,
+ sizeof(UInt32),
+ &drift_compensation_value);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/kAudioSubDevicePropertyDriftCompensation, rv=%d", rv);
+ return CUBEB_OK;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id);
+static void audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max, uint32_t * def);
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, AudioObjectID devid, cubeb_device_type type);
+static void audiounit_device_destroy(cubeb_device_info * device);
+
+static void
+audiounit_workaround_for_airpod(cubeb_stream * stm)
+{
+ cubeb_device_info input_device_info;
+ audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id, CUBEB_DEVICE_TYPE_INPUT);
+
+ cubeb_device_info output_device_info;
+ audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id, CUBEB_DEVICE_TYPE_OUTPUT);
+
+ std::string input_name_str(input_device_info.friendly_name);
+ std::string output_name_str(output_device_info.friendly_name);
+
+ if(input_name_str.find("AirPods") != std::string::npos &&
+ output_name_str.find("AirPods") != std::string::npos) {
+ uint32_t input_min_rate = 0;
+ uint32_t input_max_rate = 0;
+ uint32_t input_nominal_rate = 0;
+ audiounit_get_available_samplerate(stm->input_device.id, kAudioObjectPropertyScopeGlobal,
+ &input_min_rate, &input_max_rate, &input_nominal_rate);
+ LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->input_device.id
+ , input_device_info.friendly_name, input_min_rate, input_max_rate, input_nominal_rate);
+ uint32_t output_min_rate = 0;
+ uint32_t output_max_rate = 0;
+ uint32_t output_nominal_rate = 0;
+ audiounit_get_available_samplerate(stm->output_device.id, kAudioObjectPropertyScopeGlobal,
+ &output_min_rate, &output_max_rate, &output_nominal_rate);
+ LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->output_device.id
+ , output_device_info.friendly_name, output_min_rate, output_max_rate, output_nominal_rate);
+
+ Float64 rate = input_nominal_rate;
+ AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id,
+ &addr,
+ 0,
+ nullptr,
+ sizeof(Float64),
+ &rate);
+ if (rv != noErr) {
+ LOG("Non fatal error, AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, rv=%d", rv);
+ }
+ }
+ audiounit_device_destroy(&input_device_info);
+ audiounit_device_destroy(&output_device_info);
+}
+
+/*
+ * Aggregate Device is a virtual audio interface which utilizes inputs and outputs
+ * of one or more physical audio interfaces. It is possible to use the clock of
+ * one of the devices as a master clock for all the combined devices and enable
+ * drift compensation for the devices that are not designated clock master.
+ *
+ * Creating a new aggregate device programmatically requires [0][1]:
+ * 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
+ * 2. Create a dictionary that describes the aggregate device
+ * (don't add sub-devices in that step, prone to fail [0])
+ * 3. Ask the base plug-in to create the aggregate device (blank)
+ * 4. Add the array of sub-devices.
+ * 5. Set the master device (1st output device in our case)
+ * 6. Enable drift compensation for the non-master devices
+ *
+ * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
+ * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
+ * [2] CoreAudio.framework/Headers/AudioHardware.h
+ * */
+static int
+audiounit_create_aggregate_device(cubeb_stream * stm)
+{
+ int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to create blank aggregate device", stm);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set aggregate sub-device list", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_master_aggregate_device(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set master sub-device for aggregate device", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to activate clock drift compensation for aggregate device", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ audiounit_workaround_for_airpod(stm);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id)
+{
+ assert(aggregate_device_id &&
+ *aggregate_device_id != kAudioDeviceUnknown &&
+ plugin_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress destroy_aggregate_device_addr = { kAudioPlugInDestroyAggregateDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size;
+ OSStatus rv = AudioObjectGetPropertyDataSize(plugin_id,
+ &destroy_aggregate_device_addr,
+ 0,
+ NULL,
+ &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ rv = AudioObjectGetPropertyData(plugin_id,
+ &destroy_aggregate_device_addr,
+ 0,
+ NULL,
+ &size,
+ aggregate_device_id);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ LOG("Destroyed aggregate device %d", *aggregate_device_id);
+ *aggregate_device_id = kAudioObjectUnknown;
+ return CUBEB_OK;
+}
+
+static int
+audiounit_new_unit_instance(AudioUnit * unit, device_info * device)
+{
+ AudioComponentDescription desc;
+ AudioComponent comp;
+ OSStatus rv;
+
+ desc.componentType = kAudioUnitType_Output;
+#if TARGET_OS_IPHONE
+ 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.
+ if ((device->flags & DEV_SYSTEM_DEFAULT) &&
+ (device->flags & DEV_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) {
+ LOG("AudioComponentInstanceNew rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+enum enable_state {
+ DISABLE,
+ ENABLE,
+};
+
+static int
+audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state)
+{
+ OSStatus rv;
+ UInt32 enable = state;
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
+ (side == io_side::INPUT) ? kAudioUnitScope_Input : kAudioUnitScope_Output,
+ (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS,
+ &enable,
+ sizeof(UInt32));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit, device_info * device)
+{
+ assert(*unit == nullptr);
+ assert(device);
+
+ OSStatus rv;
+ int r;
+
+ r = audiounit_new_unit_instance(unit, device);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ assert(*unit);
+
+ if ((device->flags & DEV_SYSTEM_DEFAULT) &&
+ (device->flags & DEV_OUTPUT)) {
+ return CUBEB_OK;
+ }
+
+
+ if (device->flags & DEV_INPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit input scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit output scope");
+ return r;
+ }
+ } else if (device->flags & DEV_OUTPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit output scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit input scope");
+ return r;
+ }
+ } else {
+ assert(false);
+ }
+
+ rv = AudioUnitSetProperty(*unit,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &device->id, sizeof(AudioDeviceID));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
+{
+ uint32_t size = capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame;
+ if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl(size));
+ } else {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl(size));
+ }
+ assert(stream->input_linear_buffer->length() == 0);
+
+ return CUBEB_OK;
+}
+
+static uint32_t
+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
+{
+ // For the 1st stream set anything within safe min-max
+ assert(audiounit_active_streams(stm->context) > 0);
+ if (audiounit_active_streams(stm->context) == 1) {
+ return max(min(latency_frames, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
+ }
+ assert(stm->output_unit);
+
+ // If more than one stream operates in parallel
+ // allow only lower values of latency
+ 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) {
+ LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize rv=%d", r);
+ return 0;
+ }
+
+ output_buffer_size = max(min(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) {
+ LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize rv=%d", r);
+ return 0;
+ }
+
+ input_buffer_size = max(min(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 = min(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 max(min(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;
+ char const * au_type = "output";
+
+ if (AU_IN_BUS == inElement) {
+ au_scope = kAudioUnitScope_Output;
+ au_type = "input";
+ }
+
+ switch (inPropertyID) {
+
+ case kAudioDevicePropertyBufferFrameSize: {
+ if (inScope != au_scope) {
+ break;
+ }
+ UInt32 new_buffer_size;
+ UInt32 outSize = sizeof(UInt32);
+ OSStatus r = AudioUnitGetProperty(au,
+ kAudioDevicePropertyBufferFrameSize,
+ au_scope,
+ au_element,
+ &new_buffer_size,
+ &outSize);
+ if (r != noErr) {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
+ } else {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
+ au_type, new_buffer_size, inScope);
+ }
+ stm->buffer_size_change_state = true;
+ break;
+ }
+ }
+}
+
+static int
+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side side)
+{
+ AudioUnit au = stm->output_unit;
+ AudioUnitScope au_scope = kAudioUnitScope_Input;
+ AudioUnitElement au_element = AU_OUT_BUS;
+
+ if (side == io_side::INPUT) {
+ au = stm->input_unit;
+ au_scope = kAudioUnitScope_Output;
+ au_element = AU_IN_BUS;
+ }
+
+ 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) {
+ LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ if (new_size_frames == buffer_frames) {
+ LOG("(%p) No need to update %s buffer size already %u frames", stm, to_string(side), buffer_frames);
+ return CUBEB_OK;
+ }
+
+ r = AudioUnitAddPropertyListener(au,
+ kAudioDevicePropertyBufferFrameSize,
+ buffer_size_changed_callback,
+ stm);
+ if (r != noErr) {
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ stm->buffer_size_change_state = false;
+
+ r = AudioUnitSetProperty(au,
+ kAudioDevicePropertyBufferFrameSize,
+ au_scope,
+ au_element,
+ &new_size_frames,
+ sizeof(new_size_frames));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r);
+
+ r = AudioUnitRemovePropertyListenerWithUserData(au,
+ kAudioDevicePropertyBufferFrameSize,
+ buffer_size_changed_callback,
+ stm);
+ if (r != noErr) {
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), 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) {
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ 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, to_string(side), new_size_frames);
+ return CUBEB_OK;
+}
+
+static int
+audiounit_configure_input(cubeb_stream * stm)
+{
+ assert(stm && stm->input_unit);
+
+ int r = 0;
+ UInt32 size;
+ AURenderCallbackStruct aurcbs_in;
+
+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
+ 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) {
+ LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", 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
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Error in change input buffer size.", stm);
+ return CUBEB_ERROR;
+ }
+
+ 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) {
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ /* Frames per buffer in the input callback. */
+ r = AudioUnitSetProperty(stm->input_unit,
+ kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &stm->latency_frames,
+ sizeof(UInt32));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice rv=%d", 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;
+ }
+
+ 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) {
+ LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ stm->frames_read = 0;
+
+ LOG("(%p) Input audiounit init successfully.", stm);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_configure_output(cubeb_stream * stm)
+{
+ assert(stm && stm->output_unit);
+
+ int r;
+ AURenderCallbackStruct aurcbs_out;
+ UInt32 size;
+
+
+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
+ 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) {
+ LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ stm->output_hw_rate = output_hw_desc.mSampleRate;
+ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
+ stm->context->channels = output_hw_desc.mChannelsPerFrame;
+
+ // Set the input layout to match the output device layout.
+ audiounit_layout_init(stm, io_side::OUTPUT);
+ if (stm->context->channels != stm->output_stream_params.channels ||
+ stm->context->layout != stm->output_stream_params.layout) {
+ LOG("Incompatible channel layouts detected, setting up remixer");
+ audiounit_init_mixer(stm);
+ // We will be remixing the data before it reaches the output device.
+ // We need to adjust the number of channels and other
+ // AudioStreamDescription details.
+ stm->output_desc.mChannelsPerFrame = stm->context->channels;
+ stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *
+ stm->output_desc.mChannelsPerFrame;
+ stm->output_desc.mBytesPerPacket =
+ stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
+ } else {
+ stm->mixer = nullptr;
+ }
+
+ r = AudioUnitSetProperty(stm->output_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ AU_OUT_BUS,
+ &stm->output_desc,
+ sizeof(AudioStreamBasicDescription));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Error in change output buffer size.", stm);
+ return CUBEB_ERROR;
+ }
+
+ /* Frames per buffer in the input callback. */
+ r = AudioUnitSetProperty(stm->output_unit,
+ kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global,
+ AU_OUT_BUS,
+ &stm->latency_frames,
+ sizeof(UInt32));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ 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) {
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ stm->frames_written = 0;
+
+ LOG("(%p) Output audiounit init successfully.", stm);
+ return CUBEB_OK;
+}
+
+static int
+audiounit_setup_stream(cubeb_stream * stm)
+{
+ stm->mutex.assert_current_thread_owns();
+
+ if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||
+ (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {
+ LOG("(%p) Loopback not supported for audiounit.", stm);
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ int r = 0;
+
+ device_info in_dev_info = stm->input_device;
+ device_info out_dev_info = stm->output_device;
+
+ if (has_input(stm) && has_output(stm) &&
+ stm->input_device.id != stm->output_device.id) {
+ r = audiounit_create_aggregate_device(stm);
+ if (r != CUBEB_OK) {
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ LOG("(%p) Create aggregate devices failed.", stm);
+ // !!!NOTE: It is not necessary to return here. If it does not
+ // return it will fallback to the old implementation. The intention
+ // is to investigate how often it fails. I plan to remove
+ // it after a couple of weeks.
+ return r;
+ } else {
+ in_dev_info.id = out_dev_info.id = stm->aggregate_device_id;
+ in_dev_info.flags = DEV_INPUT;
+ out_dev_info.flags = DEV_OUTPUT;
+ }
+ }
+
+ if (has_input(stm)) {
+ r = audiounit_create_unit(&stm->input_unit, &in_dev_info);
+ 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, &out_dev_info);
+ if (r != CUBEB_OK) {
+ LOG("(%p) AudioUnit creation for output failed.", stm);
+ return r;
+ }
+ }
+
+ /* Latency cannot change if another stream is operating in parallel. In this case
+ * latency is set to the other stream value. */
+ if (audiounit_active_streams(stm->context) > 1) {
+ LOG("(%p) More than one active stream, use global latency.", stm);
+ stm->latency_frames = stm->context->global_latency_frames;
+ } else {
+ /* Silently clamp the latency down to the platform default, because we
+ * synthetize the clock from the callbacks, and we want the clock to update
+ * often. */
+ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
+ assert(stm->latency_frames); // Ugly error check
+ audiounit_set_global_latency(stm->context, stm->latency_frames);
+ }
+
+ /* Configure I/O stream */
+ if (has_input(stm)) {
+ r = audiounit_configure_input(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Configure audiounit input failed.", stm);
+ return r;
+ }
+ }
+
+ 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.reset(cubeb_resampler_create(stm,
+ has_input(stm) ? &input_unconverted_params : NULL,
+ has_output(stm) ? &stm->output_stream_params : NULL,
+ target_sample_rate,
+ stm->data_callback,
+ stm->user_ptr,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP));
+ if (!stm->resampler) {
+ LOG("(%p) Could not create resampler.", stm);
+ return CUBEB_ERROR;
+ }
+
+ if (stm->input_unit != NULL) {
+ r = AudioUnitInitialize(stm->input_unit);
+ if (r != noErr) {
+ LOG("AudioUnitInitialize/input rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->output_unit != NULL) {
+ r = AudioUnitInitialize(stm->output_unit);
+ if (r != noErr) {
+ LOG("AudioUnitInitialize/output rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ stm->current_latency_frames = audiounit_get_device_presentation_latency(stm->output_device.id, kAudioDevicePropertyScopeOutput);
+
+ Float64 unit_s;
+ UInt32 size = sizeof(unit_s);
+ if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &unit_s, &size) == noErr) {
+ stm->current_latency_frames += static_cast(unit_s * stm->output_desc.mSampleRate);
+ }
+ }
+
+ if (stm->input_unit && stm->output_unit) {
+ // According to the I/O hardware rate it is expected a specific pattern of callbacks
+ // for example is input is 44100 and output is 48000 we expected no more than 2
+ // out callback in a row.
+ stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
+ }
+
+ r = audiounit_install_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not install all device change callback.", stm);
+ }
+
+
+ return CUBEB_OK;
+}
+
+cubeb_stream::cubeb_stream(cubeb * context)
+ : context(context)
+ , resampler(nullptr, cubeb_resampler_destroy)
+ , mixer(nullptr, cubeb_mixer_destroy)
+{
+ PodZero(&input_desc, 1);
+ PodZero(&output_desc, 1);
+}
+
+static void audiounit_stream_destroy_internal(cubeb_stream * stm);
+
+static int
+audiounit_stream_init(cubeb * context,
+ cubeb_stream ** stream,
+ char const * /* stream_name */,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr)
+{
+ assert(context);
+ auto_lock context_lock(context->mutex);
+ audiounit_increment_active_streams(context);
+ unique_ptr stm(new cubeb_stream(context),
+ audiounit_stream_destroy_internal);
+ int r;
+ *stream = NULL;
+ assert(latency_frames > 0);
+
+ /* These could be different in the future if we have both
+ * full-duplex stream and different devices for input vs output. */
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->latency_frames = latency_frames;
+
+ if ((input_device && !input_stream_params) ||
+ (output_device && !output_stream_params)) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ if (input_stream_params) {
+ stm->input_stream_params = *input_stream_params;
+ r = audiounit_set_device_info(stm.get(), reinterpret_cast(input_device), io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for input.", stm.get());
+ return r;
+ }
+ }
+ if (output_stream_params) {
+ stm->output_stream_params = *output_stream_params;
+ r = audiounit_set_device_info(stm.get(), reinterpret_cast(output_device), io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for output.", stm.get());
+ return r;
+ }
+ }
+
+ {
+ // 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`.
+ auto_lock lock(stm->mutex);
+ r = audiounit_setup_stream(stm.get());
+ }
+
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not setup the audiounit stream.", stm.get());
+ return r;
+ }
+
+ r = audiounit_install_system_changed_callback(stm.get());
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not install the device change callback.", stm.get());
+ return r;
+ }
+
+ *stream = stm.release();
+ LOG("(%p) Cubeb stream init successful.", *stream);
+ 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);
+ stm->input_unit = nullptr;
+ }
+
+ stm->input_linear_buffer.reset();
+
+ if (stm->output_unit) {
+ AudioUnitUninitialize(stm->output_unit);
+ AudioComponentInstanceDispose(stm->output_unit);
+ stm->output_unit = nullptr;
+ }
+
+ stm->resampler.reset();
+ stm->mixer.reset();
+
+ if (stm->aggregate_device_id != kAudioObjectUnknown) {
+ audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ }
+}
+
+static void
+audiounit_stream_destroy_internal(cubeb_stream *stm)
+{
+ stm->context->mutex.assert_current_thread_owns();
+
+ int r = audiounit_uninstall_system_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall the device changed callback", stm);
+ }
+ r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
+
+ auto_lock lock(stm->mutex);
+ audiounit_close_stream(stm);
+ assert(audiounit_active_streams(stm->context) >= 1);
+ audiounit_decrement_active_streams(stm->context);
+}
+
+static void
+audiounit_stream_destroy(cubeb_stream * stm)
+{
+ int r = audiounit_uninstall_system_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall the device changed callback", stm);
+ }
+ r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
+
+ if (!stm->shutdown.load()){
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_stop_internal(stm);
+ stm->shutdown = true;
+ }
+
+ stm->destroy_pending = true;
+ // Execute close in serial queue to avoid collision
+ // with reinit when un/plug devices
+ dispatch_sync(stm->context->serial_queue, ^() {
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_destroy_internal(stm);
+ });
+
+ LOG("Cubeb stream (%p) destroyed successful.", stm);
+ delete stm;
+}
+
+static int
+audiounit_stream_start_internal(cubeb_stream * stm)
+{
+ OSStatus r;
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStart(stm->input_unit);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (input) rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+ if (stm->output_unit != NULL) {
+ r = AudioOutputUnitStart(stm->output_unit);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (output) rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_stream_start(cubeb_stream * stm)
+{
+ auto_lock context_lock(stm->context->mutex);
+ stm->shutdown = false;
+ stm->draining = false;
+
+ int r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ 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)
+{
+ assert(stm);
+ if (stm->current_latency_frames > stm->frames_played) {
+ *position = 0;
+ } else {
+ *position = stm->frames_played - stm->current_latency_frames;
+ }
+ 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
+ *latency = stm->total_output_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;
+}
+
+static int
+audiounit_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ assert(stm->output_unit);
+ OSStatus r;
+ r = AudioUnitSetParameter(stm->output_unit,
+ kHALOutputParam_Volume,
+ kAudioUnitScope_Global,
+ 0, volume, 0);
+
+ if (r != noErr) {
+ LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+unique_ptr convert_uint32_into_string(UInt32 data)
+{
+ // Simply create an empty string if no data.
+ size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32.
+ auto str = unique_ptr { new char[size + 1] }; // + 1 for '\0'.
+ str[size] = '\0';
+ if (size < 4) {
+ return str;
+ }
+
+ // Reverse 0xWXYZ into 0xZYXW.
+ str[0] = (char)(data >> 24);
+ str[1] = (char)(data >> 16);
+ str[2] = (char)(data >> 8);
+ str[3] = (char)(data);
+ return str;
+}
+
+int audiounit_get_default_device_datasource(cubeb_device_type type,
+ UInt32 * data)
+{
+ AudioDeviceID id = audiounit_get_default_device_id(type);
+ if (id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+
+ UInt32 size = sizeof(*data);
+ /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */
+ OSStatus r = AudioObjectGetPropertyData(id,
+ type == CUBEB_DEVICE_TYPE_INPUT ?
+ &INPUT_DATA_SOURCE_PROPERTY_ADDRESS :
+ &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ 0, NULL, &size, data);
+ if (r != noErr) {
+ *data = 0;
+ }
+
+ return CUBEB_OK;
+}
+
+int audiounit_get_default_device_name(cubeb_stream * stm,
+ cubeb_device * const device,
+ cubeb_device_type type)
+{
+ assert(stm);
+ assert(device);
+
+ UInt32 data;
+ int r = audiounit_get_default_device_datasource(type, &data);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ char ** name = type == CUBEB_DEVICE_TYPE_INPUT ?
+ &device->input_name : &device->output_name;
+ *name = convert_uint32_into_string(data).release();
+ if (!strlen(*name)) { // empty string.
+ LOG("(%p) name of %s device is empty!", stm,
+ type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output");
+ }
+ return CUBEB_OK;
+}
+
+
+int audiounit_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+#if TARGET_OS_IPHONE
+ //TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ *device = new cubeb_device;
+ if (!*device) {
+ return CUBEB_ERROR;
+ }
+ PodZero(*device, 1);
+
+ int r = audiounit_get_default_device_name(stm, *device,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ r = audiounit_get_default_device_name(stm, *device,
+ CUBEB_DEVICE_TYPE_INPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ return CUBEB_OK;
+#endif
+}
+
+int audiounit_stream_device_destroy(cubeb_stream * /* stream */,
+ cubeb_device * device)
+{
+ 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)
+{
+ auto_lock dev_cb_lock(stream->device_changed_callback_lock);
+ /* Note: second register without unregister first causes 'nope' error.
+ * Current implementation requires unregister before register a new cb. */
+ assert(!device_changed_callback || !stream->device_changed_callback);
+ stream->device_changed_callback = device_changed_callback;
+ return CUBEB_OK;
+}
+
+static char *
+audiounit_strref_to_cstr_utf8(CFStringRef strref)
+{
+ CFIndex len, size;
+ char * ret;
+ if (strref == NULL) {
+ return NULL;
+ }
+
+ len = CFStringGetLength(strref);
+ // Add 1 to size to allow for '\0' termination character.
+ size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+ ret = new char[size];
+
+ if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {
+ delete [] 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(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 count = size / sizeof(AudioValueRange);
+ vector ranges(count);
+ range.mMinimum = 9999999999.0;
+ range.mMaximum = 0.0;
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges.data()) == noErr) {
+ for (uint32_t i = 0; i < count; i++) {
+ if (ranges[i].mMaximum > range.mMaximum)
+ range.mMaximum = ranges[i].mMaximum;
+ if (ranges[i].mMinimum < range.mMinimum)
+ range.mMinimum = ranges[i].mMinimum;
+ }
+ }
+ *max = static_cast(range.mMaximum);
+ *min = static_cast(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;
+ 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);
+ }
+
+ return dev + stream;
+}
+
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, AudioObjectID devid, cubeb_device_type type)
+{
+ AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster };
+ UInt32 size;
+
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ adr.mScope = kAudioDevicePropertyScopeOutput;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ adr.mScope = kAudioDevicePropertyScopeInput;
+ } else {
+ return CUBEB_ERROR;
+ }
+
+ UInt32 ch = audiounit_get_channel_count(devid, adr.mScope);
+ if (ch == 0) {
+ return CUBEB_ERROR;
+ }
+
+ PodZero(dev_info, 1);
+
+ CFStringRef device_id_str = nullptr;
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioDevicePropertyDeviceUID;
+ OSStatus ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str);
+ if ( ret == noErr && device_id_str != NULL) {
+ dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str);
+ static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)), "cubeb_devid can't represent devid");
+ dev_info->devid = reinterpret_cast(devid);
+ dev_info->group_id = dev_info->device_id;
+ CFRelease(device_id_str);
+ }
+
+ CFStringRef friendly_name_str = nullptr;
+ UInt32 ds;
+ size = sizeof(UInt32);
+ adr.mSelector = kAudioDevicePropertyDataSource;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds);
+ if (ret == noErr) {
+ AudioValueTranslation trl = { &ds, sizeof(ds), &friendly_name_str, sizeof(CFStringRef) };
+ adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
+ size = sizeof(AudioValueTranslation);
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl);
+ }
+
+ // If there is no datasource for this device, fall back to the
+ // device name.
+ if (!friendly_name_str) {
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioObjectPropertyName;
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str);
+ }
+
+ if (friendly_name_str) {
+ dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str);
+ CFRelease(friendly_name_str);
+ } else {
+ // Couldn't get a datasource name nor a device name, return a
+ // valid string of length 0.
+ char * fallback_name = new char[1];
+ fallback_name[0] = '\0';
+ dev_info->friendly_name = fallback_name;
+ }
+
+ CFStringRef vendor_name_str = nullptr;
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioObjectPropertyManufacturer;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str);
+ if (ret == noErr && vendor_name_str != NULL) {
+ dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str);
+ CFRelease(vendor_name_str);
+ }
+
+ dev_info->type = type;
+ dev_info->state = CUBEB_DEVICE_STATE_ENABLED;
+ dev_info->preferred = (devid == audiounit_get_default_device_id(type)) ?
+ CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+
+ dev_info->max_channels = ch;
+ dev_info->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
+ /* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */
+ dev_info->default_format = CUBEB_DEVICE_FMT_F32NE;
+ audiounit_get_available_samplerate(devid, adr.mScope,
+ &dev_info->min_rate, &dev_info->max_rate, &dev_info->default_rate);
+
+ UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+
+ AudioValueRange range;
+ adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
+ size = sizeof(AudioValueRange);
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range);
+ if (ret == noErr) {
+ dev_info->latency_lo = latency + range.mMinimum;
+ dev_info->latency_hi = latency + range.mMaximum;
+ } else {
+ dev_info->latency_lo = 10 * dev_info->default_rate / 1000; /* Default to 10ms */
+ dev_info->latency_hi = 100 * dev_info->default_rate / 1000; /* Default to 100ms */
+ }
+
+ return CUBEB_OK;
+}
+
+bool
+is_aggregate_device(cubeb_device_info * device_info)
+{
+ assert(device_info->friendly_name);
+ return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME,
+ strlen(PRIVATE_AGGREGATE_DEVICE_NAME));
+}
+
+static int
+audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ vector input_devs;
+ vector output_devs;
+
+ // Count number of input and output devices. This is not
+ // necessarily the same as the count of raw devices supported by the
+ // system since, for example, with Soundflower installed, some
+ // devices may report as being both input *and* output and cubeb
+ // separates those into two different devices.
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ }
+
+ auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()];
+ collection->count = 0;
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ for (auto dev: output_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
+ }
+ collection->count += 1;
+ }
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ for (auto dev: input_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_INPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
+ }
+ collection->count += 1;
+ }
+ }
+
+ if (collection->count > 0) {
+ collection->device = devices;
+ } else {
+ delete [] devices;
+ collection->device = NULL;
+ }
+
+ return CUBEB_OK;
+}
+
+static void
+audiounit_device_destroy(cubeb_device_info * device)
+{
+ delete [] device->device_id;
+ delete [] device->friendly_name;
+ delete [] device->vendor_name;
+}
+
+static int
+audiounit_device_collection_destroy(cubeb * /* context */,
+ cubeb_device_collection * collection)
+{
+ for (size_t i = 0; i < collection->count; i++) {
+ audiounit_device_destroy(&collection->device[i]);
+ }
+ delete [] collection->device;
+
+ return CUBEB_OK;
+}
+
+static vector
+audiounit_get_devices_of_type(cubeb_device_type devtype)
+{
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS, 0,
+ NULL, &size);
+ if (ret != noErr) {
+ return vector();
+ }
+ vector devices(size / sizeof(AudioObjectID));
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size,
+ devices.data());
+ if (ret != noErr) {
+ return vector();
+ }
+
+ // Remove the aggregate device from the list of devices (if any).
+ for (auto it = devices.begin(); it != devices.end();) {
+ CFStringRef name = get_device_name(*it);
+ if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location !=
+ kCFNotFound) {
+ it = devices.erase(it);
+ } else {
+ it++;
+ }
+ if (name) {
+ CFRelease(name);
+ }
+ }
+
+ /* Expected sorted but did not find anything in the docs. */
+ sort(devices.begin(), devices.end(), [](AudioObjectID a, AudioObjectID b) {
+ return a < b;
+ });
+
+ if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
+ return devices;
+ }
+
+ AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ?
+ kAudioDevicePropertyScopeInput :
+ kAudioDevicePropertyScopeOutput;
+
+ vector devices_in_scope;
+ for (uint32_t i = 0; i < devices.size(); ++i) {
+ /* For device in the given scope channel must be > 0. */
+ if (audiounit_get_channel_count(devices[i], scope) > 0) {
+ devices_in_scope.push_back(devices[i]);
+ }
+ }
+
+ return devices_in_scope;
+}
+
+static OSStatus
+audiounit_collection_changed_callback(AudioObjectID /* inObjectID */,
+ UInt32 /* inNumberAddresses */,
+ const AudioObjectPropertyAddress * /* inAddresses */,
+ void * inClientData)
+{
+ cubeb * context = static_cast(inClientData);
+
+ // This can be called from inside an AudioUnit function, dispatch to another queue.
+ dispatch_async(context->serial_queue, ^() {
+ auto_lock lock(context->mutex);
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ /* Listener removed while waiting in mutex, abort. */
+ return;
+ }
+ if (context->input_collection_changed_callback) {
+ vector devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->input_device_array != devices) {
+ context->input_device_array = devices;
+ context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr);
+ }
+ }
+ if (context->output_collection_changed_callback) {
+ vector devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->output_device_array != devices) {
+ context->output_device_array = devices;
+ context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr);
+ }
+ }
+ });
+ return noErr;
+}
+
+static OSStatus
+audiounit_add_device_listener(cubeb * context,
+ cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ context->mutex.assert_current_thread_owns();
+ assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
+ /* Note: second register without unregister first causes 'nope' error.
+ * Current implementation requires unregister before register a new cb. */
+ assert((devtype & CUBEB_DEVICE_TYPE_INPUT) && !context->input_collection_changed_callback ||
+ (devtype & CUBEB_DEVICE_TYPE_OUTPUT) && !context->output_collection_changed_callback);
+
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback,
+ context);
+ if (ret != noErr) {
+ return ret;
+ }
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ /* Expected empty after unregister. */
+ assert(context->input_device_array.empty());
+ context->input_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ /* Expected empty after unregister. */
+ assert(context->output_device_array.empty());
+ context->output_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+ return noErr;
+}
+
+static OSStatus
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype)
+{
+ context->mutex.assert_current_thread_owns();
+
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ context->input_device_array.clear();
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ context->output_device_array.clear();
+ }
+
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ return noErr;
+ }
+ /* Note: unregister a non registered cb is not a problem, not checking. */
+ return AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback,
+ context);
+}
+
+int audiounit_register_device_collection_changed(cubeb * context,
+ cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ OSStatus ret;
+ auto_lock lock(context->mutex);
+ if (collection_changed_callback) {
+ ret = audiounit_add_device_listener(context,
+ devtype,
+ collection_changed_callback,
+ user_ptr);
+ } else {
+ ret = audiounit_remove_device_listener(context, devtype);
+ }
+ return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR;
+}
+
+cubeb_ops const audiounit_ops = {
+ /*.init =*/ audiounit_init,
+ /*.get_backend_id =*/ audiounit_get_backend_id,
+ /*.get_max_channel_count =*/ audiounit_get_max_channel_count,
+ /*.get_min_latency =*/ audiounit_get_min_latency,
+ /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate,
+ /*.enumerate_devices =*/ audiounit_enumerate_devices,
+ /*.device_collection_destroy =*/ audiounit_device_collection_destroy,
+ /*.destroy =*/ audiounit_destroy,
+ /*.stream_init =*/ audiounit_stream_init,
+ /*.stream_destroy =*/ audiounit_stream_destroy,
+ /*.stream_start =*/ audiounit_stream_start,
+ /*.stream_stop =*/ audiounit_stream_stop,
+ /*.stream_reset_default_device =*/ nullptr,
+ /*.stream_get_position =*/ audiounit_stream_get_position,
+ /*.stream_get_latency =*/ audiounit_stream_get_latency,
+ /*.stream_get_input_latency =*/ NULL,
+ /*.stream_set_volume =*/ audiounit_stream_set_volume,
+ /*.stream_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/thirdparty/cubeb/src/cubeb_jack.cpp b/thirdparty/cubeb/src/cubeb_jack.cpp
new file mode 100644
index 0000000..2320b73
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_jack.cpp
@@ -0,0 +1,1076 @@
+/*
+ * 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
+#ifndef __FreeBSD__
+#define _POSIX_SOURCE
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
+
+#include
+#include
+
+#define JACK_API_VISIT(X) \
+ X(jack_activate) \
+ X(jack_client_close) \
+ X(jack_client_open) \
+ X(jack_connect) \
+ X(jack_free) \
+ X(jack_get_ports) \
+ X(jack_get_sample_rate) \
+ X(jack_get_xrun_delayed_usecs) \
+ X(jack_get_buffer_size) \
+ X(jack_port_get_buffer) \
+ X(jack_port_name) \
+ X(jack_port_register) \
+ X(jack_port_unregister) \
+ X(jack_port_get_latency_range) \
+ X(jack_set_process_callback) \
+ X(jack_set_xrun_callback) \
+ X(jack_set_graph_order_callback) \
+ X(jack_set_error_function) \
+ X(jack_set_info_function)
+
+#define IMPORT_FUNC(x) static decltype(x) * api_##x;
+JACK_API_VISIT(IMPORT_FUNC);
+
+#define JACK_DEFAULT_IN "JACK capture"
+#define JACK_DEFAULT_OUT "JACK playback"
+
+static const int MAX_STREAMS = 16;
+static const int MAX_CHANNELS = 8;
+static const int 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_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
+static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr);
+static void cbjack_stream_destroy(cubeb_stream * stream);
+static int cbjack_stream_start(cubeb_stream * stream);
+static int cbjack_stream_stop(cubeb_stream * stream);
+static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
+static int cbjack_stream_set_volume(cubeb_stream * stm, float volume);
+
+static struct cubeb_ops const cbjack_ops = {
+ .init = jack_init,
+ .get_backend_id = cbjack_get_backend_id,
+ .get_max_channel_count = cbjack_get_max_channel_count,
+ .get_min_latency = cbjack_get_min_latency,
+ .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+ .enumerate_devices = cbjack_enumerate_devices,
+ .device_collection_destroy = cbjack_device_collection_destroy,
+ .destroy = cbjack_destroy,
+ .stream_init = cbjack_stream_init,
+ .stream_destroy = cbjack_stream_destroy,
+ .stream_start = cbjack_stream_start,
+ .stream_stop = cbjack_stream_stop,
+ .stream_reset_default_device = NULL,
+ .stream_get_position = cbjack_stream_get_position,
+ .stream_get_latency = cbjack_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = cbjack_stream_set_volume,
+ .stream_get_current_device = cbjack_stream_get_current_device,
+ .stream_device_destroy = cbjack_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+
+ /**< Mutex for each stream */
+ pthread_mutex_t mutex;
+
+ bool in_use; /**< Set to false iff the stream is free */
+ bool ports_ready; /**< Set to true iff the JACK ports are ready */
+
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ 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_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);
+ if (!context->libjack) {
+ context->libjack = dlopen("libjack.so", RTLD_LAZY);
+ }
+#endif
+ if (!context->libjack) {
+ return CUBEB_ERROR;
+ }
+
+#define LOAD(x) \
+ { \
+ api_##x = (decltype(x)*)dlsym(context->libjack, #x); \
+ if (!api_##x) { \
+ dlclose(context->libjack); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ JACK_API_VISIT(LOAD);
+#undef LOAD
+
+ return CUBEB_OK;
+}
+
+static int
+cbjack_connect_ports (cubeb_stream * stream)
+{
+ int r = CUBEB_ERROR;
+ const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client,
+ NULL, NULL,
+ JackPortIsInput
+ | JackPortIsPhysical);
+ const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client,
+ NULL, NULL,
+ JackPortIsOutput
+ | JackPortIsPhysical);
+
+ if (phys_in_ports == NULL || *phys_in_ports == NULL) {
+ 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]);
+ }
+ r = CUBEB_OK;
+
+skipplayback:
+ if (phys_out_ports == NULL || *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);
+ }
+ r = CUBEB_OK;
+end:
+ if (phys_out_ports) {
+ api_jack_free(phys_out_ports);
+ }
+ if (phys_in_ports) {
+ api_jack_free(phys_in_ports);
+ }
+ return r;
+}
+
+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 && input_device != JACK_DEFAULT_IN) ||
+ (output_device && output_device != JACK_DEFAULT_OUT)) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ // Loopback is unsupported
+ if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
+ (output_stream_params && (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ *stream = NULL;
+
+ // 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);
+ }
+ }
+
+ if (cbjack_connect_ports(stm) != CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *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_DEFAULT_IN;
+ const char * j_out = JACK_DEFAULT_OUT;
+ 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;
+ cbjack_get_preferred_sample_rate(context, &rate);
+
+ cubeb_device_info * devices = new cubeb_device_info[2];
+ if (!devices)
+ return CUBEB_ERROR;
+ PodZero(devices, 2);
+ collection->count = 0;
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_OUT;
+ cur->devid = (cubeb_devid) cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_OUT;
+ cur->group_id = JACK_DEFAULT_OUT;
+ cur->vendor_name = JACK_DEFAULT_OUT;
+ cur->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count +=1 ;
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_IN;
+ cur->devid = (cubeb_devid) cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_IN;
+ cur->group_id = JACK_DEFAULT_IN;
+ cur->vendor_name = JACK_DEFAULT_IN;
+ cur->type = CUBEB_DEVICE_TYPE_INPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
+ }
+
+ collection->device = devices;
+
+ return CUBEB_OK;
+}
+
+static int
+cbjack_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+ delete [] collection->device;
+ return CUBEB_OK;
+}
diff --git a/thirdparty/cubeb/src/cubeb_kai.c b/thirdparty/cubeb/src/cubeb_kai.c
new file mode 100644
index 0000000..0a621c9
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_kai.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright © 2015 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include
+#include
+#include
+#include
+
+#include
+
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+
+/* We don't support more than 2 channels in KAI */
+#define MAX_CHANNELS 2
+
+#define NBUFS 2
+#define FRAME_SIZE 2048
+
+struct cubeb_stream_item {
+ cubeb_stream * stream;
+};
+
+static struct cubeb_ops const kai_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ cubeb_stream_params params;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+
+ HKAI hkai;
+ KAISPEC spec;
+ uint64_t total_frames;
+ float soft_volume;
+ _fmutex mutex;
+ float float_buffer[FRAME_SIZE * MAX_CHANNELS];
+};
+
+static inline long
+frames_to_bytes(long frames, cubeb_stream_params params)
+{
+ return frames * 2 * params.channels; /* 2 bytes per frame */
+}
+
+static inline long
+bytes_to_frames(long bytes, cubeb_stream_params params)
+{
+ return bytes / 2 / params.channels; /* 2 bytes per frame */
+}
+
+static void kai_destroy(cubeb * ctx);
+
+/*static*/ int
+kai_init(cubeb ** context, char const * context_name)
+{
+ cubeb * ctx;
+
+ XASSERT(context);
+ *context = NULL;
+
+ if (kaiInit(KAIM_AUTO))
+ return CUBEB_ERROR;
+
+ ctx = calloc(1, sizeof(*ctx));
+ XASSERT(ctx);
+
+ ctx->ops = &kai_ops;
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+kai_get_backend_id(cubeb * ctx)
+{
+ return "kai";
+}
+
+static void
+kai_destroy(cubeb * ctx)
+{
+ kaiDone();
+
+ free(ctx);
+}
+
+static void
+float_to_s16ne(int16_t *dst, float *src, size_t n)
+{
+ long l;
+
+ while (n--) {
+ l = lrintf(*src++ * 0x8000);
+ if (l > 32767)
+ l = 32767;
+ if (l < -32768)
+ l = -32768;
+ *dst++ = (int16_t)l;
+ }
+}
+
+static ULONG APIENTRY
+kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
+{
+ cubeb_stream * stm = cbdata;
+ void *p;
+ long wanted_frames;
+ long frames;
+ float soft_volume;
+ int elements = len / sizeof(int16_t);
+
+ p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE
+ ? stm->float_buffer : buffer;
+
+ wanted_frames = bytes_to_frames(len, stm->params);
+ frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
+
+ _fmutex_request(&stm->mutex, 0);
+ stm->total_frames += frames;
+ soft_volume = stm->soft_volume;
+ _fmutex_release(&stm->mutex);
+
+ if (frames < wanted_frames)
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+
+ if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE)
+ float_to_s16ne(buffer, p, elements);
+
+ if (soft_volume != -1.0f) {
+ int16_t *b = buffer;
+ int i;
+
+ for (i = 0; i < elements; i++)
+ *b++ *= soft_volume;
+ }
+
+ return frames_to_bytes(frames, stm->params);
+}
+
+static void kai_stream_destroy(cubeb_stream * stm);
+
+static int
+kai_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)
+{
+ cubeb_stream * stm;
+ KAISPEC wanted_spec;
+
+ XASSERT(!input_stream_params && "not supported.");
+ if (input_device || output_device) {
+ /* Device selection not yet implemented. */
+ return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ }
+
+ if (!output_stream_params)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+
+ // Loopback is unsupported
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ if (output_stream_params->channels < 1 ||
+ output_stream_params->channels > MAX_CHANNELS)
+ return CUBEB_ERROR_INVALID_FORMAT;
+
+ XASSERT(context);
+ XASSERT(stream);
+
+ *stream = NULL;
+
+ 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->soft_volume = -1.0f;
+
+ if (_fmutex_create(&stm->mutex, 0)) {
+ free(stm);
+ return CUBEB_ERROR;
+ }
+
+ wanted_spec.usDeviceIndex = 0;
+ wanted_spec.ulType = KAIT_PLAY;
+ wanted_spec.ulBitsPerSample = BPS_16;
+ wanted_spec.ulSamplingRate = stm->params.rate;
+ wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
+ wanted_spec.ulChannels = stm->params.channels;
+ wanted_spec.ulNumBuffers = NBUFS;
+ wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
+ wanted_spec.fShareable = TRUE;
+ wanted_spec.pfnCallBack = kai_callback;
+ wanted_spec.pCallBackData = stm;
+
+ if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
+ _fmutex_close(&stm->mutex);
+ free(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+static void
+kai_stream_destroy(cubeb_stream * stm)
+{
+ kaiClose(stm->hkai);
+ _fmutex_close(&stm->mutex);
+ free(stm);
+}
+
+static int
+kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ XASSERT(ctx && max_channels);
+
+ *max_channels = MAX_CHANNELS;
+
+ return CUBEB_OK;
+}
+
+static int
+kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
+{
+ /* We have at least two buffers. One is being played, the other one is being
+ filled. So there is as much latency as one buffer. */
+ *latency = FRAME_SIZE;
+
+ return CUBEB_OK;
+}
+
+static int
+kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ cubeb_stream_params params;
+ KAISPEC wanted_spec;
+ KAISPEC spec;
+ HKAI hkai;
+
+ params.format = CUBEB_SAMPLE_S16NE;
+ params.rate = 48000;
+ params.channels = 2;
+
+ wanted_spec.usDeviceIndex = 0;
+ wanted_spec.ulType = KAIT_PLAY;
+ wanted_spec.ulBitsPerSample = BPS_16;
+ wanted_spec.ulSamplingRate = params.rate;
+ wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
+ wanted_spec.ulChannels = params.channels;
+ wanted_spec.ulNumBuffers = NBUFS;
+ wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
+ wanted_spec.fShareable = TRUE;
+ wanted_spec.pfnCallBack = kai_callback;
+ wanted_spec.pCallBackData = NULL;
+
+ /* Test 48KHz */
+ if (kaiOpen(&wanted_spec, &spec, &hkai)) {
+ /* Not supported. Fall back to 44.1KHz */
+ params.rate = 44100;
+ } else {
+ /* Supported. Use 48KHz */
+ kaiClose(hkai);
+ }
+
+ *rate = params.rate;
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_start(cubeb_stream * stm)
+{
+ if (kaiPlay(stm->hkai))
+ return CUBEB_ERROR;
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_stop(cubeb_stream * stm)
+{
+ if (kaiStop(stm->hkai))
+ return CUBEB_ERROR;
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ _fmutex_request(&stm->mutex, 0);
+ *position = stm->total_frames;
+ _fmutex_release(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ /* Out of buffers, one is being played, the others are being filled.
+ So there is as much latency as total buffers - 1. */
+ *latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params)
+ * (stm->spec.ulNumBuffers - 1);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ _fmutex_request(&stm->mutex, 0);
+ stm->soft_volume = volume;
+ _fmutex_release(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const kai_ops = {
+ /*.init =*/ kai_init,
+ /*.get_backend_id =*/ kai_get_backend_id,
+ /*.get_max_channel_count=*/ kai_get_max_channel_count,
+ /*.get_min_latency=*/ kai_get_min_latency,
+ /*.get_preferred_sample_rate =*/ kai_get_preferred_sample_rate,
+ /*.get_preferred_channel_layout =*/ NULL,
+ /*.enumerate_devices =*/ NULL,
+ /*.device_collection_destroy =*/ NULL,
+ /*.destroy =*/ kai_destroy,
+ /*.stream_init =*/ kai_stream_init,
+ /*.stream_destroy =*/ kai_stream_destroy,
+ /*.stream_start =*/ kai_stream_start,
+ /*.stream_stop =*/ kai_stream_stop,
+ /*.stream_reset_default_device =*/ NULL,
+ /*.stream_get_position =*/ kai_stream_get_position,
+ /*.stream_get_latency = */ kai_stream_get_latency,
+ /*.stream_get_input_latency = */ NULL,
+ /*.stream_set_volume =*/ kai_stream_set_volume,
+ /*.stream_get_current_device =*/ NULL,
+ /*.stream_device_destroy =*/ NULL,
+ /*.stream_register_device_changed_callback=*/ NULL,
+ /*.register_device_collection_changed=*/ NULL
+};
diff --git a/thirdparty/cubeb/src/cubeb_log.cpp b/thirdparty/cubeb/src/cubeb_log.cpp
new file mode 100644
index 0000000..54c7f4a
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_log.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define NOMINMAX
+
+#include "cubeb_log.h"
+#include "cubeb_ringbuffer.h"
+#include
+#ifdef _WIN32
+#include
+#else
+#include
+#endif
+
+cubeb_log_level g_cubeb_log_level;
+cubeb_log_callback g_cubeb_log_callback;
+
+/** The maximum size of a log message, after having been formatted. */
+const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
+/** The maximum number of log messages that can be queued before dropping
+ * messages. */
+const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
+/** Number of milliseconds to wait before dequeuing log messages. */
+#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
+
+/**
+ * This wraps an inline buffer, that represents a log message, that must be
+ * null-terminated.
+ * This class should not use system calls or other potentially blocking code.
+ */
+class cubeb_log_message
+{
+public:
+ cubeb_log_message()
+ {
+ *storage = '\0';
+ }
+ cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ size_t length = strlen(str);
+ /* paranoia against malformed message */
+ assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
+ if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
+ return;
+ }
+ PodCopy(storage, str, length);
+ storage[length] = '\0';
+ }
+ char const * get() {
+ return storage;
+ }
+private:
+ char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
+};
+
+/** Lock-free asynchronous logger, made so that logging from a
+ * real-time audio callback does not block the audio thread. */
+class cubeb_async_logger
+{
+public:
+ /* This is thread-safe since C++11 */
+ static cubeb_async_logger & get() {
+ static cubeb_async_logger instance;
+ return instance;
+ }
+ void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ cubeb_log_message msg(str);
+ msg_queue.enqueue(msg);
+ }
+ void run()
+ {
+ std::thread([this]() {
+ while (true) {
+ cubeb_log_message msg;
+ while (msg_queue.dequeue(&msg, 1)) {
+ LOGV("%s", msg.get());
+ }
+#ifdef _WIN32
+ Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
+#else
+ timespec sleep_duration = sleep_for;
+ timespec remainder;
+ do {
+ if (nanosleep(&sleep_duration, &remainder) == 0 ||
+ errno != EINTR) {
+ break;
+ }
+ sleep_duration = remainder;
+ } while (remainder.tv_sec || remainder.tv_nsec);
+#endif
+ }
+ }).detach();
+ }
+ // Tell the underlying queue the producer thread has changed, so it does not
+ // assert in debug. This should be called with the thread stopped.
+ void reset_producer_thread()
+ {
+ msg_queue.reset_thread_ids();
+ }
+private:
+#ifndef _WIN32
+ const struct timespec sleep_for = {
+ CUBEB_LOG_BATCH_PRINT_INTERVAL_MS/1000,
+ (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS%1000)*1000*1000
+ };
+#endif
+ cubeb_async_logger()
+ : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH)
+ {
+ run();
+ }
+ /** This is quite a big data structure, but is only instantiated if the
+ * asynchronous logger is used.*/
+ lock_free_queue msg_queue;
+};
+
+
+void cubeb_async_log(char const * fmt, ...)
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ // This is going to copy a 256 bytes array around, which is fine.
+ // We don't want to allocate memory here, because this is made to
+ // be called from a real-time callback.
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ cubeb_async_logger::get().push(msg);
+ va_end(args);
+}
+
+void cubeb_async_log_reset_threads()
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ cubeb_async_logger::get().reset_producer_thread();
+}
diff --git a/thirdparty/cubeb/src/cubeb_log.h b/thirdparty/cubeb/src/cubeb_log.h
new file mode 100644
index 0000000..446e29a
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_log.h
@@ -0,0 +1,54 @@
+/*
+ * 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
+
+#include "cubeb/cubeb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
+#if defined(__FILE_NAME__)
+#define __FILENAME__ __FILE_NAME__
+#else
+#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
+#endif
+#else
+#define PRINTF_FORMAT(fmt, args)
+#include
+#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#endif
+
+extern cubeb_log_level g_cubeb_log_level;
+extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
+void cubeb_async_log(const char * fmt, ...);
+void cubeb_async_log_reset_threads();
+
+#ifdef __cplusplus
+}
+#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_cubeb_log_callback && level <= g_cubeb_log_level) { \
+ g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, ##__VA_ARGS__); \
+ } \
+ } while(0)
+
+/* Asynchronous verbose logging, to log in real-time callbacks. */
+#define ALOGV(fmt, ...) \
+do { \
+ cubeb_async_log(fmt, ##__VA_ARGS__); \
+} while(0)
+
+#endif // CUBEB_LOG
diff --git a/thirdparty/cubeb/src/cubeb_mixer.cpp b/thirdparty/cubeb/src/cubeb_mixer.cpp
new file mode 100644
index 0000000..2ab7f67
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_mixer.cpp
@@ -0,0 +1,663 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ *
+ * Adapted from code based on libswresample's rematrix.c
+ */
+
+#define NOMINMAX
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "cubeb-internal.h"
+#include "cubeb_mixer.h"
+#include "cubeb_utils.h"
+
+#ifndef FF_ARRAY_ELEMS
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#define CHANNELS_MAX 32
+#define FRONT_LEFT 0
+#define FRONT_RIGHT 1
+#define FRONT_CENTER 2
+#define LOW_FREQUENCY 3
+#define BACK_LEFT 4
+#define BACK_RIGHT 5
+#define FRONT_LEFT_OF_CENTER 6
+#define FRONT_RIGHT_OF_CENTER 7
+#define BACK_CENTER 8
+#define SIDE_LEFT 9
+#define SIDE_RIGHT 10
+#define TOP_CENTER 11
+#define TOP_FRONT_LEFT 12
+#define TOP_FRONT_CENTER 13
+#define TOP_FRONT_RIGHT 14
+#define TOP_BACK_LEFT 15
+#define TOP_BACK_CENTER 16
+#define TOP_BACK_RIGHT 17
+#define NUM_NAMED_CHANNELS 18
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
+
+#define C30DB M_SQRT2
+#define C15DB 1.189207115
+#define C__0DB 1.0
+#define C_15DB 0.840896415
+#define C_30DB M_SQRT1_2
+#define C_45DB 0.594603558
+#define C_60DB 0.5
+
+static cubeb_channel_layout
+cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
+{
+ if (l == CUBEB_LAYOUT_UNDEFINED) {
+ switch (c) {
+ case 1: return CUBEB_LAYOUT_MONO;
+ case 2: return CUBEB_LAYOUT_STEREO;
+ }
+ }
+ return l;
+}
+
+unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
+{
+#if __GNUC__ || __clang__
+ return __builtin_popcount (x);
+#else
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+#endif
+}
+
+struct MixerContext {
+ MixerContext(cubeb_sample_format f,
+ uint32_t in_channels,
+ cubeb_channel_layout in,
+ uint32_t out_channels,
+ cubeb_channel_layout out)
+ : _format(f)
+ , _in_ch_layout(cubeb_channel_layout_check(in, in_channels))
+ , _out_ch_layout(cubeb_channel_layout_check(out, out_channels))
+ , _in_ch_count(in_channels)
+ , _out_ch_count(out_channels)
+ {
+ if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+ out_channels != cubeb_channel_layout_nb_channels(out)) {
+ // Mismatch between channels and layout, aborting.
+ return;
+ }
+ _valid = init() >= 0;
+ }
+
+ static bool even(cubeb_channel_layout layout)
+ {
+ if (!layout) {
+ return true;
+ }
+ if (layout & (layout - 1)) {
+ return true;
+ }
+ return false;
+ }
+
+ // Ensure that the layout is sane (that is have symmetrical left/right
+ // channels), if not, layout will be treated as mono.
+ static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
+ {
+ if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
+ LOG("Treating layout as mono");
+ return CHANNEL_FRONT_CENTER;
+ }
+
+ return layout;
+ }
+
+ static bool sane_layout(cubeb_channel_layout layout)
+ {
+ if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
+ return false;
+ }
+ if (!even(layout & (CHANNEL_FRONT_LEFT |
+ CHANNEL_FRONT_RIGHT))) { // no asymetric front
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
+ return false;
+ }
+ if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
+ return false;
+ }
+ if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
+ return false;
+ }
+ return true;
+ }
+
+ int auto_matrix();
+ int init();
+
+ const cubeb_sample_format _format;
+ const cubeb_channel_layout _in_ch_layout; ///< input channel layout
+ const cubeb_channel_layout _out_ch_layout; ///< output channel layout
+ const uint32_t _in_ch_count; ///< input channel count
+ const uint32_t _out_ch_count; ///< output channel count
+ const float _surround_mix_level = C_30DB; ///< surround mixing level
+ const float _center_mix_level = C_30DB; ///< center mixing level
+ const float _lfe_mix_level = 1; ///< LFE mixing level
+ double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< floating point rematrixing coefficients
+ float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< single precision floating point rematrixing coefficients
+ int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< 17.15 fixed point rematrixing coefficients
+ uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX+1] = {{ 0 }}; ///< Lists of input channels per output channel that have non zero rematrixing coefficients
+ bool _clipping = false; ///< Set to true if clipping detection is required
+ bool _valid = false; ///< Set to true if context is valid.
+};
+
+int MixerContext::auto_matrix()
+{
+ double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = { { 0 } };
+ double maxcoef = 0;
+ float maxval;
+
+ cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
+ cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
+
+ if (!sane_layout(in_ch_layout)) {
+ // Channel Not Supported
+ LOG("Input Layout %x is not supported", _in_ch_layout);
+ return -1;
+ }
+
+ if (!sane_layout(out_ch_layout)) {
+ LOG("Output Layout %x is not supported", _out_ch_layout);
+ return -1;
+ }
+
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
+ if (in_ch_layout & out_ch_layout & (1U << i)) {
+ matrix[i][i] = 1.0;
+ }
+ }
+
+ cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
+
+ // Rematrixing is done via a matrix of coefficient that should be applied to
+ // all channels. Channels are treated as pair and must be symmetrical (if a
+ // left channel exists, the corresponding right should exist too) unless the
+ // output layout has similar layout. Channels are then mixed toward the front
+ // center or back center if they exist with a slight bias toward the front.
+
+ if (unaccounted & CHANNEL_FRONT_CENTER) {
+ if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
+ if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
+ matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
+ } else {
+ matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
+ }
+ }
+ }
+ if (unaccounted & CUBEB_LAYOUT_STEREO) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
+ if (in_ch_layout & CHANNEL_FRONT_CENTER)
+ matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_BACK_CENTER) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_CENTER] +=
+ _surround_mix_level * M_SQRT1_2;
+ }
+ }
+ if (unaccounted & CHANNEL_BACK_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ if (in_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_SIDE_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ /* if back channels do not exist in the input, just copy side
+ channels to back channels, otherwise mix side into back */
+ if (in_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
+ if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
+ matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
+ }
+ }
+ /* mix LFE into front left/right or center */
+ if (unaccounted & CHANNEL_LOW_FREQUENCY) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ }
+ }
+
+ // Normalize the conversion matrix.
+ for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
+ double sum = 0;
+ int in_i = 0;
+ if ((out_ch_layout & (1U << i)) == 0) {
+ continue;
+ }
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ if ((in_ch_layout & (1U << j)) == 0) {
+ continue;
+ }
+ if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
+ _matrix[out_i][in_i] = matrix[i][j];
+ } else {
+ _matrix[out_i][in_i] =
+ i == j && (in_ch_layout & out_ch_layout & (1U << i));
+ }
+ sum += fabs(_matrix[out_i][in_i]);
+ in_i++;
+ }
+ maxcoef = std::max(maxcoef, sum);
+ out_i++;
+ }
+
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ maxval = 1.0;
+ } else {
+ maxval = INT_MAX;
+ }
+
+ // Normalize matrix if needed.
+ if (maxcoef > maxval) {
+ maxcoef /= maxval;
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++)
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix[i][j] /= maxcoef;
+ }
+ }
+
+ if (_format == CUBEB_SAMPLE_FLOAT32NE) {
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
+ for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
+ _matrix_flt[i][j] = _matrix[i][j];
+ }
+ }
+ }
+
+ return 0;
+}
+
+int MixerContext::init()
+{
+ int r = auto_matrix();
+ if (r) {
+ return r;
+ }
+
+ // Determine if matrix operation would overflow
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ int maxsum = 0;
+ for (uint32_t i = 0; i < _out_ch_count; i++) {
+ double rem = 0;
+ int sum = 0;
+
+ for (uint32_t j = 0; j < _in_ch_count; j++) {
+ double target = _matrix[i][j] * 32768 + rem;
+ int value = lrintf(target);
+ rem += target - value;
+ sum += std::abs(value);
+ }
+ maxsum = std::max(maxsum, sum);
+ }
+ if (maxsum > 32768) {
+ _clipping = true;
+ }
+ }
+
+ // FIXME quantize for integers
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
+ int ch_in = 0;
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
+ if (_matrix[i][j]) {
+ _matrix_ch[i][++ch_in] = j;
+ }
+ }
+ _matrix_ch[i][0] = ch_in;
+ }
+
+ return 0;
+}
+
+template
+void
+sum2(TYPE_SAMPLE * out,
+ uint32_t stride_out,
+ const TYPE_SAMPLE * in1,
+ const TYPE_SAMPLE * in2,
+ uint32_t stride_in,
+ TYPE_COEFF coeff1,
+ TYPE_COEFF coeff2,
+ F&& operand,
+ uint32_t frames)
+{
+ static_assert(
+ std::is_same::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff1 * *in1 + coeff2 * *in2);
+ out += stride_out;
+ in1 += stride_in;
+ in2 += stride_in;
+ }
+}
+
+template
+void
+copy(TYPE_SAMPLE * out,
+ uint32_t stride_out,
+ const TYPE_SAMPLE * in,
+ uint32_t stride_in,
+ TYPE_COEFF coeff,
+ F&& operand,
+ uint32_t frames)
+{
+ static_assert(
+ std::is_same::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff * *in);
+ out += stride_out;
+ in += stride_in;
+ }
+}
+
+template
+static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
+ const TYPE_COEFF (&matrix_coeff)[COLS][COLS],
+ F&& aF, uint32_t frames)
+{
+ static_assert(
+ std::is_same::type>::value,
+ "function must return the same type as used by matrix_coeff");
+
+ for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
+ TYPE* out = aOut + out_i;
+ switch (s->_matrix_ch[out_i][0]) {
+ case 0:
+ for (uint32_t i = 0; i < frames; i++) {
+ out[i * s->_out_ch_count] = 0;
+ }
+ break;
+ case 1: {
+ int in_i = s->_matrix_ch[out_i][1];
+ copy(out,
+ s->_out_ch_count,
+ aIn + in_i,
+ s->_in_ch_count,
+ matrix_coeff[out_i][in_i],
+ aF,
+ frames);
+ } break;
+ case 2:
+ sum2(out,
+ s->_out_ch_count,
+ aIn + s->_matrix_ch[out_i][1],
+ aIn + s->_matrix_ch[out_i][2],
+ s->_in_ch_count,
+ matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
+ matrix_coeff[out_i][s->_matrix_ch[out_i][2]],
+ aF,
+ frames);
+ break;
+ default:
+ for (uint32_t i = 0; i < frames; i++) {
+ TYPE_COEFF v = 0;
+ for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
+ uint32_t in_i = s->_matrix_ch[out_i][1 + j];
+ v +=
+ *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
+ }
+ out[i * s->_out_ch_count] = aF(v);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+struct cubeb_mixer
+{
+ cubeb_mixer(cubeb_sample_format format,
+ uint32_t in_channels,
+ cubeb_channel_layout in_layout,
+ uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+ : _context(format, in_channels, in_layout, out_channels, out_layout)
+ {
+ }
+
+ template
+ void copy_and_trunc(size_t frames,
+ const T * input_buffer,
+ T * output_buffer) const
+ {
+ if (_context._in_ch_count <= _context._out_ch_count) {
+ // Not enough channels to copy, fill the gaps with silence.
+ if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) {
+ // Special case for upmixing mono input to stereo and more. We will
+ // duplicate the mono channel to the first two channels. On most system,
+ // the first two channels are for left and right. It is commonly
+ // expected that mono will on both left+right channels
+ for (uint32_t i = 0; i < frames; i++) {
+ output_buffer[0] = output_buffer[1] = *input_buffer;
+ PodZero(output_buffer + 2, _context._out_ch_count - 2);
+ output_buffer += _context._out_ch_count;
+ input_buffer++;
+ }
+ return;
+ }
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+ output_buffer += _context._in_ch_count;
+ input_buffer += _context._in_ch_count;
+ PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+ output_buffer += _context._out_ch_count - _context._in_ch_count;
+ }
+ } else {
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+ output_buffer += _context._out_ch_count;
+ input_buffer += _context._in_ch_count;
+ }
+ }
+ }
+
+ int mix(size_t frames,
+ const void * input_buffer,
+ size_t input_buffer_size,
+ void * output_buffer,
+ size_t output_buffer_size) const
+ {
+ if (frames <= 0 || _context._out_ch_count == 0) {
+ return 0;
+ }
+
+ // Check if output buffer is of sufficient size.
+ size_t size_read_needed =
+ frames * _context._in_ch_count * cubeb_sample_size(_context._format);
+ if (input_buffer_size < size_read_needed) {
+ // We don't have enough data to read!
+ return -1;
+ }
+ if (output_buffer_size * _context._in_ch_count <
+ size_read_needed * _context._out_ch_count) {
+ return -1;
+ }
+
+ if (!valid()) {
+ // The channel layouts were invalid or unsupported, instead we will simply
+ // either drop the extra channels, or fill with silence the missing ones
+ if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+ copy_and_trunc(frames,
+ static_cast(input_buffer),
+ static_cast(output_buffer));
+ } else {
+ assert(_context._format == CUBEB_SAMPLE_S16NE);
+ copy_and_trunc(frames,
+ static_cast(input_buffer),
+ reinterpret_cast(output_buffer));
+ }
+ return 0;
+ }
+
+ switch (_context._format)
+ {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ auto f = [](float x) { return x; };
+ return rematrix(&_context,
+ static_cast(output_buffer),
+ static_cast(input_buffer),
+ _context._matrix_flt,
+ f,
+ frames);
+ }
+ case CUBEB_SAMPLE_S16NE:
+ if (_context._clipping) {
+ auto f = [](int x) {
+ int y = (x + 16384) >> 15;
+ // clip the signed integer value into the -32768,32767 range.
+ if ((y + 0x8000U) & ~0xFFFF) {
+ return (y >> 31) ^ 0x7FFF;
+ }
+ return y;
+ };
+ return rematrix(&_context,
+ static_cast(output_buffer),
+ static_cast(input_buffer),
+ _context._matrix32,
+ f,
+ frames);
+ } else {
+ auto f = [](int x) { return (x + 16384) >> 15; };
+ return rematrix(&_context,
+ static_cast(output_buffer),
+ static_cast(input_buffer),
+ _context._matrix32,
+ f,
+ frames);
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return -1;
+ }
+
+ // Return false if any of the input or ouput layout were invalid.
+ bool valid() const { return _context._valid; }
+
+ virtual ~cubeb_mixer(){};
+
+ MixerContext _context;
+};
+
+cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format,
+ uint32_t in_channels,
+ cubeb_channel_layout in_layout,
+ uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+{
+ return new cubeb_mixer(
+ format, in_channels, in_layout, out_channels, out_layout);
+}
+
+void cubeb_mixer_destroy(cubeb_mixer * mixer)
+{
+ delete mixer;
+}
+
+int cubeb_mixer_mix(cubeb_mixer * mixer,
+ size_t frames,
+ const void * input_buffer,
+ size_t input_buffer_size,
+ void * output_buffer,
+ size_t output_buffer_size)
+{
+ return mixer->mix(
+ frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size);
+}
diff --git a/thirdparty/cubeb/src/cubeb_mixer.h b/thirdparty/cubeb/src/cubeb_mixer.h
new file mode 100644
index 0000000..d43a237
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_mixer.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_MIXER
+#define CUBEB_MIXER
+
+#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct cubeb_mixer cubeb_mixer;
+cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
+ uint32_t in_channels,
+ cubeb_channel_layout in_layout,
+ uint32_t out_channels,
+ cubeb_channel_layout out_layout);
+void cubeb_mixer_destroy(cubeb_mixer * mixer);
+int cubeb_mixer_mix(cubeb_mixer * mixer,
+ size_t frames,
+ const void * input_buffer,
+ size_t input_buffer_size,
+ void * output_buffer,
+ size_t output_buffer_size);
+
+unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBEB_MIXER
diff --git a/thirdparty/cubeb/src/cubeb_opensl.c b/thirdparty/cubeb/src/cubeb_opensl.c
new file mode 100644
index 0000000..65520cf
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_opensl.c
@@ -0,0 +1,1763 @@
+/*
+ * Copyright © 2012 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if defined(__ANDROID__)
+#include
+#include
+#include "android/sles_definitions.h"
+#include
+#include
+#include
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include "cubeb_resampler.h"
+#include "cubeb-sles.h"
+#include "cubeb_array_queue.h"
+#include "android/cubeb-output-latency.h"
+
+#if defined(__ANDROID__)
+#ifdef LOG
+#undef LOG
+#endif
+//#define LOGGING_ENABLED
+#ifdef LOGGING_ENABLED
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
+#else
+#define LOG(...)
+#endif
+
+//#define TIMESTAMP_ENABLED
+#ifdef TIMESTAMP_ENABLED
+#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#define LOG_TS(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)" , ## args)
+#define TIMESTAMP(msg) do { \
+ struct timeval timestamp; \
+ int ts_ret = gettimeofday(×tamp, NULL); \
+ if (ts_ret == 0) { \
+ LOG_TS("%lld: %s (%s %s:%d)", timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, __FUNCTION__, FILENAME, __LINE__);\
+ } else { \
+ LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, __LINE__);\
+ } \
+} while(0)
+#else
+#define TIMESTAMP(...)
+#endif
+
+#define ANDROID_VERSION_GINGERBREAD_MR1 10
+#define ANDROID_VERSION_JELLY_BEAN 18
+#define ANDROID_VERSION_LOLLIPOP 21
+#define ANDROID_VERSION_MARSHMALLOW 23
+#define ANDROID_VERSION_N_MR1 25
+#endif
+
+#define DEFAULT_SAMPLE_RATE 48000
+#define DEFAULT_NUM_OF_FRAMES 480
+// If the latency requested is above this threshold, this stream is considered
+// intended for playback (vs. real-time). Tell Android it should favor saving
+// power over performance or latency.
+// This is around 100ms at 44100 or 48000
+#define POWERSAVE_LATENCY_FRAMES_THRESHOLD 4000
+
+static struct cubeb_ops const opensl_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * lib;
+ SLInterfaceID SL_IID_BUFFERQUEUE;
+ SLInterfaceID SL_IID_PLAY;
+#if defined(__ANDROID__)
+ SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
+ SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
+#endif
+ SLInterfaceID SL_IID_VOLUME;
+ SLInterfaceID SL_IID_RECORD;
+ SLObjectItf engObj;
+ SLEngineItf eng;
+ SLObjectItf outmixObj;
+ output_latency_function * p_output_latency_function;
+};
+
+#define NELEMS(A) (sizeof(A) / sizeof A[0])
+#define NBUFS 2
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ pthread_mutex_t mutex;
+ SLObjectItf playerObj;
+ SLPlayItf play;
+ SLBufferQueueItf bufq;
+ SLVolumeItf volume;
+ void ** queuebuf;
+ uint32_t queuebuf_capacity;
+ int queuebuf_idx;
+ long queuebuf_len;
+ long bytespersec;
+ long framesize;
+ /* Total number of played frames.
+ * Synchronized by stream::mutex lock. */
+ long written;
+ /* Flag indicating draining. Synchronized
+ * by stream::mutex lock. */
+ int draining;
+ /* Flags to determine in/out.*/
+ uint32_t input_enabled;
+ uint32_t output_enabled;
+ /* Recorder abstract object. */
+ SLObjectItf recorderObj;
+ /* Recorder Itf for input capture. */
+ SLRecordItf recorderItf;
+ /* Buffer queue for input capture. */
+ SLAndroidSimpleBufferQueueItf recorderBufferQueueItf;
+ /* Store input buffers. */
+ void ** input_buffer_array;
+ /* The capacity of the array.
+ * On capture only can be small (4).
+ * On full duplex is calculated to
+ * store 1 sec of data buffers. */
+ uint32_t input_array_capacity;
+ /* Current filled index of input buffer array.
+ * It is initiated to -1 indicating buffering
+ * have not started yet. */
+ int input_buffer_index;
+ /* Length of input buffer.*/
+ uint32_t input_buffer_length;
+ /* Input frame size */
+ uint32_t input_frame_size;
+ /* Device sampling rate. If user rate is not
+ * accepted an compatible rate is set. If it is
+ * accepted this is equal to params.rate. */
+ uint32_t input_device_rate;
+ /* Exchange input buffers between input
+ * and full duplex threads. */
+ array_queue * input_queue;
+ /* Silent input buffer used on full duplex. */
+ void * input_silent_buffer;
+ /* Number of input frames from the start of the stream*/
+ uint32_t input_total_frames;
+ /* Flag to stop the execution of user callback and
+ * close all working threads. Synchronized by
+ * stream::mutex lock. */
+ uint32_t shutdown;
+ /* Store user callback. */
+ cubeb_data_callback data_callback;
+ /* Store state callback. */
+ cubeb_state_callback state_callback;
+
+ cubeb_resampler * resampler;
+ unsigned int user_output_rate;
+ unsigned int output_configured_rate;
+ unsigned int buffer_size_frames;
+ // Audio output latency used in cubeb_stream_get_position().
+ unsigned int output_latency_ms;
+ int64_t lastPosition;
+ int64_t lastPositionTimeStamp;
+ int64_t lastCompensativePosition;
+ int voice_input;
+ int voice_output;
+};
+
+/* Forward declaration. */
+static int opensl_stop_player(cubeb_stream * stm);
+static int opensl_stop_recorder(cubeb_stream * stm);
+
+static int
+opensl_get_draining(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) && "get_draining: mutex should be locked but it's not.");
+#endif
+ return stm->draining;
+}
+
+static void
+opensl_set_draining(cubeb_stream * stm, int value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set draining try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) && "set_draining: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->draining = value;
+}
+
+static void
+opensl_notify_drained(cubeb_stream * stm)
+{
+ assert(stm);
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->play) {
+ LOG("stop player in play_callback");
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+}
+
+static uint32_t
+opensl_get_shutdown(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) && "get_shutdown: mutex should be locked but it's not.");
+#endif
+ return stm->shutdown;
+}
+
+static void
+opensl_set_shutdown(cubeb_stream * stm, uint32_t value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set shutdown try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) && "set_shutdown: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->shutdown = value;
+}
+
+static void
+play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
+{
+ cubeb_stream * stm = user_ptr;
+ assert(stm);
+ switch (event) {
+ case SL_PLAYEVENT_HEADATMARKER:
+ opensl_notify_drained(stm);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+recorder_marker_callback (SLRecordItf caller, void * pContext, SLuint32 event)
+{
+ cubeb_stream * stm = pContext;
+ assert(stm);
+
+ if (event == SL_RECORDEVENT_HEADATMARKER) {
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->play) {
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+ }
+}
+
+static void
+bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ cubeb_stream * stm = user_ptr;
+ assert(stm);
+ SLBufferQueueState state;
+ SLresult res;
+ long written = 0;
+
+ res = (*stm->bufq)->GetState(stm->bufq, &state);
+ assert(res == SL_RESULT_SUCCESS);
+
+ if (state.count > 1) {
+ return;
+ }
+
+ uint8_t *buf = stm->queuebuf[stm->queuebuf_idx];
+ written = 0;
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (!draining && !shutdown) {
+ written = cubeb_resampler_fill(stm->resampler,
+ NULL, NULL,
+ buf, stm->queuebuf_len / stm->framesize);
+ LOG("bufferqueue_callback: resampler fill returned %ld frames", written);
+ if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
+ }
+ }
+
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ assert(stm->queuebuf_len >= written * stm->framesize);
+ memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize);
+ res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+
+ if (written > 0) {
+ pthread_mutex_lock(&stm->mutex);
+ stm->written += written;
+ pthread_mutex_unlock(&stm->mutex);
+ }
+
+ if (!draining && written * stm->framesize < stm->queuebuf_len) {
+ LOG("bufferqueue_callback draining");
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written_duration == 0) {
+ // since we didn't write any sample, it's not possible to reach the marker
+ // time and trigger the callback. We should initiative notify drained.
+ opensl_notify_drained(stm);
+ } else {
+ // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+ // to make sure all the data has been processed.
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
+ }
+ return;
+ }
+}
+
+static int
+opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
+{
+ assert(stm);
+
+ int current_index = stm->input_buffer_index;
+ void * last_buffer = NULL;
+
+ if (current_index < 0) {
+ // This is the first enqueue
+ current_index = 0;
+ } else {
+ // The current index hold the last filled buffer get it before advance index.
+ last_buffer = stm->input_buffer_array[current_index];
+ // Advance to get next available buffer
+ current_index = (current_index + 1) % stm->input_array_capacity;
+ }
+ // enqueue next empty buffer to be filled by the recorder
+ SLresult res = (*stm->recorderBufferQueueItf)->Enqueue(stm->recorderBufferQueueItf,
+ stm->input_buffer_array[current_index],
+ stm->input_buffer_length);
+ if (res != SL_RESULT_SUCCESS ) {
+ LOG("Enqueue recorder failed. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // All good, update buffer and index.
+ stm->input_buffer_index = current_index;
+ if (last_filled_buffer) {
+ *last_filled_buffer = last_buffer;
+ }
+ return CUBEB_OK;
+}
+
+// input data callback
+void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = context;
+ assert(stm->recorderBufferQueueItf);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf
+ // page 184, on transition to the SL_RECORDSTATE_STOPPED state,
+ // the application should continue to enqueue buffers onto the queue
+ // to retrieve the residual recorded data in the system.
+ r = opensl_enqueue_recorder(stm, NULL);
+ assert(r == CUBEB_OK);
+ return;
+ }
+
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = NULL;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+ // Fill resampler with last input
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long got = cubeb_resampler_fill(stm->resampler,
+ input_buffer,
+ &input_frame_count,
+ NULL,
+ 0);
+ // Error case
+ if (got < 0 || got > input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+
+ // Advance total stream frames
+ stm->input_total_frames += got;
+
+ if (got < input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ int64_t duration = INT64_C(1000) * stm->input_total_frames / stm->input_device_rate;
+ (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration);
+ return;
+ }
+}
+
+void recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = context;
+ assert(stm->recorderBufferQueueItf);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ /* On draining and shutdown the recorder should have been stoped from
+ * the one set the flags. Accordint to the doc, on transition to
+ * the SL_RECORDSTATE_STOPPED state, the application should
+ * continue to enqueue buffers onto the queue to retrieve the residual
+ * recorded data in the system. */
+ LOG("Input shutdown %d or drain %d", shutdown, draining);
+ int r = opensl_enqueue_recorder(stm, NULL);
+ assert(r == CUBEB_OK);
+ return;
+ }
+
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = NULL;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+
+ assert(stm->input_queue);
+ r = array_queue_push(stm->input_queue, input_buffer);
+ if (r == -1) {
+ LOG("Input queue is full, drop input ...");
+ return;
+ }
+
+ LOG("Input pushed in the queue, input array %zu",
+ array_queue_get_size(stm->input_queue));
+}
+
+static void
+player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ TIMESTAMP("ENTER");
+ cubeb_stream * stm = user_ptr;
+ assert(stm);
+ SLresult res;
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Get output
+ void * output_buffer = NULL;
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ output_buffer = stm->queuebuf[stm->queuebuf_idx];
+ // Advance the output buffer queue index
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ LOG("Shutdown/draining, send silent");
+ // Set silent on buffer
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq,
+ output_buffer,
+ stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Get input.
+ void * input_buffer = array_queue_pop(stm->input_queue);
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long frames_needed = stm->queuebuf_len / stm->framesize;
+ if (!input_buffer) {
+ LOG("Input hole set silent input buffer");
+ input_buffer = stm->input_silent_buffer;
+ }
+
+ long written = 0;
+ // Trigger user callback through resampler
+ written = cubeb_resampler_fill(stm->resampler,
+ input_buffer,
+ &input_frame_count,
+ output_buffer,
+ frames_needed);
+
+ LOG("Fill: written %ld, frames_needed %ld, input array size %zu",
+ written, frames_needed, array_queue_get_size(stm->input_queue));
+
+ if (written < 0 || written > frames_needed) {
+ // Error case
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ opensl_stop_recorder(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq,
+ output_buffer,
+ stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Advance total out written frames counter
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ stm->written += written;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if ( written < frames_needed) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+ // to make sure all the data has been processed.
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
+ }
+
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ memset((uint8_t *)output_buffer + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq,
+ output_buffer,
+ stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ TIMESTAMP("EXIT");
+}
+
+static void opensl_destroy(cubeb * ctx);
+
+#if defined(__ANDROID__)
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+typedef int (system_property_get)(const char*, char*);
+
+static int
+wrap_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
+
+static int
+get_android_version(void)
+{
+ char version_string[PROP_VALUE_MAX];
+
+ memset(version_string, 0, PROP_VALUE_MAX);
+
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ int len = wrap_system_property_get("ro.build.version.sdk", version_string);
+#else
+ int len = __system_property_get("ro.build.version.sdk", version_string);
+#endif
+ if (len <= 0) {
+ LOG("Failed to get Android version!\n");
+ return len;
+ }
+
+ int version = (int)strtol(version_string, NULL, 10);
+ LOG("Android version %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);
+ if (!ctx->lib) {
+ free(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");
+ ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
+#endif
+ ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
+ ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD");
+
+ if (!f_slCreateEngine ||
+ !SL_IID_ENGINE ||
+ !SL_IID_OUTPUTMIX ||
+ !ctx->SL_IID_BUFFERQUEUE ||
+#if defined(__ANDROID__)
+ !ctx->SL_IID_ANDROIDCONFIGURATION ||
+ !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE ||
+#endif
+ !ctx->SL_IID_PLAY ||
+ !ctx->SL_IID_RECORD) {
+ 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;
+ }
+
+ ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version);
+ if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
+ LOG("Warning: output latency is not available, cubeb_stream_get_position() is not supported");
+ }
+
+ *context = ctx;
+
+ LOG("Cubeb init (%p) success", ctx);
+ return CUBEB_OK;
+}
+
+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 void
+opensl_destroy(cubeb * ctx)
+{
+ if (ctx->outmixObj)
+ (*ctx->outmixObj)->Destroy(ctx->outmixObj);
+ if (ctx->engObj)
+ cubeb_destroy_sles_engine(&ctx->engObj);
+ dlclose(ctx->lib);
+ if (ctx->p_output_latency_function)
+ cubeb_output_latency_unload_method(ctx->p_output_latency_function);
+ free(ctx);
+}
+
+static void opensl_stream_destroy(cubeb_stream * stm);
+
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+static int
+opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
+
+ format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ format->numChannels = params->channels;
+ // sampleRate is in milliHertz
+ format->sampleRate = params->rate * 1000;
+ format->channelMask = params->channels == 1 ?
+ SL_SPEAKER_FRONT_CENTER :
+ SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ return CUBEB_OK;
+}
+#endif
+
+static int
+opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
+
+ format->formatType = SL_DATAFORMAT_PCM;
+ format->numChannels = params->channels;
+ // samplesPerSec is in milliHertz
+ format->samplesPerSec = params->rate * 1000;
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->channelMask = params->channels == 1 ?
+ SL_SPEAKER_FRONT_CENTER :
+ SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
+{
+ assert(stm);
+ assert(params);
+
+ SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut;
+ lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ lDataLocatorOut.numBuffers = NBUFS;
+
+ SLDataFormat_PCM lDataFormat;
+ int r = opensl_set_format(&lDataFormat, params);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ /* For now set device rate to params rate. */
+ stm->input_device_rate = params->rate;
+
+ SLDataSink lDataSink;
+ lDataSink.pLocator = &lDataLocatorOut;
+ lDataSink.pFormat = &lDataFormat;
+
+ SLDataLocator_IODevice lDataLocatorIn;
+ lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE;
+ lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT;
+ lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ lDataLocatorIn.device = NULL;
+
+ SLDataSource lDataSource;
+ lDataSource.pLocator = &lDataLocatorIn;
+ lDataSource.pFormat = NULL;
+
+ const SLInterfaceID lSoundRecorderIIDs[] = { stm->context->SL_IID_RECORD,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ stm->context->SL_IID_ANDROIDCONFIGURATION };
+
+ const SLboolean lSoundRecorderReqs[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
+ // create the audio recorder abstract object
+ SLresult res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng,
+ &stm->recorderObj,
+ &lDataSource,
+ &lDataSink,
+ NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs,
+ lSoundRecorderReqs);
+ // Sample rate not supported. Try again with default sample rate!
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
+ if (stm->output_enabled && stm->output_configured_rate != 0) {
+ // Set the same with the player. Since there is no
+ // api for input device this is a safe choice.
+ stm->input_device_rate = stm->output_configured_rate;
+ } else {
+ // The output preferred rate is used for an input only scenario.
+ // The default rate expected to be supported from all android devices.
+ stm->input_device_rate = DEFAULT_SAMPLE_RATE;
+ }
+ lDataFormat.samplesPerSec = stm->input_device_rate * 1000;
+ res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng,
+ &stm->recorderObj,
+ &lDataSource,
+ &lDataSink,
+ NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs,
+ lSoundRecorderReqs);
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to create recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+
+
+ if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) {
+ SLAndroidConfigurationItf recorderConfig;
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &recorderConfig);
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get the android configuration interface for recorder. Error "
+ "code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+
+ // Voice recognition is the lowest latency, according to the docs. Camcorder
+ // uses a microphone that is in the same direction as the camera.
+ SLint32 streamType = stm->voice_input ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
+ : SL_ANDROID_RECORDING_PRESET_CAMCORDER;
+
+ res = (*recorderConfig)
+ ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
+ &streamType, sizeof(SLint32));
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set the android configuration to VOICE for the recorder. "
+ "Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ // realize the audio recorder
+ res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to realize recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the record interface
+ res = (*stm->recorderObj)->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_RECORD,
+ &stm->recorderItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->recorderItf)->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder marker callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0);
+
+ res = (*stm->recorderItf)->SetCallbackEventsMask(stm->recorderItf, (SLuint32)SL_RECORDEVENT_HEADATMARKER);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the simple android buffer queue interface
+ res = (*stm->recorderObj)->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder (android) buffer queue interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ // register callback on record (input) buffer queue
+ slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback;
+ if (stm->output_enabled) {
+ // Register full duplex callback instead.
+ rec_callback = recorder_fullduplex_callback;
+ }
+ res = (*stm->recorderBufferQueueItf)->RegisterCallback(stm->recorderBufferQueueItf,
+ rec_callback,
+ stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder buffer queue callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ // Calculate length of input buffer according to requested latency
+ stm->input_frame_size = params->channels * sizeof(int16_t);
+ stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames);
+
+ // Calculate the capacity of input array
+ stm->input_array_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->input_array_capacity = 1 * stm->input_device_rate / stm->input_buffer_length;
+ }
+ // Allocate input array
+ stm->input_buffer_array = (void**)calloc(1, sizeof(void*)*stm->input_array_capacity);
+ // Buffering has not started yet.
+ stm->input_buffer_index = -1;
+ // Prepare input buffers
+ for(uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length);
+ }
+
+ // On full duplex allocate input queue and silent buffer
+ if (stm->output_enabled) {
+ stm->input_queue = array_queue_create(stm->input_array_capacity);
+ assert(stm->input_queue);
+ stm->input_silent_buffer = calloc(1, stm->input_buffer_length);
+ assert(stm->input_silent_buffer);
+ }
+
+ // Enqueue buffer to start rolling once recorder started
+ r = opensl_enqueue_recorder(stm, NULL);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ LOG("Cubeb stream init recorder success");
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
+ assert(stm);
+ assert(params);
+
+ stm->user_output_rate = params->rate;
+ if(params->format == CUBEB_SAMPLE_S16NE || params->format == CUBEB_SAMPLE_S16BE) {
+ stm->framesize = params->channels * sizeof(int16_t);
+ } else if(params->format == CUBEB_SAMPLE_FLOAT32NE || params->format == CUBEB_SAMPLE_FLOAT32BE) {
+ stm->framesize = params->channels * sizeof(float);
+ }
+ stm->lastPosition = -1;
+ stm->lastPositionTimeStamp = 0;
+ stm->lastCompensativePosition = -1;
+
+ void* format = NULL;
+ SLuint32* format_sample_rate = NULL;
+
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ SLAndroidDataFormat_PCM_EX pcm_ext_format;
+ if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) {
+ if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_ext_format;
+ format_sample_rate = &pcm_ext_format.sampleRate;
+ }
+#endif
+
+ SLDataFormat_PCM pcm_format;
+ if(!format) {
+ if(opensl_set_format(&pcm_format, params) != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_format;
+ format_sample_rate = &pcm_format.samplesPerSec;
+ }
+
+ SLDataLocator_BufferQueue loc_bufq;
+ loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+ loc_bufq.numBuffers = NBUFS;
+ SLDataSource source;
+ source.pLocator = &loc_bufq;
+ source.pFormat = format;
+
+ SLDataLocator_OutputMix loc_outmix;
+ loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ loc_outmix.outputMix = stm->context->outmixObj;
+ SLDataSink sink;
+ sink.pLocator = &loc_outmix;
+ sink.pFormat = NULL;
+
+#if defined(__ANDROID__)
+ const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE,
+ stm->context->SL_IID_VOLUME,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+#else
+ const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
+ const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+#endif
+ assert(NELEMS(ids) == NELEMS(req));
+
+ uint32_t preferred_sampling_rate = stm->user_output_rate;
+ SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
+ if (preferred_sampling_rate) {
+ res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng,
+ &stm->playerObj,
+ &source,
+ &sink,
+ NELEMS(ids),
+ ids,
+ req);
+ }
+
+ // Sample rate not supported? Try again with primary sample rate!
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
+ preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
+ preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
+ *format_sample_rate = preferred_sampling_rate * 1000;
+ res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng,
+ &stm->playerObj,
+ &source,
+ &sink,
+ NELEMS(ids),
+ ids,
+ req);
+ }
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to create audio player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ stm->output_configured_rate = preferred_sampling_rate;
+ stm->bytespersec = stm->output_configured_rate * stm->framesize;
+ stm->queuebuf_len = stm->framesize * stm->buffer_size_frames;
+
+ // Calculate the capacity of input array
+ stm->queuebuf_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->queuebuf_capacity = 1 * stm->output_configured_rate / stm->queuebuf_len;
+ }
+ // Allocate input array
+ stm->queuebuf = (void**)calloc(1, sizeof(void*) * stm->queuebuf_capacity);
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ stm->queuebuf[i] = calloc(1, stm->queuebuf_len);
+ assert(stm->queuebuf[i]);
+ }
+
+ SLAndroidConfigurationItf playerConfig = NULL;
+
+ if (get_android_version() >= ANDROID_VERSION_N_MR1) {
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &playerConfig);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get Android configuration interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
+ if (stm->voice_output) {
+ streamType = SL_ANDROID_STREAM_VOICE;
+ }
+ res = (*playerConfig)->SetConfiguration(playerConfig,
+ SL_ANDROID_KEY_STREAM_TYPE,
+ &streamType,
+ sizeof(streamType));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android configuration to %d Error code: %lu",
+ streamType, res);
+ }
+
+ SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY;
+ if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+ }
+
+ res = (*playerConfig)->SetConfiguration(playerConfig,
+ SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode,
+ sizeof(performanceMode));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android performance mode to %d Error code: %lu. This is"
+ " not fatal", performanceMode, res);
+ }
+ }
+
+ res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to realize player object. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ // There are two ways of getting the audio output latency:
+ // - a configuration value, only available on some devices (notably devices
+ // running FireOS)
+ // - A Java method, that we call using JNI.
+ //
+ // The first method is prefered, if available, because it can account for more
+ // latency causes, and is more precise.
+
+ // Latency has to be queried after the realization of the interface, when
+ // using SL_IID_ANDROIDCONFIGURATION.
+ SLuint32 audioLatency = 0;
+ SLuint32 paramSize = sizeof(SLuint32);
+ // The reported latency is in milliseconds.
+ if (playerConfig) {
+ res = (*playerConfig)->GetConfiguration(playerConfig,
+ (const SLchar *)"androidGetAudioLatency",
+ ¶mSize,
+ &audioLatency);
+ if (res == SL_RESULT_SUCCESS) {
+ LOG("Got playback latency using android configuration extension");
+ stm->output_latency_ms = audioLatency;
+ }
+ }
+ // `playerConfig` is available, but the above failed, or `playerConfig` is not
+ // available. In both cases, we need to acquire the output latency by an other
+ // mean.
+ if ((playerConfig && res != SL_RESULT_SUCCESS) ||
+ !playerConfig) {
+ if (cubeb_output_latency_method_is_loaded(stm->context->p_output_latency_function)) {
+ LOG("Got playback latency using JNI");
+ stm->output_latency_ms = cubeb_get_output_latency(stm->context->p_output_latency_function);
+ } else {
+ LOG("No alternate latency querying method loaded, A/V sync will be off.");
+ stm->output_latency_ms = 0;
+ }
+ }
+
+ LOG("Audio output latency: %dms", stm->output_latency_ms);
+
+ res = (*stm->playerObj)->GetInterface(stm->playerObj,
+ stm->context->SL_IID_PLAY,
+ &stm->play);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get play interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)->GetInterface(stm->playerObj,
+ stm->context->SL_IID_BUFFERQUEUE,
+ &stm->bufq);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get bufferqueue interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)->GetInterface(stm->playerObj,
+ stm->context->SL_IID_VOLUME,
+ &stm->volume);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get volume interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register play callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ // Work around wilhelm/AudioTrack badness, bug 1221228
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);
+
+ res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ slBufferQueueCallback player_callback = bufferqueue_callback;
+ if (stm->input_enabled) {
+ player_callback = player_fullduplex_callback;
+ }
+ res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register bufferqueue callback. Error code: %lu", res);
+ 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);
+ }
+
+ LOG("Cubeb stream init playback success");
+ return CUBEB_OK;
+}
+
+static int
+opensl_validate_stream_param(cubeb_stream_params * stream_params)
+{
+ if ((stream_params &&
+ (stream_params->channels < 1 || stream_params->channels > 32))) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ if ((stream_params &&
+ (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ LOG("Loopback is not supported");
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ return CUBEB_OK;
+}
+
+int has_pref_set(cubeb_stream_params* input_params,
+ cubeb_stream_params* output_params,
+ cubeb_stream_prefs pref)
+{
+ return (input_params && input_params->prefs & pref) ||
+ (output_params && output_params->prefs & pref);
+}
+
+static int
+opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback, cubeb_state_callback state_callback,
+ void * user_ptr)
+{
+ cubeb_stream * stm;
+
+ assert(ctx);
+ if (input_device || output_device) {
+ LOG("Device selection is not supported in Android. The default will be used");
+ }
+
+ *stream = NULL;
+
+ int r = opensl_validate_stream_param(output_stream_params);
+ if(r != CUBEB_OK) {
+ LOG("Output stream params not valid");
+ return r;
+ }
+ r = opensl_validate_stream_param(input_stream_params);
+ if(r != CUBEB_OK) {
+ LOG("Input stream params not valid");
+ return r;
+ }
+
+ stm = calloc(1, sizeof(*stm));
+ assert(stm);
+
+ stm->context = ctx;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->buffer_size_frames = latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES;
+ stm->input_enabled = (input_stream_params) ? 1 : 0;
+ stm->output_enabled = (output_stream_params) ? 1 : 0;
+ stm->shutdown = 1;
+ stm->voice_input = has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output = has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE);
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s", stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+#ifdef DEBUG
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ r = pthread_mutex_init(&stm->mutex, &attr);
+#else
+ r = pthread_mutex_init(&stm->mutex, NULL);
+#endif
+ assert(r == 0);
+
+ if (output_stream_params) {
+ LOG("Playback params: Rate %d, channels %d, format %d, latency in frames %d.",
+ output_stream_params->rate, output_stream_params->channels,
+ output_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_playback(stm, output_stream_params);
+ if (r != CUBEB_OK) {
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ if (input_stream_params) {
+ LOG("Capture params: Rate %d, channels %d, format %d, latency in frames %d.",
+ input_stream_params->rate, input_stream_params->channels,
+ input_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_capture(stm, input_stream_params);
+ if (r != CUBEB_OK) {
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ /* Configure resampler*/
+ uint32_t target_sample_rate;
+ if (input_stream_params) {
+ target_sample_rate = input_stream_params->rate;
+ } else {
+ assert(output_stream_params);
+ target_sample_rate = output_stream_params->rate;
+ }
+
+ // Use the actual configured rates for input
+ // and output.
+ cubeb_stream_params input_params;
+ if (input_stream_params) {
+ input_params = *input_stream_params;
+ input_params.rate = stm->input_device_rate;
+ }
+ cubeb_stream_params output_params;
+ if (output_stream_params) {
+ output_params = *output_stream_params;
+ output_params.rate = stm->output_configured_rate;
+ }
+
+ stm->resampler = cubeb_resampler_create(stm,
+ input_stream_params ? &input_params : NULL,
+ output_stream_params ? &output_params : NULL,
+ target_sample_rate,
+ data_callback,
+ user_ptr,
+ CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+ LOG("Cubeb stream (%p) init success", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_player(cubeb_stream * stm)
+{
+ assert(stm->playerObj);
+ SLuint32 playerState;
+ (*stm->playerObj)->GetState(stm->playerObj, &playerState);
+ if (playerState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
+ if(res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ SLuint32 recorderState;
+ (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState);
+ if (recorderState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING);
+ if(res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_start(cubeb_stream * stm)
+{
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 0);
+ opensl_set_draining(stm, 0);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_start_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_start_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ LOG("Cubeb stream (%p) started", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_stop_player(cubeb_stream * stm)
+{
+ assert(stm->playerObj);
+ assert(stm->shutdown || stm->draining);
+
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stop_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ assert(stm->shutdown || stm->draining);
+
+ SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_stop(cubeb_stream * stm)
+{
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_stop_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_stop_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ LOG("Cubeb stream (%p) stopped", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_destroy_recorder(cubeb_stream * stm)
+{
+ assert(stm);
+ assert(stm->recorderObj);
+
+ if (stm->recorderBufferQueueItf) {
+ SLresult res = (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to clear recorder buffer queue. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ stm->recorderBufferQueueItf = NULL;
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ free(stm->input_buffer_array[i]);
+ }
+ }
+
+ (*stm->recorderObj)->Destroy(stm->recorderObj);
+ stm->recorderObj = NULL;
+ stm->recorderItf = NULL;
+
+ if (stm->input_queue) {
+ array_queue_destroy(stm->input_queue);
+ }
+ free(stm->input_silent_buffer);
+
+ return CUBEB_OK;
+}
+
+static void
+opensl_stream_destroy(cubeb_stream * stm)
+{
+ assert(stm->draining || stm->shutdown);
+
+ if (stm->playerObj) {
+ (*stm->playerObj)->Destroy(stm->playerObj);
+ stm->playerObj = NULL;
+ stm->play = NULL;
+ stm->bufq = NULL;
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ free(stm->queuebuf[i]);
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_destroy_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ }
+
+ pthread_mutex_destroy(&stm->mutex);
+
+ LOG("Cubeb stream (%p) destroyed", stm);
+ free(stm);
+}
+
+static int
+opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ SLmillisecond msec;
+ uint32_t compensation_msec = 0;
+ SLresult res;
+
+ 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;
+ }
+
+ uint64_t samplerate = stm->user_output_rate;
+ uint32_t output_latency = stm->output_latency_ms;
+
+ pthread_mutex_lock(&stm->mutex);
+ int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate;
+ pthread_mutex_unlock(&stm->mutex);
+ assert(maximum_position >= 0);
+
+ if (msec > output_latency) {
+ int64_t unadjusted_position;
+ if (stm->lastCompensativePosition > msec + compensation_msec) {
+ // Over compensation, use lastCompensativePosition.
+ unadjusted_position =
+ samplerate * (stm->lastCompensativePosition - output_latency) / 1000;
+ } else {
+ unadjusted_position =
+ samplerate * (msec - output_latency + compensation_msec) / 1000;
+ stm->lastCompensativePosition = msec + compensation_msec;
+ }
+ *position = unadjusted_position < maximum_position ?
+ unadjusted_position : maximum_position;
+ } else {
+ *position = 0;
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ assert(stm);
+ assert(latency);
+
+ uint32_t stream_latency_frames =
+ stm->user_output_rate * (stm->output_latency_ms / 1000);
+
+ return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
+}
+
+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 = NULL,
+ .get_preferred_sample_rate = NULL,
+ .enumerate_devices = NULL,
+ .device_collection_destroy = NULL,
+ .destroy = opensl_destroy,
+ .stream_init = opensl_stream_init,
+ .stream_destroy = opensl_stream_destroy,
+ .stream_start = opensl_stream_start,
+ .stream_stop = opensl_stream_stop,
+ .stream_reset_default_device = NULL,
+ .stream_get_position = opensl_stream_get_position,
+ .stream_get_latency = opensl_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = opensl_stream_set_volume,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL
+};
diff --git a/thirdparty/cubeb/src/cubeb_osx_run_loop.cpp b/thirdparty/cubeb/src/cubeb_osx_run_loop.cpp
new file mode 100644
index 0000000..de4e439
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_osx_run_loop.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include
+#include "cubeb_osx_run_loop.h"
+#include "cubeb_log.h"
+#include
+#include
+#include
+#include
+
+void cubeb_set_coreaudio_notification_runloop()
+{
+ /* This is needed so that AudioUnit listeners get called on this thread, and
+ * not the main thread. If we don't do that, they are not called, or a crash
+ * occur, depending on the OSX version. */
+ AudioObjectPropertyAddress runloop_address = {
+ kAudioHardwarePropertyRunLoop,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ CFRunLoopRef run_loop = nullptr;
+
+ OSStatus r;
+ r = AudioObjectSetPropertyData(kAudioObjectSystemObject,
+ &runloop_address,
+ 0, NULL, sizeof(CFRunLoopRef), &run_loop);
+ if (r != noErr) {
+ LOG("Could not make global CoreAudio notifications use their own thread.");
+ }
+}
diff --git a/thirdparty/cubeb/src/cubeb_osx_run_loop.h b/thirdparty/cubeb/src/cubeb_osx_run_loop.h
new file mode 100644
index 0000000..78cd68d
--- /dev/null
+++ b/thirdparty/cubeb/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/thirdparty/cubeb/src/cubeb_pulse.c b/thirdparty/cubeb/src/cubeb_pulse.c
new file mode 100644
index 0000000..a393b66
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_pulse.c
@@ -0,0 +1,1606 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "cubeb_strings.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) \
+ 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_sink_input_info) \
+ 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) \
+ X(pa_get_library_version) \
+ X(pa_channel_map_init_auto) \
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBPULSE_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+#endif
+
+#if PA_CHECK_VERSION(2, 0, 0)
+static int has_pulse_v2 = 0;
+#endif
+
+static struct cubeb_ops const pulse_ops;
+
+struct cubeb_default_sink_info {
+ pa_channel_map channel_map;
+ uint32_t sample_spec_rate;
+ pa_sink_flags_t flags;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libpulse;
+ pa_threaded_mainloop * mainloop;
+ pa_context * context;
+ struct cubeb_default_sink_info * default_sink_info;
+ char * context_name;
+ int error;
+ cubeb_device_collection_changed_callback output_collection_changed_callback;
+ void * output_collection_changed_user_ptr;
+ cubeb_device_collection_changed_callback input_collection_changed_callback;
+ void * input_collection_changed_user_ptr;
+ cubeb_strings * device_ids;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ pa_stream * output_stream;
+ pa_stream * input_stream;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ 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 int
+intern_device_id(cubeb * ctx, char const ** id)
+{
+ char const * interned;
+
+ assert(ctx);
+ assert(id);
+
+ interned = cubeb_strings_intern(ctx->device_ids, *id);
+ if (!interned) {
+ return CUBEB_ERROR;
+ }
+
+ *id = interned;
+
+ return CUBEB_OK;
+}
+
+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(struct cubeb_default_sink_info));
+ memcpy(&ctx->default_sink_info->channel_map, &info->channel_map, sizeof(pa_channel_map));
+ ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate;
+ ctx->default_sink_info->flags = info->flags;
+ }
+ WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
+}
+
+static void
+server_info_callback(pa_context * context, const pa_server_info * info, void * u)
+{
+ pa_operation * o;
+ o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
+ if (o) {
+ WRAP(pa_operation_unref)(o);
+ }
+}
+
+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 pa_channel_position_t
+cubeb_channel_to_pa_channel(cubeb_channel channel)
+{
+ switch (channel) {
+ case CHANNEL_FRONT_LEFT:
+ return PA_CHANNEL_POSITION_FRONT_LEFT;
+ case CHANNEL_FRONT_RIGHT:
+ return PA_CHANNEL_POSITION_FRONT_RIGHT;
+ case CHANNEL_FRONT_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_CENTER;
+ case CHANNEL_LOW_FREQUENCY:
+ return PA_CHANNEL_POSITION_LFE;
+ case CHANNEL_BACK_LEFT:
+ return PA_CHANNEL_POSITION_REAR_LEFT;
+ case CHANNEL_BACK_RIGHT:
+ return PA_CHANNEL_POSITION_REAR_RIGHT;
+ case CHANNEL_FRONT_LEFT_OF_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+ case CHANNEL_FRONT_RIGHT_OF_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+ case CHANNEL_BACK_CENTER:
+ return PA_CHANNEL_POSITION_REAR_CENTER;
+ case CHANNEL_SIDE_LEFT:
+ return PA_CHANNEL_POSITION_SIDE_LEFT;
+ case CHANNEL_SIDE_RIGHT:
+ return PA_CHANNEL_POSITION_SIDE_RIGHT;
+ case CHANNEL_TOP_CENTER:
+ return PA_CHANNEL_POSITION_TOP_CENTER;
+ case CHANNEL_TOP_FRONT_LEFT:
+ return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+ case CHANNEL_TOP_FRONT_CENTER:
+ return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
+ case CHANNEL_TOP_FRONT_RIGHT:
+ return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+ case CHANNEL_TOP_BACK_LEFT:
+ return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+ case CHANNEL_TOP_BACK_CENTER:
+ return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+ case CHANNEL_TOP_BACK_RIGHT:
+ return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+ default:
+ return PA_CHANNEL_POSITION_INVALID;
+ }
+}
+
+static void
+layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
+{
+ assert(cm && layout != CUBEB_LAYOUT_UNDEFINED);
+
+ WRAP(pa_channel_map_init)(cm);
+
+ uint32_t channels = 0;
+ cubeb_channel_layout channelMap = layout;
+ for (uint32_t i = 0 ; channelMap != 0; ++i) {
+ uint32_t channel = (channelMap & 1) << i;
+ if (channel != 0) {
+ cm->map[channels] = cubeb_channel_to_pa_channel(channel);
+ channels++;
+ }
+ channelMap = channelMap >> 1;
+ }
+ unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout);
+ assert(channels_from_layout <= UINT8_MAX);
+ cm->channels = (uint8_t) channels_from_layout;
+
+ // Special case single channel center mapping as mono.
+ if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_FRONT_CENTER) {
+ cm->map[0] = PA_CHANNEL_POSITION_MONO;
+ }
+}
+
+static void pulse_context_destroy(cubeb * ctx);
+static void pulse_destroy(cubeb * ctx);
+
+static int
+pulse_context_init(cubeb * ctx)
+{
+ int r;
+
+ 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);
+ r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
+
+ if (r < 0 || 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_subscribe_notifications(cubeb * context,
+ pa_subscription_mask_t mask);
+
+/*static*/ int
+pulse_init(cubeb ** context, char const * context_name)
+{
+ void * libpulse = NULL;
+ cubeb * ctx;
+ pa_operation * o;
+
+ *context = NULL;
+
+#ifndef DISABLE_LIBPULSE_DLOPEN
+ libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
+ if (!libpulse) {
+ libpulse = dlopen("libpulse.so", 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
+
+#if PA_CHECK_VERSION(2, 0, 0)
+ const char* version = WRAP(pa_get_library_version)();
+ has_pulse_v2 = strtol(version, NULL, 10) >= 2;
+#endif
+
+ ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+
+ ctx->ops = &pulse_ops;
+ ctx->libpulse = libpulse;
+ if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
+ pulse_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ 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;
+ }
+
+ /* server_info_callback performs a second async query, which is
+ responsible for initializing default_sink_info and signalling the
+ mainloop to end the wait. */
+ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
+ o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
+ if (o) {
+ operation_wait(ctx, NULL, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+
+ /* Update `default_sink_info` when the default device changes. */
+ pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER);
+
+ *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);
+
+ if (!ctx->default_sink_info)
+ return CUBEB_ERROR;
+
+ *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;
+
+ if (!ctx->default_sink_info)
+ return CUBEB_ERROR;
+
+ *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)
+{
+ 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->device_ids) {
+ cubeb_strings_destroy(ctx->device_ids);
+ }
+
+ if (ctx->libpulse) {
+ dlclose(ctx->libpulse);
+ }
+ 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 cubeb_channel_layout
+pulse_default_layout_for_channels(uint32_t ch)
+{
+ assert (ch > 0 && ch <= 8);
+ switch (ch) {
+ case 1: return CUBEB_LAYOUT_MONO;
+ case 2: return CUBEB_LAYOUT_STEREO;
+ case 3: return CUBEB_LAYOUT_3F;
+ case 4: return CUBEB_LAYOUT_QUAD;
+ case 5: return CUBEB_LAYOUT_3F2;
+ case 6: return CUBEB_LAYOUT_3F_LFE |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT;
+ case 7: return CUBEB_LAYOUT_3F3R_LFE;
+ case 8: return CUBEB_LAYOUT_3F4_LFE;
+ }
+ // Never get here!
+ return CUBEB_LAYOUT_UNDEFINED;
+}
+
+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);
+ assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm &&
+ (stream_params->layout == CUBEB_LAYOUT_UNDEFINED ||
+ (stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ cubeb_channel_layout_nb_channels(stream_params->layout) == stream_params->channels))));
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ *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;
+ if (stream_params->channels > UINT8_MAX)
+ return CUBEB_ERROR_INVALID_FORMAT;
+ ss.channels = (uint8_t) stream_params->channels;
+
+ if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) {
+ pa_channel_map cm;
+ if (stream_params->channels <= 8 &&
+ !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels, PA_CHANNEL_MAP_DEFAULT)) {
+ LOG("Layout undefined and PulseAudio's default layout has not been configured, guess one.");
+ layout_to_channel_map(pulse_default_layout_for_channels(stream_params->channels), &cm);
+ *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
+ } else {
+ LOG("Layout undefined, PulseAudio will use its default.");
+ *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
+ }
+ } else {
+ pa_channel_map cm;
+ layout_to_channel_map(stream_params->layout, &cm);
+ *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
+ }
+ 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,
+ (char const *) 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,
+ (char const *) 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_cubeb_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;
+ LOG("Cubeb stream (%p) init successful.", *stream);
+
+ 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);
+
+ LOG("Cubeb stream (%p) destroyed successfully.", stm);
+ 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);
+ }
+
+ LOG("Cubeb stream (%p) started successfully.", stm);
+ 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);
+ LOG("Cubeb stream (%p) stopped successfully.", stm);
+ 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;
+ cubeb * ctx;
+
+ if (!stm->output_stream) {
+ return CUBEB_ERROR;
+ }
+
+ WRAP(pa_threaded_mainloop_lock)(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. */
+ ctx = stm->context;
+ if (ctx->default_sink_info &&
+ (ctx->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)(ctx->context,
+ index, &cvol, volume_success,
+ stm);
+ if (op) {
+ operation_wait(ctx, stm->output_stream, op);
+ WRAP(pa_operation_unref)(op);
+ }
+ }
+
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+
+ 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 (has_pulse_v2 && 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_ENABLED;
+}
+
+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;
+ char const * prop = NULL;
+ char const * device_id = NULL;
+
+ (void)context;
+
+ if (eol) {
+ WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
+ return;
+ }
+
+ if (info == NULL)
+ return;
+
+ device_id = info->name;
+ if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
+ assert(NULL);
+ return;
+ }
+
+ pulse_ensure_dev_list_data_list_size(list_data);
+ devinfo = &list_data->devinfo[list_data->count];
+ memset(devinfo, 0, sizeof(cubeb_device_info));
+
+ devinfo->device_id = device_id;
+ devinfo->devid = (cubeb_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) ?
+ CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+
+ 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;
+
+ list_data->count += 1;
+}
+
+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 (has_pulse_v2 && 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_ENABLED;
+}
+
+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;
+ char const * prop = NULL;
+ char const * device_id = NULL;
+
+ (void)context;
+
+ if (eol) {
+ WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
+ return;
+ }
+
+ device_id = info->name;
+ if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
+ assert(NULL);
+ return;
+ }
+
+ pulse_ensure_dev_list_data_list_size(list_data);
+ devinfo = &list_data->devinfo[list_data->count];
+ memset(devinfo, 0, sizeof(cubeb_device_info));
+
+ devinfo->device_id = device_id;
+ devinfo->devid = (cubeb_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) ?
+ CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+
+ 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;
+
+ list_data->count += 1;
+}
+
+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 =
+ i->default_sink_name ? strdup(i->default_sink_name) : NULL;
+ list_data->default_source_name =
+ i->default_source_name ? strdup(i->default_source_name) : NULL;
+
+ 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;
+
+ 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->device = user_data.devinfo;
+ collection->count = user_data.count;
+
+ free(user_data.default_sink_name);
+ free(user_data.default_source_name);
+ return CUBEB_OK;
+}
+
+static int
+pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection)
+{
+ size_t n;
+
+ for (n = 0; n < collection->count; n++) {
+ free((void *) collection->device[n].friendly_name);
+ free((void *) collection->device[n].vendor_name);
+ free((void *) collection->device[n].group_id);
+ }
+
+ free(collection->device);
+ 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_SERVER:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
+ LOG("Server changed %d", index);
+ WRAP(pa_context_get_server_info)(context->context, server_info_callback, context);
+ }
+ break;
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+ case PA_SUBSCRIPTION_EVENT_SINK:
+
+ if (g_cubeb_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 source 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 source 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 sink 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 sink index %d", index);
+ }
+ }
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
+ (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
+ context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr);
+ }
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
+ context->output_collection_changed_callback(context, context->output_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_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) {
+ WRAP(pa_threaded_mainloop_lock)(context->mainloop);
+
+ WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
+
+ pa_operation * o;
+ o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
+ if (o == NULL) {
+ WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
+ 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 int
+pulse_register_device_collection_changed(cubeb * context,
+ cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+
+ pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL;
+ if (context->input_collection_changed_callback) {
+ /* Input added or removed */
+ mask |= PA_SUBSCRIPTION_MASK_SOURCE;
+ }
+ if (context->output_collection_changed_callback) {
+ /* Output added or removed */
+ mask |= PA_SUBSCRIPTION_MASK_SINK;
+ }
+ /* Default device changed, this is always registered in order to update the
+ * `default_sink_info` when the default device changes. */
+ mask |= PA_SUBSCRIPTION_MASK_SERVER;
+
+ return pulse_subscribe_notifications(context, mask);
+}
+
+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,
+ .device_collection_destroy = pulse_device_collection_destroy,
+ .destroy = pulse_destroy,
+ .stream_init = pulse_stream_init,
+ .stream_destroy = pulse_stream_destroy,
+ .stream_start = pulse_stream_start,
+ .stream_stop = pulse_stream_stop,
+ .stream_reset_default_device = NULL,
+ .stream_get_position = pulse_stream_get_position,
+ .stream_get_latency = pulse_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = pulse_stream_set_volume,
+ .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/thirdparty/cubeb/src/cubeb_resampler.cpp b/thirdparty/cubeb/src/cubeb_resampler.cpp
new file mode 100644
index 0000000..43a3ca5
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_resampler.cpp
@@ -0,0 +1,388 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+#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;
+ }
+}
+
+uint32_t min_buffered_audio_frame(uint32_t sample_rate)
+{
+ return sample_rate / 20;
+}
+
+template
+passthrough_resampler::passthrough_resampler(cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr,
+ uint32_t input_channels,
+ uint32_t sample_rate)
+ : processor(input_channels)
+ , stream(s)
+ , data_callback(cb)
+ , user_ptr(ptr)
+ , sample_rate(sample_rate)
+{
+}
+
+template
+long passthrough_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) ||
+ (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
+ (input_buffer && !output_buffer && output_frames == 0));
+
+ // When we have no pending input data and exactly as much input
+ // as output data, we don't need to copy it into the internal buffer
+ // and can directly forward it to the callback.
+ void * in_buf = input_buffer;
+ unsigned long pop_input_count = 0u;
+ if (input_buffer && !output_buffer) {
+ output_frames = *input_frames_count;
+ } else if(input_buffer) {
+ if (internal_input_buffer.length() != 0 ||
+ *input_frames_count < output_frames) {
+ // If we have pending input data left and have to first append the input
+ // so we can pass it as one pointer to the callback. Or this is a glitch.
+ // It can happen when system's performance is poor. Audible silence is
+ // being pushed at the end of the short input buffer. An improvement for
+ // the future is to resample to the output number of frames, when that happens.
+ internal_input_buffer.push(static_cast(input_buffer),
+ frames_to_samples(*input_frames_count));
+ if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
+ // This is unxpected but it can happen when a glitch occurs. Fill the
+ // buffer with silence. First keep the actual number of input samples
+ // used without the silence.
+ pop_input_count = internal_input_buffer.length();
+ internal_input_buffer.push_silence(
+ frames_to_samples(output_frames) - internal_input_buffer.length());
+ } else {
+ pop_input_count = frames_to_samples(output_frames);
+ }
+ in_buf = internal_input_buffer.data();
+ } else if(*input_frames_count > output_frames) {
+ // In this case we have more input that we need output and
+ // fill the overflowing input into internal_input_buffer
+ // Since we have no other pending data, we can nonetheless
+ // pass the current input data directly to the callback
+ assert(pop_input_count == 0);
+ unsigned long samples_off = frames_to_samples(output_frames);
+ internal_input_buffer.push(static_cast(input_buffer) + samples_off,
+ frames_to_samples(*input_frames_count - output_frames));
+ }
+ }
+
+ long rv = data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
+
+ if (input_buffer) {
+ if (pop_input_count) {
+ internal_input_buffer.pop(nullptr, pop_input_count);
+ *input_frames_count = samples_to_frames(pop_input_count);
+ } else {
+ *input_frames_count = output_frames;
+ }
+ drop_audio_if_needed();
+ }
+
+ return rv;
+}
+
+// Explicit instantiation of template class.
+template class passthrough_resampler;
+template class passthrough_resampler;
+
+template
+cubeb_resampler_speex
+ ::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
+cubeb_resampler_speex
+ ::~cubeb_resampler_speex()
+{ }
+
+template
+long
+cubeb_resampler_speex
+::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(input_buffer);
+ T * out_buffer = reinterpret_cast(output_buffer);
+ return (this->*fill_internal)(in_buffer, input_frames_count,
+ out_buffer, output_frames_needed);
+}
+
+template
+long
+cubeb_resampler_speex
+::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);
+
+ if (!draining) {
+ long got = 0;
+ T * out_unprocessed = nullptr;
+ long output_frames_before_processing = 0;
+
+ /* fill directly the input buffer of the output processor to save a copy */
+ output_frames_before_processing =
+ output_processor->input_needed_for_output(output_frames_needed);
+
+ 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 < output_frames_before_processing) {
+ draining = true;
+
+ 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
+long
+cubeb_resampler_speex
+::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);
+
+ size_t frames_resampled = 0;
+ resampled_input = input_processor->output(resampled_frame_count, &frames_resampled);
+ *input_frames_count = frames_resampled;
+
+ long got = data_callback(stream, user_ptr,
+ resampled_input, nullptr, resampled_frame_count);
+
+ /* Return the number of initial input frames or part of it.
+ * Since output_frames_needed == 0 in input scenario, the only
+ * available number outside resampler is the initial number of frames. */
+ return (*input_frames_count) * (got / resampled_frame_count);
+}
+
+template
+long
+cubeb_resampler_speex
+::fill_internal_duplex(T * in_buffer, long * input_frames_count,
+ T * out_buffer, long output_frames_needed)
+{
+ if (draining) {
+ // discard input and drain any signal remaining in the resampler.
+ return output_processor->output(out_buffer, output_frames_needed);
+ }
+
+ /* The input data, after eventual resampling. This is passed to the callback. */
+ T * resampled_input = nullptr;
+ /* The output buffer passed down in the callback, that might be resampled. */
+ T * out_unprocessed = nullptr;
+ long 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);
+
+ size_t frames_resampled = 0;
+ resampled_input =
+ input_processor->output(output_frames_before_processing, &frames_resampled);
+ *input_frames_count = frames_resampled;
+ } else {
+ resampled_input = nullptr;
+ }
+
+ got = data_callback(stream, user_ptr,
+ resampled_input, out_unprocessed,
+ output_frames_before_processing);
+
+ if (got < output_frames_before_processing) {
+ draining = true;
+
+ if (got < 0) {
+ return got;
+ }
+ }
+
+ output_processor->written(got);
+
+ input_processor->drop_audio_if_needed();
+
+ /* Process the output. If not enough frames have been returned from the
+ * callback, drain the processors. */
+ got = output_processor->output(out_buffer, output_frames_needed);
+
+ output_processor->drop_audio_if_needed();
+
+ return got;
+}
+
+/* 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(stream,
+ input_params,
+ output_params,
+ target_rate,
+ callback,
+ user_ptr,
+ quality);
+ case CUBEB_SAMPLE_FLOAT32NE:
+ return cubeb_resampler_create_internal(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/thirdparty/cubeb/src/cubeb_resampler.h b/thirdparty/cubeb/src/cubeb_resampler.h
new file mode 100644
index 0000000..f6b5513
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_resampler.h
@@ -0,0 +1,85 @@
+/*
+ * 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 input_params Used to calculate bytes per frame and buffer size for
+ * resampling of the input side of the stream. NULL if input should not be
+ * resampled.
+ * @param output_params Used to calculate bytes per frame and buffer size for
+ * resampling of the output side of the stream. NULL if output should not be
+ * resampled.
+ * @param target_rate The sampling rate after resampling for the input side of
+ * the stream, and/or the sampling rate prior to resampling of the output side
+ * of the stream.
+ * @param callback A callback to request data for resampling.
+ * @param user_ptr User data supplied to the data callback.
+ * @param quality Quality of the resampler.
+ * @retval A non-null pointer if success.
+ */
+cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate,
+ cubeb_data_callback callback,
+ void * user_ptr,
+ cubeb_resampler_quality quality);
+
+/**
+ * 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 output_buffer The buffer to be filled.
+ * @param output_frames_needed Number of frames that should be produced.
+ * @retval Number of frames that are actually produced.
+ * @retval CUBEB_ERROR on error.
+ */
+long cubeb_resampler_fill(cubeb_resampler * resampler,
+ void * input_buffer,
+ long * input_frame_count,
+ void * output_buffer,
+ long output_frames_needed);
+
+/**
+ * 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/thirdparty/cubeb/src/cubeb_resampler_internal.h b/thirdparty/cubeb/src/cubeb_resampler_internal.h
new file mode 100644
index 0000000..fb69992
--- /dev/null
+++ b/thirdparty/cubeb/src/cubeb_resampler_internal.h
@@ -0,0 +1,602 @@
+/*
+ * 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