From 812520cc6564fe19b95c1a78606e2cf11a809cdb Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Wed, 22 Mar 2017 16:09:04 -0700 Subject: [PATCH] Add cubeb@62871b2 to Externals/ Includes submodule sanitizers-cmake@f09151b --- CMakeLists.txt | 2 + Externals/cubeb/AUTHORS | 16 + Externals/cubeb/CMakeLists.txt | 150 + Externals/cubeb/INSTALL.md | 24 + Externals/cubeb/LICENSE | 13 + Externals/cubeb/README.md | 6 + .../cubeb/cmake/sanitizers-cmake/LICENSE | 22 + .../cubeb/cmake/sanitizers-cmake/README.md | 73 + .../sanitizers-cmake/cmake/FindASan.cmake | 59 + .../sanitizers-cmake/cmake/FindMSan.cmake | 57 + .../cmake/FindSanitizers.cmake | 87 + .../sanitizers-cmake/cmake/FindTSan.cmake | 64 + .../sanitizers-cmake/cmake/FindUBSan.cmake | 46 + .../cmake/sanitizers-cmake/cmake/asan-wrapper | 55 + .../cmake/sanitize-helpers.cmake | 170 + Externals/cubeb/cubeb.supp | 36 + Externals/cubeb/include/cubeb/cubeb.h | 655 ++++ .../src/android/audiotrack_definitions.h | 81 + .../cubeb/src/android/sles_definitions.h | 77 + Externals/cubeb/src/cubeb-internal.h | 88 + Externals/cubeb/src/cubeb-sles.h | 43 + Externals/cubeb/src/cubeb-speex-resampler.h | 1 + Externals/cubeb/src/cubeb.c | 630 ++++ Externals/cubeb/src/cubeb_alsa.c | 1372 +++++++ Externals/cubeb/src/cubeb_array_queue.h | 97 + Externals/cubeb/src/cubeb_assert.h | 26 + Externals/cubeb/src/cubeb_audiotrack.c | 440 +++ Externals/cubeb/src/cubeb_audiounit.cpp | 3350 +++++++++++++++++ Externals/cubeb/src/cubeb_jack.cpp | 1035 +++++ Externals/cubeb/src/cubeb_kai.c | 362 ++ Externals/cubeb/src/cubeb_log.cpp | 130 + Externals/cubeb/src/cubeb_log.h | 46 + Externals/cubeb/src/cubeb_mixer.cpp | 569 +++ Externals/cubeb/src/cubeb_mixer.h | 90 + Externals/cubeb/src/cubeb_opensl.c | 1757 +++++++++ Externals/cubeb/src/cubeb_osx_run_loop.cpp | 36 + Externals/cubeb/src/cubeb_osx_run_loop.h | 22 + Externals/cubeb/src/cubeb_panner.cpp | 60 + Externals/cubeb/src/cubeb_panner.h | 28 + Externals/cubeb/src/cubeb_pulse.c | 1509 ++++++++ Externals/cubeb/src/cubeb_resampler.cpp | 315 ++ Externals/cubeb/src/cubeb_resampler.h | 78 + .../cubeb/src/cubeb_resampler_internal.h | 556 +++ Externals/cubeb/src/cubeb_ring_array.h | 159 + Externals/cubeb/src/cubeb_ringbuffer.h | 484 +++ Externals/cubeb/src/cubeb_sndio.c | 387 ++ Externals/cubeb/src/cubeb_utils.c | 38 + Externals/cubeb/src/cubeb_utils.h | 352 ++ Externals/cubeb/src/cubeb_utils_unix.h | 89 + Externals/cubeb/src/cubeb_utils_win.h | 71 + Externals/cubeb/src/cubeb_wasapi.cpp | 2349 ++++++++++++ Externals/cubeb/src/cubeb_winmm.c | 1042 +++++ Externals/cubeb/src/speex/arch.h | 235 ++ Externals/cubeb/src/speex/fixed_generic.h | 110 + Externals/cubeb/src/speex/resample.c | 1237 ++++++ Externals/cubeb/src/speex/resample_neon.h | 201 + Externals/cubeb/src/speex/resample_sse.h | 128 + .../cubeb/src/speex/speex_config_types.h | 10 + Externals/cubeb/src/speex/speex_resampler.h | 343 ++ Externals/cubeb/src/speex/stack_alloc.h | 115 + Externals/licenses.md | 2 + 61 files changed, 21685 insertions(+) create mode 100644 Externals/cubeb/AUTHORS create mode 100644 Externals/cubeb/CMakeLists.txt create mode 100644 Externals/cubeb/INSTALL.md create mode 100644 Externals/cubeb/LICENSE create mode 100644 Externals/cubeb/README.md create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/LICENSE create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/README.md create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake create mode 100755 Externals/cubeb/cmake/sanitizers-cmake/cmake/asan-wrapper create mode 100644 Externals/cubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake create mode 100644 Externals/cubeb/cubeb.supp create mode 100644 Externals/cubeb/include/cubeb/cubeb.h create mode 100644 Externals/cubeb/src/android/audiotrack_definitions.h create mode 100644 Externals/cubeb/src/android/sles_definitions.h create mode 100644 Externals/cubeb/src/cubeb-internal.h create mode 100644 Externals/cubeb/src/cubeb-sles.h create mode 100644 Externals/cubeb/src/cubeb-speex-resampler.h create mode 100644 Externals/cubeb/src/cubeb.c create mode 100644 Externals/cubeb/src/cubeb_alsa.c create mode 100644 Externals/cubeb/src/cubeb_array_queue.h create mode 100644 Externals/cubeb/src/cubeb_assert.h create mode 100644 Externals/cubeb/src/cubeb_audiotrack.c create mode 100644 Externals/cubeb/src/cubeb_audiounit.cpp create mode 100644 Externals/cubeb/src/cubeb_jack.cpp create mode 100644 Externals/cubeb/src/cubeb_kai.c create mode 100644 Externals/cubeb/src/cubeb_log.cpp create mode 100644 Externals/cubeb/src/cubeb_log.h create mode 100644 Externals/cubeb/src/cubeb_mixer.cpp create mode 100644 Externals/cubeb/src/cubeb_mixer.h create mode 100644 Externals/cubeb/src/cubeb_opensl.c create mode 100644 Externals/cubeb/src/cubeb_osx_run_loop.cpp create mode 100644 Externals/cubeb/src/cubeb_osx_run_loop.h create mode 100644 Externals/cubeb/src/cubeb_panner.cpp create mode 100644 Externals/cubeb/src/cubeb_panner.h create mode 100644 Externals/cubeb/src/cubeb_pulse.c create mode 100644 Externals/cubeb/src/cubeb_resampler.cpp create mode 100644 Externals/cubeb/src/cubeb_resampler.h create mode 100644 Externals/cubeb/src/cubeb_resampler_internal.h create mode 100644 Externals/cubeb/src/cubeb_ring_array.h create mode 100644 Externals/cubeb/src/cubeb_ringbuffer.h create mode 100644 Externals/cubeb/src/cubeb_sndio.c create mode 100644 Externals/cubeb/src/cubeb_utils.c create mode 100644 Externals/cubeb/src/cubeb_utils.h create mode 100644 Externals/cubeb/src/cubeb_utils_unix.h create mode 100644 Externals/cubeb/src/cubeb_utils_win.h create mode 100644 Externals/cubeb/src/cubeb_wasapi.cpp create mode 100644 Externals/cubeb/src/cubeb_winmm.c create mode 100644 Externals/cubeb/src/speex/arch.h create mode 100644 Externals/cubeb/src/speex/fixed_generic.h create mode 100644 Externals/cubeb/src/speex/resample.c create mode 100644 Externals/cubeb/src/speex/resample_neon.h create mode 100644 Externals/cubeb/src/speex/resample_sse.h create mode 100644 Externals/cubeb/src/speex/speex_config_types.h create mode 100644 Externals/cubeb/src/speex/speex_resampler.h create mode 100644 Externals/cubeb/src/speex/stack_alloc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 380193676b..0c4bf7c677 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -660,6 +660,8 @@ find_package(OpenAL) add_subdirectory(Externals/soundtouch) include_directories(Externals) +add_subdirectory(Externals/cubeb EXCLUDE_FROM_ALL) + if(NOT ANDROID) add_definitions(-D__LIBUSB__) if(NOT APPLE) diff --git a/Externals/cubeb/AUTHORS b/Externals/cubeb/AUTHORS new file mode 100644 index 0000000000..f0f9595227 --- /dev/null +++ b/Externals/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/Externals/cubeb/CMakeLists.txt b/Externals/cubeb/CMakeLists.txt new file mode 100644 index 0000000000..de7a5b9682 --- /dev/null +++ b/Externals/cubeb/CMakeLists.txt @@ -0,0 +1,150 @@ +# 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) + +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() + +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) + +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_panner.cpp + src/cubeb_log.cpp + src/cubeb_utils.c + $) +target_include_directories(cubeb PUBLIC include) +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 ${CMAKE_BINARY_DIR}/exports) + +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 dl) +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 asound pthread) +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 dl pthread) +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) +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) + 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 sndio) +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() diff --git a/Externals/cubeb/INSTALL.md b/Externals/cubeb/INSTALL.md new file mode 100644 index 0000000000..81df5ea025 --- /dev/null +++ b/Externals/cubeb/INSTALL.md @@ -0,0 +1,24 @@ +# 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 (the default) or MinGW-w64 +with Win32 threads (by passing `cmake -G` to generate the appropriate build +configuration). 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 above, but at step 3 run: + `cmake -G "MinGW Makefiles" ..` +- Continue the build steps above. + diff --git a/Externals/cubeb/LICENSE b/Externals/cubeb/LICENSE new file mode 100644 index 0000000000..fffc9dc405 --- /dev/null +++ b/Externals/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/Externals/cubeb/README.md b/Externals/cubeb/README.md new file mode 100644 index 0000000000..d26b3b645b --- /dev/null +++ b/Externals/cubeb/README.md @@ -0,0 +1,6 @@ +[![Build Status](https://travis-ci.org/kinetiknz/cubeb.svg?branch=master)](https://travis-ci.org/kinetiknz/cubeb) +[![Build status](https://ci.appveyor.com/api/projects/status/osv2r0m1j1nt9csr/branch/master?svg=true)](https://ci.appveyor.com/project/kinetiknz/cubeb/branch/master) + +See INSTALL.md for build instructions. + +Licensed under an ISC-style license. See LICENSE for details. diff --git a/Externals/cubeb/cmake/sanitizers-cmake/LICENSE b/Externals/cubeb/cmake/sanitizers-cmake/LICENSE new file mode 100644 index 0000000000..2520efdc03 --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) + 2013 Matthew Arsenault + 2015-2016 RWTH Aachen University, Federal Republic of Germany + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Externals/cubeb/cmake/sanitizers-cmake/README.md b/Externals/cubeb/cmake/sanitizers-cmake/README.md new file mode 100644 index 0000000000..54a82556be --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/README.md @@ -0,0 +1,73 @@ +# sanitizers-cmake + + [![](https://img.shields.io/github/issues-raw/arsenm/sanitizers-cmake.svg?style=flat-square)](https://github.com/arsenm/sanitizers-cmake/issues) +[![MIT](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) + +CMake module to enable sanitizers for binary targets. + + +## Include into your project + +To use [FindSanitizers.cmake](cmake/FindSanitizers.cmake), simply add this repository as git submodule into your own repository +```Shell +mkdir externals +git submodule add git://github.com/arsenm/sanitizers-cmake.git externals/sanitizers-cmake +``` +and adding ```externals/sanitizers-cmake/cmake``` to your ```CMAKE_MODULE_PATH``` +```CMake +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH}) +``` + +If you don't use git or dislike submodules you can copy the files in [cmake directory](cmake) into your repository. *Be careful and keep updates in mind!* + +Now you can simply run ```find_package``` in your CMake files: +```CMake +find_package(Sanitizers) +``` + + +## Usage + +You can enable the sanitizers with ``SANITIZE_ADDRESS``, ``SANITIZE_MEMORY``, ``SANITIZE_THREAD`` or ``SANITIZE_UNDEFINED`` options in your CMake configuration. You can do this by passing e.g. ``-DSANITIZE_ADDRESS=On`` on your command line or with your graphical interface. + +If sanitizers are supported by your compiler, the specified targets will be build with sanitizer support. If your compiler has no sanitizing capabilities (I asume intel compiler doesn't) you'll get a warning but CMake will continue processing and sanitizing will simply just be ignored. + +#### Compiler issues + +Different compilers may be using different implementations for sanitizers. If you'll try to sanitize targets with C and Fortran code but don't use gcc & gfortran but clang & gfortran, this will cause linking problems. To avoid this, such problems will be detected and sanitizing will be disabled for these targets. + +Even C only targets may cause problems in certain situations. Some problems have been seen with AddressSanitizer for preloading or dynamic linking. In such cases you may try the ``SANITIZE_LINK_STATIC`` to link sanitizers for gcc static. + + + +## Build targets with sanitizer support + +To enable sanitizer support you simply have to add ``add_sanitizers()`` after defining your target. To provide a sanitizer blacklist file you can use the ``add_sanitizer_blacklist()`` function: +```CMake +find_package(Sanitizers) + +add_sanitizer_blacklist("blacklist.txt") + +add_executable(some_exe foo.c bar.c) +add_sanitizers(some_exe) + +add_library(some_lib foo.c bar.c) +add_sanitizers(some_lib) +``` + +## Run your application + +The sanitizers check your program, while it's running. In some situations (e.g. LD_PRELOAD your target) it might be required to preload the used AddressSanitizer library first. In this case you may use the ``asan-wrapper`` script defined in ``ASan_WRAPPER`` variable to execute your application with ``${ASan_WRAPPER} myexe arg1 ...``. + + +## Contribute + +Anyone is welcome to contribute. Simply fork this repository, make your changes **in an own branch** and create a pull-request for your change. Please do only one change per pull-request. + +You found a bug? Please fill out an [issue](https://github.com/arsenm/sanitizers-cmake/issues) and include any data to reproduce the bug. + + +#### Contributors + +* [Matt Arsenault](https://github.com/arsenm) +* [Alexander Haase](https://github.com/alehaa) diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake new file mode 100644 index 0000000000..fcebb43757 --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake @@ -0,0 +1,59 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + # Clang 3.2+ use this version. The no-omit-frame-pointer option is optional. + "-g -fsanitize=address -fno-omit-frame-pointer" + "-g -fsanitize=address" + + # Older deprecated flag for ASan + "-g -faddress-sanitizer" +) + + +if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY)) + message(FATAL_ERROR "AddressSanitizer is not compatible with " + "ThreadSanitizer or MemorySanitizer.") +endif () + + +include(sanitize-helpers) + +if (SANITIZE_ADDRESS) + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer" + "ASan") + + find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH}) + mark_as_advanced(ASan_WRAPPER) +endif () + +function (add_sanitize_address TARGET) + if (NOT SANITIZE_ADDRESS) + return() + endif () + + saitizer_add_flags(${TARGET} "AddressSanitizer" "ASan") +endfunction () diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake new file mode 100644 index 0000000000..3b0a4addcb --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake @@ -0,0 +1,57 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + "-g -fsanitize=memory" +) + + +include(sanitize-helpers) + +if (SANITIZE_MEMORY) + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(WARNING "MemorySanitizer disabled for target ${TARGET} because " + "MemorySanitizer is supported for Linux systems only.") + set(SANITIZE_MEMORY Off CACHE BOOL + "Enable MemorySanitizer for sanitized targets." FORCE) + elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) + message(WARNING "MemorySanitizer disabled for target ${TARGET} because " + "MemorySanitizer is supported for 64bit systems only.") + set(SANITIZE_MEMORY Off CACHE BOOL + "Enable MemorySanitizer for sanitized targets." FORCE) + else () + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer" + "MSan") + endif () +endif () + +function (add_sanitize_memory TARGET) + if (NOT SANITIZE_MEMORY) + return() + endif () + + saitizer_add_flags(${TARGET} "MemorySanitizer" "MSan") +endfunction () diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake new file mode 100644 index 0000000000..1c4622fc53 --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake @@ -0,0 +1,87 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# If any of the used compiler is a GNU compiler, add a second option to static +# link against the sanitizers. +option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off) + + + + +set(FIND_QUIETLY_FLAG "") +if (DEFINED Sanitizers_FIND_QUIETLY) + set(FIND_QUIETLY_FLAG "QUIET") +endif () + +find_package(ASan ${FIND_QUIETLY_FLAG}) +find_package(TSan ${FIND_QUIETLY_FLAG}) +find_package(MSan ${FIND_QUIETLY_FLAG}) +find_package(UBSan ${FIND_QUIETLY_FLAG}) + + + + +function(sanitizer_add_blacklist_file FILE) + if(NOT IS_ABSOLUTE ${FILE}) + set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}") + endif() + get_filename_component(FILE "${FILE}" REALPATH) + + sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}" + "SanitizerBlacklist" "SanBlist") +endfunction() + +function(add_sanitizers ...) + # If no sanitizer is enabled, return immediately. + if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR + SANITIZE_UNDEFINED)) + return() + endif () + + foreach (TARGET ${ARGV}) + # Check if this target will be compiled by exactly one compiler. Other- + # wise sanitizers can't be used and a warning should be printed once. + sanitizer_target_compilers(${TARGET} TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + if (NUM_COMPILERS GREATER 1) + message(WARNING "Can't use any sanitizers for target ${TARGET}, " + "because it will be compiled by incompatible compilers. " + "Target will be compiled without sanitzers.") + return() + + # If the target is compiled by no known compiler, ignore it. + elseif (NUM_COMPILERS EQUAL 0) + message(WARNING "Can't use any sanitizers for target ${TARGET}, " + "because it uses an unknown compiler. Target will be " + "compiled without sanitzers.") + return() + endif () + + # Add sanitizers for target. + add_sanitize_address(${TARGET}) + add_sanitize_thread(${TARGET}) + add_sanitize_memory(${TARGET}) + add_sanitize_undefined(${TARGET}) + endforeach () +endfunction(add_sanitizers) diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake new file mode 100644 index 0000000000..0e80f29b64 --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake @@ -0,0 +1,64 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + "-g -fsanitize=thread" +) + + +# ThreadSanitizer is not compatible with MemorySanitizer. +if (SANITIZE_THREAD AND SANITIZE_MEMORY) + message(FATAL_ERROR "ThreadSanitizer is not compatible with " + "MemorySanitizer.") +endif () + + +include(sanitize-helpers) + +if (SANITIZE_THREAD) + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " + "ThreadSanitizer is supported for Linux systems only.") + set(SANITIZE_THREAD Off CACHE BOOL + "Enable ThreadSanitizer for sanitized targets." FORCE) + elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) + message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " + "ThreadSanitizer is supported for 64bit systems only.") + set(SANITIZE_THREAD Off CACHE BOOL + "Enable ThreadSanitizer for sanitized targets." FORCE) + else () + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer" + "TSan") + endif () +endif () + +function (add_sanitize_thread TARGET) + if (NOT SANITIZE_THREAD) + return() + endif () + + saitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan") +endfunction () diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake new file mode 100644 index 0000000000..69486742a4 --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake @@ -0,0 +1,46 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_UNDEFINED + "Enable UndefinedBehaviorSanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + "-g -fsanitize=undefined" +) + + +include(sanitize-helpers) + +if (SANITIZE_UNDEFINED) + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" + "UndefinedBehaviorSanitizer" "UBSan") +endif () + +function (add_sanitize_undefined TARGET) + if (NOT SANITIZE_UNDEFINED) + return() + endif () + + saitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan") +endfunction () diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/asan-wrapper b/Externals/cubeb/cmake/sanitizers-cmake/cmake/asan-wrapper new file mode 100755 index 0000000000..5d54103372 --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/asan-wrapper @@ -0,0 +1,55 @@ +#!/bin/sh + +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This script is a wrapper for AddressSanitizer. In some special cases you need +# to preload AddressSanitizer to avoid error messages - e.g. if you're +# preloading another library to your application. At the moment this script will +# only do something, if we're running on a Linux platform. OSX might not be +# affected. + + +# Exit immediately, if platform is not Linux. +if [ "$(uname)" != "Linux" ] +then + exec $@ +fi + + +# Get the used libasan of the application ($1). If a libasan was found, it will +# be prepended to LD_PRELOAD. +libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1) +if [ -n "$libasan" ] +then + if [ -n "$LD_PRELOAD" ] + then + export LD_PRELOAD="$libasan:$LD_PRELOAD" + else + export LD_PRELOAD="$libasan" + fi +fi + +# Execute the application. +exec $@ diff --git a/Externals/cubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake b/Externals/cubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake new file mode 100644 index 0000000000..6f3decdfac --- /dev/null +++ b/Externals/cubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake @@ -0,0 +1,170 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Helper function to get the language of a source file. +function (sanitizer_lang_of_source FILE RETURN_VAR) + get_filename_component(FILE_EXT "${FILE}" EXT) + string(TOLOWER "${FILE_EXT}" FILE_EXT) + string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) + if (NOT ${TEMP} EQUAL -1) + set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) + return() + endif () + endforeach() + + set(${RETURN_VAR} "" PARENT_SCOPE) +endfunction () + + +# Helper function to get compilers used by a target. +function (sanitizer_target_compilers TARGET RETURN_VAR) + # Check if all sources for target use the same compiler. If a target uses + # e.g. C and Fortran mixed and uses different compilers (e.g. clang and + # gfortran) this can trigger huge problems, because different compilers may + # use different implementations for sanitizers. + set(BUFFER "") + get_target_property(TSOURCES ${TARGET} SOURCES) + foreach (FILE ${TSOURCES}) + # If expression was found, FILE is a generator-expression for an object + # library. Object libraries will be ignored. + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) + if ("${_file}" STREQUAL "") + sanitizer_lang_of_source(${FILE} LANG) + if (LANG) + list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + list(REMOVE_DUPLICATES BUFFER) + set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE) +endfunction () + + +# Helper function to check compiler flags for language compiler. +function (sanitizer_check_compiler_flag FLAG LANG VARIABLE) + if (${LANG} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${FLAG}" ${VARIABLE}) + + elseif (${LANG} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${FLAG}" ${VARIABLE}) + + elseif (${LANG} STREQUAL "Fortran") + # CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible + # with older Cmake versions, we will check if this module is present + # before we use it. Otherwise we will define Fortran coverage support as + # not available. + include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED) + if (INCLUDED) + check_fortran_compiler_flag("${FLAG}" ${VARIABLE}) + elseif (NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Performing Test ${VARIABLE}") + message(STATUS "Performing Test ${VARIABLE}" + " - Failed (Check not supported)") + endif () + endif() +endfunction () + + +# Helper function to test compiler flags. +function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX) + set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY}) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + # Sanitizer flags are not dependend on language, but the used compiler. + # So instead of searching flags foreach language, search flags foreach + # compiler used. + set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS) + foreach (FLAG ${FLAG_CANDIDATES}) + if(NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${FLAG}") + unset(${PREFIX}_FLAG_DETECTED CACHE) + sanitizer_check_compiler_flag("${FLAG}" ${LANG} + ${PREFIX}_FLAG_DETECTED) + + if (${PREFIX}_FLAG_DETECTED) + # If compiler is a GNU compiler, search for static flag, if + # SANITIZE_LINK_STATIC is enabled. + if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU")) + string(TOLOWER ${PREFIX} PREFIX_lower) + sanitizer_check_compiler_flag( + "-static-lib${PREFIX_lower}" ${LANG} + ${PREFIX}_STATIC_FLAG_DETECTED) + + if (${PREFIX}_STATIC_FLAG_DETECTED) + set(FLAG "-static-lib${PREFIX_lower} ${FLAG}") + endif () + endif () + + set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING + "${NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) + break() + endif () + endforeach () + + if (NOT ${PREFIX}_FLAG_DETECTED) + set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING + "${NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) + + message(WARNING "${NAME} is not available for ${COMPILER} " + "compiler. Targets using this compiler will be " + "compiled without ${NAME}.") + endif () + endif () + endforeach () +endfunction () + + +# Helper to assign sanitizer flags for TARGET. +function (saitizer_add_flags TARGET NAME PREFIX) + # Get list of compilers used by target and check, if sanitizer is available + # for this target. Other compiler checks like check for conflicting + # compilers will be done in add_sanitizers function. + sanitizer_target_compilers(${TARGET} TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "") + return() + endif() + + # Set compile- and link-flags for target. + set_property(TARGET ${TARGET} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TARGET} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TARGET} APPEND_STRING + PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") +endfunction () diff --git a/Externals/cubeb/cubeb.supp b/Externals/cubeb/cubeb.supp new file mode 100644 index 0000000000..0012ea51e6 --- /dev/null +++ b/Externals/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/Externals/cubeb/include/cubeb/cubeb.h b/Externals/cubeb/include/cubeb/cubeb.h new file mode 100644 index 0000000000..5e09e2d301 --- /dev/null +++ b/Externals/cubeb/include/cubeb/cubeb.h @@ -0,0 +1,655 @@ +/* + * 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"); + int rv; + int rate; + int latency_frames; + uint64_t ts; + + rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames); + if (rv != CUBEB_OK) { + fprintf(stderr, "Could not get minimum latency"); + return rv; + } + + rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate); + if (rv != CUBEB_OK) { + fprintf(stderr, "Could not get preferred sample-rate"); + return rv; + } + + cubeb_stream_params output_params; + output_params.format = CUBEB_SAMPLE_FLOAT32NE; + output_params.rate = rate; + output_params.channels = 2; + + cubeb_stream_params input_params; + output_params.format = CUBEB_SAMPLE_FLOAT32NE; + output_params.rate = rate; + output_params.channels = 1; + + cubeb_stream * stm; + rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1", + NULL, input_params, + NULL, output_params, + latency_frames, + data_cb, state_cb, + NULL); + if (rv != CUBEB_OK) { + fprintf(stderr, "Could not open the stream"); + return rv; + } + + rv = cubeb_stream_start(stm); + if (rv != CUBEB_OK) { + fprintf(stderr, "Could not start the stream"); + return rv; + } + for (;;) { + cubeb_stream_get_position(stm, &ts); + printf("time=%llu\n", ts); + sleep(1); + } + rv = cubeb_stream_stop(stm); + if (rv != CUBEB_OK) { + fprintf(stderr, "Could not stop the stream"); + return rv; + } + + cubeb_stream_destroy(stm); + cubeb_destroy(app_ctx); + @endcode + + @code + long data_cb(cubeb_stream * stm, void * user, + void * input_buffer, void * output_buffer, long nframes) + { + float * in = input_buffer; + float * out = output_buffer; + + for (i = 0; i < nframes; ++i) { + for (c = 0; c < 2; ++c) { + buf[i][c] = in[i]; + } + } + return nframes; + } + @endcode + + @code + void state_cb(cubeb_stream * stm, void * user, cubeb_state state) + { + printf("state=%d\n", state); + } + @endcode +*/ + +/** @file + The 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; + +#if defined(__ANDROID__) +/** + * This maps to the underlying stream types on supported platforms, e.g. + * Android. + */ +typedef enum { + CUBEB_STREAM_TYPE_VOICE_CALL = 0, + CUBEB_STREAM_TYPE_SYSTEM = 1, + CUBEB_STREAM_TYPE_RING = 2, + CUBEB_STREAM_TYPE_MUSIC = 3, + CUBEB_STREAM_TYPE_ALARM = 4, + CUBEB_STREAM_TYPE_NOTIFICATION = 5, + CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6, + CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7, + CUBEB_STREAM_TYPE_DTMF = 8, + CUBEB_STREAM_TYPE_TTS = 9, + CUBEB_STREAM_TYPE_FM = 10, + + CUBEB_STREAM_TYPE_MAX +} cubeb_stream_type; +#endif + +/** An opaque handle used to refer a particular input or output device + * across calls. */ +typedef void 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; + +/** SMPTE channel layout (also known as wave order) + * DUAL-MONO L R + * DUAL-MONO-LFE L R LFE + * MONO M + * MONO-LFE M LFE + * STEREO L R + * STEREO-LFE L R LFE + * 3F L R C + * 3F-LFE L R C LFE + * 2F1 L R S + * 2F1-LFE L R LFE S + * 3F1 L R C S + * 3F1-LFE L R C LFE S + * 2F2 L R LS RS + * 2F2-LFE L R LFE LS RS + * 3F2 L R C LS RS + * 3F2-LFE L R C LFE LS RS + * 3F3R-LFE L R C LFE RC LS RS + * 3F4-LFE L R C LFE RLS RRS LS RS + * + * The abbreviation of channel name is defined in following table: + * Abbr Channel name + * --------------------------- + * M Mono + * L Left + * R Right + * C Center + * LS Left Surround + * RS Right Surround + * RLS Rear Left Surround + * RC Rear Center + * RRS Rear Right Surround + * LFE Low Frequency Effects + */ + +typedef enum { + CUBEB_LAYOUT_UNDEFINED, // Indicate the speaker's layout is undefined. + CUBEB_LAYOUT_DUAL_MONO, + CUBEB_LAYOUT_DUAL_MONO_LFE, + CUBEB_LAYOUT_MONO, + CUBEB_LAYOUT_MONO_LFE, + CUBEB_LAYOUT_STEREO, + CUBEB_LAYOUT_STEREO_LFE, + CUBEB_LAYOUT_3F, + CUBEB_LAYOUT_3F_LFE, + CUBEB_LAYOUT_2F1, + CUBEB_LAYOUT_2F1_LFE, + CUBEB_LAYOUT_3F1, + CUBEB_LAYOUT_3F1_LFE, + CUBEB_LAYOUT_2F2, + CUBEB_LAYOUT_2F2_LFE, + CUBEB_LAYOUT_3F2, + CUBEB_LAYOUT_3F2_LFE, + CUBEB_LAYOUT_3F3R_LFE, + CUBEB_LAYOUT_3F4_LFE, + CUBEB_LAYOUT_MAX +} cubeb_channel_layout; + +/** Stream format initialization parameters. */ +typedef struct { + cubeb_sample_format format; /**< Requested sample format. One of + #cubeb_sample_format. */ + unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ + unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */ + cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. */ +#if defined(__ANDROID__) + cubeb_stream_type stream_type; /**< Used to map Android audio stream types */ +#endif +} cubeb_stream_params; + +/** Audio device description */ +typedef struct { + char * output_name; /**< The name of the output device */ + char * input_name; /**< The name of the input device */ +} cubeb_device; + +/** Stream states signaled via state_callback. */ +typedef enum { + CUBEB_STATE_STARTED, /**< Stream started. */ + CUBEB_STATE_STOPPED, /**< Stream stopped. */ + CUBEB_STATE_DRAINED, /**< Stream drained. */ + CUBEB_STATE_ERROR /**< Stream disabled due to error. */ +} cubeb_state; + +/** Result code enumeration. */ +enum { + CUBEB_OK = 0, /**< Success. */ + CUBEB_ERROR = -1, /**< Unclassified error. */ + CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */ + CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */ + CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */ + CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */ +}; + +/** + * Whether a particular device is an input device (e.g. a microphone), or an + * output device (e.g. headphones). */ +typedef enum { + CUBEB_DEVICE_TYPE_UNKNOWN, + CUBEB_DEVICE_TYPE_INPUT, + CUBEB_DEVICE_TYPE_OUTPUT +} cubeb_device_type; + +/** + * The state of a device. + */ +typedef enum { + CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */ + CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */ + CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */ +} cubeb_device_state; + +/** + * Architecture specific sample type. + */ +typedef enum { + CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */ + CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */ + CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */ + CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */ +} cubeb_device_fmt; + +#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) +/** 16-bit integers, native endianess, when on a Big Endian environment. */ +#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE +/** 32-bit floating points, native endianess, when on a Big Endian environment. */ +#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE +#else +/** 16-bit integers, native endianess, when on a Little Endian environment. */ +#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE +/** 32-bit floating points, native endianess, when on a Little Endian + * environment. */ +#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE +#endif +/** All the 16-bit integers types. */ +#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE) +/** All the 32-bit floating points types. */ +#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE) +/** All the device formats types. */ +#define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK) + +/** Channel type for a `cubeb_stream`. Depending on the backend and platform + * used, this can control inter-stream interruption, ducking, and volume + * control. + */ +typedef enum { + CUBEB_DEVICE_PREF_NONE = 0x00, + CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01, + CUBEB_DEVICE_PREF_VOICE = 0x02, + CUBEB_DEVICE_PREF_NOTIFICATION = 0x04, + CUBEB_DEVICE_PREF_ALL = 0x0F +} cubeb_device_pref; + +/** This structure holds the characteristics + * of an input or output audio device. It 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. */ + unsigned int max_channels; /**< Channels. */ + unsigned int default_rate; /**< Default/Preferred sample rate. */ + unsigned int max_rate; /**< Maximum sample rate supported. */ + unsigned int min_rate; /**< Minimum sample rate supported. */ + + unsigned int latency_lo; /**< Lowest possible latency in frames. */ + unsigned int latency_hi; /**< Higest possible latency in frames. */ +} cubeb_device_info; + +/** Device collection. + * 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 Number of frames written to the output buffer. If this number is + less than nframes, then the stream will start to drain. + @retval CUBEB_ERROR on error, in which case the data callback will stop + and the stream will enter a shutdown state. */ +typedef long (* cubeb_data_callback)(cubeb_stream * stream, + void * user_ptr, + 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_stream_init. */ +typedef void (* cubeb_device_collection_changed_callback)(cubeb * context, + void * user_ptr); + +/** User supplied callback called when a message needs logging. */ +typedef void (* cubeb_log_callback)(char const * fmt, ...); + +/** Initialize an application context. This will perform any library or + application scoped initialization. + @param context A out param where an opaque pointer to the application + context will be returned. + @param context_name A name for the context. Depending on the platform this + can appear in different locations. + @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); + +/** Get the preferred layout for this backend: this is hardware and + platform dependent. + @param context A pointer to the cubeb context. + @param layout The layout of the current speaker configuration. + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout); + +/** Destroy an application context. This must be called after all stream have + * been destroyed. + @param context A pointer to the cubeb context.*/ +CUBEB_EXPORT void cubeb_destroy(cubeb * context); + +/** Initialize a stream associated with the supplied application context. + @param context A pointer to the cubeb context. + @param stream An out parameter to be filled with the an opaque pointer to a + cubeb stream. + @param stream_name A name for this stream. + @param input_device Device for the input side of the stream. If NULL the + default input device is used. + @param input_stream_params Parameters for the input side of the stream, or + NULL if this stream is output only. + @param output_device Device for the output side of the stream. If NULL the + default output device is used. + @param output_stream_params Parameters for the output side of the stream, or + NULL if this stream is input only. + @param latency_frames Stream latency in frames. Valid range + is [1, 96000]. + @param data_callback Will be called to preroll data before playback is + started by cubeb_stream_start. + @param state_callback A pointer to a state callback. + @param user_ptr A pointer that will be passed to the callbacks. This pointer + must outlive the life time of the stream. + @retval CUBEB_OK + @retval CUBEB_ERROR + @retval CUBEB_ERROR_INVALID_FORMAT + @retval CUBEB_ERROR_DEVICE_UNAVAILABLE */ +CUBEB_EXPORT int cubeb_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr); + +/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a + stream. + @param stream The stream to destroy. */ +CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream); + +/** Start playback. + @param stream + @retval CUBEB_OK + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream); + +/** Stop playback. + @param stream + @retval CUBEB_OK + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream); + +/** Get the current stream playback position. + @param stream + @param position Playback position in frames. + @retval CUBEB_OK + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position); + +/** Get the latency for this stream, in frames. This is the number of frames + between the time cubeb acquires the data in the callback and the listener + can hear the sound. + @param stream + @param latency Current approximate stream latency in frames. + @retval CUBEB_OK + @retval CUBEB_ERROR_NOT_SUPPORTED + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency); + +/** Set the volume for a stream. + @param stream the stream for which to adjust the volume. + @param volume a float between 0.0 (muted) and 1.0 (maximum volume) + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or + stream is an invalid pointer + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume); + +/** If the stream is stereo, set the left/right panning. If the stream is mono, + this has no effect. + @param stream the stream for which to change the panning + @param panning a number from -1.0 to 1.0. -1.0 means that the stream is + fully mixed in the left channel, 1.0 means the stream is fully + mixed in the right channel. 0.0 is equal power in the right and + left channel (default). + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is + outside the [-1.0, 1.0] range. + @retval CUBEB_ERROR_NOT_SUPPORTED + @retval CUBEB_ERROR stream is not mono nor stereo */ +CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning); + +/** Get the current output device for this stream. + @param stm the stream for which to query the current output device + @param device a pointer in which the current output device will be stored. + @retval CUBEB_OK in case of success + @retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are + invalid pointers + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm, + cubeb_device ** const device); + +/** Destroy a cubeb_device structure. + @param stream the stream passed in cubeb_stream_get_current_device + @param devices the devices to destroy + @retval CUBEB_OK in case of success + @retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream, + cubeb_device * devices); + +/** Set a callback to be notified when the output device changes. + @param stream the stream for which to set the callback. + @param device_changed_callback a function called whenever the device has + changed. Passing NULL allow to unregister a function + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER if either stream or + device_changed_callback are invalid pointers. + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, + cubeb_device_changed_callback device_changed_callback); + +/** Returns enumerated devices. + @param context + @param devtype device type to include + @param collection output collection. Must be destroyed with cubeb_device_collection_destroy + @retval CUBEB_OK in case of success + @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection * collection); + +/** Destroy a cubeb_device_collection, and its `cubeb_device_info`. + @param 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 + @param callback a function called whenever the system device list changes. + Passing NULL allow to unregister a function + @param user_ptr pointer to user specified data which will be present in + subsequent callbacks. + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback callback, + void * user_ptr); + +/** Set a callback to be called with a message. + @param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL. + @param log_callback A function called with a message when there is + something to log. Pass NULL to unregister. + @retval CUBEB_OK in case of success. + @retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are + invalid pointers, or if level is not + in cubeb_log_level. */ +CUBEB_EXPORT int cubeb_set_log_callback(cubeb_log_level log_level, + cubeb_log_callback log_callback); + +#if defined(__cplusplus) +} +#endif + +#endif /* CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 */ diff --git a/Externals/cubeb/src/android/audiotrack_definitions.h b/Externals/cubeb/src/android/audiotrack_definitions.h new file mode 100644 index 0000000000..cd501533d9 --- /dev/null +++ b/Externals/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/Externals/cubeb/src/android/sles_definitions.h b/Externals/cubeb/src/android/sles_definitions.h new file mode 100644 index 0000000000..1b1ace567e --- /dev/null +++ b/Externals/cubeb/src/android/sles_definitions.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in + * the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep + * using a C compiler in cubeb. + */ + +#ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_ +#define OPENSL_ES_ANDROIDCONFIGURATION_H_ + +/*---------------------------------------------------------------------------*/ +/* Android AudioRecorder configuration */ +/*---------------------------------------------------------------------------*/ + +/** Audio recording preset */ +/** Audio recording preset key */ +#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset") +/** Audio recording preset values */ +/** preset "none" cannot be set, it is used to indicate the current settings + * do not match any of the presets. */ +#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000) +/** generic recording configuration on the platform */ +#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001) +/** uses the microphone audio source with the same orientation as the camera + * if available, the main device microphone otherwise */ +#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002) +/** uses the main microphone tuned for voice recognition */ +#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003) +/** uses the main microphone tuned for audio communications */ +#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004) + +/** Audio recording get session ID (read only) */ +/** Audio recording get session ID key */ +#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId") + +/*---------------------------------------------------------------------------*/ +/* Android AudioPlayer configuration */ +/*---------------------------------------------------------------------------*/ + +/** Audio playback stream type */ +/** Audio playback stream type key */ +#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType") + +/** Audio playback stream type values */ +/* same as android.media.AudioManager.STREAM_VOICE_CALL */ +#define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000) +/* same as android.media.AudioManager.STREAM_SYSTEM */ +#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001) +/* same as android.media.AudioManager.STREAM_RING */ +#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002) +/* same as android.media.AudioManager.STREAM_MUSIC */ +#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003) +/* same as android.media.AudioManager.STREAM_ALARM */ +#define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004) +/* same as android.media.AudioManager.STREAM_NOTIFICATION */ +#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005) +/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */ +#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006) +/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */ +#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007) + +#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */ diff --git a/Externals/cubeb/src/cubeb-internal.h b/Externals/cubeb/src/cubeb-internal.h new file mode 100644 index 0000000000..b95246987d --- /dev/null +++ b/Externals/cubeb/src/cubeb-internal.h @@ -0,0 +1,88 @@ +/* + * 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 + +typedef struct { + char const * name; + unsigned int const channels; + cubeb_channel_layout const layout; +} cubeb_layout_map; + +extern cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX]; + +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 (* get_preferred_channel_layout)(cubeb * context, cubeb_channel_layout * layout); + int (* enumerate_devices)(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection); + int (* device_collection_destroy)(cubeb * context, + cubeb_device_collection * collection); + void (* destroy)(cubeb * context); + int (* stream_init)(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr); + void (* stream_destroy)(cubeb_stream * stream); + int (* stream_start)(cubeb_stream * stream); + int (* stream_stop)(cubeb_stream * stream); + int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); + int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); + int (* stream_set_volume)(cubeb_stream * stream, float volumes); + int (* stream_set_panning)(cubeb_stream * stream, float panning); + int (* stream_get_current_device)(cubeb_stream * stream, + cubeb_device ** const device); + int (* stream_device_destroy)(cubeb_stream * stream, + cubeb_device * device); + int (* stream_register_device_changed_callback)(cubeb_stream * stream, + cubeb_device_changed_callback device_changed_callback); + int (* register_device_collection_changed)(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback callback, + void * user_ptr); +}; + +#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */ diff --git a/Externals/cubeb/src/cubeb-sles.h b/Externals/cubeb/src/cubeb-sles.h new file mode 100644 index 0000000000..ac22150e1f --- /dev/null +++ b/Externals/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/Externals/cubeb/src/cubeb-speex-resampler.h b/Externals/cubeb/src/cubeb-speex-resampler.h new file mode 100644 index 0000000000..9ecf747cb0 --- /dev/null +++ b/Externals/cubeb/src/cubeb-speex-resampler.h @@ -0,0 +1 @@ +#include diff --git a/Externals/cubeb/src/cubeb.c b/Externals/cubeb/src/cubeb.c new file mode 100644 index 0000000000..714f88f722 --- /dev/null +++ b/Externals/cubeb/src/cubeb.c @@ -0,0 +1,630 @@ +/* + * 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 { + struct cubeb * context; +}; + +#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_WINMM) +int winmm_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_WASAPI) +int wasapi_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_SNDIO) +int sndio_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_OPENSL) +int opensl_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_AUDIOTRACK) +int audiotrack_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_KAI) +int kai_init(cubeb ** context, char const * context_name); +#endif + +static int +validate_stream_params(cubeb_stream_params * input_stream_params, + cubeb_stream_params * output_stream_params) +{ + XASSERT(input_stream_params || output_stream_params); + if (output_stream_params) { + if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 || + output_stream_params->channels < 1 || output_stream_params->channels > 8) { + return CUBEB_ERROR_INVALID_FORMAT; + } + } + if (input_stream_params) { + if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 || + input_stream_params->channels < 1 || input_stream_params->channels > 8) { + return CUBEB_ERROR_INVALID_FORMAT; + } + } + // Rate and sample format must be the same for input and output, if using a + // duplex stream + if (input_stream_params && output_stream_params) { + if (input_stream_params->rate != output_stream_params->rate || + input_stream_params->format != output_stream_params->format) { + return CUBEB_ERROR_INVALID_FORMAT; + } + } + + cubeb_stream_params * params = input_stream_params ? + input_stream_params : output_stream_params; + + switch (params->format) { + case CUBEB_SAMPLE_S16LE: + case CUBEB_SAMPLE_S16BE: + case CUBEB_SAMPLE_FLOAT32LE: + case CUBEB_SAMPLE_FLOAT32BE: + return CUBEB_OK; + } + + return CUBEB_ERROR_INVALID_FORMAT; +} + +static int +validate_latency(int latency) +{ + if (latency < 1 || latency > 96000) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + return CUBEB_OK; +} + +int +cubeb_init(cubeb ** context, char const * context_name, 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, "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, "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) + pulse_init, +#endif +#if defined(USE_JACK) + jack_init, +#endif +#if defined(USE_ALSA) + alsa_init, +#endif +#if defined(USE_AUDIOUNIT) + audiounit_init, +#endif +#if defined(USE_WASAPI) + wasapi_init, +#endif +#if defined(USE_WINMM) + winmm_init, +#endif +#if defined(USE_SNDIO) + sndio_init, +#endif +#if defined(USE_OPENSL) + opensl_init, +#endif +#if defined(USE_AUDIOTRACK) + audiotrack_init, +#endif +#if defined(USE_KAI) + kai_init, +#endif + }; + int i; + + if (!context) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + +#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 || !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); +} + +int +cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout) +{ + if (!context || !layout) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!context->ops->get_preferred_channel_layout) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return context->ops->get_preferred_channel_layout(context, layout); +} + +void +cubeb_destroy(cubeb * context) +{ + if (!context) { + return; + } + + context->ops->destroy(context); +} + +int +cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) +{ + int r; + + if (!context || !stream) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK || + (r = validate_latency(latency)) != CUBEB_OK) { + return r; + } + + 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_get_position(cubeb_stream * stream, uint64_t * position) +{ + if (!stream || !position) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + return stream->context->ops->stream_get_position(stream, position); +} + +int +cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +{ + if (!stream || !latency) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_get_latency) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_get_latency(stream, latency); +} + +int +cubeb_stream_set_volume(cubeb_stream * stream, float volume) +{ + if (!stream || volume > 1.0 || volume < 0.0) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_set_volume) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_set_volume(stream, volume); +} + +int cubeb_stream_set_panning(cubeb_stream * stream, float panning) +{ + if (!stream || panning < -1.0 || panning > 1.0) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_set_panning) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_set_panning(stream, panning); +} + +int cubeb_stream_get_current_device(cubeb_stream * stream, + cubeb_device ** const device) +{ + if (!stream || !device) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_get_current_device) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_get_current_device(stream, device); +} + +int cubeb_stream_device_destroy(cubeb_stream * stream, + cubeb_device * device) +{ + if (!stream || !device) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_device_destroy) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_device_destroy(stream, device); +} + +int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, + cubeb_device_changed_callback device_changed_callback) +{ + if (!stream) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_register_device_changed_callback) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback); +} + +static +void log_device(cubeb_device_info * device_info) +{ + char devfmts[128] = ""; + const char * devtype, * devstate, * devdeffmt; + + switch (device_info->type) { + case CUBEB_DEVICE_TYPE_INPUT: + devtype = "input"; + break; + case CUBEB_DEVICE_TYPE_OUTPUT: + devtype = "output"; + break; + case CUBEB_DEVICE_TYPE_UNKNOWN: + default: + devtype = "unknown?"; + break; + }; + + switch (device_info->state) { + case CUBEB_DEVICE_STATE_DISABLED: + devstate = "disabled"; + break; + case CUBEB_DEVICE_STATE_UNPLUGGED: + devstate = "unplugged"; + break; + case CUBEB_DEVICE_STATE_ENABLED: + devstate = "enabled"; + break; + default: + devstate = "unknown?"; + break; + }; + + switch (device_info->default_format) { + case CUBEB_DEVICE_FMT_S16LE: + devdeffmt = "S16LE"; + break; + case CUBEB_DEVICE_FMT_S16BE: + devdeffmt = "S16BE"; + break; + case CUBEB_DEVICE_FMT_F32LE: + devdeffmt = "F32LE"; + break; + case CUBEB_DEVICE_FMT_F32BE: + devdeffmt = "F32BE"; + break; + default: + devdeffmt = "unknown?"; + break; + }; + + if (device_info->format & CUBEB_DEVICE_FMT_S16LE) { + strcat(devfmts, " S16LE"); + } + if (device_info->format & CUBEB_DEVICE_FMT_S16BE) { + strcat(devfmts, " S16BE"); + } + if (device_info->format & CUBEB_DEVICE_FMT_F32LE) { + strcat(devfmts, " F32LE"); + } + if (device_info->format & CUBEB_DEVICE_FMT_F32BE) { + strcat(devfmts, " F32BE"); + } + + LOG("DeviceID: \"%s\"%s\n" + "\tName:\t\"%s\"\n" + "\tGroup:\t\"%s\"\n" + "\tVendor:\t\"%s\"\n" + "\tType:\t%s\n" + "\tState:\t%s\n" + "\tMaximum channels:\t%u\n" + "\tFormat:\t%s (0x%x) (default: %s)\n" + "\tRate:\t[%u, %u] (default: %u)\n" + "\tLatency: lo %u frames, hi %u frames", + device_info->device_id, device_info->preferred ? " (PREFERRED)" : "", + device_info->friendly_name, + device_info->group_id, + device_info->vendor_name, + devtype, + devstate, + device_info->max_channels, + (devfmts[0] == '\0') ? devfmts : devfmts + 1, (unsigned int)device_info->format, devdeffmt, + device_info->min_rate, device_info->max_rate, device_info->default_rate, + device_info->latency_lo, device_info->latency_hi); +} + +int cubeb_enumerate_devices(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection * collection) +{ + int rv; + if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) + return CUBEB_ERROR_INVALID_PARAMETER; + if (collection == NULL) + return CUBEB_ERROR_INVALID_PARAMETER; + if (!context->ops->enumerate_devices) + return CUBEB_ERROR_NOT_SUPPORTED; + + rv = context->ops->enumerate_devices(context, devtype, collection); + + if (g_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) +{ + if (context == NULL || collection == NULL) + return CUBEB_ERROR_INVALID_PARAMETER; + + if (!context->ops->device_collection_destroy) + return CUBEB_ERROR_NOT_SUPPORTED; + + return context->ops->device_collection_destroy(context, collection); +} + +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/Externals/cubeb/src/cubeb_alsa.c b/Externals/cubeb/src/cubeb_alsa.c new file mode 100644 index 0000000000..4bd80487dd --- /dev/null +++ b/Externals/cubeb/src/cubeb_alsa.c @@ -0,0 +1,1372 @@ +/* + * 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 "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_utils.h" + +#define CUBEB_STREAM_MAX 16 +#define CUBEB_WATCHDOG_MS 10000 + +#define CUBEB_ALSA_PCM_NAME "default" + +#define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin" + +/* ALSA is not thread-safe. snd_pcm_t instances are individually protected + by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction + is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1), + so those calls must be wrapped in the following mutex. */ +static pthread_mutex_t cubeb_alsa_mutex = PTHREAD_MUTEX_INITIALIZER; +static int cubeb_alsa_error_handler_set = 0; + +static struct cubeb_ops const alsa_ops; + +struct cubeb { + struct cubeb_ops const * ops; + + pthread_t thread; + + /* Mutex for streams array, must not be held while blocked in poll(2). */ + pthread_mutex_t mutex; + + /* Sparse array of streams managed by this context. */ + cubeb_stream * streams[CUBEB_STREAM_MAX]; + + /* fds and nfds are only updated by alsa_run when rebuild is set. */ + struct pollfd * fds; + nfds_t nfds; + int rebuild; + + int shutdown; + + /* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */ + int control_fd_read; + int control_fd_write; + + /* Track number of active streams. This is limited to CUBEB_STREAM_MAX + due to resource contraints. */ + unsigned int active_streams; + + /* Local configuration with handle_underrun workaround set for PulseAudio + ALSA plugin. Will be NULL if the PA ALSA plugin is not in use or the + workaround is not required. */ + snd_config_t * local_config; + int is_pa; +}; + +enum stream_state { + INACTIVE, + RUNNING, + DRAINING, + PROCESSING, + ERROR +}; + +struct cubeb_stream { + cubeb * context; + pthread_mutex_t mutex; + snd_pcm_t * pcm; + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + snd_pcm_uframes_t 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 + snd_pcm_frames_to_bytes(stm->pcm, count); + memmove(stm->buffer, bufremains, 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. */ + snd_pcm_poll_descriptors_revents(stm->pcm, stm->fds, stm->nfds, &revents); + + avail = 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 = 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 + 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 + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes); + memset(buftail, 0, 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 = 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 = 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 && + snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) { + avail = 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 = snd_config_search(root_pcm, "slave", &slave_pcm); + if (r < 0) { + return NULL; + } + + r = snd_config_get_string(slave_pcm, &string); + if (r >= 0) { + r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def); + if (r < 0) { + return NULL; + } + } + + do { + r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm); + if (r < 0) { + break; + } + + r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string); + if (r < 0) { + break; + } + + r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); + if (r < 0 || r > (int) sizeof(node_name)) { + break; + } + r = snd_config_search(lconf, node_name, &pcm); + if (r < 0) { + break; + } + + return pcm; + } while (0); + + if (slave_def) { + snd_config_delete(slave_def); + } + + return NULL; +} + +/* Work around PulseAudio ALSA plugin bug where the PA server forces a + higher than requested latency, but the plugin does not update its (and + ALSA's) internal state to reflect that, leading to an immediate underrun + situation. Inspired by WINE's make_handle_underrun_config. + Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */ +static snd_config_t * +init_local_config_with_workaround(char const * pcm_name) +{ + int r; + snd_config_t * lconf; + snd_config_t * pcm_node; + snd_config_t * node; + char const * string; + char node_name[64]; + + lconf = NULL; + + if (snd_config == NULL) { + return NULL; + } + + r = snd_config_copy(&lconf, snd_config); + if (r < 0) { + return NULL; + } + + do { + r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node); + if (r < 0) { + break; + } + + r = snd_config_get_id(pcm_node, &string); + if (r < 0) { + break; + } + + r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); + if (r < 0 || r > (int) sizeof(node_name)) { + break; + } + r = snd_config_search(lconf, node_name, &pcm_node); + if (r < 0) { + break; + } + + /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */ + while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) { + pcm_node = node; + } + + /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ + r = snd_config_search(pcm_node, "type", &node); + if (r < 0) { + break; + } + + r = snd_config_get_string(node, &string); + if (r < 0) { + break; + } + + if (strcmp(string, "pulse") != 0) { + break; + } + + /* Don't clobber an explicit existing handle_underrun value, set it only + if it doesn't already exist. */ + r = snd_config_search(pcm_node, "handle_underrun", &node); + if (r != -ENOENT) { + break; + } + + /* Disable pcm_pulse's asynchronous underrun handling. */ + r = snd_config_imake_integer(&node, "handle_underrun", 0); + if (r < 0) { + break; + } + + r = snd_config_add(pcm_node, node); + if (r < 0) { + break; + } + + return lconf; + } while (0); + + snd_config_delete(lconf); + + return NULL; +} + +static int +alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config) +{ + int r; + + pthread_mutex_lock(&cubeb_alsa_mutex); + if (local_config) { + r = snd_pcm_open_lconf(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config); + } else { + r = 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 = snd_pcm_close(pcm); + pthread_mutex_unlock(&cubeb_alsa_mutex); + + return r; +} + +static int +alsa_register_stream(cubeb * ctx, cubeb_stream * stm) +{ + int i; + + pthread_mutex_lock(&ctx->mutex); + for (i = 0; i < CUBEB_STREAM_MAX; ++i) { + if (!ctx->streams[i]) { + ctx->streams[i] = stm; + break; + } + } + pthread_mutex_unlock(&ctx->mutex); + + return i == CUBEB_STREAM_MAX; +} + +static void +alsa_unregister_stream(cubeb_stream * stm) +{ + cubeb * ctx; + int i; + + ctx = stm->context; + + pthread_mutex_lock(&ctx->mutex); + for (i = 0; i < CUBEB_STREAM_MAX; ++i) { + if (ctx->streams[i] == stm) { + ctx->streams[i] = NULL; + break; + } + } + pthread_mutex_unlock(&ctx->mutex); +} + +static void +silent_error_handler(char const * file, int line, char const * function, + int err, char const * fmt, ...) +{ + (void)file; + (void)line; + (void)function; + (void)err; + (void)fmt; +} + +/*static*/ int +alsa_init(cubeb ** context, char const * context_name) +{ + (void)context_name; + cubeb * ctx; + int r; + int i; + int fd[2]; + pthread_attr_t attr; + snd_pcm_t * dummy; + + assert(context); + *context = NULL; + + pthread_mutex_lock(&cubeb_alsa_mutex); + if (!cubeb_alsa_error_handler_set) { + snd_lib_error_set_handler(silent_error_handler); + cubeb_alsa_error_handler_set = 1; + } + pthread_mutex_unlock(&cubeb_alsa_mutex); + + ctx = calloc(1, sizeof(*ctx)); + assert(ctx); + + ctx->ops = &alsa_ops; + + r = pthread_mutex_init(&ctx->mutex, NULL); + assert(r == 0); + + r = pipe(fd); + assert(r == 0); + + for (i = 0; i < 2; ++i) { + fcntl(fd[i], F_SETFD, fcntl(fd[i], F_GETFD) | FD_CLOEXEC); + fcntl(fd[i], F_SETFL, fcntl(fd[i], F_GETFL) | O_NONBLOCK); + } + + ctx->control_fd_read = fd[0]; + ctx->control_fd_write = fd[1]; + + /* Force an early rebuild when alsa_run is first called to ensure fds and + nfds have been initialized. */ + ctx->rebuild = 1; + + r = pthread_attr_init(&attr); + assert(r == 0); + + r = pthread_attr_setstacksize(&attr, 256 * 1024); + assert(r == 0); + + r = pthread_create(&ctx->thread, &attr, alsa_run_thread, ctx); + assert(r == 0); + + r = pthread_attr_destroy(&attr); + assert(r == 0); + + /* Open a dummy PCM to force the configuration space to be evaluated so that + init_local_config_with_workaround can find and modify the default node. */ + r = alsa_locked_pcm_open(&dummy, 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); + snd_config_delete(ctx->local_config); + pthread_mutex_unlock(&cubeb_alsa_mutex); + ctx->local_config = NULL; + } else if (r >= 0) { + alsa_locked_pcm_close(dummy); + } + } + + *context = ctx; + + return CUBEB_OK; +} + +static char const * +alsa_get_backend_id(cubeb * ctx) +{ + (void)ctx; + return "alsa"; +} + +static void +alsa_destroy(cubeb * ctx) +{ + int r; + + assert(ctx); + + pthread_mutex_lock(&ctx->mutex); + ctx->shutdown = 1; + poll_wake(ctx); + pthread_mutex_unlock(&ctx->mutex); + + r = pthread_join(ctx->thread, NULL); + assert(r == 0); + + close(ctx->control_fd_read); + close(ctx->control_fd_write); + pthread_mutex_destroy(&ctx->mutex); + free(ctx->fds); + + if (ctx->local_config) { + pthread_mutex_lock(&cubeb_alsa_mutex); + snd_config_delete(ctx->local_config); + pthread_mutex_unlock(&cubeb_alsa_mutex); + } + + free(ctx); +} + +static void alsa_stream_destroy(cubeb_stream * stm); + +static int +alsa_stream_init_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; + + 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 = snd_pcm_nonblock(stm->pcm, 1); + assert(r == 0); + + latency_us = latency_frames * 1e6 / stm->params.rate; + + /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't + possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274. + Only resort to this hack if the handle_underrun workaround failed. */ + if (!ctx->local_config && ctx->is_pa) { + const int min_latency = 5e5; + latency_us = latency_us < min_latency ? min_latency: latency_us; + } + + r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, + stm->params.channels, stm->params.rate, 1, + latency_us); + if (r < 0) { + alsa_stream_destroy(stm); + return CUBEB_ERROR_INVALID_FORMAT; + } + + r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); + assert(r == 0); + + /* 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, snd_pcm_frames_to_bytes(stm->pcm, stm->buffer_size)); + assert(stm->buffer); + + stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); + assert(stm->nfds > 0); + + stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); + assert(stm->saved_fds); + r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds); + assert((nfds_t) r == stm->nfds); + + 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) { + 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 = snd_pcm_hw_params_any(stm->pcm, hw_params); + if (r < 0) { + return CUBEB_ERROR; + } + + r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels); + if (r < 0) { + return CUBEB_ERROR; + } + + alsa_stream_destroy(stm); + + return CUBEB_OK; +} + +static int +alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { + (void)ctx; + int r, dir; + snd_pcm_t * pcm; + snd_pcm_hw_params_t * hw_params; + + snd_pcm_hw_params_alloca(&hw_params); + + /* get a pcm, disabling resampling, so we get a rate the + * hardware/dmix/pulse/etc. supports. */ + r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); + if (r < 0) { + return CUBEB_ERROR; + } + + r = snd_pcm_hw_params_any(pcm, hw_params); + if (r < 0) { + snd_pcm_close(pcm); + return CUBEB_ERROR; + } + + r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir); + if (r >= 0) { + /* There is a default rate: use it. */ + snd_pcm_close(pcm); + return CUBEB_OK; + } + + /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */ + *rate = 44100; + + r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL); + if (r < 0) { + snd_pcm_close(pcm); + return CUBEB_ERROR; + } + + snd_pcm_close(pcm); + + return CUBEB_OK; +} + +static int +alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) +{ + (void)ctx; + /* 40ms is found to be an acceptable minimum, even on a super low-end + * machine. */ + *latency_frames = 40 * params.rate / 1000; + + return CUBEB_OK; +} + +static int +alsa_stream_start(cubeb_stream * stm) +{ + cubeb * ctx; + + assert(stm); + ctx = stm->context; + + 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 && + snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) { + snd_pcm_start(stm->pcm); + } + 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); + snd_pcm_pause(stm->pcm, 1); + pthread_mutex_unlock(&stm->mutex); + + return CUBEB_OK; +} + +static int +alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + snd_pcm_sframes_t delay; + + assert(stm && position); + + pthread_mutex_lock(&stm->mutex); + + delay = -1; + if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING || + snd_pcm_delay(stm->pcm, &delay) != 0) { + *position = stm->last_position; + pthread_mutex_unlock(&stm->mutex); + return CUBEB_OK; + } + + assert(delay >= 0); + + *position = 0; + if (stm->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 (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, + .get_preferred_channel_layout = NULL, + .enumerate_devices = alsa_enumerate_devices, + .device_collection_destroy = alsa_device_collection_destroy, + .destroy = alsa_destroy, + .stream_init = alsa_stream_init, + .stream_destroy = alsa_stream_destroy, + .stream_start = alsa_stream_start, + .stream_stop = alsa_stream_stop, + .stream_get_position = alsa_stream_get_position, + .stream_get_latency = alsa_stream_get_latency, + .stream_set_volume = alsa_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; diff --git a/Externals/cubeb/src/cubeb_array_queue.h b/Externals/cubeb/src/cubeb_array_queue.h new file mode 100644 index 0000000000..a8ea4cd177 --- /dev/null +++ b/Externals/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/Externals/cubeb/src/cubeb_assert.h b/Externals/cubeb/src/cubeb_assert.h new file mode 100644 index 0000000000..9257a2c865 --- /dev/null +++ b/Externals/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/Externals/cubeb/src/cubeb_audiotrack.c b/Externals/cubeb/src/cubeb_audiotrack.c new file mode 100644 index 0000000000..0ffa9980e9 --- /dev/null +++ b/Externals/cubeb/src/cubeb_audiotrack.c @@ -0,0 +1,440 @@ +/* + * 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 { + cubeb * context; + cubeb_stream_params params; + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * instance; + void * user_ptr; + /* Number of frames that have been passed to the AudioTrack callback */ + long unsigned written; + int draining; +}; + +static void +audiotrack_refill(int event, void* user, void* info) +{ + cubeb_stream * stream = user; + switch (event) { + case EVENT_MORE_DATA: { + long got = 0; + struct Buffer * b = (struct Buffer*)info; + + if (stream->draining) { + return; + } + + got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount); + + stream->written += got; + + if (got != (long)b->frameCount) { + stream->draining = 1; + /* set a marker so we are notified when the are done draining, that is, + * when every frame has been played by android. */ + stream->context->klass.set_marker_position(stream->instance, stream->written); + } + + break; + } + case EVENT_UNDERRUN: + ALOG("underrun in cubeb backend."); + break; + case EVENT_LOOP_END: + assert(0 && "We don't support the loop feature of audiotrack."); + break; + case EVENT_MARKER: + assert(stream->draining); + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); + break; + case EVENT_NEW_POS: + assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack."); + break; + case EVENT_BUFFER_END: + assert(0 && "Should not happen."); + break; + } +} + +/* We are running on gingerbread if we found the gingerbread signature for + * getMinFrameCount */ +static int +audiotrack_version_is_gingerbread(cubeb * ctx) +{ + return ctx->klass.get_min_frame_count_gingerbread != NULL; +} + +int +audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count) +{ + status_t status; + /* Recent Android have a getMinFrameCount method. */ + if (!audiotrack_version_is_gingerbread(ctx)) { + status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate); + } else { + status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate); + } + if (status != 0) { + ALOG("error getting the min frame count"); + return CUBEB_ERROR; + } + return CUBEB_OK; +} + +int +audiotrack_init(cubeb ** context, char const * context_name) +{ + cubeb * ctx; + struct AudioTrack* c; + + assert(context); + *context = NULL; + + ctx = calloc(1, sizeof(*ctx)); + assert(ctx); + + /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android + * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on + * the first call to a dlsym'ed function. Somehow this does not happen when + * using only the name of the library. */ + ctx->library = dlopen("libmedia.so", RTLD_LAZY); + if (!ctx->library) { + ALOG("dlopen error: %s.", dlerror()); + free(ctx); + return CUBEB_ERROR; + } + + /* Recent Android first, then Gingerbread. */ + DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library); + DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library); + + DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library); + DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library); + + DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library); + + /* |getMinFrameCount| is available on gingerbread and ICS with different signatures. */ + DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library); + if (!ctx->klass.get_min_frame_count) { + DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library); + } + + DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library); + DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library); + DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library); + DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library); + DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, ctx->library); + + /* check that we have a combination of symbol that makes sense */ + c = &ctx->klass; + if(!(c->ctor && + c->dtor && c->latency && c->check && + /* at least one way to get the minimum frame count to request. */ + (c->get_min_frame_count || + c->get_min_frame_count_gingerbread) && + c->start && c->pause && c->get_position && c->set_marker_position)) { + ALOG("Could not find all the symbols we need."); + audiotrack_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->ops = &audiotrack_ops; + + *context = ctx; + + return CUBEB_OK; +} + +char const * +audiotrack_get_backend_id(cubeb * context) +{ + return "audiotrack"; +} + +static int +audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + assert(ctx && max_channels); + + /* The android mixer handles up to two channels, see + http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */ + *max_channels = 2; + + return CUBEB_OK; +} + +static int +audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) +{ + /* We always use the lowest latency possible when using this backend (see + * audiotrack_stream_init), so this value is not going to be used. */ + int r; + + r = audiotrack_get_min_frame_count(ctx, ¶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, stm->params.stream_type, stm->params.rate, + AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0, + audiotrack_refill, stm, 0, 0); + + assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad); + + if (ctx->klass.check(stm->instance)) { + ALOG("stream not initialized properly."); + audiotrack_stream_destroy(stm); + return CUBEB_ERROR; + } + + *stream = stm; + + return CUBEB_OK; +} + +void +audiotrack_stream_destroy(cubeb_stream * stream) +{ + assert(stream->context); + + stream->context->klass.dtor(stream->instance); + + free(stream->instance); + stream->instance = NULL; + free(stream); +} + +int +audiotrack_stream_start(cubeb_stream * stream) +{ + assert(stream->instance); + + stream->context->klass.start(stream->instance); + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED); + + return CUBEB_OK; +} + +int +audiotrack_stream_stop(cubeb_stream * stream) +{ + assert(stream->instance); + + stream->context->klass.pause(stream->instance); + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED); + + return CUBEB_OK; +} + +int +audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position) +{ + uint32_t p; + + assert(stream->instance && position); + stream->context->klass.get_position(stream->instance, &p); + *position = p; + + return CUBEB_OK; +} + +int +audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +{ + assert(stream->instance && latency); + + /* Android returns the latency in ms, we want it in frames. */ + *latency = stream->context->klass.latency(stream->instance); + /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */ + *latency = (*latency * stream->params.rate) / 1000; + + return 0; +} + +int +audiotrack_stream_set_volume(cubeb_stream * stream, float volume) +{ + status_t status; + + status = stream->context->klass.set_volume(stream->instance, volume, volume); + + if (status) { + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static struct cubeb_ops const audiotrack_ops = { + .init = audiotrack_init, + .get_backend_id = audiotrack_get_backend_id, + .get_max_channel_count = audiotrack_get_max_channel_count, + .get_min_latency = audiotrack_get_min_latency, + .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, + .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_get_position = audiotrack_stream_get_position, + .stream_get_latency = audiotrack_stream_get_latency, + .stream_set_volume = audiotrack_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; diff --git a/Externals/cubeb/src/cubeb_audiounit.cpp b/Externals/cubeb/src/cubeb_audiounit.cpp new file mode 100644 index 0000000000..3ee13553f2 --- /dev/null +++ b/Externals/cubeb/src/cubeb_audiounit.cpp @@ -0,0 +1,3350 @@ +/* + * 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" +#include "cubeb_panner.h" +#if !TARGET_OS_IPHONE +#include "cubeb_osx_run_loop.h" +#endif +#include "cubeb_resampler.h" +#include "cubeb_ring_array.h" +#include +#include +#include +#include + +#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"; + +#ifdef ALOGV +#undef ALOGV +#endif +#define ALOGV(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOGV(msg, ##__VA_ARGS__);}) + +#ifdef ALOG +#undef ALOG +#endif +#define ALOG(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOG(msg, ##__VA_ARGS__);}) + +/* Testing empirically, some headsets report a minimal latency that is very + * low, but this does not work in practice. Lie and say the minimum is 256 + * frames. */ +const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; +const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; + +void audiounit_stream_stop_internal(cubeb_stream * stm); +void audiounit_stream_start_internal(cubeb_stream * stm); +static void audiounit_close_stream(cubeb_stream *stm); +static int audiounit_setup_stream(cubeb_stream *stm); + +extern cubeb_ops const audiounit_ops; + +struct cubeb { + cubeb_ops const * ops = &audiounit_ops; + owned_critical_section mutex; + std::atomic active_streams{ 0 }; + uint32_t global_latency_frames = 0; + cubeb_device_collection_changed_callback collection_changed_callback = nullptr; + void * collection_changed_user_ptr = nullptr; + /* Differentiate input from output devices. */ + cubeb_device_type collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN; + std::vector devtype_device_array; + // The queue is asynchronously deallocated once all references to it are released + dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); + // Current used channel layout + cubeb_channel_layout layout; +}; + +static std::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 std::unique_ptr(acl, free); +} + +enum io_side { + INPUT, + OUTPUT, +}; + +static char const * +to_string(io_side side) +{ + switch (side) { + case INPUT: + return "input"; + case OUTPUT: + return "output"; + } +} + +struct cubeb_stream { + explicit cubeb_stream(cubeb * context); + + cubeb * context; + cubeb_data_callback data_callback = nullptr; + cubeb_state_callback state_callback = nullptr; + cubeb_device_changed_callback device_changed_callback = nullptr; + /* Stream creation parameters */ + cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + bool is_default_input; + AudioDeviceID input_device = 0; + AudioDeviceID output_device = 0; + /* User pointer of data_callback */ + void * user_ptr = nullptr; + /* 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 */ + std::unique_ptr input_linear_buffer; + owned_critical_section input_linear_buffer_lock; + // After the resampling some input data remains stored inside + // the resampler. This number is used in order to calculate + // the number of extra silence frames in input. + std::atomic available_input_frames{ 0 }; + /* Frames on input buffer */ + std::atomic input_buffer_frames{ 0 }; + /* Frame counters */ + std::atomic frames_played{ 0 }; + uint64_t frames_queued = 0; + std::atomic frames_read{ 0 }; + std::atomic shutdown{ true }; + std::atomic draining{ false }; + /* Latency requested by the user. */ + uint32_t latency_frames = 0; + std::atomic current_latency_frames{ 0 }; + uint64_t hw_latency_frames = UINT64_MAX; + std::atomic panning{ 0 }; + std::unique_ptr resampler; + /* This is true if a device change callback is currently running. */ + std::atomic switching_device{ false }; + std::atomic buffer_size_change_state{ false }; + AudioDeviceID aggregate_device_id = 0; // the aggregate device id + AudioObjectID plugin_id = 0; // used to create aggregate device + /* Mixer interface */ + std::unique_ptr mixer; +}; + +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_Mono: return CHANNEL_MONO; + case kAudioChannelLabel_Left: return CHANNEL_LEFT; + case kAudioChannelLabel_Right: return CHANNEL_RIGHT; + case kAudioChannelLabel_Center: return CHANNEL_CENTER; + case kAudioChannelLabel_LFEScreen: return CHANNEL_LFE; + case kAudioChannelLabel_LeftSurround: return CHANNEL_LS; + case kAudioChannelLabel_RightSurround: return CHANNEL_RS; + case kAudioChannelLabel_RearSurroundLeft: return CHANNEL_RLS; + case kAudioChannelLabel_RearSurroundRight: return CHANNEL_RRS; + case kAudioChannelLabel_CenterSurround: return CHANNEL_RCENTER; + case kAudioChannelLabel_Unknown: return CHANNEL_UNMAPPED; + default: return CHANNEL_INVALID; + } +} + +AudioChannelLabel +cubeb_channel_to_channel_label(cubeb_channel channel) +{ + switch (channel) { + case CHANNEL_MONO: return kAudioChannelLabel_Mono; + case CHANNEL_LEFT: return kAudioChannelLabel_Left; + case CHANNEL_RIGHT: return kAudioChannelLabel_Right; + case CHANNEL_CENTER: return kAudioChannelLabel_Center; + case CHANNEL_LFE: return kAudioChannelLabel_LFEScreen; + case CHANNEL_LS: return kAudioChannelLabel_LeftSurround; + case CHANNEL_RS: return kAudioChannelLabel_RightSurround; + case CHANNEL_RLS: return kAudioChannelLabel_RearSurroundLeft; + case CHANNEL_RRS: return kAudioChannelLabel_RearSurroundRight; + case CHANNEL_RCENTER: return kAudioChannelLabel_CenterSurround; + case CHANNEL_UNMAPPED: return kAudioChannelLabel_Unknown; + default: return kAudioChannelLabel_Unknown; + } +} + +#if TARGET_OS_IPHONE +typedef UInt32 AudioDeviceID; +typedef UInt32 AudioObjectID; + +#define AudioGetCurrentHostTime mach_absolute_time + +uint64_t +AudioConvertHostTimeToNanos(uint64_t host_time) +{ + static struct mach_timebase_info timebase_info; + static bool initialized = false; + if (!initialized) { + mach_timebase_info(&timebase_info); + initialized = true; + } + + long double answer = host_time; + if (timebase_info.numer != timebase_info.denom) { + answer *= timebase_info.numer; + answer /= timebase_info.denom; + } + return (uint64_t)answer; +} +#endif + +static int64_t +audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) +{ + if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { + return 0; + } + + uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); + uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + + return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; +} + +static void +audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) +{ + stm->mutex.assert_current_thread_owns(); + assert(stm->context->active_streams == 1); + stm->context->global_latency_frames = latency_frames; +} + +static void +audiounit_make_silent(AudioBuffer * ioData) +{ + assert(ioData); + assert(ioData->mData); + memset(ioData->mData, 0, ioData->mDataByteSize); +} + +static OSStatus +audiounit_render_input(cubeb_stream * stm, + AudioUnitRenderActionFlags * flags, + AudioTimeStamp const * tstamp, + UInt32 bus, + UInt32 input_frames) +{ + /* Create the AudioBufferList to store input. */ + AudioBufferList input_buffer_list; + input_buffer_list.mBuffers[0].mDataByteSize = + stm->input_desc.mBytesPerFrame * input_frames; + input_buffer_list.mBuffers[0].mData = nullptr; + input_buffer_list.mBuffers[0].mNumberChannels = stm->input_desc.mChannelsPerFrame; + input_buffer_list.mNumberBuffers = 1; + + /* Render input samples */ + OSStatus r = AudioUnitRender(stm->input_unit, + flags, + tstamp, + bus, + input_frames, + &input_buffer_list); + + if (r != noErr) { + LOG("AudioUnitRender rv=%d", r); + return r; + } + + /* Copy input data in linear buffer. */ + { + auto_lock l(stm->input_linear_buffer_lock); + 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; + stm->available_input_frames += input_frames; + + ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, total frames %d.", + 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->available_input_frames.load()); + + 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; + } + + 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. */ + { + auto_lock l(stm->input_linear_buffer_lock); + 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); + assert(outframes >= 0); + + // Reset input buffer + stm->input_linear_buffer->clear(); + } + + return noErr; +} + +static uint32_t +minimum_resampling_input_frames(cubeb_stream *stm) +{ + return ceilf(stm->input_hw_rate / stm->output_hw_rate * stm->input_buffer_frames); +} + +static bool +is_extra_input_needed(cubeb_stream * stm) +{ + /* If the output callback came first and this is a duplex stream, we need to + * fill in some additional silence in the resampler. + * Otherwise, if we had more than expected callbacks in a row, or we're currently + * switching, we add some silence as well to compensate for the fact that + * we're lacking some input data. */ + return stm->frames_read == 0 || + stm->available_input_frames.load() < minimum_resampling_input_frames(stm); +} + +static void +audiounit_mix_output_buffer(cubeb_stream * stm, + long output_frames, + void * output_buffer, + unsigned long output_buffer_length) +{ + cubeb_stream_params output_mixer_params = { + stm->output_stream_params.format, + stm->output_stream_params.rate, + CUBEB_CHANNEL_LAYOUT_MAPS[stm->context->layout].channels, + stm->context->layout + }; + + // The downmixing(from 5.1) supports in-place conversion, so we can use + // the same buffer for both input and output of the mixer. + cubeb_mixer_mix(stm->mixer.get(), output_frames, + output_buffer, output_buffer_length, + output_buffer, output_buffer_length, + &stm->output_stream_params, &output_mixer_params); +} + +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); + + ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input frames %d.", + stm, + (unsigned int) outBufferList->mNumberBuffers, + (unsigned int) outBufferList->mBuffers[0].mDataByteSize, + (unsigned int) outBufferList->mBuffers[0].mNumberChannels, + (unsigned int) output_frames, + stm->available_input_frames.load()); + + long input_frames = 0, input_frames_before_fill = 0; + void * output_buffer = NULL, * input_buffer = NULL; + + if (stm->shutdown) { + ALOG("(%p) output shutdown.", stm); + audiounit_make_silent(&outBufferList->mBuffers[0]); + return noErr; + } + + stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); + if (stm->draining) { + OSStatus r = AudioOutputUnitStop(stm->output_unit); + assert(r == 0); + if (stm->input_unit) { + r = AudioOutputUnitStop(stm->input_unit); + assert(r == 0); + } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + audiounit_make_silent(&outBufferList->mBuffers[0]); + return noErr; + } + /* Get output buffer. */ + output_buffer = outBufferList->mBuffers[0].mData; + /* If Full duplex get also input buffer */ + if (stm->input_unit != NULL) { + if (is_extra_input_needed(stm)) { + uint32_t min_input_frames = minimum_resampling_input_frames(stm); + { + auto_lock l(stm->input_linear_buffer_lock); + stm->input_linear_buffer->push_silence(min_input_frames * stm->input_desc.mChannelsPerFrame); + } + stm->available_input_frames += min_input_frames; + + ALOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," : + stm->switching_device ? "Device switching," : "Drop out,", min_input_frames); + } + 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; + // Number of input frames pushed inside resampler. + input_frames_before_fill = input_frames; + } + + /* 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) { + // Decrease counter by the number of frames used by resampler + stm->available_input_frames -= input_frames; + assert(stm->available_input_frames.load() >= 0); + // Pop from the buffer the frames pushed to the resampler. + auto_lock l(stm->input_linear_buffer_lock); + stm->input_linear_buffer->pop(input_frames_before_fill * 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; + } + + size_t outbpf = stm->output_desc.mBytesPerFrame; + stm->draining = (UInt32) outframes < output_frames; + stm->frames_played = stm->frames_queued; + stm->frames_queued += outframes; + + AudioFormatFlags outaff = stm->output_desc.mFormatFlags; + float panning = (stm->output_desc.mChannelsPerFrame == 2) ? + stm->panning.load(std::memory_order_relaxed) : 0.0f; + + /* Post process output samples. */ + if (stm->draining) { + /* Clear missing frames (silence) */ + memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf); + } + /* Pan stereo. */ + if (panning != 0.0f) { + if (outaff & kAudioFormatFlagIsFloat) { + cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning); + } else if (outaff & kAudioFormatFlagIsSignedInteger) { + cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning); + } + } + + /* Mixing */ + unsigned long output_buffer_length = outBufferList->mBuffers[0].mDataByteSize; + audiounit_mix_output_buffer(stm, output_frames, output_buffer, output_buffer_length); + + 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_get_output_device_id(AudioDeviceID * device_id) +{ + UInt32 size; + OSStatus r; + AudioObjectPropertyAddress output_device_address = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + size = sizeof(*device_id); + + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &output_device_address, + 0, + NULL, + &size, + device_id); + if (r != noErr) { + LOG("output_device_id rv=%d", r); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +audiounit_get_input_device_id(AudioDeviceID * device_id) +{ + UInt32 size; + OSStatus r; + AudioObjectPropertyAddress input_device_address = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + size = sizeof(*device_id); + + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &input_device_address, + 0, + NULL, + &size, + device_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); +static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); +static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); + +static int +audiounit_reinit_stream(cubeb_stream * stm) +{ + auto_lock context_lock(stm->context->mutex); + if (!stm->shutdown) { + audiounit_stream_stop_internal(stm); + } + + int r = audiounit_uninstall_device_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not uninstall the device changed callback", stm); + } + + { + auto_lock lock(stm->mutex); + float volume = 0.0; + int vol_rv = CUBEB_ERROR; + if (has_output(stm)) { + vol_rv = audiounit_stream_get_volume(stm, &volume); + } + + audiounit_close_stream(stm); + + if (audiounit_setup_stream(stm) != CUBEB_OK) { + LOG("(%p) Stream reinit failed.", stm); + return CUBEB_ERROR; + } + + if (vol_rv == CUBEB_OK) { + audiounit_stream_set_volume(stm, volume); + } + + // Reset input frames to force new stream pre-buffer + // silence if needed, check `is_extra_input_needed()` + stm->frames_read = 0; + + // If the stream was running, start it again. + if (!stm->shutdown) { + audiounit_stream_start_internal(stm); + } + } + return CUBEB_OK; +} + +static OSStatus +audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, + const AudioObjectPropertyAddress * addresses, + void * user) +{ + cubeb_stream * stm = (cubeb_stream*) user; + stm->switching_device = true; + + LOG("(%p) Audio device changed, %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", (unsigned int) i); + // Allow restart to choose the new default + stm->output_device = 0; + } + break; + case kAudioHardwarePropertyDefaultInputDevice: { + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice", (unsigned int) i); + // Allow restart to choose the new default + stm->input_device = 0; + } + break; + case kAudioDevicePropertyDeviceIsAlive: { + LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive", (unsigned int) i); + // If this is the default input device ignore the event, + // kAudioHardwarePropertyDefaultInputDevice will take care of the switch + if (stm->is_default_input) { + LOG("It's the default input device, ignore the event"); + return noErr; + } + // Allow restart to choose the new default. Event register only for input. + stm->input_device = 0; + } + break; + case kAudioDevicePropertyDataSource: { + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i); + return noErr; + } + default: + LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector); + return noErr; + } + } + + for (UInt32 i = 0; i < address_count; i++) { + switch(addresses[i].mSelector) { + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultInputDevice: + case kAudioDevicePropertyDeviceIsAlive: + /* fall through */ + case kAudioDevicePropertyDataSource: { + auto_lock lock(stm->mutex); + if (stm->device_changed_callback) { + stm->device_changed_callback(stm->user_ptr); + } + break; + } + } + } + + // Use a new thread, through the queue, to avoid deadlock when calling + // Get/SetProperties method from inside notify callback + dispatch_async(stm->context->serial_queue, ^() { + if (audiounit_reinit_stream(stm) != CUBEB_OK) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + LOG("(%p) Could not reopen the stream after switching.", stm); + } + stm->switching_device = false; + }); + + return noErr; +} + +OSStatus +audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) +{ + AudioObjectPropertyAddress address = { + selector, + scope, + kAudioObjectPropertyElementMaster + }; + + return AudioObjectAddPropertyListener(id, &address, listener, stm); +} + +OSStatus +audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id, + AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, + AudioObjectPropertyListenerProc listener) +{ + AudioObjectPropertyAddress address = { + selector, + scope, + kAudioObjectPropertyElementMaster + }; + + return AudioObjectRemovePropertyListener(id, &address, listener, stm); +} + +static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); + +static AudioObjectID +audiounit_get_input_device_id(cubeb_stream * stm) +{ + AudioObjectID input_dev = stm->input_device ? stm->input_device : + audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + assert(input_dev); + return input_dev; +} + +static int +audiounit_install_device_changed_callback(cubeb_stream * stm) +{ + OSStatus r; + + if (stm->output_unit) { + /* This event will notify us when the data source on the same device changes, + * for example when the user plugs in a normal (non-usb) headset in the + * headphone jack. */ + AudioDeviceID output_dev_id; + r = audiounit_get_output_device_id(&output_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d", r); + return CUBEB_ERROR; + } + } + + if (stm->input_unit) { + /* This event will notify us when the data source on the input device changes. */ + AudioDeviceID input_dev_id; + r = audiounit_get_input_device_id(&input_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); + if (r != noErr) { + LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d", r); + return CUBEB_ERROR; + } + + /* Event to notify when the input is going away. */ + AudioDeviceID dev = audiounit_get_input_device_id(stm); + r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d", r); + return CUBEB_ERROR; + } + } + + return CUBEB_OK; +} + +static int +audiounit_install_system_changed_callback(cubeb_stream * stm) +{ + OSStatus r; + + if (stm->output_unit) { + /* This event will notify us when the default audio device changes, + * for example when the user plugs in a USB headset and the system chooses it + * automatically as the default, or when another device is chosen in the + * dropdown list. */ + r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r); + return CUBEB_ERROR; + } + } + + if (stm->input_unit) { + /* This event will notify us when the default input device changes. */ + r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); + return CUBEB_ERROR; + } + } + + return CUBEB_OK; +} + +static int +audiounit_uninstall_device_changed_callback(cubeb_stream * stm) +{ + OSStatus r; + + if (stm->output_unit) { + AudioDeviceID output_dev_id; + r = audiounit_get_output_device_id(&output_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } + } + + if (stm->input_unit) { + AudioDeviceID input_dev_id; + r = audiounit_get_input_device_id(&input_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } + + AudioDeviceID dev = audiounit_get_input_device_id(stm); + r = audiounit_remove_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d", r); + return CUBEB_ERROR; + } + } + return CUBEB_OK; +} + +static int +audiounit_uninstall_system_changed_callback(cubeb_stream * stm) +{ + OSStatus r; + + if (stm->output_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } + } + + if (stm->input_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } + } + return CUBEB_OK; +} + +/* Get the acceptable buffer size (in frames) that this device can work with. */ +static int +audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) +{ + UInt32 size; + OSStatus r; + AudioDeviceID output_device_id; + AudioObjectPropertyAddress output_device_buffer_size_range = { + kAudioDevicePropertyBufferFrameSizeRange, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + LOG("Could not get default output device id."); + return CUBEB_ERROR; + } + + /* Get the buffer size range this device supports */ + size = sizeof(*latency_range); + + r = AudioObjectGetPropertyData(output_device_id, + &output_device_buffer_size_range, + 0, + NULL, + &size, + latency_range); + if (r != noErr) { + 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) +{ + AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + AudioDeviceID devid; + UInt32 size; + + if (type == CUBEB_DEVICE_TYPE_OUTPUT) { + adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + } else if (type == CUBEB_DEVICE_TYPE_INPUT) { + adr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + } else { + return kAudioObjectUnknown; + } + + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) { + return kAudioObjectUnknown; + } + + return devid; +} + +int +audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ +#if TARGET_OS_IPHONE + //TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels] + *max_channels = 2; +#else + UInt32 size; + OSStatus r; + AudioDeviceID output_device_id; + AudioStreamBasicDescription stream_format; + AudioObjectPropertyAddress stream_format_address = { + kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + assert(ctx && max_channels); + + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + return CUBEB_ERROR; + } + + size = sizeof(stream_format); + + r = AudioObjectGetPropertyData(output_device_id, + &stream_format_address, + 0, + NULL, + &size, + &stream_format); + if (r != noErr) { + 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 = std::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 + }; + + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + return CUBEB_ERROR; + } + + size = sizeof(fsamplerate); + r = AudioObjectGetPropertyData(output_device_id, + &samplerate_address, + 0, + NULL, + &size, + &fsamplerate); + + if (r != noErr) { + return CUBEB_ERROR; + } + + *rate = static_cast(fsamplerate); +#endif + return CUBEB_OK; +} + +static cubeb_channel_layout +audiounit_convert_channel_layout(AudioChannelLayout * layout) +{ + if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) { + // kAudioChannelLayoutTag_UseChannelBitmap + // kAudioChannelLayoutTag_Mono + // kAudioChannelLayoutTag_Stereo + // .... + LOG("Only handle UseChannelDescriptions for now.\n"); + return CUBEB_LAYOUT_UNDEFINED; + } + + // This devices has more channels that we can support, bail out. + if (layout->mNumberChannelDescriptions >= CHANNEL_MAX) { + LOG("Audio device has more than %d channels, bailing out.", CHANNEL_MAX); + return CUBEB_LAYOUT_UNDEFINED; + } + + cubeb_channel_map cm; + cm.channels = layout->mNumberChannelDescriptions; + for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) { + cm.map[i] = channel_label_to_cubeb_channel(layout->mChannelDescriptions[i].mChannelLabel); + } + + return cubeb_channel_map_to_layout(&cm); +} + +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); + return CUBEB_LAYOUT_UNDEFINED; + } + 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 cubeb_channel_layout +audiounit_get_preferred_channel_layout() +{ + OSStatus rv = noErr; + UInt32 size = 0; + AudioDeviceID id; + + if (audiounit_get_output_device_id(&id) != CUBEB_OK) { + return CUBEB_LAYOUT_UNDEFINED; + } + + AudioObjectPropertyAddress adr = { kAudioDevicePropertyPreferredChannelLayout, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster }; + rv = AudioObjectGetPropertyDataSize(id, &adr, 0, NULL, &size); + if (rv != noErr) { + return CUBEB_LAYOUT_UNDEFINED; + } + assert(size > 0); + + auto layout = make_sized_audio_channel_layout(size); + rv = AudioObjectGetPropertyData(id, &adr, 0, NULL, &size, layout.get()); + if (rv != noErr) { + return CUBEB_LAYOUT_UNDEFINED; + } + + return audiounit_convert_channel_layout(layout.get()); +} + +static int audiounit_create_unit(AudioUnit * unit, io_side side, AudioDeviceID device); + +static int +audiounit_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout) +{ + // The preferred layout is only returned when the connected sound device + // (e.g. ASUS Xonar U7), has preferred layout setting. + // For default output on Mac, there is no preferred channel layout, + // so it might return UNDEFINED. + *layout = audiounit_get_preferred_channel_layout(); + + // If the preferred channel layout is UNDEFINED, then we try to access the + // current applied channel layout. + if (*layout == CUBEB_LAYOUT_UNDEFINED) { + // If we already have at least one cubeb stream, then the current channel + // layout must be updated. We can return it directly. + if (ctx->active_streams) { + *layout = ctx->layout; + return CUBEB_OK; + } + + // If there is no existed stream, then we create a default ouput unit and + // use it to get the current used channel layout. + AudioUnit output_unit = nullptr; + audiounit_create_unit(&output_unit, OUTPUT, 0); + *layout = audiounit_get_current_channel_layout(output_unit); + } + + if (*layout == CUBEB_LAYOUT_UNDEFINED) { + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static OSStatus audiounit_remove_device_listener(cubeb * context); + +static void +audiounit_destroy(cubeb * ctx) +{ + // Disabling this assert for bug 1083664 -- we seem to leak a stream + // assert(ctx->active_streams == 0); + if (ctx->active_streams > 0) { + LOG("(%p) API misuse, %d streams active when context destroyed!", ctx, ctx->active_streams.load()); + } + + { + auto_lock lock(ctx->mutex); + /* Unregister the callback if necessary. */ + if (ctx->collection_changed_callback) { + audiounit_remove_device_listener(ctx); + } + } + + 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 only handle downmixing for now. + // The audio rendering mechanism on OS X will drop the extra channels beyond + // the channels that audio device can provide, so we need to downmix the + // audio data by ourselves to keep all the information. + stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format, + CUBEB_MIXER_DIRECTION_DOWNMIX)); +} + +static int +audiounit_set_channel_layout(AudioUnit unit, + io_side side, + const cubeb_stream_params * stream_params) +{ + if (side != OUTPUT) { + return CUBEB_ERROR; + } + + assert(stream_params->layout != CUBEB_LAYOUT_UNDEFINED); + assert(stream_params->channels == CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels); + + OSStatus r; + size_t size = sizeof(AudioChannelLayout); + auto layout = make_sized_audio_channel_layout(size); + + switch (stream_params->layout) { + case CUBEB_LAYOUT_DUAL_MONO: + case CUBEB_LAYOUT_STEREO: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; + break; + case CUBEB_LAYOUT_MONO: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_Mono; + break; + case CUBEB_LAYOUT_3F: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_3_0; + break; + case CUBEB_LAYOUT_2F1: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_2_1; + break; + case CUBEB_LAYOUT_3F1: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_3_1; + break; + case CUBEB_LAYOUT_2F2: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_2_2; + break; + case CUBEB_LAYOUT_3F2: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_3_2; + break; + case CUBEB_LAYOUT_3F2_LFE: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_5_1; + break; + default: + layout->mChannelLayoutTag = kAudioChannelLayoutTag_Unknown; + break; + } + + // For those layouts that can't be matched to coreaudio's predefined layout, + // we use customized layout. + if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_Unknown) { + size = offsetof(AudioChannelLayout, mChannelDescriptions[stream_params->channels]); + layout = make_sized_audio_channel_layout(size); + layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; + layout->mNumberChannelDescriptions = stream_params->channels; + for (UInt32 i = 0 ; i < stream_params->channels ; ++i) { + layout->mChannelDescriptions[i].mChannelLabel = + cubeb_channel_to_channel_label(CHANNEL_INDEX_TO_ORDER[stream_params->layout][i]); + layout->mChannelDescriptions[i].mChannelFlags = kAudioChannelFlags_AllOff; + } + } + + r = AudioUnitSetProperty(unit, + kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Input, + AU_OUT_BUS, + 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 == INPUT) { + return; + } + + audiounit_set_channel_layout(stm->output_unit, OUTPUT, &stm->output_stream_params); + + // Update the current used channel layout for the cubeb context. + // Notice that this channel layout may be different from the layout we set above, + // because OSX doesn't return error when the output device can NOT provide + // our desired layout. Thus, we update the layout evertime when the cubeb_stream + // is created and use it when we need to mix audio data. + stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit); +} + +static std::vector +audiounit_get_sub_devices(AudioDeviceID device_id) +{ + std::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("AudioHardwareGetPropertyInfo/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("AudioHardwareGetProperty/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("CubebAggregateDevice_%llx"), 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.CubebAggregateDevice_%llx"), time_id); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceUIDKey), aggregate_device_UID); + CFRelease(aggregate_device_UID); + + int private_key = 1; + CFNumberRef aggregate_device_private_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_key); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsPrivateKey), aggregate_device_private_key); + CFRelease(aggregate_device_private_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; +} + +static CFStringRef +get_device_name(AudioDeviceID id) +{ + UInt32 size = sizeof(CFStringRef); + CFStringRef UIname; + 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 std::vector input_sub_devices = audiounit_get_sub_devices(input_device_id); + const std::vector output_sub_devices = audiounit_get_sub_devices(output_device_id); + + CFMutableArrayRef aggregate_sub_devices_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + 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); + } + 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); + } + + 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); + 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 std::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 (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); + 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 }; + + for (UInt32 i = 0; 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); + +/* + * 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); + audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + return CUBEB_ERROR; + } + + AudioDeviceID input_device_id = audiounit_get_input_device_id(stm); + AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, input_device_id, 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; + } + + return CUBEB_OK; +} + +static int +audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID aggregate_device_id) +{ + 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; + } + + return CUBEB_OK; +} + +static int +audiounit_new_unit_instance(AudioUnit * unit, io_side side, AudioDeviceID device) +{ + AudioComponentDescription desc; + AudioComponent comp; + OSStatus rv; + + desc.componentType = kAudioUnitType_Output; +#if TARGET_OS_IPHONE + bool use_default_output = false; + desc.componentSubType = kAudioUnitSubType_RemoteIO; +#else + // Use the DefaultOutputUnit for output when no device is specified + // so we retain automatic output device switching when the default + // changes. Once we have complete support for device notifications + // and switching, we can use the AUHAL for everything. + bool use_default_output = device == 0 && (side == OUTPUT); + if (use_default_output) { + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + } else { + desc.componentSubType = kAudioUnitSubType_HALOutput; + } +#endif + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + comp = AudioComponentFindNext(NULL, &desc); + if (comp == NULL) { + LOG("Could not find matching audio hardware."); + return CUBEB_ERROR; + } + + rv = AudioComponentInstanceNew(comp, unit); + if (rv != noErr) { + 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 == INPUT) ? kAudioUnitScope_Input : kAudioUnitScope_Output, + (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, io_side side, AudioDeviceID device) +{ + AudioDeviceID devid; + OSStatus rv; + int r; + + assert(*unit == nullptr); + r = audiounit_new_unit_instance(unit, side, device); + if (r != CUBEB_OK) { + return r; + } + assert(*unit); + +#if TARGET_OS_IPHONE + bool use_default_output = false; +#else + bool use_default_output = device == 0 && (side == OUTPUT); +#endif + + if (!use_default_output) { + switch (side) { + case INPUT: + r = audiounit_enable_unit_scope(unit, INPUT, ENABLE); + if (r != CUBEB_OK) { + LOG("Failed to enable audiounit input scope "); + return r; + } + r = audiounit_enable_unit_scope(unit, OUTPUT, DISABLE); + if (r != CUBEB_OK) { + LOG("Failed to disable audiounit output scope "); + return r; + } + break; + case OUTPUT: + r = audiounit_enable_unit_scope(unit, OUTPUT, ENABLE); + if (r != CUBEB_OK) { + LOG("Failed to enable audiounit output scope "); + return r; + } + r = audiounit_enable_unit_scope(unit, INPUT, DISABLE); + if (r != CUBEB_OK) { + LOG("Failed to disable audiounit input scope "); + return r; + } + break; + default: + assert(false); + } + + if (device == 0) { + assert(side == INPUT); + devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + } else { + devid = device; + } + + rv = AudioUnitSetProperty(*unit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &devid, 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->input_buffer_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(stm->context->active_streams > 0); + if (stm->context->active_streams == 1) { + return std::max(std::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 = std::max(std::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 = std::max(std::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 = std::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 std::max(std::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 == 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 + stm->input_buffer_frames = stm->latency_frames; + r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); + if (r != CUBEB_OK) { + LOG("(%p) Error in change input buffer size.", stm); + return CUBEB_ERROR; + } + + AudioStreamBasicDescription src_desc = stm->input_desc; + /* Input AudioUnit must be configured with device's sample rate. + we will resample inside input callback. */ + src_desc.mSampleRate = stm->input_hw_rate; + + r = AudioUnitSetProperty(stm->input_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + AU_IN_BUS, + &src_desc, + sizeof(AudioStreamBasicDescription)); + if (r != noErr) { + LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); + return CUBEB_ERROR; + } + + /* Frames per buffer in the input callback. */ + r = AudioUnitSetProperty(stm->input_unit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + AU_IN_BUS, + &stm->input_buffer_frames, + sizeof(UInt32)); + 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; + } + + 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); + + 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, 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; + } + + audiounit_layout_init(stm, OUTPUT); + audiounit_init_mixer(stm); + + LOG("(%p) Output audiounit init successfully.", stm); + return CUBEB_OK; +} + +static int +audiounit_setup_stream(cubeb_stream * stm) +{ + stm->mutex.assert_current_thread_owns(); + + int r = 0; + + AudioDeviceID in_dev = stm->input_device; + AudioDeviceID out_dev = stm->output_device; + if (has_input(stm) && has_output(stm)) { + r = audiounit_create_aggregate_device(stm); + if (r != CUBEB_OK) { + stm->aggregate_device_id = 0; + 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 = out_dev = stm->aggregate_device_id; + } + } + + if (has_input(stm)) { + r = audiounit_create_unit(&stm->input_unit, + INPUT, + in_dev); + 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, + OUTPUT, + out_dev); + if (r != CUBEB_OK) { + LOG("(%p) AudioUnit creation for output failed.", stm); + return r; + } + } + + /* Latency cannot change if another stream is operating in parallel. In this case + * latecy is set to the other stream value. */ + if (stm->context->active_streams > 1) { + LOG("(%p) More than one active stream, use global latency.", stm); + stm->latency_frames = stm->context->global_latency_frames; + } else { + /* Silently clamp the latency down to the platform default, because we + * synthetize the clock from the callbacks, and we want the clock to update + * often. */ + stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); + assert(stm->latency_frames); // Ungly error check + audiounit_set_global_latency(stm, stm->latency_frames); + } + + /* 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; + } + } + + if (stm->input_unit && stm->output_unit) { + // According to the I/O hardware rate it is expected a specific pattern of callbacks + // for example is input is 44100 and output is 48000 we expected no more than 2 + // out callback in a row. + stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate); + } + + r = audiounit_install_device_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not install the device change callback.", stm); + return r; + } + + + return CUBEB_OK; +} + +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 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) +{ + std::unique_ptr stm(nullptr, audiounit_stream_destroy); + int r; + + assert(context); + *stream = NULL; + assert(latency_frames > 0); + if ((input_device && !input_stream_params) || + (output_device && !output_stream_params)) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + stm.reset(new cubeb_stream(context)); + + /* 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_stream_params) { + stm->input_stream_params = *input_stream_params; + stm->input_device = reinterpret_cast(input_device); + stm->is_default_input = stm->input_device == 0 || + (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == stm->input_device); + } + if (output_stream_params) { + stm->output_stream_params = *output_stream_params; + stm->output_device = reinterpret_cast(output_device); + } + + auto_lock context_lock(context->mutex); + { + // It's not critical to lock here, because no other thread has been started + // yet, but it allows to assert that the lock has been taken in + // `audiounit_setup_stream`. + context->active_streams += 1; + auto_lock lock(stm->mutex); + r = audiounit_setup_stream(stm.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) { + audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + stm->aggregate_device_id = 0; + } +} + +static void +audiounit_stream_destroy(cubeb_stream * stm) +{ + stm->shutdown = true; + + int r = audiounit_uninstall_system_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not uninstall the device changed callback", stm); + } + + r = audiounit_uninstall_device_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not uninstall the device changed callback", stm); + } + + auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + // Execute close in serial queue to avoid collision + // with reinit when un/plug devices + dispatch_sync(stm->context->serial_queue, ^() { + auto_lock lock(stm->mutex); + audiounit_close_stream(stm); + }); + + assert(stm->context->active_streams >= 1); + stm->context->active_streams -= 1; + + LOG("Cubeb stream (%p) destroyed successful.", stm); + delete stm; +} + +void +audiounit_stream_start_internal(cubeb_stream * stm) +{ + OSStatus r; + if (stm->input_unit != NULL) { + r = AudioOutputUnitStart(stm->input_unit); + assert(r == 0); + } + if (stm->output_unit != NULL) { + r = AudioOutputUnitStart(stm->output_unit); + assert(r == 0); + } +} + +static int +audiounit_stream_start(cubeb_stream * stm) +{ + auto_lock context_lock(stm->context->mutex); + stm->shutdown = false; + stm->draining = false; + + audiounit_stream_start_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + + LOG("Cubeb stream (%p) started successfully.", stm); + return CUBEB_OK; +} + +void +audiounit_stream_stop_internal(cubeb_stream * stm) +{ + OSStatus r; + if (stm->input_unit != NULL) { + r = AudioOutputUnitStop(stm->input_unit); + assert(r == 0); + } + if (stm->output_unit != NULL) { + r = AudioOutputUnitStop(stm->output_unit); + assert(r == 0); + } +} + +static int +audiounit_stream_stop(cubeb_stream * stm) +{ + auto_lock context_lock(stm->context->mutex); + stm->shutdown = true; + + audiounit_stream_stop_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + + LOG("Cubeb stream (%p) stopped successfully.", stm); + return CUBEB_OK; +} + +static int +audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + auto_lock lock(stm->mutex); + + *position = stm->frames_played; + return CUBEB_OK; +} + +int +audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ +#if TARGET_OS_IPHONE + //TODO + return CUBEB_ERROR_NOT_SUPPORTED; +#else + auto_lock lock(stm->mutex); + if (stm->hw_latency_frames == UINT64_MAX) { + UInt32 size; + uint32_t device_latency_frames, device_safety_offset; + double unit_latency_sec; + AudioDeviceID output_device_id; + OSStatus r; + AudioObjectPropertyAddress latency_address = { + kAudioDevicePropertyLatency, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + AudioObjectPropertyAddress safety_offset_address = { + kAudioDevicePropertySafetyOffset, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + r = audiounit_get_output_device_id(&output_device_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + size = sizeof(unit_latency_sec); + r = AudioUnitGetProperty(stm->output_unit, + kAudioUnitProperty_Latency, + kAudioUnitScope_Global, + 0, + &unit_latency_sec, + &size); + if (r != noErr) { + LOG("AudioUnitGetProperty/kAudioUnitProperty_Latency rv=%d", r); + return CUBEB_ERROR; + } + + size = sizeof(device_latency_frames); + r = AudioObjectGetPropertyData(output_device_id, + &latency_address, + 0, + NULL, + &size, + &device_latency_frames); + if (r != noErr) { + LOG("AudioUnitGetPropertyData/latency_frames rv=%d", r); + return CUBEB_ERROR; + } + + size = sizeof(device_safety_offset); + r = AudioObjectGetPropertyData(output_device_id, + &safety_offset_address, + 0, + NULL, + &size, + &device_safety_offset); + if (r != noErr) { + LOG("AudioUnitGetPropertyData/safety_offset rv=%d", r); + return CUBEB_ERROR; + } + + /* This part is fixed and depend on the stream parameter and the hardware. */ + stm->hw_latency_frames = + static_cast(unit_latency_sec * stm->output_desc.mSampleRate) + + device_latency_frames + + device_safety_offset; + } + + *latency = stm->hw_latency_frames + stm->current_latency_frames; + + return CUBEB_OK; +#endif +} + +static int +audiounit_stream_get_volume(cubeb_stream * stm, float * volume) +{ + assert(stm->output_unit); + OSStatus r = AudioUnitGetParameter(stm->output_unit, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, volume); + if (r != noErr) { + LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r); + return CUBEB_ERROR; + } + return CUBEB_OK; +} + +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; +} + +int audiounit_stream_set_panning(cubeb_stream * stm, float panning) +{ + if (stm->output_desc.mChannelsPerFrame > 2) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + stm->panning.store(panning, std::memory_order_relaxed); + return CUBEB_OK; +} + +int audiounit_stream_get_current_device(cubeb_stream * stm, + cubeb_device ** const device) +{ +#if TARGET_OS_IPHONE + //TODO + return CUBEB_ERROR_NOT_SUPPORTED; +#else + OSStatus r; + UInt32 size; + UInt32 data; + char strdata[4]; + AudioDeviceID output_device_id; + AudioDeviceID input_device_id; + + AudioObjectPropertyAddress datasource_address = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + AudioObjectPropertyAddress datasource_address_input = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster + }; + + *device = NULL; + + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + return CUBEB_ERROR; + } + + *device = new cubeb_device; + if (!*device) { + return CUBEB_ERROR; + } + PodZero(*device, 1); + + size = sizeof(UInt32); + /* This fails with some USB headset, so simply return an empty string. */ + r = AudioObjectGetPropertyData(output_device_id, + &datasource_address, + 0, NULL, &size, &data); + if (r != noErr) { + size = 0; + data = 0; + } + + (*device)->output_name = new char[size + 1]; + if (!(*device)->output_name) { + return CUBEB_ERROR; + } + + // Turn the four chars packed into a uint32 into a string + strdata[0] = (char)(data >> 24); + strdata[1] = (char)(data >> 16); + strdata[2] = (char)(data >> 8); + strdata[3] = (char)(data); + + memcpy((*device)->output_name, strdata, size); + (*device)->output_name[size] = '\0'; + + if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) { + return CUBEB_ERROR; + } + + size = sizeof(UInt32); + r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data); + if (r != noErr) { + LOG("(%p) Error when getting device !", stm); + size = 0; + data = 0; + } + + (*device)->input_name = new char[size + 1]; + if (!(*device)->input_name) { + return CUBEB_ERROR; + } + + // Turn the four chars packed into a uint32 into a string + strdata[0] = (char)(data >> 24); + strdata[1] = (char)(data >> 16); + strdata[2] = (char)(data >> 8); + strdata[3] = (char)(data); + + memcpy((*device)->input_name, strdata, size); + (*device)->input_name[size] = '\0'; + + return CUBEB_OK; +#endif +} + +int audiounit_stream_device_destroy(cubeb_stream * /* stream */, + cubeb_device * device) +{ + delete [] device->output_name; + delete [] device->input_name; + delete device; + return CUBEB_OK; +} + +int audiounit_stream_register_device_changed_callback(cubeb_stream * stream, + cubeb_device_changed_callback device_changed_callback) +{ + /* Note: second register without unregister first causes 'nope' error. + * Current implementation requires unregister before register a new cb. */ + assert(!stream->device_changed_callback); + + auto_lock lock(stream->mutex); + + stream->device_changed_callback = device_changed_callback; + + return CUBEB_OK; +} + +static OSStatus +audiounit_get_devices(std::vector & devices) +{ + OSStatus ret; + UInt32 size = 0; + AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); + if (ret != noErr) { + return ret; + } + + uint32_t count = static_cast(size / sizeof(AudioObjectID)); + if (count == 0) { + return -1; + } + assert(devices.empty()); + devices.resize(count); + + ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, devices.data()); + if (ret != noErr) { + devices.clear(); + } + + return ret; +} + +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); + std::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, offset; + AudioStreamID sid[1]; + + adr.mSelector = kAudioDevicePropertyLatency; + size = sizeof(UInt32); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr) { + dev = 0; + } + + adr.mSelector = kAudioDevicePropertyStreams; + size = sizeof(sid); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) { + adr.mSelector = kAudioStreamPropertyLatency; + size = sizeof(UInt32); + AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream); + } + + adr.mSelector = kAudioDevicePropertySafetyOffset; + size = sizeof(UInt32); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) { + offset = 0; + } + + return dev + stream + offset; +} + +static int +audiounit_create_device_from_hwdev(cubeb_device_info * ret, AudioObjectID devid, cubeb_device_type type) +{ + AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster }; + UInt32 size, ch, latency; + CFStringRef str = NULL; + AudioValueRange range; + + if (type == CUBEB_DEVICE_TYPE_OUTPUT) { + adr.mScope = kAudioDevicePropertyScopeOutput; + } else if (type == CUBEB_DEVICE_TYPE_INPUT) { + adr.mScope = kAudioDevicePropertyScopeInput; + } else { + return CUBEB_ERROR; + } + + ch = audiounit_get_channel_count(devid, adr.mScope); + if (ch == 0) { + return CUBEB_ERROR; + } + + PodZero(ret, 1); + + size = sizeof(CFStringRef); + adr.mSelector = kAudioDevicePropertyDeviceUID; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { + ret->device_id = audiounit_strref_to_cstr_utf8(str); + static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)), "cubeb_devid can't represent devid"); + ret->devid = reinterpret_cast(devid); + ret->group_id = ret->device_id; + CFRelease(str); + } + + size = sizeof(CFStringRef); + adr.mSelector = kAudioObjectPropertyName; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { + UInt32 ds; + size = sizeof(UInt32); + adr.mSelector = kAudioDevicePropertyDataSource; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) { + CFStringRef dsname; + AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) }; + adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; + size = sizeof(AudioValueTranslation); + // If there is a datasource for this device, use it instead of the device + // name. + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) { + CFRelease(str); + str = dsname; + } + } + + ret->friendly_name = audiounit_strref_to_cstr_utf8(str); + CFRelease(str); + } + + size = sizeof(CFStringRef); + adr.mSelector = kAudioObjectPropertyManufacturer; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { + ret->vendor_name = audiounit_strref_to_cstr_utf8(str); + CFRelease(str); + } + + ret->type = type; + ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret->preferred = (devid == audiounit_get_default_device_id(type)) ? + CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; + + ret->max_channels = ch; + ret->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ + /* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */ + ret->default_format = CUBEB_DEVICE_FMT_F32NE; + audiounit_get_available_samplerate(devid, adr.mScope, + &ret->min_rate, &ret->max_rate, &ret->default_rate); + + latency = audiounit_get_device_presentation_latency(devid, adr.mScope); + + adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + size = sizeof(AudioValueRange); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) { + ret->latency_lo = latency + range.mMinimum; + ret->latency_hi = latency + range.mMaximum; + } else { + ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */ + ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */ + } + + return CUBEB_OK; +} + +static int +audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, + cubeb_device_collection * collection) +{ + std::vector hwdevs; + uint32_t i; + OSStatus err; + + err = audiounit_get_devices(hwdevs); + if (err != noErr) { + return CUBEB_ERROR; + } + + auto devices = new cubeb_device_info[hwdevs.size()]; + collection->count = 0; + + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + for (i = 0; i < hwdevs.size(); i++) { + auto device = &devices[collection->count]; + auto err = audiounit_create_device_from_hwdev(device, hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT); + if (err != CUBEB_OK) { + continue; + } + collection->count += 1; + } + } + + if (type & CUBEB_DEVICE_TYPE_INPUT) { + for (i = 0; i < hwdevs.size(); i++) { + auto device = &devices[collection->count]; + auto err = audiounit_create_device_from_hwdev(device, hwdevs[i], CUBEB_DEVICE_TYPE_INPUT); + if (err != CUBEB_OK) { + continue; + } + collection->count += 1; + } + } + + if (collection->count > 0) { + collection->device = devices; + } else { + delete [] devices; + collection->device = NULL; + } + + return CUBEB_OK; +} + +static int +audiounit_device_collection_destroy(cubeb * /* context */, + cubeb_device_collection * collection) +{ + for (size_t i = 0; i < collection->count; i++) { + delete [] collection->device[i].device_id; + delete [] collection->device[i].friendly_name; + delete [] collection->device[i].vendor_name; + } + delete [] collection->device; + + return CUBEB_OK; +} + +static std::vector +audiounit_get_devices_of_type(cubeb_device_type devtype) +{ + AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + UInt32 size = 0; + OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); + if (ret != noErr) { + return std::vector(); + } + /* Total number of input and output devices. */ + uint32_t count = (uint32_t)(size / sizeof(AudioObjectID)); + + std::vector devices(count); + ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, devices.data()); + if (ret != noErr) { + return std::vector(); + } + /* Expected sorted but did not find anything in the docs. */ + std::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; + + std::vector devices_in_scope; + for (uint32_t i = 0; i < count; ++i) { + /* For device in the given scope channel must be > 0. */ + if (audiounit_get_channel_count(devices[i], scope) > 0) { + devices_in_scope.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); + auto_lock lock(context->mutex); + + if (context->collection_changed_callback == NULL) { + /* Listener removed while waiting in mutex, abort. */ + return noErr; + } + + /* Differentiate input from output changes. */ + if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT || + context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) { + std::vector devices = audiounit_get_devices_of_type(context->collection_changed_devtype); + /* When count is the same examine the devid for the case of coalescing. */ + if (context->devtype_device_array == devices) { + /* Device changed for the other scope, ignore. */ + return noErr; + } + /* Device on desired scope changed. */ + context->devtype_device_array = devices; + } + + context->collection_changed_callback(context, context->collection_changed_user_ptr); + return noErr; +} + +static OSStatus +audiounit_add_device_listener(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + /* Note: second register without unregister first causes 'nope' error. + * Current implementation requires unregister before register a new cb. */ + assert(context->collection_changed_callback == NULL); + + AudioObjectPropertyAddress devAddr; + devAddr.mSelector = kAudioHardwarePropertyDevices; + devAddr.mScope = kAudioObjectPropertyScopeGlobal; + devAddr.mElement = kAudioObjectPropertyElementMaster; + + OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &devAddr, + audiounit_collection_changed_callback, + context); + if (ret == noErr) { + /* Expected empty after unregister. */ + assert(context->devtype_device_array.empty()); + /* Listener works for input and output. + * When requested one of them we need to differentiate. */ + if (devtype == CUBEB_DEVICE_TYPE_INPUT || + devtype == CUBEB_DEVICE_TYPE_OUTPUT) { + /* Used to differentiate input from output device changes. */ + context->devtype_device_array = audiounit_get_devices_of_type(devtype); + } + context->collection_changed_devtype = devtype; + context->collection_changed_callback = collection_changed_callback; + context->collection_changed_user_ptr = user_ptr; + } + return ret; +} + +static OSStatus +audiounit_remove_device_listener(cubeb * context) +{ + AudioObjectPropertyAddress devAddr; + devAddr.mSelector = kAudioHardwarePropertyDevices; + devAddr.mScope = kAudioObjectPropertyScopeGlobal; + devAddr.mElement = kAudioObjectPropertyElementMaster; + + /* Note: unregister a non registered cb is not a problem, not checking. */ + OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &devAddr, + audiounit_collection_changed_callback, + context); + if (ret == noErr) { + /* Reset all values. */ + context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN; + context->collection_changed_callback = NULL; + context->collection_changed_user_ptr = NULL; + context->devtype_device_array.clear(); + } + return ret; +} + +int audiounit_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + OSStatus ret; + auto_lock lock(context->mutex); + if (collection_changed_callback) { + ret = audiounit_add_device_listener(context, devtype, + collection_changed_callback, + user_ptr); + } else { + ret = audiounit_remove_device_listener(context); + } + return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR; +} + +cubeb_ops const audiounit_ops = { + /*.init =*/ audiounit_init, + /*.get_backend_id =*/ audiounit_get_backend_id, + /*.get_max_channel_count =*/ audiounit_get_max_channel_count, + /*.get_min_latency =*/ audiounit_get_min_latency, + /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate, + /*.get_preferred_channel_layout =*/ audiounit_get_preferred_channel_layout, + /*.enumerate_devices =*/ audiounit_enumerate_devices, + /*.device_collection_destroy =*/ audiounit_device_collection_destroy, + /*.destroy =*/ audiounit_destroy, + /*.stream_init =*/ audiounit_stream_init, + /*.stream_destroy =*/ audiounit_stream_destroy, + /*.stream_start =*/ audiounit_stream_start, + /*.stream_stop =*/ audiounit_stream_stop, + /*.stream_get_position =*/ audiounit_stream_get_position, + /*.stream_get_latency =*/ audiounit_stream_get_latency, + /*.stream_set_volume =*/ audiounit_stream_set_volume, + /*.stream_set_panning =*/ audiounit_stream_set_panning, + /*.stream_get_current_device =*/ audiounit_stream_get_current_device, + /*.stream_device_destroy =*/ audiounit_stream_device_destroy, + /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback, + /*.register_device_collection_changed =*/ audiounit_register_device_collection_changed +}; diff --git a/Externals/cubeb/src/cubeb_jack.cpp b/Externals/cubeb/src/cubeb_jack.cpp new file mode 100644 index 0000000000..b49b66785f --- /dev/null +++ b/Externals/cubeb/src/cubeb_jack.cpp @@ -0,0 +1,1035 @@ +/* + * Copyright © 2012 David Richards + * Copyright © 2013 Sebastien Alaiwan + * Copyright © 2016 Damien Zammit + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _POSIX_SOURCE +#include +#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); + +static const int MAX_STREAMS = 16; +static const int MAX_CHANNELS = 8; +static const int FIFO_SIZE = 4096 * sizeof(float); + +enum devstream { + NONE = 0, + IN_ONLY, + OUT_ONLY, + DUPLEX, +}; + +static void +s16ne_to_float(float * dst, const int16_t * src, size_t n) +{ + for (size_t i = 0; i < n; i++) + *(dst++) = (float)((float)*(src++) / 32767.0f); +} + +static void +float_to_s16ne(int16_t * dst, float * src, size_t n) +{ + for (size_t i = 0; i < n; i++) { + if (*src > 1.f) *src = 1.f; + if (*src < -1.f) *src = -1.f; + *(dst++) = (int16_t)((int16_t)(*(src++) * 32767)); + } +} + +extern "C" +{ +/*static*/ int jack_init (cubeb ** context, char const * context_name); +} +static char const * cbjack_get_backend_id(cubeb * context); +static int cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels); +static int cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames); +static int cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames); +static int cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate); +static void cbjack_destroy(cubeb * context); +static void cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch); +static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short **bufs_in, float **bufs_out, jack_nframes_t nframes); +static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float **bufs_in, float **bufs_out, jack_nframes_t nframes); +static int cbjack_stream_device_destroy(cubeb_stream * stream, + cubeb_device * device); +static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device); +static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection); +static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr); +static void cbjack_stream_destroy(cubeb_stream * stream); +static int cbjack_stream_start(cubeb_stream * stream); +static int cbjack_stream_stop(cubeb_stream * stream); +static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position); +static int cbjack_stream_set_volume(cubeb_stream * stm, float volume); + +static struct cubeb_ops const cbjack_ops = { + .init = jack_init, + .get_backend_id = cbjack_get_backend_id, + .get_max_channel_count = cbjack_get_max_channel_count, + .get_min_latency = cbjack_get_min_latency, + .get_preferred_sample_rate = cbjack_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, + .enumerate_devices = cbjack_enumerate_devices, + .device_collection_destroy = cubeb_utils_default_device_collection_destroy, + .destroy = cbjack_destroy, + .stream_init = cbjack_stream_init, + .stream_destroy = cbjack_stream_destroy, + .stream_start = cbjack_stream_start, + .stream_stop = cbjack_stream_stop, + .stream_get_position = cbjack_stream_get_position, + .stream_get_latency = cbjack_get_latency, + .stream_set_volume = cbjack_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = cbjack_stream_get_current_device, + .stream_device_destroy = cbjack_stream_device_destroy, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; + +struct cubeb_stream { + cubeb * context; + + /**< Mutex for each stream */ + pthread_mutex_t mutex; + + bool in_use; /**< Set to false iff the stream is free */ + bool ports_ready; /**< Set to true iff the JACK ports are ready */ + + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + cubeb_stream_params in_params; + cubeb_stream_params out_params; + + cubeb_resampler * resampler; + + uint64_t position; + bool pause; + float ratio; + enum devstream devs; + char stream_name[256]; + jack_port_t * output_ports[MAX_CHANNELS]; + jack_port_t * input_ports[MAX_CHANNELS]; + float volume; +}; + +struct cubeb { + struct cubeb_ops const * ops; + void * libjack; + + /**< Mutex for whole context */ + pthread_mutex_t mutex; + + /**< Audio buffers, converted to float */ + float in_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS]; + float out_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS]; + + /**< Audio buffer, at the sampling rate of the output */ + float in_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3]; + int16_t in_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3]; + float out_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3]; + int16_t out_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3]; + + cubeb_stream streams[MAX_STREAMS]; + unsigned int active_streams; + + cubeb_device_collection_changed_callback collection_changed_callback; + + bool active; + unsigned int jack_sample_rate; + unsigned int jack_latency; + unsigned int jack_xruns; + unsigned int jack_buffer_size; + unsigned int fragment_size; + unsigned int output_bytes_per_frame; + jack_client_t * jack_client; +}; + +static int +load_jack_lib(cubeb * context) +{ +#ifdef __APPLE__ + context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY); + context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY); +#elif defined(__WIN32__) +# ifdef _WIN64 + context->libjack = LoadLibrary("libjack64.dll"); +# else + context->libjack = LoadLibrary("libjack.dll"); +# endif +#else + context->libjack = dlopen("libjack.so.0", RTLD_LAZY); +#endif + if (!context->libjack) { + return CUBEB_ERROR; + } + +#define LOAD(x) \ + { \ + api_##x = (decltype(x)*)dlsym(context->libjack, #x); \ + if (!api_##x) { \ + dlclose(context->libjack); \ + return CUBEB_ERROR; \ + } \ + } + + JACK_API_VISIT(LOAD); +#undef LOAD + + return CUBEB_OK; +} + +static void +cbjack_connect_ports (cubeb_stream * stream) +{ + const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client, + NULL, NULL, + JackPortIsInput + | JackPortIsPhysical); + const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client, + NULL, NULL, + JackPortIsOutput + | JackPortIsPhysical); + + if (*phys_in_ports == NULL) { + goto skipplayback; + } + + // Connect outputs to playback + for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) { + const char *src_port = api_jack_port_name (stream->output_ports[c]); + + api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]); + } + +skipplayback: + if (*phys_out_ports == NULL) { + goto end; + } + // Connect inputs to capture + for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) { + const char *src_port = api_jack_port_name (stream->input_ports[c]); + + api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port); + } +end: + api_jack_free(phys_out_ports); + api_jack_free(phys_in_ports); +} + +static int +cbjack_xrun_callback(void * arg) +{ + cubeb * ctx = (cubeb *)arg; + + float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client); + int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate ) + / (float)(ctx->jack_buffer_size) ); + ctx->jack_xruns += fragments; + return 0; +} + +static int +cbjack_graph_order_callback(void * arg) +{ + cubeb * ctx = (cubeb *)arg; + int i; + jack_latency_range_t latency_range; + jack_nframes_t port_latency, max_latency = 0; + + for (int j = 0; j < MAX_STREAMS; j++) { + cubeb_stream *stm = &ctx->streams[j]; + + if (!stm->in_use) + continue; + if (!stm->ports_ready) + continue; + + for (i = 0; i < (int)stm->out_params.channels; ++i) { + api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency, &latency_range); + port_latency = latency_range.max; + if (port_latency > max_latency) + max_latency = port_latency; + } + /* Cap minimum latency to 128 frames */ + if (max_latency < 128) + max_latency = 128; + } + + ctx->jack_latency = max_latency; + + return 0; +} + +static int +cbjack_process(jack_nframes_t nframes, void * arg) +{ + cubeb * ctx = (cubeb *)arg; + int t_jack_xruns = ctx->jack_xruns; + int i; + + for (int j = 0; j < MAX_STREAMS; j++) { + cubeb_stream *stm = &ctx->streams[j]; + float *bufs_out[stm->out_params.channels]; + float *bufs_in[stm->in_params.channels]; + + if (!stm->in_use) + continue; + + // handle xruns by skipping audio that should have been played + for (i = 0; i < t_jack_xruns; i++) { + stm->position += ctx->fragment_size * stm->ratio; + } + ctx->jack_xruns -= t_jack_xruns; + + if (!stm->ports_ready) + continue; + + if (stm->devs & OUT_ONLY) { + // get jack output buffers + for (i = 0; i < (int)stm->out_params.channels; i++) + bufs_out[i] = (float*)api_jack_port_get_buffer(stm->output_ports[i], nframes); + } + if (stm->devs & IN_ONLY) { + // get jack input buffers + for (i = 0; i < (int)stm->in_params.channels; i++) + bufs_in[i] = (float*)api_jack_port_get_buffer(stm->input_ports[i], nframes); + } + if (stm->pause) { + // paused, play silence on output + if (stm->devs & OUT_ONLY) { + for (unsigned int c = 0; c < stm->out_params.channels; c++) { + float* buffer_out = bufs_out[c]; + for (long f = 0; f < nframes; f++) { + buffer_out[f] = 0.f; + } + } + } + if (stm->devs & IN_ONLY) { + // paused, capture silence + for (unsigned int c = 0; c < stm->in_params.channels; c++) { + float* buffer_in = bufs_in[c]; + for (long f = 0; f < nframes; f++) { + buffer_in[f] = 0.f; + } + } + } + } else { + + // try to lock stream mutex + if (pthread_mutex_trylock(&stm->mutex) == 0) { + + int16_t *in_s16ne = stm->context->in_resampled_interleaved_buffer_s16ne; + float *in_float = stm->context->in_resampled_interleaved_buffer_float; + + // unpaused, play audio + if (stm->devs == DUPLEX) { + if (stm->out_params.format == CUBEB_SAMPLE_S16NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, true); + cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out, nframes); + } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, false); + cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out, nframes); + } + } else if (stm->devs == IN_ONLY) { + if (stm->in_params.format == CUBEB_SAMPLE_S16NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, true); + cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr, nframes); + } else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, false); + cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr, nframes); + } + } else if (stm->devs == OUT_ONLY) { + if (stm->out_params.format == CUBEB_SAMPLE_S16NE) { + cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out, nframes); + } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out, nframes); + } + } + // unlock stream mutex + pthread_mutex_unlock(&stm->mutex); + + } else { + // could not lock mutex + // output silence + if (stm->devs & OUT_ONLY) { + for (unsigned int c = 0; c < stm->out_params.channels; c++) { + float* buffer_out = bufs_out[c]; + for (long f = 0; f < nframes; f++) { + buffer_out[f] = 0.f; + } + } + } + if (stm->devs & IN_ONLY) { + // capture silence + for (unsigned int c = 0; c < stm->in_params.channels; c++) { + float* buffer_in = bufs_in[c]; + for (long f = 0; f < nframes; f++) { + buffer_in[f] = 0.f; + } + } + } + } + } + } + return 0; +} + +static void +cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes) +{ + float * out_interleaved_buffer = nullptr; + + float * inptr = (in != NULL) ? *in : nullptr; + float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr; + + long needed_frames = (bufs_out != NULL) ? nframes : 0; + long done_frames = 0; + long input_frames_count = (in != NULL) ? nframes : 0; + + done_frames = cubeb_resampler_fill(stream->resampler, + inptr, + &input_frames_count, + (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL, + needed_frames); + + out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; + + if (outptr) { + // convert interleaved output buffers to contiguous buffers + for (unsigned int c = 0; c < stream->out_params.channels; c++) { + float* buffer = bufs_out[c]; + for (long f = 0; f < done_frames; f++) { + buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; + } + if (done_frames < needed_frames) { + // draining + for (long f = done_frames; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + if (done_frames == 0) { + // stop, but first zero out the existing buffer + for (long f = 0; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + } + } + + if (done_frames >= 0 && done_frames < needed_frames) { + // set drained + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); + // stop stream + cbjack_stream_stop(stream); + } + if (done_frames > 0 && done_frames <= needed_frames) { + // advance stream position + stream->position += done_frames * stream->ratio; + } + if (done_frames < 0 || done_frames > needed_frames) { + // stream error + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR); + } +} + +static void +cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes) +{ + float * out_interleaved_buffer = nullptr; + + short * inptr = (in != NULL) ? *in : nullptr; + float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr; + + long needed_frames = (bufs_out != NULL) ? nframes : 0; + long done_frames = 0; + long input_frames_count = (in != NULL) ? nframes : 0; + + done_frames = cubeb_resampler_fill(stream->resampler, + inptr, + &input_frames_count, + (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL, + needed_frames); + + s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels); + + out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; + + if (outptr) { + // convert interleaved output buffers to contiguous buffers + for (unsigned int c = 0; c < stream->out_params.channels; c++) { + float* buffer = bufs_out[c]; + for (long f = 0; f < done_frames; f++) { + buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; + } + if (done_frames < needed_frames) { + // draining + for (long f = done_frames; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + if (done_frames == 0) { + // stop, but first zero out the existing buffer + for (long f = 0; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + } + } + + if (done_frames >= 0 && done_frames < needed_frames) { + // set drained + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); + // stop stream + cbjack_stream_stop(stream); + } + if (done_frames > 0 && done_frames <= needed_frames) { + // advance stream position + stream->position += done_frames * stream->ratio; + } + if (done_frames < 0 || done_frames > needed_frames) { + // stream error + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR); + } +} + +static void +cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch) +{ + float *in_buffer = stream->context->in_float_interleaved_buffer; + + for (unsigned int c = 0; c < stream->in_params.channels; c++) { + for (long f = 0; f < nframes; f++) { + in_buffer[(f * stream->in_params.channels) + c] = in[c][f] * stream->volume; + } + } + if (format_mismatch) { + float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne, in_buffer, nframes * stream->in_params.channels); + } else { + memset(stream->context->in_resampled_interleaved_buffer_float, 0, (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float)); + memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer, (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float)); + } +} + +static void +silent_jack_error_callback(char const * /*msg*/) +{ +} + +/*static*/ int +jack_init (cubeb ** context, char const * context_name) +{ + int r; + + *context = NULL; + + cubeb * ctx = (cubeb *)calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return CUBEB_ERROR; + } + + r = load_jack_lib(ctx); + if (r != 0) { + cbjack_destroy(ctx); + return CUBEB_ERROR; + } + + api_jack_set_error_function(silent_jack_error_callback); + api_jack_set_info_function(silent_jack_error_callback); + + ctx->ops = &cbjack_ops; + + ctx->mutex = PTHREAD_MUTEX_INITIALIZER; + for (r = 0; r < MAX_STREAMS; r++) { + ctx->streams[r].mutex = PTHREAD_MUTEX_INITIALIZER; + } + + const char * jack_client_name = "cubeb"; + if (context_name) + jack_client_name = context_name; + + ctx->jack_client = api_jack_client_open(jack_client_name, + JackNoStartServer, + NULL); + + if (ctx->jack_client == NULL) { + cbjack_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->jack_xruns = 0; + + api_jack_set_process_callback (ctx->jack_client, cbjack_process, ctx); + api_jack_set_xrun_callback (ctx->jack_client, cbjack_xrun_callback, ctx); + api_jack_set_graph_order_callback (ctx->jack_client, cbjack_graph_order_callback, ctx); + + if (api_jack_activate (ctx->jack_client)) { + cbjack_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->jack_sample_rate = api_jack_get_sample_rate(ctx->jack_client); + ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate; + + ctx->active = true; + *context = ctx; + + return CUBEB_OK; +} + +static char const * +cbjack_get_backend_id(cubeb * /*context*/) +{ + return "jack"; +} + +static int +cbjack_get_max_channel_count(cubeb * /*ctx*/, uint32_t * max_channels) +{ + *max_channels = MAX_CHANNELS; + return CUBEB_OK; +} + +static int +cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms) +{ + *latency_ms = stm->context->jack_latency; + return CUBEB_OK; +} + +static int +cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/, uint32_t * latency_ms) +{ + *latency_ms = ctx->jack_latency; + return CUBEB_OK; +} + +static int +cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + if (!ctx->jack_client) { + jack_client_t * testclient = api_jack_client_open("test-samplerate", + JackNoStartServer, + NULL); + if (!testclient) { + return CUBEB_ERROR; + } + + *rate = api_jack_get_sample_rate(testclient); + api_jack_client_close(testclient); + + } else { + *rate = api_jack_get_sample_rate(ctx->jack_client); + } + return CUBEB_OK; +} + +static void +cbjack_destroy(cubeb * context) +{ + context->active = false; + + if (context->jack_client != NULL) + api_jack_client_close (context->jack_client); + + if (context->libjack) + dlclose(context->libjack); + + free(context); +} + +static cubeb_stream * +context_alloc_stream(cubeb * context, char const * stream_name) +{ + for (int i = 0; i < MAX_STREAMS; i++) { + if (!context->streams[i].in_use) { + cubeb_stream * stm = &context->streams[i]; + stm->in_use = true; + snprintf(stm->stream_name, 255, "%s_%u", stream_name, i); + return stm; + } + } + return NULL; +} + +static int +cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int /*latency_frames*/, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) +{ + int stream_actual_rate = 0; + int jack_rate = api_jack_get_sample_rate(context->jack_client); + + if (output_stream_params + && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && + output_stream_params->format != CUBEB_SAMPLE_S16NE) + ) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + if (input_stream_params + && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && + input_stream_params->format != CUBEB_SAMPLE_S16NE) + ) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + if (input_device || output_device) + return CUBEB_ERROR_NOT_SUPPORTED; + + *stream = NULL; + + // Find a free stream. + pthread_mutex_lock(&context->mutex); + cubeb_stream * stm = context_alloc_stream(context, stream_name); + + // No free stream? + if (stm == NULL) { + pthread_mutex_unlock(&context->mutex); + return CUBEB_ERROR; + } + + // unlock context mutex + pthread_mutex_unlock(&context->mutex); + + // Lock active stream + pthread_mutex_lock(&stm->mutex); + + stm->ports_ready = false; + stm->user_ptr = user_ptr; + stm->context = context; + stm->devs = NONE; + if (output_stream_params && !input_stream_params) { + stm->out_params = *output_stream_params; + stream_actual_rate = stm->out_params.rate; + stm->out_params.rate = jack_rate; + stm->devs = OUT_ONLY; + if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + context->output_bytes_per_frame = sizeof(float); + } else { + context->output_bytes_per_frame = sizeof(short); + } + } + if (input_stream_params && output_stream_params) { + stm->in_params = *input_stream_params; + stm->out_params = *output_stream_params; + stream_actual_rate = stm->out_params.rate; + stm->in_params.rate = jack_rate; + stm->out_params.rate = jack_rate; + stm->devs = DUPLEX; + if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + context->output_bytes_per_frame = sizeof(float); + stm->in_params.format = CUBEB_SAMPLE_FLOAT32NE; + } else { + context->output_bytes_per_frame = sizeof(short); + stm->in_params.format = CUBEB_SAMPLE_S16NE; + } + } else if (input_stream_params && !output_stream_params) { + stm->in_params = *input_stream_params; + stream_actual_rate = stm->in_params.rate; + stm->in_params.rate = jack_rate; + stm->devs = IN_ONLY; + if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) { + context->output_bytes_per_frame = sizeof(float); + } else { + context->output_bytes_per_frame = sizeof(short); + } + } + + stm->ratio = (float)stream_actual_rate / (float)jack_rate; + + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->position = 0; + stm->volume = 1.0f; + context->jack_buffer_size = api_jack_get_buffer_size(context->jack_client); + context->fragment_size = context->jack_buffer_size; + + if (stm->devs == NONE) { + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + stm->resampler = NULL; + + if (stm->devs == DUPLEX) { + stm->resampler = cubeb_resampler_create(stm, + &stm->in_params, + &stm->out_params, + stream_actual_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); + } else if (stm->devs == IN_ONLY) { + stm->resampler = cubeb_resampler_create(stm, + &stm->in_params, + nullptr, + stream_actual_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); + } else if (stm->devs == OUT_ONLY) { + stm->resampler = cubeb_resampler_create(stm, + nullptr, + &stm->out_params, + stream_actual_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); + } + + if (!stm->resampler) { + stm->in_use = false; + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + if (stm->devs == DUPLEX || stm->devs == OUT_ONLY) { + for (unsigned int c = 0; c < stm->out_params.channels; c++) { + char portname[256]; + snprintf(portname, 255, "%s_out_%d", stm->stream_name, c); + stm->output_ports[c] = api_jack_port_register(stm->context->jack_client, + portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, + 0); + } + } + + if (stm->devs == DUPLEX || stm->devs == IN_ONLY) { + for (unsigned int c = 0; c < stm->in_params.channels; c++) { + char portname[256]; + snprintf(portname, 255, "%s_in_%d", stm->stream_name, c); + stm->input_ports[c] = api_jack_port_register(stm->context->jack_client, + portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, + 0); + } + } + + cbjack_connect_ports(stm); + + *stream = stm; + + stm->ports_ready = true; + stm->pause = true; + pthread_mutex_unlock(&stm->mutex); + + return CUBEB_OK; +} + +static void +cbjack_stream_destroy(cubeb_stream * stream) +{ + pthread_mutex_lock(&stream->mutex); + stream->ports_ready = false; + + if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) { + for (unsigned int c = 0; c < stream->out_params.channels; c++) { + if (stream->output_ports[c]) { + api_jack_port_unregister (stream->context->jack_client, stream->output_ports[c]); + stream->output_ports[c] = NULL; + } + } + } + + if (stream->devs == DUPLEX || stream->devs == IN_ONLY) { + for (unsigned int c = 0; c < stream->in_params.channels; c++) { + if (stream->input_ports[c]) { + api_jack_port_unregister (stream->context->jack_client, stream->input_ports[c]); + stream->input_ports[c] = NULL; + } + } + } + + if (stream->resampler) { + cubeb_resampler_destroy(stream->resampler); + stream->resampler = NULL; + } + stream->in_use = false; + pthread_mutex_unlock(&stream->mutex); +} + +static int +cbjack_stream_start(cubeb_stream * stream) +{ + stream->pause = false; + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED); + return CUBEB_OK; +} + +static int +cbjack_stream_stop(cubeb_stream * stream) +{ + stream->pause = true; + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED); + return CUBEB_OK; +} + +static int +cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position) +{ + *position = stream->position; + return CUBEB_OK; +} + +static int +cbjack_stream_set_volume(cubeb_stream * stm, float volume) +{ + stm->volume = volume; + return CUBEB_OK; +} + +static int +cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) +{ + *device = (cubeb_device *)calloc(1, sizeof(cubeb_device)); + if (*device == NULL) + return CUBEB_ERROR; + + const char * j_in = "JACK capture"; + const char * j_out = "JACK playback"; + const char * empty = ""; + + if (stm->devs == DUPLEX) { + (*device)->input_name = strdup(j_in); + (*device)->output_name = strdup(j_out); + } else if (stm->devs == IN_ONLY) { + (*device)->input_name = strdup(j_in); + (*device)->output_name = strdup(empty); + } else if (stm->devs == OUT_ONLY) { + (*device)->input_name = strdup(empty); + (*device)->output_name = strdup(j_out); + } + + return CUBEB_OK; +} + +static int +cbjack_stream_device_destroy(cubeb_stream * /*stream*/, + cubeb_device * device) +{ + if (device->input_name) + free(device->input_name); + if (device->output_name) + free(device->output_name); + free(device); + return CUBEB_OK; +} + +static int +cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + if (!context) + return CUBEB_ERROR; + + uint32_t rate; + cbjack_get_preferred_sample_rate(context, &rate); + const char * j_in = "JACK capture"; + const char * j_out = "JACK playback"; + + cubeb_device_info * devices = new cubeb_device_info[2]; + reinterpret_cast(calloc(2, sizeof(cubeb_device_info))); + collection->count = 0; + + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + cubeb_device_info * cur = &devices[collection->count]; + cur->device_id = strdup(j_out); + cur->devid = (cubeb_devid) cur->device_id; + cur->friendly_name = strdup(j_out); + cur->group_id = strdup(j_out); + cur->vendor_name = strdup(j_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 = strdup(j_in); + cur->devid = (cubeb_devid) cur->device_id; + cur->friendly_name = strdup(j_in); + cur->group_id = strdup(j_in); + cur->vendor_name = strdup(j_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; +} diff --git a/Externals/cubeb/src/cubeb_kai.c b/Externals/cubeb/src/cubeb_kai.c new file mode 100644 index 0000000000..f6a79be71e --- /dev/null +++ b/Externals/cubeb/src/cubeb_kai.c @@ -0,0 +1,362 @@ +/* + * 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 { + cubeb * context; + cubeb_stream_params params; + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + + 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; + + 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_get_position =*/ kai_stream_get_position, + /*.stream_get_latency = */ kai_stream_get_latency, + /*.stream_set_volume =*/ kai_stream_set_volume, + /*.stream_set_panning =*/ NULL, + /*.stream_get_current_device =*/ NULL, + /*.stream_device_destroy =*/ NULL, + /*.stream_register_device_changed_callback=*/ NULL, + /*.register_device_collection_changed=*/ NULL +}; diff --git a/Externals/cubeb/src/cubeb_log.cpp b/Externals/cubeb/src/cubeb_log.cpp new file mode 100644 index 0000000000..4c6169354a --- /dev/null +++ b/Externals/cubeb/src/cubeb_log.cpp @@ -0,0 +1,130 @@ +/* + * 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(); + } +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); +} diff --git a/Externals/cubeb/src/cubeb_log.h b/Externals/cubeb/src/cubeb_log.h new file mode 100644 index 0000000000..5ca1462a13 --- /dev/null +++ b/Externals/cubeb/src/cubeb_log.h @@ -0,0 +1,46 @@ +/* + * 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))) +#else +#define PRINTF_FORMAT(fmt, args) +#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, ...); + +#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", __FILE__, __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/Externals/cubeb/src/cubeb_mixer.cpp b/Externals/cubeb/src/cubeb_mixer.cpp new file mode 100644 index 0000000000..375f12402f --- /dev/null +++ b/Externals/cubeb/src/cubeb_mixer.cpp @@ -0,0 +1,569 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include +#include "cubeb-internal.h" +#include "cubeb_mixer.h" + +// DUAL_MONO(_LFE) is same as STEREO(_LFE). +#define MASK_MONO (1 << CHANNEL_MONO) +#define MASK_MONO_LFE (MASK_MONO | (1 << CHANNEL_LFE)) +#define MASK_STEREO ((1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT)) +#define MASK_STEREO_LFE (MASK_STEREO | (1 << CHANNEL_LFE)) +#define MASK_3F (MASK_STEREO | (1 << CHANNEL_CENTER)) +#define MASK_3F_LFE (MASK_3F | (1 << CHANNEL_LFE)) +#define MASK_2F1 (MASK_STEREO | (1 << CHANNEL_RCENTER)) +#define MASK_2F1_LFE (MASK_2F1 | (1 << CHANNEL_LFE)) +#define MASK_3F1 (MASK_3F | (1 << CHANNEL_RCENTER)) +#define MASK_3F1_LFE (MASK_3F1 | (1 << CHANNEL_LFE)) +#define MASK_2F2 (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS)) +#define MASK_2F2_LFE (MASK_2F2 | (1 << CHANNEL_LFE)) +#define MASK_3F2 (MASK_2F2 | (1 << CHANNEL_CENTER)) +#define MASK_3F2_LFE (MASK_3F2 | (1 << CHANNEL_LFE)) +#define MASK_3F3R_LFE (MASK_3F2_LFE | (1 << CHANNEL_RCENTER)) +#define MASK_3F4_LFE (MASK_3F2_LFE | (1 << CHANNEL_RLS) | (1 << CHANNEL_RRS)) + +cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map) +{ + uint32_t channel_mask = 0; + for (uint8_t i = 0 ; i < channel_map->channels ; ++i) { + if (channel_map->map[i] == CHANNEL_INVALID || + channel_map->map[i] == CHANNEL_UNMAPPED) { + return CUBEB_LAYOUT_UNDEFINED; + } + channel_mask |= 1 << channel_map->map[i]; + } + + switch(channel_mask) { + case MASK_MONO: return CUBEB_LAYOUT_MONO; + case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE; + case MASK_STEREO: return CUBEB_LAYOUT_STEREO; + case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE; + case MASK_3F: return CUBEB_LAYOUT_3F; + case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE; + case MASK_2F1: return CUBEB_LAYOUT_2F1; + case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE; + case MASK_3F1: return CUBEB_LAYOUT_3F1; + case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE; + case MASK_2F2: return CUBEB_LAYOUT_2F2; + case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE; + case MASK_3F2: return CUBEB_LAYOUT_3F2; + case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE; + case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE; + case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE; + default: return CUBEB_LAYOUT_UNDEFINED; + } +} + +cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX] = { + { "undefined", 0, CUBEB_LAYOUT_UNDEFINED }, + { "dual mono", 2, CUBEB_LAYOUT_DUAL_MONO }, + { "dual mono lfe", 3, CUBEB_LAYOUT_DUAL_MONO_LFE }, + { "mono", 1, CUBEB_LAYOUT_MONO }, + { "mono lfe", 2, CUBEB_LAYOUT_MONO_LFE }, + { "stereo", 2, CUBEB_LAYOUT_STEREO }, + { "stereo lfe", 3, CUBEB_LAYOUT_STEREO_LFE }, + { "3f", 3, CUBEB_LAYOUT_3F }, + { "3f lfe", 4, CUBEB_LAYOUT_3F_LFE }, + { "2f1", 3, CUBEB_LAYOUT_2F1 }, + { "2f1 lfe", 4, CUBEB_LAYOUT_2F1_LFE }, + { "3f1", 4, CUBEB_LAYOUT_3F1 }, + { "3f1 lfe", 5, CUBEB_LAYOUT_3F1_LFE }, + { "2f2", 4, CUBEB_LAYOUT_2F2 }, + { "2f2 lfe", 5, CUBEB_LAYOUT_2F2_LFE }, + { "3f2", 5, CUBEB_LAYOUT_3F2 }, + { "3f2 lfe", 6, CUBEB_LAYOUT_3F2_LFE }, + { "3f3r lfe", 7, CUBEB_LAYOUT_3F3R_LFE }, + { "3f4 lfe", 8, CUBEB_LAYOUT_3F4_LFE } +}; + +static int const CHANNEL_ORDER_TO_INDEX[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = { +// M | L | R | C | LS | RS | RLS | RC | RRS | LFE + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // UNDEFINED + { -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // DUAL_MONO + { -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // DUAL_MONO_LFE + { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MONO + { 0, -1, -1, -1, -1, -1, -1, -1, -1, 1 }, // MONO_LFE + { -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // STEREO + { -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // STEREO_LFE + { -1, 0, 1, 2, -1, -1, -1, -1, -1, -1 }, // 3F + { -1, 0, 1, 2, -1, -1, -1, -1, -1, 3 }, // 3F_LFE + { -1, 0, 1, -1, -1, -1, -1, 2, -1, -1 }, // 2F1 + { -1, 0, 1, -1, -1, -1, -1, 3, -1, 2 }, // 2F1_LFE + { -1, 0, 1, 2, -1, -1, -1, 3, -1, -1 }, // 3F1 + { -1, 0, 1, 2, -1, -1, -1, 4, -1, 3 }, // 3F1_LFE + { -1, 0, 1, -1, 2, 3, -1, -1, -1, -1 }, // 2F2 + { -1, 0, 1, -1, 3, 4, -1, -1, -1, 2 }, // 2F2_LFE + { -1, 0, 1, 2, 3, 4, -1, -1, -1, -1 }, // 3F2 + { -1, 0, 1, 2, 4, 5, -1, -1, -1, 3 }, // 3F2_LFE + { -1, 0, 1, 2, 5, 6, -1, 4, -1, 3 }, // 3F3R_LFE + { -1, 0, 1, 2, 6, 7, 4, -1, 5, 3 }, // 3F4_LFE +}; + +// The downmix matrix from TABLE 2 in the ITU-R BS.775-3[1] defines a way to +// convert 3F2 input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 output data. We extend it +// to convert 3F2-LFE input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs +// output data. +// [1] https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.775-3-201208-I!!PDF-E.pdf + +// Number of converted layouts: 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs. +unsigned int const SUPPORTED_LAYOUT_NUM = 12; +// Number of input channel for downmix conversion. +unsigned int const INPUT_CHANNEL_NUM = 6; // 3F2-LFE +// Max number of possible output channels. +unsigned int const MAX_OUTPUT_CHANNEL_NUM = 5; // 2F2-LFE or 3F1-LFE +float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2) +// Each array contains coefficients that will be multiplied with +// { L, R, C, LFE, LS, RS } channels respectively. +static float const DOWNMIX_MATRIX_3F2_LFE[SUPPORTED_LAYOUT_NUM][MAX_OUTPUT_CHANNEL_NUM][INPUT_CHANNEL_NUM] = +{ +// 1F Mono + { + { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M + }, +// 1F Mono-LFE + { + { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M + { 0, 0, 0, 1, 0, 0 } // LFE + }, +// 2F Stereo + { + { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L + { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 } // R + }, +// 2F Stereo-LFE + { + { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L + { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 }, // R + { 0, 0, 0, 1, 0, 0 } // LFE + }, +// 3F + { + { 1, 0, 0, 0, INV_SQRT_2, 0 }, // L + { 0, 1, 0, 0, 0, INV_SQRT_2 }, // R + { 0, 0, 1, 0, 0, 0 } // C + }, +// 3F-LFE + { + { 1, 0, 0, 0, INV_SQRT_2, 0 }, // L + { 0, 1, 0, 0, 0, INV_SQRT_2 }, // R + { 0, 0, 1, 0, 0, 0 }, // C + { 0, 0, 0, 1, 0, 0 } // LFE + }, +// 2F1 + { + { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L + { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R + { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S + }, +// 2F1-LFE + { + { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L + { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R + { 0, 0, 0, 1, 0, 0 }, // LFE + { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S + }, +// 3F1 + { + { 1, 0, 0, 0, 0, 0 }, // L + { 0, 1, 0, 0, 0, 0 }, // R + { 0, 0, 1, 0, 0, 0 }, // C + { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S + }, +// 3F1-LFE + { + { 1, 0, 0, 0, 0, 0 }, // L + { 0, 1, 0, 0, 0, 0 }, // R + { 0, 0, 1, 0, 0, 0 }, // C + { 0, 0, 0, 1, 0, 0 }, // LFE + { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S + }, +// 2F2 + { + { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L + { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R + { 0, 0, 0, 0, 1, 0 }, // LS + { 0, 0, 0, 0, 0, 1 } // RS + }, +// 2F2-LFE + { + { 1, 0, INV_SQRT_2, 0, 0, 0 }, // L + { 0, 1, INV_SQRT_2, 0, 0, 0 }, // R + { 0, 0, 0, 1, 0, 0 }, // LFE + { 0, 0, 0, 0, 1, 0 }, // LS + { 0, 0, 0, 0, 0, 1 } // RS + } +}; + +// Convert audio data from 3F2(-LFE) to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs. +// +// ITU-R BS.775-3[1] provides spec for downmixing 3F2 data to 1F, 2F, 3F, 2F1, +// 3F1, 2F2 data. We simply add LFE to its defined matrix. If both the input +// and output have LFE channel, then we pass it's data. If only input or output +// has LFE, then we either drop it or append 0 to the LFE channel. +// +// Fig. 1: +// |<-------------- 1 -------------->|<-------------- 2 -------------->| +// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+ +// | L0 | R0 | C0 | LFE0 | LS0 | RS0 | L1 | R1 | C1 | LFE1 | LS1 | RS1 | ... +// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+ +// +// Fig. 2: +// |<-- 1 -->|<-- 2 -->| +// +----+----+----+----+ +// | L0 | R0 | L1 | R1 | ... +// +----+----+----+----+ +// +// The figures above shows an example for downmixing from 3F2-LFE(Fig. 1) to +// to stereo(Fig. 2), where L0 = L0 + 0.707 * (C0 + LS0), +// R0 = R0 + 0.707 * (C0 + RS0), L1 = L1 + 0.707 * (C1 + LS1), +// R1 = R1 + 0.707 * (C1 + RS1), ... +// +// Nevertheless, the downmixing method is a little bit different on OSX. +// The audio rendering mechanism on OS X will drop the extra channels beyond +// the channels that audio device can provide. The trick here is that OSX allows +// us to set the layout containing other channels that the output device can +// NOT provide. For example, setting 3F2-LFE layout to a stereo device is fine. +// Therefore, OSX expects we fill the buffer for playing sound by the defined +// layout, so there are some will-be-dropped data in the buffer: +// +// +---+---+---+-----+----+----+ +// | L | R | C | LFE | LS | RS | ... +// +---+---+---+-----+----+----+ +// ^ ^ ^ ^ +// The data for these four channels will be dropped! +// +// To keep all the information, we need to downmix the data before it's dropped. +// The figure below shows an example for downmixing from 3F2-LFE(Fig. 1) +// to stereo(Fig. 3) on OSX, where the LO, R0, L1, R0 are same as above. +// +// Fig. 3: +// |<---------- 1 ---------->|<---------- 2 ---------->| +// +----+----+---+---+---+---+----+----+---+---+---+---+ +// | L0 | R0 | x | x | x | x | L1 | R1 | x | x | x | x | ... +// +----+----+---+---+---+---+----+----+---+---+---+---+ +// |<-- dummy -->| |<-- dummy -->| +template +bool +downmix_3f2(unsigned long inframes, + T const * const in, unsigned long in_len, + T * out, unsigned long out_len, + cubeb_channel_layout in_layout, cubeb_channel_layout out_layout) +{ + if ((in_layout != CUBEB_LAYOUT_3F2 && in_layout != CUBEB_LAYOUT_3F2_LFE) || + out_layout < CUBEB_LAYOUT_MONO || out_layout > CUBEB_LAYOUT_2F2_LFE) { + return false; + } + + unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels; + unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels; + + // Conversion from 3F2 to 2F2-LFE or 3F1-LFE is allowed, so we use '<=' instead of '<'. + assert(out_channels <= in_channels); + + auto & downmix_matrix = DOWNMIX_MATRIX_3F2_LFE[out_layout - CUBEB_LAYOUT_MONO]; // The matrix is started from mono. + unsigned long out_index = 0; + for (unsigned long i = 0 ; i < inframes * in_channels; i += in_channels) { + for (unsigned int j = 0; j < out_channels; ++j) { + T sample = 0; + for (unsigned int k = 0 ; k < INPUT_CHANNEL_NUM ; ++k) { + // 3F2-LFE has 6 channels: L, R, C, LFE, LS, RS, while 3F2 has only 5 + // channels: L, R, C, LS, RS. Thus, we need to append 0 to LFE(index 3) + // to simulate a 3F2-LFE data when input layout is 3F2. + assert((in_layout == CUBEB_LAYOUT_3F2_LFE || k < 3) ? (i + k < in_len) : (k == 3) ? true : (i + k - 1 < in_len)); + T data = (in_layout == CUBEB_LAYOUT_3F2_LFE) ? in[i + k] : (k == 3) ? 0 : in[i + ((k < 3) ? k : k - 1)]; + sample += downmix_matrix[j][k] * data; + } + assert(out_index + j < out_len); + out[out_index + j] = sample; + } +#if defined(USE_AUDIOUNIT) + out_index += in_channels; +#else + out_index += out_channels; +#endif + } + + return true; +} + +/* Map the audio data by channel name. */ +template +bool +mix_remap(long inframes, + T const * const in, unsigned long in_len, + T * out, unsigned long out_len, + cubeb_channel_layout in_layout, cubeb_channel_layout out_layout) +{ + assert(in_layout != out_layout); + + // We might overwrite the data before we copied them to the mapped index + // (e.g. upmixing from stereo to 2F1 or mapping [L, R] to [R, L]) + if (in == out) { + return false; + } + + unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels; + unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels; + + uint32_t in_layout_mask = 0; + for (unsigned int i = 0 ; i < in_channels ; ++i) { + in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i]; + } + + uint32_t out_layout_mask = 0; + for (unsigned int i = 0 ; i < out_channels ; ++i) { + out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i]; + } + + // If there is no matched channel, then do nothing. + if (!(out_layout_mask & in_layout_mask)) { + return false; + } + + for (unsigned long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) { + for (unsigned int j = 0; j < out_channels; ++j) { + cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j]; + uint32_t channel_mask = 1 << channel; + int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel]; + assert(out_index + j < out_len); + if (in_layout_mask & channel_mask) { + assert(i + channel_index < in_len); + assert(channel_index != -1); + out[out_index + j] = in[i + channel_index]; + } else { + assert(channel_index == -1); + out[out_index + j] = 0; + } + } + } + + return true; +} + +/* Drop the extra channels beyond the provided output channels. */ +template +void +downmix_fallback(long inframes, + T const * const in, unsigned long in_len, + T * out, unsigned long out_len, + unsigned int in_channels, unsigned int out_channels) +{ + assert(in_channels >= out_channels); + + if (in_channels == out_channels && in == out) { + return; + } + + for (unsigned long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) { + for (unsigned int j = 0; j < out_channels; ++j) { + assert(i + j < in_len && out_index + j < out_len); + out[out_index + j] = in[i + j]; + } + } +} + + +template +void +cubeb_downmix(long inframes, + T const * const in, unsigned long in_len, + T * out, unsigned long out_len, + cubeb_stream_params const * stream_params, + cubeb_stream_params const * mixer_params) +{ + assert(in && out); + assert(inframes); + assert(stream_params->channels >= mixer_params->channels && + mixer_params->channels > 0); + assert(stream_params->layout != CUBEB_LAYOUT_UNDEFINED); + + unsigned int in_channels = stream_params->channels; + cubeb_channel_layout in_layout = stream_params->layout; + + unsigned int out_channels = mixer_params->channels; + cubeb_channel_layout out_layout = mixer_params->layout; + + // If the channel number is different from the layout's setting, + // then we use fallback downmix mechanism. + if (out_channels == CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels && + in_channels == CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels) { + if (downmix_3f2(inframes, in, in_len, out, out_len, in_layout, out_layout)) { + return; + } + +#if defined(USE_AUDIOUNIT) + // We only support downmix for audio 5.1 on OS X currently. + return; +#endif + + if (mix_remap(inframes, in, in_len, out, out_len, in_layout, out_layout)) { + return; + } + } + + downmix_fallback(inframes, in, in_len, out, out_len, in_channels, out_channels); +} + +/* Upmix function, copies a mono channel into L and R. */ +template +void +mono_to_stereo(long insamples, T const * in, unsigned long in_len, + T * out, unsigned long out_len, unsigned int out_channels) +{ + for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) { + assert(i < in_len && j + 1 < out_len); + out[j] = out[j + 1] = in[i]; + } +} + +template +void +cubeb_upmix(long inframes, + T const * const in, unsigned long in_len, + T * out, unsigned long out_len, + cubeb_stream_params const * stream_params, + cubeb_stream_params const * mixer_params) +{ + assert(in && out); + assert(inframes); + assert(mixer_params->channels >= stream_params->channels && + stream_params->channels > 0); + + unsigned int in_channels = stream_params->channels; + unsigned int out_channels = mixer_params->channels; + + /* Either way, if we have 2 or more channels, the first two are L and R. */ + /* If we are playing a mono stream over stereo speakers, copy the data over. */ + if (in_channels == 1 && out_channels >= 2) { + mono_to_stereo(inframes, in, in_len, out, out_len, out_channels); + } else { + /* Copy through. */ + for (unsigned int i = 0, o = 0; i < inframes * in_channels; + i += in_channels, o += out_channels) { + for (unsigned int j = 0; j < in_channels; ++j) { + assert(i + j < in_len && o + j < out_len); + out[o + j] = in[i + j]; + } + } + } + + /* Check if more channels. */ + if (out_channels <= 2) { + return; + } + + /* Put silence in remaining channels. */ + for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) { + for (unsigned int j = 2; j < out_channels; ++j) { + assert(o + j < out_len); + out[o + j] = 0.0; + } + } +} + +bool +cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer) +{ + return mixer->channels > stream->channels; +} + +bool +cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer) +{ + if (mixer->channels > stream->channels || mixer->layout == stream->layout) { + return false; + } + + return mixer->channels < stream->channels || + // When mixer.channels == stream.channels + mixer->layout == CUBEB_LAYOUT_UNDEFINED || // fallback downmix + (stream->layout == CUBEB_LAYOUT_3F2 && // 3f2 downmix + (mixer->layout == CUBEB_LAYOUT_2F2_LFE || + mixer->layout == CUBEB_LAYOUT_3F1_LFE)); +} + +bool +cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer) +{ + return cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer); +} + +struct cubeb_mixer { + virtual void mix(long frames, + void * input_buffer, unsigned long input_buffer_length, + void * output_buffer, unsigned long output_buffer_length, + cubeb_stream_params const * stream_params, + cubeb_stream_params const * mixer_params) = 0; + virtual ~cubeb_mixer() {}; +}; + +template +struct cubeb_mixer_impl : public cubeb_mixer { + explicit cubeb_mixer_impl(unsigned int d) + : direction(d) + { + } + + void mix(long frames, + void * input_buffer, unsigned long input_buffer_length, + void * output_buffer, unsigned long output_buffer_length, + cubeb_stream_params const * stream_params, + cubeb_stream_params const * mixer_params) + { + if (frames <= 0) { + return; + } + + T * in = static_cast(input_buffer); + T * out = static_cast(output_buffer); + + if ((direction & CUBEB_MIXER_DIRECTION_DOWNMIX) && + cubeb_should_downmix(stream_params, mixer_params)) { + cubeb_downmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params); + } else if ((direction & CUBEB_MIXER_DIRECTION_UPMIX) && + cubeb_should_upmix(stream_params, mixer_params)) { + cubeb_upmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params); + } + } + + ~cubeb_mixer_impl() {}; + + unsigned char const direction; +}; + +cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format, + unsigned char direction) +{ + assert(direction & CUBEB_MIXER_DIRECTION_DOWNMIX || + direction & CUBEB_MIXER_DIRECTION_UPMIX); + switch(format) { + case CUBEB_SAMPLE_S16NE: + return new cubeb_mixer_impl(direction); + case CUBEB_SAMPLE_FLOAT32NE: + return new cubeb_mixer_impl(direction); + default: + assert(false); + return nullptr; + } +} + +void cubeb_mixer_destroy(cubeb_mixer * mixer) +{ + delete mixer; +} + +void cubeb_mixer_mix(cubeb_mixer * mixer, long frames, + void * input_buffer, unsigned long input_buffer_length, + void * output_buffer, unsigned long outputput_buffer_length, + cubeb_stream_params const * stream_params, + cubeb_stream_params const * mixer_params) +{ + assert(mixer); + mixer->mix(frames, input_buffer, input_buffer_length, output_buffer, outputput_buffer_length, + stream_params, mixer_params); +} diff --git a/Externals/cubeb/src/cubeb_mixer.h b/Externals/cubeb/src/cubeb_mixer.h new file mode 100644 index 0000000000..eeb69f6f91 --- /dev/null +++ b/Externals/cubeb/src/cubeb_mixer.h @@ -0,0 +1,90 @@ +/* + * 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 ,CUBEB_CHANNEL_LAYOUT_MAPS and cubeb_stream_params. +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef enum { + CHANNEL_INVALID = -1, + CHANNEL_MONO = 0, + CHANNEL_LEFT, + CHANNEL_RIGHT, + CHANNEL_CENTER, + CHANNEL_LS, + CHANNEL_RS, + CHANNEL_RLS, + CHANNEL_RCENTER, + CHANNEL_RRS, + CHANNEL_LFE, + CHANNEL_UNMAPPED, + CHANNEL_MAX = 256 // Max number of supported channels. +} cubeb_channel; + +static cubeb_channel const CHANNEL_INDEX_TO_ORDER[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = { + { CHANNEL_INVALID }, // UNDEFINED + { CHANNEL_LEFT, CHANNEL_RIGHT }, // DUAL_MONO + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // DUAL_MONO_LFE + { CHANNEL_MONO }, // MONO + { CHANNEL_MONO, CHANNEL_LFE }, // MONO_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT }, // STEREO + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // STEREO_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER }, // 3F + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE }, // 3F_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER }, // 2F1 + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER }, // 2F1_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER }, // 3F1 + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER }, // 3F1_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS }, // 2F2 + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 2F2_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LS, CHANNEL_RS }, // 3F2 + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 3F2_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER, CHANNEL_LS, CHANNEL_RS }, // 3F3R_LFE + { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RLS, CHANNEL_RRS, CHANNEL_LS, CHANNEL_RS } // 3F4_LFE + // When more channels are present, the stream is considered unmapped to a + // particular speaker set. +}; + +typedef struct { + unsigned int channels; + cubeb_channel map[CHANNEL_MAX]; +} cubeb_channel_map; + +cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map); + +bool cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer); + +bool cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer); + +bool cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer); + +typedef enum { + CUBEB_MIXER_DIRECTION_DOWNMIX = 0x01, + CUBEB_MIXER_DIRECTION_UPMIX = 0x02, +} cubeb_mixer_direction; + +typedef struct cubeb_mixer cubeb_mixer; +cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format, + unsigned char direction); +void cubeb_mixer_destroy(cubeb_mixer * mixer); +void cubeb_mixer_mix(cubeb_mixer * mixer, long frames, + void * input_buffer, unsigned long input_buffer_length, + void * output_buffer, unsigned long outputput_buffer_length, + cubeb_stream_params const * stream_params, + cubeb_stream_params const * mixer_params); + +#if defined(__cplusplus) +} +#endif + +#endif // CUBEB_MIXER diff --git a/Externals/cubeb/src/cubeb_opensl.c b/Externals/cubeb/src/cubeb_opensl.c new file mode 100644 index 0000000000..335c095e45 --- /dev/null +++ b/Externals/cubeb/src/cubeb_opensl.c @@ -0,0 +1,1757 @@ +/* + * 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" + +#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_LOLLIPOP 21 +#define ANDROID_VERSION_MARSHMALLOW 23 +#endif + +#define DEFAULT_SAMPLE_RATE 48000 + +static struct cubeb_ops const opensl_ops; + +struct cubeb { + struct cubeb_ops const * ops; + void * lib; + void * libmedia; + int32_t (* get_output_latency)(uint32_t * latency, int stream_type); + SLInterfaceID SL_IID_BUFFERQUEUE; + SLInterfaceID SL_IID_PLAY; +#if defined(__ANDROID__) + SLInterfaceID SL_IID_ANDROIDCONFIGURATION; + SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; +#endif + SLInterfaceID SL_IID_VOLUME; + SLInterfaceID SL_IID_RECORD; + SLObjectItf engObj; + SLEngineItf eng; + SLObjectItf outmixObj; +}; + +#define NELEMS(A) (sizeof(A) / sizeof A[0]) +#define NBUFS 4 +#define AUDIO_STREAM_TYPE_MUSIC 3 + +struct cubeb_stream { + cubeb * context; + pthread_mutex_t mutex; + SLObjectItf playerObj; + SLPlayItf play; + SLBufferQueueItf bufq; + SLVolumeItf volume; + void ** queuebuf; + uint32_t queuebuf_capacity; + int queuebuf_idx; + long queuebuf_len; + long bytespersec; + long framesize; + /* Total number of played frames. + * Synchronized by stream::mutex lock. */ + long written; + /* Flag indicating draining. Synchronized + * by stream::mutex lock. */ + int draining; + cubeb_stream_type stream_type; + /* Flags to determine in/out.*/ + uint32_t input_enabled; + uint32_t output_enabled; + /* Recorder abstract object. */ + SLObjectItf recorderObj; + /* Recorder Itf for input capture. */ + SLRecordItf recorderItf; + /* Buffer queue for input capture. */ + SLAndroidSimpleBufferQueueItf recorderBufferQueueItf; + /* Store input buffers. */ + void ** input_buffer_array; + /* The capacity of the array. + * On capture only can be small (4). + * On full duplex is calculated to + * store 1 sec of data buffers. */ + uint32_t input_array_capacity; + /* Current filled index of input buffer array. + * It is initiated to -1 indicating buffering + * have not started yet. */ + int input_buffer_index; + /* Length of input buffer.*/ + uint32_t input_buffer_length; + /* Input frame size */ + uint32_t input_frame_size; + /* Device sampling rate. If user rate is not + * accepted an compatible rate is set. If it is + * accepted this is equal to params.rate. */ + uint32_t input_device_rate; + /* Exchange input buffers between input + * and full duplex threads. */ + array_queue * input_queue; + /* Silent input buffer used on full duplex. */ + void * input_silent_buffer; + /* Number of input frames from the start of the stream*/ + uint32_t input_total_frames; + /* Flag to stop the execution of user callback and + * close all working threads. Synchronized by + * stream::mutex lock. */ + uint32_t shutdown; + /* Store user callback. */ + cubeb_data_callback data_callback; + /* Store state callback. */ + cubeb_state_callback state_callback; + /* User pointer for data & state callbacks*/ + void * user_ptr; + + cubeb_resampler * resampler; + unsigned int inputrate; + unsigned int output_configured_rate; + unsigned int latency_frames; + int64_t lastPosition; + int64_t lastPositionTimeStamp; + int64_t lastCompensativePosition; +}; + +/* 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 uint32_t +opensl_get_shutdown(cubeb_stream * stm) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + assert((r == EDEADLK || r == EBUSY) && "get_shutdown: mutex should be locked but it's not."); +#endif + return stm->shutdown; +} + +static void +opensl_set_shutdown(cubeb_stream * stm, uint32_t value) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + LOG("set shutdown try r = %d", r); + assert((r == EDEADLK || r == EBUSY) && "set_shutdown: mutex should be locked but it's not."); +#endif + assert(value == 0 || value == 1); + stm->shutdown = value; +} + +static void +play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) +{ + cubeb_stream * stm = user_ptr; + int draining; + assert(stm); + switch (event) { + case SL_PLAYEVENT_HEADATMARKER: + { + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + 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) { + r = opensl_stop_player(stm); + assert(r == CUBEB_OK); + } + if (stm->recorderItf) { + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + } + } + } + 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); + // 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"); +} + +#if defined(__ANDROID__) +static SLuint32 +convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) +{ + switch(stream_type) { + case CUBEB_STREAM_TYPE_SYSTEM: + return SL_ANDROID_STREAM_SYSTEM; + case CUBEB_STREAM_TYPE_MUSIC: + return SL_ANDROID_STREAM_MEDIA; + case CUBEB_STREAM_TYPE_NOTIFICATION: + return SL_ANDROID_STREAM_NOTIFICATION; + case CUBEB_STREAM_TYPE_ALARM: + return SL_ANDROID_STREAM_ALARM; + case CUBEB_STREAM_TYPE_VOICE_CALL: + return SL_ANDROID_STREAM_VOICE; + case CUBEB_STREAM_TYPE_RING: + return SL_ANDROID_STREAM_RING; + case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED: + return SL_ANDROID_STREAM_SYSTEM_ENFORCED; + default: + return 0xFFFFFFFF; + } +} +#endif + +static void opensl_destroy(cubeb * ctx); + +#if defined(__ANDROID__) +#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); + ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!ctx->lib || !ctx->libmedia) { + free(ctx); + return CUBEB_ERROR; + } + + /* Get the latency, in ms, from AudioFlinger */ + /* status_t AudioSystem::getOutputLatency(uint32_t* latency, + * audio_stream_type_t streamType) */ + /* First, try the most recent signature. */ + ctx->get_output_latency = + dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); + if (!ctx->get_output_latency) { + /* in case of failure, try the legacy version. */ + /* status_t AudioSystem::getOutputLatency(uint32_t* latency, + * int streamType) */ + ctx->get_output_latency = + dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); + if (!ctx->get_output_latency) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + } + + typedef SLresult (*slCreateEngine_t)(SLObjectItf *, + SLuint32, + const SLEngineOption *, + SLuint32, + const SLInterfaceID *, + const SLboolean *); + slCreateEngine_t f_slCreateEngine = + (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); + SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); + SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); + ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME"); + ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); +#if defined(__ANDROID__) + ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); + 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; + } + + *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 int +opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html + * We don't want to deal with JNI here (and we don't have Java on b2g anyways), + * so we just dlopen the library and get the two symbols we need. */ + int r; + void * libmedia; + uint32_t (*get_primary_output_samplingrate)(); + uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType); + + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { + return CUBEB_ERROR; + } + + /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */ + get_primary_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv"); + if (!get_primary_output_samplingrate) { + /* fallback to + * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) + * if we cannot find getPrimaryOutputSamplingRate. */ + get_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t"); + if (!get_output_samplingrate) { + /* Another signature exists, with a int instead of an audio_stream_type_t */ + get_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"); + if (!get_output_samplingrate) { + dlclose(libmedia); + return CUBEB_ERROR; + } + } + } + + if (get_primary_output_samplingrate) { + *rate = get_primary_output_samplingrate(); + } else { + /* We don't really know about the type, here, so we just pass music. */ + r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC); + if (r) { + dlclose(libmedia); + return CUBEB_ERROR; + } + } + + dlclose(libmedia); + + /* Depending on which method we called above, we can get a zero back, yet have + * a non-error return value, especially if the audio system is not + * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger + * thread). */ + if (*rate == 0) { + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) +{ + /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html + * We don't want to deal with JNI here (and we don't have Java on b2g anyways), + * so we just dlopen the library and get the two symbols we need. */ + + int r; + void * libmedia; + size_t (*get_primary_output_frame_count)(void); + int (*get_output_frame_count)(size_t * frameCount, int streamType); + uint32_t primary_sampling_rate; + size_t primary_buffer_size; + + r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); + + if (r) { + return CUBEB_ERROR; + } + + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { + return CUBEB_ERROR; + } + + /* JB variant */ + /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */ + get_primary_output_frame_count = + dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv"); + if (!get_primary_output_frame_count) { + /* ICS variant */ + /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */ + get_output_frame_count = + dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii"); + if (!get_output_frame_count) { + dlclose(libmedia); + return CUBEB_ERROR; + } + } + + if (get_primary_output_frame_count) { + primary_buffer_size = get_primary_output_frame_count(); + } else { + if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { + return CUBEB_ERROR; + } + } + + /* To get a fast track in Android's mixer, we need to be at the native + * samplerate, which is device dependant. Some devices might be able to + * resample when playing a fast track, but it's pretty rare. */ + *latency_frames = primary_buffer_size; + + dlclose(libmedia); + + return CUBEB_OK; +} + +static void +opensl_destroy(cubeb * ctx) +{ + if (ctx->outmixObj) + (*ctx->outmixObj)->Destroy(ctx->outmixObj); + if (ctx->engObj) + cubeb_destroy_sles_engine(&ctx->engObj); + dlclose(ctx->lib); + dlclose(ctx->libmedia); + free(ctx); +} + +static void opensl_stream_destroy(cubeb_stream * stm); + +static int +opensl_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 SLuint32 lSoundRecorderIIDCount = 2; + const SLInterfaceID lSoundRecorderIIDs[] = { stm->context->SL_IID_RECORD, + stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; + const SLboolean lSoundRecorderReqs[] = { 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, + lSoundRecorderIIDCount, + 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 input only scenario. This is + // the correct rate to use to get a fast track for input only. + r = opensl_get_preferred_sample_rate(stm->context, &stm->input_device_rate); + if (r != CUBEB_OK) { + // If everything else fail use a safe choice for Android. + 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, + lSoundRecorderIIDCount, + lSoundRecorderIIDs, + lSoundRecorderReqs); + + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to create 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->latency_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->inputrate = params->rate; + stm->stream_type = params->stream_type; + stm->framesize = params->channels * sizeof(int16_t); + stm->lastPosition = -1; + stm->lastPositionTimeStamp = 0; + stm->lastCompensativePosition = -1; + + SLDataFormat_PCM format; + int r = opensl_set_format(&format, params); + if (r != CUBEB_OK) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + 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)); + + unsigned int latency_frames = stm->latency_frames; + uint32_t preferred_sampling_rate = stm->inputrate; +#if defined(__ANDROID__) + if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) { + // Reset preferred samping rate to trigger fallback to native sampling rate. + preferred_sampling_rate = 0; + if (opensl_get_min_latency(stm->context, *params, &latency_frames) != CUBEB_OK) { + // Default to AudioFlinger's advertised fast track latency of 10ms. + latency_frames = 440; + } + stm->latency_frames = latency_frames; + } +#endif + + 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) { + if (opensl_get_preferred_sample_rate(stm->context, &preferred_sampling_rate)) { + // If fail default is used + preferred_sampling_rate = DEFAULT_SAMPLE_RATE; + } + + format.samplesPerSec = 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 * latency_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]); + } + +#if defined(__ANDROID__) + SLuint32 stream_type = convert_stream_type_to_sl_stream(params->stream_type); + if (stream_type != 0xFFFFFFFF) { + SLAndroidConfigurationItf playerConfig; + 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; + } + + res = (*playerConfig)->SetConfiguration(playerConfig, + SL_ANDROID_KEY_STREAM_TYPE, + &stream_type, + sizeof(SLint32)); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set android configuration interface. Error code: %lu", res); + return CUBEB_ERROR; + } + } +#endif + + 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; + } + + 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; + } + return CUBEB_OK; +} + +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->latency_frames = latency_frames; + stm->input_enabled = (input_stream_params) ? 1 : 0; + stm->output_enabled = (output_stream_params) ? 1 : 0; + stm->shutdown = 1; + +#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->latency_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->latency_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; + uint64_t samplerate; + SLresult res; + int r; + uint32_t mixer_latency; + uint32_t compensation_msec = 0; + + res = (*stm->play)->GetPosition(stm->play, &msec); + if (res != SL_RESULT_SUCCESS) + return CUBEB_ERROR; + + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + if(stm->lastPosition == msec) { + compensation_msec = + (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000; + } else { + stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec; + stm->lastPosition = msec; + } + + samplerate = stm->inputrate; + + r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + if (r) { + return CUBEB_ERROR; + } + + pthread_mutex_lock(&stm->mutex); + int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->output_configured_rate; + pthread_mutex_unlock(&stm->mutex); + assert(maximum_position >= 0); + + if (msec > mixer_latency) { + int64_t unadjusted_position; + if (stm->lastCompensativePosition > msec + compensation_msec) { + // Over compensation, use lastCompensativePosition. + unadjusted_position = + samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000; + } else { + unadjusted_position = + samplerate * (msec - mixer_latency + compensation_msec) / 1000; + stm->lastCompensativePosition = msec + compensation_msec; + } + *position = unadjusted_position < maximum_position ? + unadjusted_position : maximum_position; + } else { + *position = 0; + } + return CUBEB_OK; +} + +int +opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + int r; + uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms. + + /* audio_stream_type_t is an int, so this is okay. */ + r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + if (r) { + return CUBEB_ERROR; + } + + *latency = stm->latency_frames + // OpenSL latency + mixer_latency * stm->inputrate / 1000; // AudioFlinger latency + + return CUBEB_OK; +} + +int +opensl_stream_set_volume(cubeb_stream * stm, float volume) +{ + SLresult res; + SLmillibel max_level, millibels; + float unclamped_millibels; + + res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level); + + if (res != SL_RESULT_SUCCESS) { + return CUBEB_ERROR; + } + + /* millibels are 100*dB, so the conversion from the volume's linear amplitude + * is 100 * 20 * log(volume). However we clamp the resulting value before + * passing it to lroundf() in order to prevent it from silently returning an + * erroneous value when the unclamped value exceeds the size of a long. */ + unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f)); + unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN); + unclamped_millibels = fminf(unclamped_millibels, max_level); + + millibels = lroundf(unclamped_millibels); + + res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels); + + if (res != SL_RESULT_SUCCESS) { + return CUBEB_ERROR; + } + return CUBEB_OK; +} + +static struct cubeb_ops const opensl_ops = { + .init = opensl_init, + .get_backend_id = opensl_get_backend_id, + .get_max_channel_count = opensl_get_max_channel_count, + .get_min_latency = opensl_get_min_latency, + .get_preferred_sample_rate = opensl_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, + .enumerate_devices = NULL, + .device_collection_destroy = NULL, + .destroy = opensl_destroy, + .stream_init = opensl_stream_init, + .stream_destroy = opensl_stream_destroy, + .stream_start = opensl_stream_start, + .stream_stop = opensl_stream_stop, + .stream_get_position = opensl_stream_get_position, + .stream_get_latency = opensl_stream_get_latency, + .stream_set_volume = opensl_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; diff --git a/Externals/cubeb/src/cubeb_osx_run_loop.cpp b/Externals/cubeb/src/cubeb_osx_run_loop.cpp new file mode 100644 index 0000000000..de4e43970c --- /dev/null +++ b/Externals/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/Externals/cubeb/src/cubeb_osx_run_loop.h b/Externals/cubeb/src/cubeb_osx_run_loop.h new file mode 100644 index 0000000000..78cd68d09b --- /dev/null +++ b/Externals/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/Externals/cubeb/src/cubeb_panner.cpp b/Externals/cubeb/src/cubeb_panner.cpp new file mode 100644 index 0000000000..bd96ed6ef0 --- /dev/null +++ b/Externals/cubeb/src/cubeb_panner.cpp @@ -0,0 +1,60 @@ +/* + * Copyright © 2014 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#define _USE_MATH_DEFINES +#include +#include + +#include "cubeb_panner.h" + +#ifndef M_PI +#define M_PI 3.14159263 +#endif + +/** + * We use a cos/sin law. + */ + +namespace { +template +void cubeb_pan_stereo_buffer(T * buf, uint32_t frames, float pan) +{ + if (pan == 0.0) { + return; + } + /* rescale in [0; 1] */ + pan += 1; + pan /= 2; + float left_gain = float(cos(pan * M_PI * 0.5)); + float right_gain = float(sin(pan * M_PI * 0.5)); + + /* In we are panning on the left, pan the right channel into the left one and + * vice-versa. */ + if (pan < 0.5) { + for (uint32_t i = 0; i < frames * 2; i+=2) { + buf[i] = T(buf[i] + buf[i + 1] * left_gain); + buf[i + 1] = T(buf[i + 1] * right_gain); + } + } else { + for (uint32_t i = 0; i < frames * 2; i+=2) { + buf[i] = T(buf[i] * left_gain); + buf[i + 1] = T(buf[i + 1] + buf[i] * right_gain); + } + } +} +} + +void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan) +{ + cubeb_pan_stereo_buffer(buf, frames, pan); +} + +void cubeb_pan_stereo_buffer_int(short * buf, uint32_t frames, float pan) +{ + cubeb_pan_stereo_buffer(buf, frames, pan); +} + diff --git a/Externals/cubeb/src/cubeb_panner.h b/Externals/cubeb/src/cubeb_panner.h new file mode 100644 index 0000000000..c61363b2bc --- /dev/null +++ b/Externals/cubeb/src/cubeb_panner.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2014 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#if !defined(CUBEB_PANNER) +#define CUBEB_PANNER + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * Pan an integer or an float stereo buffer according to a cos/sin pan law + * @param buf the buffer to pan + * @param frames the number of frames in `buf` + * @param pan a float in [-1.0; 1.0] + */ +void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan); +void cubeb_pan_stereo_buffer_int(short* buf, uint32_t frames, float pan); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/Externals/cubeb/src/cubeb_pulse.c b/Externals/cubeb/src/cubeb_pulse.c new file mode 100644 index 0000000000..7efc6bf6e0 --- /dev/null +++ b/Externals/cubeb/src/cubeb_pulse.c @@ -0,0 +1,1509 @@ +/* + * 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 "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_mixer.h" +#include "cubeb_utils.h" +#include + +#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) \ + +#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; +LIBPULSE_API_VISIT(MAKE_TYPEDEF); +#undef MAKE_TYPEDEF +#endif + +static struct cubeb_ops const pulse_ops; + +struct cubeb { + struct cubeb_ops const * ops; + void * libpulse; + pa_threaded_mainloop * mainloop; + pa_context * context; + pa_sink_info * default_sink_info; + char * context_name; + int error; + cubeb_device_collection_changed_callback collection_changed_callback; + void * collection_changed_user_ptr; +}; + +struct cubeb_stream { + cubeb * context; + pa_stream * output_stream; + pa_stream * input_stream; + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + pa_time_event * drain_timer; + pa_sample_spec output_sample_spec; + pa_sample_spec input_sample_spec; + int shutdown; + float volume; + cubeb_state state; +}; + +static const float PULSE_NO_GAIN = -1.0; + +enum cork_state { + UNCORK = 0, + CORK = 1 << 0, + NOTIFY = 1 << 1 +}; + +static void +sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u) +{ + (void)context; + cubeb * ctx = u; + if (!eol) { + free(ctx->default_sink_info); + ctx->default_sink_info = malloc(sizeof(pa_sink_info)); + memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info)); + } + WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0); +} + +static void +server_info_callback(pa_context * context, const pa_server_info * info, void * u) +{ + 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) +{ + assert(channel != CHANNEL_INVALID); + + // This variable may be used for multiple times, so we should avoid to + // allocate it in stack, or it will be created and removed repeatedly. + // Use static to allocate this local variable in data space instead of stack. + static pa_channel_position_t map[CHANNEL_MAX] = { + // PA_CHANNEL_POSITION_INVALID, // CHANNEL_INVALID + PA_CHANNEL_POSITION_MONO, // CHANNEL_MONO + PA_CHANNEL_POSITION_FRONT_LEFT, // CHANNEL_LEFT + PA_CHANNEL_POSITION_FRONT_RIGHT, // CHANNEL_RIGHT + PA_CHANNEL_POSITION_FRONT_CENTER, // CHANNEL_CENTER + PA_CHANNEL_POSITION_SIDE_LEFT, // CHANNEL_LS + PA_CHANNEL_POSITION_SIDE_RIGHT, // CHANNEL_RS + PA_CHANNEL_POSITION_REAR_LEFT, // CHANNEL_RLS + PA_CHANNEL_POSITION_REAR_CENTER, // CHANNEL_RCENTER + PA_CHANNEL_POSITION_REAR_RIGHT, // CHANNEL_RRS + PA_CHANNEL_POSITION_LFE // CHANNEL_LFE + }; + + return map[channel]; +} + +static cubeb_channel +pa_channel_to_cubeb_channel(pa_channel_position_t channel) +{ + assert(channel != PA_CHANNEL_POSITION_INVALID); + switch(channel) { + case PA_CHANNEL_POSITION_MONO: return CHANNEL_MONO; + case PA_CHANNEL_POSITION_FRONT_LEFT: return CHANNEL_LEFT; + case PA_CHANNEL_POSITION_FRONT_RIGHT: return CHANNEL_RIGHT; + case PA_CHANNEL_POSITION_FRONT_CENTER: return CHANNEL_CENTER; + case PA_CHANNEL_POSITION_SIDE_LEFT: return CHANNEL_LS; + case PA_CHANNEL_POSITION_SIDE_RIGHT: return CHANNEL_RS; + case PA_CHANNEL_POSITION_REAR_LEFT: return CHANNEL_RLS; + case PA_CHANNEL_POSITION_REAR_CENTER: return CHANNEL_RCENTER; + case PA_CHANNEL_POSITION_REAR_RIGHT: return CHANNEL_RRS; + case PA_CHANNEL_POSITION_LFE: return CHANNEL_LFE; + default: return CHANNEL_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); + cm->channels = CUBEB_CHANNEL_LAYOUT_MAPS[layout].channels; + for (uint8_t i = 0 ; i < cm->channels ; ++i) { + cm->map[i] = cubeb_channel_to_pa_channel(CHANNEL_INDEX_TO_ORDER[layout][i]); + } +} + +static cubeb_channel_layout +channel_map_to_layout(pa_channel_map * cm) +{ + cubeb_channel_map cubeb_map; + cubeb_map.channels = cm->channels; + for (uint32_t i = 0 ; i < cm->channels ; ++i) { + cubeb_map.map[i] = pa_channel_to_cubeb_channel(cm->map[i]); + } + return cubeb_channel_map_to_layout(&cubeb_map); +} + +static void pulse_context_destroy(cubeb * ctx); +static void pulse_destroy(cubeb * ctx); + +static int +pulse_context_init(cubeb * ctx) +{ + if (ctx->context) { + assert(ctx->error == 1); + pulse_context_destroy(ctx); + } + + ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop), + ctx->context_name); + if (!ctx->context) { + return -1; + } + WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx); + + WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); + WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); + + if (wait_until_context_ready(ctx) != 0) { + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + pulse_context_destroy(ctx); + ctx->context = NULL; + return -1; + } + + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + + ctx->error = 0; + + return 0; +} + +/*static*/ int +pulse_init(cubeb ** context, char const * context_name) +{ + void * libpulse = NULL; + cubeb * ctx; + pa_operation * o; + + *context = NULL; + +#ifndef DISABLE_LIBPULSE_DLOPEN + libpulse = dlopen("libpulse.so.0", RTLD_LAZY); + if (!libpulse) { + return CUBEB_ERROR; + } + +#define LOAD(x) { \ + cubeb_##x = dlsym(libpulse, #x); \ + if (!cubeb_##x) { \ + dlclose(libpulse); \ + return CUBEB_ERROR; \ + } \ + } + + LIBPULSE_API_VISIT(LOAD); +#undef LOAD +#endif + + ctx = calloc(1, sizeof(*ctx)); + assert(ctx); + + ctx->ops = &pulse_ops; + ctx->libpulse = libpulse; + + ctx->mainloop = WRAP(pa_threaded_mainloop_new)(); + ctx->default_sink_info = NULL; + + WRAP(pa_threaded_mainloop_start)(ctx->mainloop); + + ctx->context_name = context_name ? strdup(context_name) : NULL; + if (pulse_context_init(ctx) != 0) { + pulse_destroy(ctx); + return CUBEB_ERROR; + } + + /* 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); + assert(ctx->default_sink_info); + + *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); + + *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; + + *rate = ctx->default_sink_info->sample_spec.rate; + + return CUBEB_OK; +} + +static int +pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout) +{ + assert(ctx && layout); + (void)ctx; + + *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map); + + 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->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 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 && + CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels)); + *pa_stm = NULL; + pa_sample_spec ss; + ss.format = to_pulse_format(stream_params->format); + if (ss.format == PA_SAMPLE_INVALID) + return CUBEB_ERROR_INVALID_FORMAT; + ss.rate = stream_params->rate; + ss.channels = stream_params->channels; + + if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) { + *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; + + return CUBEB_OK; +} + +static void +pulse_stream_destroy(cubeb_stream * stm) +{ + stream_cork(stm, CORK); + + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + if (stm->output_stream) { + + if (stm->drain_timer) { + /* there's no pa_rttime_free, so use this instead. */ + WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer); + } + + WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL); + WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL); + WRAP(pa_stream_disconnect)(stm->output_stream); + WRAP(pa_stream_unref)(stm->output_stream); + } + + if (stm->input_stream) { + WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL); + WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL); + WRAP(pa_stream_disconnect)(stm->input_stream); + WRAP(pa_stream_unref)(stm->input_stream); + } + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + + free(stm); +} + +static void +pulse_defer_event_cb(pa_mainloop_api * a, void * userdata) +{ + (void)a; + cubeb_stream * stm = userdata; + if (stm->shutdown) { + return; + } + size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream); + trigger_user_callback(stm->output_stream, NULL, writable_size, stm); +} + +static int +pulse_stream_start(cubeb_stream * stm) +{ + stm->shutdown = 0; + stream_cork(stm, UNCORK | NOTIFY); + + if (stm->output_stream && !stm->input_stream) { + /* On output only case need to manually call user cb once in order to make + * things roll. This is done via a defer event in order to execute it + * from PA server thread. */ + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + WRAP(pa_mainloop_api_once)(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop), + pulse_defer_event_cb, stm); + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + } + + return CUBEB_OK; +} + +static int +pulse_stream_stop(cubeb_stream * stm) +{ + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + stm->shutdown = 1; + // If draining is taking place wait to finish + while (stm->drain_timer) { + WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop); + } + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + + stream_cork(stm, CORK | NOTIFY); + return CUBEB_OK; +} + +static int +pulse_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + int r, in_thread; + pa_usec_t r_usec; + uint64_t bytes; + + if (!stm || !stm->output_stream) { + return CUBEB_ERROR; + } + + in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop); + + if (!in_thread) { + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + } + r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec); + if (!in_thread) { + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + } + + if (r != 0) { + return CUBEB_ERROR; + } + + bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec); + *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec); + + return CUBEB_OK; +} + +static int +pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + pa_usec_t r_usec; + int negative, r; + + if (!stm || !stm->output_stream) { + return CUBEB_ERROR; + } + + r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative); + assert(!negative); + if (r) { + return CUBEB_ERROR; + } + + *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC; + return CUBEB_OK; +} + +static void +volume_success(pa_context *c, int success, void *userdata) +{ + (void)success; + (void)c; + cubeb_stream * stream = userdata; + assert(success); + WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0); +} + +static int +pulse_stream_set_volume(cubeb_stream * stm, float volume) +{ + uint32_t index; + pa_operation * op; + pa_volume_t vol; + pa_cvolume cvol; + const pa_sample_spec * ss; + + if (!stm->output_stream) { + return CUBEB_ERROR; + } + + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + + /* if the pulse daemon is configured to use flat volumes, + * apply our own gain instead of changing the input volume on the sink. */ + if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) { + stm->volume = volume; + } else { + ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream); + + vol = WRAP(pa_sw_volume_from_linear)(volume); + WRAP(pa_cvolume_set)(&cvol, ss->channels, vol); + + index = WRAP(pa_stream_get_index)(stm->output_stream); + + op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, + index, &cvol, volume_success, + stm); + if (op) { + operation_wait(stm->context, stm->output_stream, op); + WRAP(pa_operation_unref)(op); + } + } + + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + + return CUBEB_OK; +} + +struct sink_input_info_result { + pa_cvolume * cvol; + pa_threaded_mainloop * mainloop; +}; + +static void +sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u) +{ + struct sink_input_info_result * r = u; + if (!eol) { + *r->cvol = i->volume; + } + WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0); +} + +static int +pulse_stream_set_panning(cubeb_stream * stm, float panning) +{ + const pa_channel_map * map; + pa_cvolume cvol; + uint32_t index; + pa_operation * op; + + if (!stm->output_stream) { + return CUBEB_ERROR; + } + + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + + map = WRAP(pa_stream_get_channel_map)(stm->output_stream); + if (!WRAP(pa_channel_map_can_balance)(map)) { + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + return CUBEB_ERROR; + } + + index = WRAP(pa_stream_get_index)(stm->output_stream); + + struct sink_input_info_result r = { &cvol, stm->context->mainloop }; + op = WRAP(pa_context_get_sink_input_info)(stm->context->context, + index, + sink_input_info_cb, + &r); + if (op) { + operation_wait(stm->context, stm->output_stream, op); + WRAP(pa_operation_unref)(op); + } + + WRAP(pa_cvolume_set_balance)(&cvol, map, panning); + + op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, + index, &cvol, volume_success, + stm); + if (op) { + operation_wait(stm->context, stm->output_stream, op); + WRAP(pa_operation_unref)(op); + } + + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + + return CUBEB_OK; +} + +typedef struct { + char * default_sink_name; + char * default_source_name; + + cubeb_device_info * devinfo; + uint32_t max; + uint32_t count; + cubeb * context; +} pulse_dev_list_data; + +static cubeb_device_fmt +pulse_format_to_cubeb_format(pa_sample_format_t format) +{ + switch (format) { + case PA_SAMPLE_S16LE: + return CUBEB_DEVICE_FMT_S16LE; + case PA_SAMPLE_S16BE: + return CUBEB_DEVICE_FMT_S16BE; + case PA_SAMPLE_FLOAT32LE: + return CUBEB_DEVICE_FMT_F32LE; + case PA_SAMPLE_FLOAT32BE: + return CUBEB_DEVICE_FMT_F32BE; + default: + return CUBEB_DEVICE_FMT_F32NE; + } +} + +static void +pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data) +{ + if (list_data->count == list_data->max) { + list_data->max += 8; + list_data->devinfo = realloc(list_data->devinfo, + sizeof(cubeb_device_info) * list_data->max); + } +} + +static cubeb_device_state +pulse_get_state_from_sink_port(pa_sink_port_info * info) +{ + if (info != NULL) { +#if PA_CHECK_VERSION(2, 0, 0) + if (info->available == PA_PORT_AVAILABLE_NO) + return CUBEB_DEVICE_STATE_UNPLUGGED; + else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ +#endif + return CUBEB_DEVICE_STATE_ENABLED; + } + + return CUBEB_DEVICE_STATE_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; + const char * prop; + + (void)context; + + if (eol || info == 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 = strdup(info->name); + 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; + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); +} + +static cubeb_device_state +pulse_get_state_from_source_port(pa_source_port_info * info) +{ + if (info != NULL) { +#if PA_CHECK_VERSION(2, 0, 0) + if (info->available == PA_PORT_AVAILABLE_NO) + return CUBEB_DEVICE_STATE_UNPLUGGED; + else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ +#endif + return CUBEB_DEVICE_STATE_ENABLED; + } + + return CUBEB_DEVICE_STATE_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; + const char * prop; + + (void)context; + + if (eol) + 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 = strdup(info->name); + 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; + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); +} + +static void +pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata) +{ + pulse_dev_list_data * list_data = userdata; + + (void)c; + + free(list_data->default_sink_name); + free(list_data->default_source_name); + list_data->default_sink_name = strdup(i->default_sink_name); + list_data->default_source_name = strdup(i->default_source_name); + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); +} + +static int +pulse_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context }; + pa_operation * o; + + 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_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) +{ +#if PA_CHECK_VERSION(0, 9, 8) + *device = calloc(1, sizeof(cubeb_device)); + if (*device == NULL) + return CUBEB_ERROR; + + if (stm->input_stream) { + const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream); + (*device)->input_name = (name == NULL) ? NULL : strdup(name); + } + + if (stm->output_stream) { + const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream); + (*device)->output_name = (name == NULL) ? NULL : strdup(name); + } + + return CUBEB_OK; +#else + return CUBEB_ERROR_NOT_SUPPORTED; +#endif +} + +static int +pulse_stream_device_destroy(cubeb_stream * stream, + cubeb_device * device) +{ + (void)stream; + free(device->input_name); + free(device->output_name); + free(device); + return CUBEB_OK; +} + +static void +pulse_subscribe_callback(pa_context * ctx, + pa_subscription_event_type_t t, + uint32_t index, void * userdata) +{ + (void)ctx; + cubeb * context = userdata; + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SOURCE: + case PA_SUBSCRIPTION_EVENT_SINK: + + if (g_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 sink index %d", index); + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + LOG("Adding sink index %d", index); + } + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + LOG("Removing source index %d", index); + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + LOG("Adding source index %d", index); + } + } + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE || + (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + context->collection_changed_callback(context, context->collection_changed_user_ptr); + } + break; + } +} + +static void +subscribe_success(pa_context *c, int success, void *userdata) +{ + (void)c; + cubeb * context = userdata; + assert(success); + WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0); +} + +static int +pulse_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + context->collection_changed_callback = collection_changed_callback; + context->collection_changed_user_ptr = user_ptr; + + WRAP(pa_threaded_mainloop_lock)(context->mainloop); + + pa_subscription_mask_t mask; + if (context->collection_changed_callback == NULL) { + // Unregister subscription + WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL); + mask = PA_SUBSCRIPTION_MASK_NULL; + } else { + WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); + if (devtype == CUBEB_DEVICE_TYPE_INPUT) + mask = PA_SUBSCRIPTION_MASK_SOURCE; + else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT) + mask = PA_SUBSCRIPTION_MASK_SINK; + else + mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; + } + + pa_operation * o; + o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context); + if (o == NULL) { + LOG("Context subscribe failed"); + return CUBEB_ERROR; + } + operation_wait(context, NULL, o); + WRAP(pa_operation_unref)(o); + + WRAP(pa_threaded_mainloop_unlock)(context->mainloop); + + return CUBEB_OK; +} + +static struct cubeb_ops const pulse_ops = { + .init = pulse_init, + .get_backend_id = pulse_get_backend_id, + .get_max_channel_count = pulse_get_max_channel_count, + .get_min_latency = pulse_get_min_latency, + .get_preferred_sample_rate = pulse_get_preferred_sample_rate, + .get_preferred_channel_layout = pulse_get_preferred_channel_layout, + .enumerate_devices = pulse_enumerate_devices, + .device_collection_destroy = cubeb_utils_default_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_get_position = pulse_stream_get_position, + .stream_get_latency = pulse_stream_get_latency, + .stream_set_volume = pulse_stream_set_volume, + .stream_set_panning = pulse_stream_set_panning, + .stream_get_current_device = pulse_stream_get_current_device, + .stream_device_destroy = pulse_stream_device_destroy, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = pulse_register_device_collection_changed +}; diff --git a/Externals/cubeb/src/cubeb_resampler.cpp b/Externals/cubeb/src/cubeb_resampler.cpp new file mode 100644 index 0000000000..65dd0a86a0 --- /dev/null +++ b/Externals/cubeb/src/cubeb_resampler.cpp @@ -0,0 +1,315 @@ +/* + * 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; + } +} + +template +passthrough_resampler::passthrough_resampler(cubeb_stream * s, + cubeb_data_callback cb, + void * ptr, + uint32_t input_channels) + : processor(input_channels) + , stream(s) + , data_callback(cb) + , user_ptr(ptr) +{ +} + +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 && + *input_frames_count + static_cast(samples_to_frames(internal_input_buffer.length())) >= output_frames) || + (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || + (input_buffer && !output_buffer && output_frames == 0)); + + if (input_buffer) { + if (!output_buffer) { + output_frames = *input_frames_count; + } + internal_input_buffer.push(static_cast(input_buffer), + frames_to_samples(*input_frames_count)); + } + + long rv = data_callback(stream, user_ptr, internal_input_buffer.data(), + output_buffer, output_frames); + + if (input_buffer) { + internal_input_buffer.pop(nullptr, frames_to_samples(output_frames)); + *input_frames_count = output_frames; + } + + return rv; +} + +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); + + long got = 0; + T * out_unprocessed = nullptr; + long output_frames_before_processing = 0; + + /* fill directly the input buffer of the output processor to save a copy */ + output_frames_before_processing = + output_processor->input_needed_for_output(output_frames_needed); + + out_unprocessed = + output_processor->input_buffer(output_frames_before_processing); + + got = data_callback(stream, user_ptr, + nullptr, out_unprocessed, + output_frames_before_processing); + + if (got < 0) { + return got; + } + + output_processor->written(got); + + /* Process the output. If not enough frames have been returned from the + * callback, drain the processors. */ + return output_processor->output(output_buffer, output_frames_needed); +} + +template +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); + resampled_input = input_processor->output(resampled_frame_count, (size_t*)input_frames_count); + + long got = data_callback(stream, user_ptr, + resampled_input, nullptr, resampled_frame_count); + + /* Return the number of initial input frames or part of it. + * Since output_frames_needed == 0 in input scenario, the only + * available number outside resampler is the initial number of frames. */ + return (*input_frames_count) * (got / resampled_frame_count); +} + +template +long +cubeb_resampler_speex +::fill_internal_duplex(T * in_buffer, long * input_frames_count, + T * out_buffer, long output_frames_needed) +{ + /* The input data, after eventual resampling. This is passed to the callback. */ + T * resampled_input = nullptr; + /* The output buffer passed down in the callback, that might be resampled. */ + T * out_unprocessed = nullptr; + size_t output_frames_before_processing = 0; + /* The number of frames returned from the callback. */ + long got = 0; + + /* We need to determine how much frames to present to the consumer. + * - If we have a two way stream, but we're only resampling input, we resample + * the input to the number of output frames. + * - If we have a two way stream, but we're only resampling the output, we + * resize the input buffer of the output resampler to the number of input + * frames, and we resample it afterwards. + * - If we resample both ways, we resample the input to the number of frames + * we would need to pass down to the consumer (before resampling the output), + * get the output data, and resample it to the number of frames needed by the + * caller. */ + + output_frames_before_processing = + output_processor->input_needed_for_output(output_frames_needed); + /* fill directly the input buffer of the output processor to save a copy */ + out_unprocessed = + output_processor->input_buffer(output_frames_before_processing); + + if (in_buffer) { + /* process the input, and present exactly `output_frames_needed` in the + * callback. */ + input_processor->input(in_buffer, *input_frames_count); + resampled_input = + input_processor->output(output_frames_before_processing, (size_t*)input_frames_count); + } else { + resampled_input = nullptr; + } + + got = data_callback(stream, user_ptr, + resampled_input, out_unprocessed, + output_frames_before_processing); + + if (got < 0) { + return got; + } + + output_processor->written(got); + + /* Process the output. If not enough frames have been returned from the + * callback, drain the processors. */ + return output_processor->output(out_buffer, output_frames_needed); +} + +/* Resampler C API */ + +cubeb_resampler * +cubeb_resampler_create(cubeb_stream * stream, + cubeb_stream_params * input_params, + cubeb_stream_params * output_params, + unsigned int target_rate, + cubeb_data_callback callback, + void * user_ptr, + cubeb_resampler_quality quality) +{ + cubeb_sample_format format; + + assert(input_params || output_params); + + if (input_params) { + format = input_params->format; + } else { + format = output_params->format; + } + + switch(format) { + case CUBEB_SAMPLE_S16NE: + return cubeb_resampler_create_internal(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/Externals/cubeb/src/cubeb_resampler.h b/Externals/cubeb/src/cubeb_resampler.h new file mode 100644 index 0000000000..020ccc17ab --- /dev/null +++ b/Externals/cubeb/src/cubeb_resampler.h @@ -0,0 +1,78 @@ +/* + * Copyright © 2014 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#ifndef CUBEB_RESAMPLER_H +#define CUBEB_RESAMPLER_H + +#include "cubeb/cubeb.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct cubeb_resampler cubeb_resampler; + +typedef enum { + CUBEB_RESAMPLER_QUALITY_VOIP, + CUBEB_RESAMPLER_QUALITY_DEFAULT, + CUBEB_RESAMPLER_QUALITY_DESKTOP +} cubeb_resampler_quality; + +/** + * Create a resampler to adapt the requested sample rate into something that + * is accepted by the audio backend. + * @param stream A cubeb_stream instance supplied to the data callback. + * @param params Used to calculate bytes per frame and buffer size for resampling. + * @param target_rate The sampling rate after resampling. + * @param callback A callback to request data for resampling. + * @param user_ptr User data supplied to the data callback. + * @param quality Quality of the resampler. + * @retval A non-null pointer if success. + */ +cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream, + cubeb_stream_params * input_params, + cubeb_stream_params * output_params, + unsigned int target_rate, + cubeb_data_callback callback, + void * user_ptr, + cubeb_resampler_quality quality); + +/** + * Fill the buffer with frames acquired using the data callback. Resampling will + * happen if necessary. + * @param resampler A cubeb_resampler instance. + * @param input_buffer A buffer of input samples + * @param input_frame_count The size of the buffer. Returns the number of frames + * consumed. + * @param buffer The buffer to be filled. + * @param frames_needed Number of frames that should be produced. + * @retval Number of frames that are actually produced. + * @retval CUBEB_ERROR on error. + */ +long cubeb_resampler_fill(cubeb_resampler * resampler, + void * input_buffer, + long * input_frame_count, + void * output_buffer, + long output_frames_needed); + +/** + * Destroy a cubeb_resampler. + * @param resampler A cubeb_resampler instance. + */ +void cubeb_resampler_destroy(cubeb_resampler * resampler); + +/** + * Returns the latency, in frames, of the resampler. + * @param resampler A cubeb resampler instance. + * @retval The latency, in frames, induced by the resampler. + */ +long cubeb_resampler_latency(cubeb_resampler * resampler); + +#if defined(__cplusplus) +} +#endif + +#endif /* CUBEB_RESAMPLER_H */ diff --git a/Externals/cubeb/src/cubeb_resampler_internal.h b/Externals/cubeb/src/cubeb_resampler_internal.h new file mode 100644 index 0000000000..bb5da05723 --- /dev/null +++ b/Externals/cubeb/src/cubeb_resampler_internal.h @@ -0,0 +1,556 @@ +/* + * 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 +#include +#include +#include +#ifdef CUBEB_GECKO_BUILD +#include "mozilla/UniquePtr.h" +// In libc++, symbols such as std::unique_ptr may be defined in std::__1. +// The _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros +// will expand to the correct namespace. +#ifdef _LIBCPP_BEGIN_NAMESPACE_STD +#define MOZ_BEGIN_STD_NAMESPACE _LIBCPP_BEGIN_NAMESPACE_STD +#define MOZ_END_STD_NAMESPACE _LIBCPP_END_NAMESPACE_STD +#else +#define MOZ_BEGIN_STD_NAMESPACE namespace std { +#define MOZ_END_STD_NAMESPACE } +#endif +MOZ_BEGIN_STD_NAMESPACE + using mozilla::DefaultDelete; + using mozilla::UniquePtr; + #define default_delete DefaultDelete + #define unique_ptr UniquePtr +MOZ_END_STD_NAMESPACE +#endif +#include "cubeb/cubeb.h" +#include "cubeb_utils.h" +#include "cubeb-speex-resampler.h" +#include "cubeb_resampler.h" +#include + +/* This header file contains the internal C++ API of the resamplers, for testing. */ + +int to_speex_quality(cubeb_resampler_quality q); + +struct cubeb_resampler { + virtual long fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long frames_needed) = 0; + virtual long latency() = 0; + virtual ~cubeb_resampler() {} +}; + +/** Base class for processors. This is just used to share methods for now. */ +class processor { +public: + explicit processor(uint32_t channels) + : channels(channels) + {} +protected: + size_t frames_to_samples(size_t frames) + { + return frames * channels; + } + size_t samples_to_frames(size_t samples) + { + assert(!(samples % channels)); + return samples / channels; + } + /** The number of channel of the audio buffers to be resampled. */ + const uint32_t channels; +}; + +template +class passthrough_resampler : public cubeb_resampler + , public processor { +public: + passthrough_resampler(cubeb_stream * s, + cubeb_data_callback cb, + void * ptr, + uint32_t input_channels); + + virtual long fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames); + + virtual long latency() + { + return 0; + } + +private: + cubeb_stream * const stream; + const cubeb_data_callback data_callback; + void * const user_ptr; + /* This allows to buffer some input to account for the fact that we buffer + * some inputs. */ + auto_array internal_input_buffer; +}; + +/** Bidirectional resampler, can resample an input and an output stream, or just + * an input stream or output stream. In this case a delay is inserted in the + * opposite direction to keep the streams synchronized. */ +template +class cubeb_resampler_speex : public cubeb_resampler { +public: + cubeb_resampler_speex(InputProcessing * input_processor, + OutputProcessing * output_processor, + cubeb_stream * s, + cubeb_data_callback cb, + void * ptr); + + virtual ~cubeb_resampler_speex(); + + virtual long fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames_needed); + + virtual long latency() + { + if (input_processor && output_processor) { + assert(input_processor->latency() == output_processor->latency()); + return input_processor->latency(); + } else if (input_processor) { + return input_processor->latency(); + } else { + return output_processor->latency(); + } + } + +private: + typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed); + + long fill_internal_duplex(T * input_buffer, long * input_frames_count, + T * output_buffer, long output_frames_needed); + long fill_internal_input(T * input_buffer, long * input_frames_count, + T * output_buffer, long output_frames_needed); + long fill_internal_output(T * input_buffer, long * input_frames_count, + T * output_buffer, long output_frames_needed); + + std::unique_ptr input_processor; + std::unique_ptr output_processor; + processing_callback fill_internal; + cubeb_stream * const stream; + const cubeb_data_callback data_callback; + void * const user_ptr; +}; + +/** Handles one way of a (possibly) duplex resampler, working on interleaved + * audio buffers of type T. This class is designed so that the number of frames + * coming out of the resampler can be precisely controled. It manages its own + * input buffer, and can use the caller's output buffer, or allocate its own. */ +template +class cubeb_resampler_speex_one_way : public processor { +public: + /** The sample type of this resampler, either 16-bit integers or 32-bit + * floats. */ + typedef T sample_type; + /** Construct a resampler resampling from #source_rate to #target_rate, that + * can be arbitrary, strictly positive number. + * @parameter channels The number of channels this resampler will resample. + * @parameter source_rate The sample-rate of the audio input. + * @parameter target_rate The sample-rate of the audio output. + * @parameter quality A number between 0 (fast, low quality) and 10 (slow, + * high quality). */ + cubeb_resampler_speex_one_way(uint32_t channels, + uint32_t source_rate, + uint32_t target_rate, + int quality) + : processor(channels) + , resampling_ratio(static_cast(source_rate) / target_rate) + , additional_latency(0) + , leftover_samples(0) + { + int r; + speex_resampler = speex_resampler_init(channels, source_rate, + target_rate, quality, &r); + assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure"); + } + + /** Destructor, deallocate the resampler */ + virtual ~cubeb_resampler_speex_one_way() + { + speex_resampler_destroy(speex_resampler); + } + + /** Sometimes, it is necessary to add latency on one way of a two-way + * resampler so that the stream are synchronized. This must be called only on + * a fresh resampler, otherwise, silent samples will be inserted in the + * stream. + * @param frames the number of frames of latency to add. */ + void add_latency(size_t frames) + { + additional_latency += frames; + resampling_in_buffer.push_silence(frames_to_samples(frames)); + } + + /* Fill the resampler with `input_frame_count` frames. */ + void input(T * input_buffer, size_t input_frame_count) + { + resampling_in_buffer.push(input_buffer, + frames_to_samples(input_frame_count)); + } + + /** Outputs exactly `output_frame_count` into `output_buffer`. + * `output_buffer` has to be at least `output_frame_count` long. */ + size_t output(T * output_buffer, size_t output_frame_count) + { + uint32_t in_len = samples_to_frames(resampling_in_buffer.length()); + uint32_t out_len = output_frame_count; + + speex_resample(resampling_in_buffer.data(), &in_len, + output_buffer, &out_len); + + /* This shifts back any unresampled samples to the beginning of the input + buffer. */ + resampling_in_buffer.pop(nullptr, frames_to_samples(in_len)); + + return out_len; + } + + size_t output_for_input(uint32_t input_frames) + { + return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length())) + / resampling_ratio); + } + + /** Returns a buffer containing exactly `output_frame_count` resampled frames. + * The consumer should not hold onto the pointer. */ + T * output(size_t output_frame_count, size_t * input_frames_used) + { + if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) { + resampling_out_buffer.reserve(frames_to_samples(output_frame_count)); + } + + uint32_t in_len = samples_to_frames(resampling_in_buffer.length()); + uint32_t out_len = output_frame_count; + + speex_resample(resampling_in_buffer.data(), &in_len, + resampling_out_buffer.data(), &out_len); + + assert(out_len == output_frame_count); + + /* This shifts back any unresampled samples to the beginning of the input + buffer. */ + resampling_in_buffer.pop(nullptr, frames_to_samples(in_len)); + *input_frames_used = in_len; + + return resampling_out_buffer.data(); + } + + /** Get the latency of the resampler, in output frames. */ + uint32_t latency() const + { + /* The documentation of the resampler talks about "samples" here, but it + * only consider a single channel here so it's the same number of frames. */ + int latency = 0; + + latency = + speex_resampler_get_output_latency(speex_resampler) + additional_latency; + + assert(latency >= 0); + + return latency; + } + + /** Returns the number of frames to pass in the input of the resampler to have + * exactly `output_frame_count` resampled frames. This can return a number + * slightly bigger than what is strictly necessary, but it guaranteed that the + * number of output frames will be exactly equal. */ + uint32_t input_needed_for_output(uint32_t output_frame_count) + { + int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); + int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); + float input_frames_needed = + (output_frame_count - unresampled_frames_left) * resampling_ratio + - resampled_frames_left; + if (input_frames_needed < 0) { + return 0; + } + return (uint32_t)ceilf(input_frames_needed); + } + + /** Returns a pointer to the input buffer, that contains empty space for at + * least `frame_count` elements. This is useful so that consumer can directly + * write into the input buffer of the resampler. The pointer returned is + * adjusted so that leftover data are not overwritten. + */ + T * input_buffer(size_t frame_count) + { + leftover_samples = resampling_in_buffer.length(); + resampling_in_buffer.reserve(leftover_samples + + frames_to_samples(frame_count)); + return resampling_in_buffer.data() + leftover_samples; + } + + /** This method works with `input_buffer`, and allows to inform the processor + how much frames have been written in the provided buffer. */ + void written(size_t written_frames) + { + resampling_in_buffer.set_length(leftover_samples + + frames_to_samples(written_frames)); + } +private: + /** Wrapper for the speex resampling functions to have a typed + * interface. */ + void speex_resample(float * input_buffer, uint32_t * input_frame_count, + float * output_buffer, uint32_t * output_frame_count) + { +#ifndef NDEBUG + int rv; + rv = +#endif + speex_resampler_process_interleaved_float(speex_resampler, + input_buffer, + input_frame_count, + output_buffer, + output_frame_count); + assert(rv == RESAMPLER_ERR_SUCCESS); + } + + void speex_resample(short * input_buffer, uint32_t * input_frame_count, + short * output_buffer, uint32_t * output_frame_count) + { +#ifndef NDEBUG + int rv; + rv = +#endif + speex_resampler_process_interleaved_int(speex_resampler, + input_buffer, + input_frame_count, + output_buffer, + output_frame_count); + assert(rv == RESAMPLER_ERR_SUCCESS); + } + /** The state for the speex resampler used internaly. */ + SpeexResamplerState * speex_resampler; + /** Source rate / target rate. */ + const float resampling_ratio; + /** Storage for the input frames, to be resampled. Also contains + * any unresampled frames after resampling. */ + auto_array resampling_in_buffer; + /* Storage for the resampled frames, to be passed back to the caller. */ + auto_array resampling_out_buffer; + /** Additional latency inserted into the pipeline for synchronisation. */ + uint32_t additional_latency; + /** When `input_buffer` is called, this allows tracking the number of samples + that were in the buffer. */ + uint32_t leftover_samples; +}; + +/** This class allows delaying an audio stream by `frames` frames. */ +template +class delay_line : public processor { +public: + /** Constructor + * @parameter frames the number of frames of delay. + * @parameter channels the number of channels of this delay line. */ + delay_line(uint32_t frames, uint32_t channels) + : processor(channels) + , length(frames) + , leftover_samples(0) + { + /* Fill the delay line with some silent frames to add latency. */ + delay_input_buffer.push_silence(frames * channels); + } + /* Add some latency to the delay line. + * @param frames the number of frames of latency to add. */ + void add_latency(size_t frames) + { + length += frames; + delay_input_buffer.push_silence(frames_to_samples(frames)); + } + /** Push some frames into the delay line. + * @parameter buffer the frames to push. + * @parameter frame_count the number of frames in #buffer. */ + void input(T * buffer, uint32_t frame_count) + { + delay_input_buffer.push(buffer, frames_to_samples(frame_count)); + } + /** Pop some frames from the internal buffer, into a internal output buffer. + * @parameter frames_needed the number of frames to be returned. + * @return a buffer containing the delayed frames. The consumer should not + * hold onto the pointer. */ + T * output(uint32_t frames_needed, size_t * input_frames_used) + { + if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) { + delay_output_buffer.reserve(frames_to_samples(frames_needed)); + } + + delay_output_buffer.clear(); + delay_output_buffer.push(delay_input_buffer.data(), + frames_to_samples(frames_needed)); + delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed)); + *input_frames_used = frames_needed; + + return delay_output_buffer.data(); + } + /** Get a pointer to the first writable location in the input buffer> + * @parameter frames_needed the number of frames the user needs to write into + * the buffer. + * @returns a pointer to a location in the input buffer where #frames_needed + * can be writen. */ + T * input_buffer(uint32_t frames_needed) + { + leftover_samples = delay_input_buffer.length(); + delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed)); + return delay_input_buffer.data() + leftover_samples; + } + /** This method works with `input_buffer`, and allows to inform the processor + how much frames have been written in the provided buffer. */ + void written(size_t frames_written) + { + delay_input_buffer.set_length(leftover_samples + + frames_to_samples(frames_written)); + } + /** Drains the delay line, emptying the buffer. + * @parameter output_buffer the buffer in which the frames are written. + * @parameter frames_needed the maximum number of frames to write. + * @return the actual number of frames written. */ + size_t output(T * output_buffer, uint32_t frames_needed) + { + uint32_t in_len = samples_to_frames(delay_input_buffer.length()); + uint32_t out_len = frames_needed; + + uint32_t to_pop = std::min(in_len, out_len); + + delay_input_buffer.pop(output_buffer, frames_to_samples(to_pop)); + + return to_pop; + } + /** Returns the number of frames one needs to input into the delay line to get + * #frames_needed frames back. + * @parameter frames_needed the number of frames one want to write into the + * delay_line + * @returns the number of frames one will get. */ + size_t input_needed_for_output(uint32_t frames_needed) + { + return frames_needed; + } + /** Returns the number of frames produces for `input_frames` frames in input */ + size_t output_for_input(uint32_t input_frames) + { + return input_frames; + } + /** The number of frames this delay line delays the stream by. + * @returns The number of frames of delay. */ + size_t latency() + { + return length; + } +private: + /** The length, in frames, of this delay line */ + uint32_t length; + /** When `input_buffer` is called, this allows tracking the number of samples + that where in the buffer. */ + uint32_t leftover_samples; + /** The input buffer, where the delay is applied. */ + auto_array delay_input_buffer; + /** The output buffer. This is only ever used if using the ::output with a + * single argument. */ + auto_array delay_output_buffer; +}; + +/** This sits behind the C API and is more typed. */ +template +cubeb_resampler * +cubeb_resampler_create_internal(cubeb_stream * stream, + cubeb_stream_params * input_params, + cubeb_stream_params * output_params, + unsigned int target_rate, + cubeb_data_callback callback, + void * user_ptr, + cubeb_resampler_quality quality) +{ + std::unique_ptr> input_resampler = nullptr; + std::unique_ptr> output_resampler = nullptr; + std::unique_ptr> input_delay = nullptr; + std::unique_ptr> output_delay = nullptr; + + assert((input_params || output_params) && + "need at least one valid parameter pointer."); + + /* All the streams we have have a sample rate that matches the target + sample rate, use a no-op resampler, that simply forwards the buffers to the + callback. */ + if (((input_params && input_params->rate == target_rate) && + (output_params && output_params->rate == target_rate)) || + (input_params && !output_params && (input_params->rate == target_rate)) || + (output_params && !input_params && (output_params->rate == target_rate))) { + return new passthrough_resampler(stream, callback, + user_ptr, + input_params ? input_params->channels : 0); + } + + /* Determine if we need to resampler one or both directions, and create the + resamplers. */ + if (output_params && (output_params->rate != target_rate)) { + output_resampler.reset( + new cubeb_resampler_speex_one_way(output_params->channels, + target_rate, + output_params->rate, + to_speex_quality(quality))); + if (!output_resampler) { + return NULL; + } + } + + if (input_params && (input_params->rate != target_rate)) { + input_resampler.reset( + new cubeb_resampler_speex_one_way(input_params->channels, + input_params->rate, + target_rate, + to_speex_quality(quality))); + if (!input_resampler) { + return NULL; + } + } + + /* If we resample only one direction but we have a duplex stream, insert a + * delay line with a length equal to the resampler latency of the + * other direction so that the streams are synchronized. */ + if (input_resampler && !output_resampler && input_params && output_params) { + output_delay.reset(new delay_line(input_resampler->latency(), + output_params->channels)); + if (!output_delay) { + return NULL; + } + } else if (output_resampler && !input_resampler && input_params && output_params) { + input_delay.reset(new delay_line(output_resampler->latency(), + input_params->channels)); + if (!input_delay) { + return NULL; + } + } + + if (input_resampler && output_resampler) { + return new cubeb_resampler_speex, + cubeb_resampler_speex_one_way> + (input_resampler.release(), + output_resampler.release(), + stream, callback, user_ptr); + } else if (input_resampler) { + return new cubeb_resampler_speex, + delay_line> + (input_resampler.release(), + output_delay.release(), + stream, callback, user_ptr); + } else { + return new cubeb_resampler_speex, + cubeb_resampler_speex_one_way> + (input_delay.release(), + output_resampler.release(), + stream, callback, user_ptr); + } +} + +#endif /* CUBEB_RESAMPLER_INTERNAL */ diff --git a/Externals/cubeb/src/cubeb_ring_array.h b/Externals/cubeb/src/cubeb_ring_array.h new file mode 100644 index 0000000000..51b3b321a3 --- /dev/null +++ b/Externals/cubeb/src/cubeb_ring_array.h @@ -0,0 +1,159 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_RING_ARRAY_H +#define CUBEB_RING_ARRAY_H + +#include "cubeb_utils.h" + +/** Ring array of pointers is used to hold buffers. In case that + asynchronous producer/consumer callbacks do not arrive in a + repeated order the ring array stores the buffers and fetch + them in the correct order. */ + +typedef struct { + AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated space for the buffers. */ + unsigned int tail; /**< Index of the last element (first to deliver). */ + unsigned int count; /**< Number of elements in the array. */ + unsigned int capacity; /**< Total length of the array. */ +} ring_array; + +static int +single_audiobuffer_init(AudioBuffer * buffer, + uint32_t bytesPerFrame, + uint32_t channelsPerFrame, + uint32_t frames) +{ + assert(buffer); + assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0); + + size_t size = bytesPerFrame * frames; + buffer->mData = operator new(size); + if (buffer->mData == NULL) { + return CUBEB_ERROR; + } + PodZero(static_cast(buffer->mData), size); + + buffer->mNumberChannels = channelsPerFrame; + buffer->mDataByteSize = size; + + return CUBEB_OK; +} + +/** Initialize the ring array. + @param ra The ring_array pointer of allocated structure. + @retval 0 on success. */ +int +ring_array_init(ring_array * ra, + uint32_t capacity, + uint32_t bytesPerFrame, + uint32_t channelsPerFrame, + uint32_t framesPerBuffer) +{ + assert(ra); + if (capacity == 0 || bytesPerFrame == 0 || + channelsPerFrame == 0 || framesPerBuffer == 0) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + ra->capacity = capacity; + ra->tail = 0; + ra->count = 0; + + ra->buffer_array = new AudioBuffer[ra->capacity]; + PodZero(ra->buffer_array, ra->capacity); + if (ra->buffer_array == NULL) { + return CUBEB_ERROR; + } + + for (unsigned int i = 0; i < ra->capacity; ++i) { + if (single_audiobuffer_init(&ra->buffer_array[i], + bytesPerFrame, + channelsPerFrame, + framesPerBuffer) != CUBEB_OK) { + return CUBEB_ERROR; + } + } + + return CUBEB_OK; +} + +/** Destroy the ring array. + @param ra The ring_array pointer.*/ +void +ring_array_destroy(ring_array * ra) +{ + assert(ra); + if (ra->buffer_array == NULL){ + return; + } + for (unsigned int i = 0; i < ra->capacity; ++i) { + if (ra->buffer_array[i].mData) { + operator delete(ra->buffer_array[i].mData); + } + } + delete [] ra->buffer_array; +} + +/** Get the allocated buffer to be stored with fresh data. + @param ra The ring_array pointer. + @retval Pointer of the allocated space to be stored with fresh data or NULL if full. */ +AudioBuffer * +ring_array_get_free_buffer(ring_array * ra) +{ + assert(ra && ra->buffer_array); + assert(ra->buffer_array[0].mData != NULL); + if (ra->count == ra->capacity) { + return NULL; + } + + assert(ra->count == 0 || (ra->tail + ra->count) % ra->capacity != ra->tail); + AudioBuffer * ret = &ra->buffer_array[(ra->tail + ra->count) % ra->capacity]; + + ++ra->count; + assert(ra->count <= ra->capacity); + + return ret; +} + +/** Get the next available buffer with data. + @param ra The ring_array pointer. + @retval Pointer of the next in order data buffer or NULL if empty. */ +AudioBuffer * +ring_array_get_data_buffer(ring_array * ra) +{ + assert(ra && ra->buffer_array); + assert(ra->buffer_array[0].mData != NULL); + + if (ra->count == 0) { + return NULL; + } + AudioBuffer * ret = &ra->buffer_array[ra->tail]; + + ra->tail = (ra->tail + 1) % ra->capacity; + assert(ra->tail < ra->capacity); + + assert(ra->count > 0); + --ra->count; + + return ret; +} + +/** When array is empty get the first allocated buffer in the array. + @param ra The ring_array pointer. + @retval If arrays is empty, pointer of the allocated space else NULL. */ +AudioBuffer * +ring_array_get_dummy_buffer(ring_array * ra) +{ + assert(ra && ra->buffer_array); + assert(ra->capacity > 0); + if (ra->count > 0) { + return NULL; + } + return &ra->buffer_array[0]; +} + +#endif //CUBEB_RING_ARRAY_H diff --git a/Externals/cubeb/src/cubeb_ringbuffer.h b/Externals/cubeb/src/cubeb_ringbuffer.h new file mode 100644 index 0000000000..ac5bf8c68b --- /dev/null +++ b/Externals/cubeb/src/cubeb_ringbuffer.h @@ -0,0 +1,484 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_RING_BUFFER_H +#define CUBEB_RING_BUFFER_H + +#include "cubeb_utils.h" +#include +#include +#include +#include +#include + +/** + * Single producer single consumer lock-free and wait-free ring buffer. + * + * This data structure allows producing data from one thread, and consuming it on + * another thread, safely and without explicit synchronization. If used on two + * threads, this data structure uses atomics for thread safety. It is possible + * to disable the use of atomics at compile time and only use this data + * structure on one thread. + * + * The role for the producer and the consumer must be constant, i.e., the + * producer should always be on one thread and the consumer should always be on + * another thread. + * + * Some words about the inner workings of this class: + * - Capacity is fixed. Only one allocation is performed, in the constructor. + * When reading and writing, the return value of the method allows checking if + * the ring buffer is empty or full. + * - We always keep the read index at least one element ahead of the write + * index, so we can distinguish between an empty and a full ring buffer: an + * empty ring buffer is when the write index is at the same position as the + * read index. A full buffer is when the write index is exactly one position + * before the read index. + * - We synchronize updates to the read index after having read the data, and + * the write index after having written the data. This means that the each + * thread can only touch a portion of the buffer that is not touched by the + * other thread. + * - Callers are expected to provide buffers. When writing to the queue, + * elements are copied into the internal storage from the buffer passed in. + * When reading from the queue, the user is expected to provide a buffer. + * Because this is a ring buffer, data might not be contiguous in memory, + * providing an external buffer to copy into is an easy way to have linear + * data for further processing. + */ +template +class ring_buffer_base +{ +public: + /** + * Constructor for a ring buffer. + * + * This performs an allocation, but is the only allocation that will happen + * for the life time of a `ring_buffer_base`. + * + * @param capacity The maximum number of element this ring buffer will hold. + */ + ring_buffer_base(int capacity) + /* One more element to distinguish from empty and full buffer. */ + : capacity_(capacity + 1) + { + assert(storage_capacity() < + std::numeric_limits::max() / 2 && + "buffer too large for the type of index used."); + assert(capacity_ > 0); + + data_.reset(new T[storage_capacity()]); + /* If this queue is using atomics, initializing those members as the last + * action in the constructor acts as a full barrier, and allow capacity() to + * be thread-safe. */ + write_index_ = 0; + read_index_ = 0; + } + /** + * Push `count` zero or default constructed elements in the array. + * + * Only safely called on the producer thread. + * + * @param count The number of elements to enqueue. + * @return The number of element enqueued. + */ + int enqueue_default(int count) + { + return enqueue(nullptr, count); + } + /** + * @brief Put an element in the queue + * + * Only safely called on the producer thread. + * + * @param element The element to put in the queue. + * + * @return 1 if the element was inserted, 0 otherwise. + */ + int enqueue(T& element) + { + return enqueue(&element, 1); + } + /** + * Push `count` elements in the ring buffer. + * + * Only safely called on the producer thread. + * + * @param elements a pointer to a buffer containing at least `count` elements. + * If `elements` is nullptr, zero or default constructed elements are enqueued. + * @param count The number of elements to read from `elements` + * @return The number of elements successfully coped from `elements` and inserted + * into the ring buffer. + */ + int enqueue(T * elements, int count) + { +#ifndef NDEBUG + assert_correct_thread(producer_id); +#endif + + int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); + int wr_idx = write_index_.load(std::memory_order::memory_order_relaxed); + + if (full_internal(rd_idx, wr_idx)) { + return 0; + } + + int to_write = + std::min(available_write_internal(rd_idx, wr_idx), count); + + /* First part, from the write index to the end of the array. */ + int first_part = std::min(storage_capacity() - wr_idx, + to_write); + /* Second part, from the beginning of the array */ + int second_part = to_write - first_part; + + if (elements) { + Copy(data_.get() + wr_idx, elements, first_part); + Copy(data_.get(), elements + first_part, second_part); + } else { + ConstructDefault(data_.get() + wr_idx, first_part); + ConstructDefault(data_.get(), second_part); + } + + write_index_.store(increment_index(wr_idx, to_write), std::memory_order::memory_order_release); + + return to_write; + } + /** + * Retrieve at most `count` elements from the ring buffer, and copy them to + * `elements`, if non-null. + * + * Only safely called on the consumer side. + * + * @param elements A pointer to a buffer with space for at least `count` + * elements. If `elements` is `nullptr`, `count` element will be discarded. + * @param count The maximum number of elements to dequeue. + * @return The number of elements written to `elements`. + */ + int dequeue(T * elements, int count) + { +#ifndef NDEBUG + assert_correct_thread(consumer_id); +#endif + + int wr_idx = write_index_.load(std::memory_order::memory_order_acquire); + int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); + + if (empty_internal(rd_idx, wr_idx)) { + return 0; + } + + int to_read = + std::min(available_read_internal(rd_idx, wr_idx), count); + + int first_part = std::min(storage_capacity() - rd_idx, to_read); + int second_part = to_read - first_part; + + if (elements) { + Copy(elements, data_.get() + rd_idx, first_part); + Copy(elements + first_part, data_.get(), second_part); + } + + read_index_.store(increment_index(rd_idx, to_read), std::memory_order::memory_order_relaxed); + + return to_read; + } + /** + * Get the number of available element for consuming. + * + * Only safely called on the consumer thread. + * + * @return The number of available elements for reading. + */ + int available_read() const + { +#ifndef NDEBUG + assert_correct_thread(consumer_id); +#endif + return available_read_internal(read_index_.load(std::memory_order::memory_order_relaxed), + write_index_.load(std::memory_order::memory_order_relaxed)); + } + /** + * Get the number of available elements for consuming. + * + * Only safely called on the producer thread. + * + * @return The number of empty slots in the buffer, available for writing. + */ + int available_write() const + { +#ifndef NDEBUG + assert_correct_thread(producer_id); +#endif + return available_write_internal(read_index_.load(std::memory_order::memory_order_relaxed), + write_index_.load(std::memory_order::memory_order_relaxed)); + } + /** + * Get the total capacity, for this ring buffer. + * + * Can be called safely on any thread. + * + * @return The maximum capacity of this ring buffer. + */ + int capacity() const + { + return storage_capacity() - 1; + } +private: + /** Return true if the ring buffer is empty. + * + * @param read_index the read index to consider + * @param write_index the write index to consider + * @return true if the ring buffer is empty, false otherwise. + **/ + bool empty_internal(int read_index, + int write_index) const + { + return write_index == read_index; + } + /** Return true if the ring buffer is full. + * + * This happens if the write index is exactly one element behind the read + * index. + * + * @param read_index the read index to consider + * @param write_index the write index to consider + * @return true if the ring buffer is full, false otherwise. + **/ + bool full_internal(int read_index, + int write_index) const + { + return (write_index + 1) % storage_capacity() == read_index; + } + /** + * Return the size of the storage. It is one more than the number of elements + * that can be stored in the buffer. + * + * @return the number of elements that can be stored in the buffer. + */ + int storage_capacity() const + { + return capacity_; + } + /** + * Returns the number of elements available for reading. + * + * @return the number of available elements for reading. + */ + int + available_read_internal(int read_index, + int write_index) const + { + if (write_index >= read_index) { + return write_index - read_index; + } else { + return write_index + storage_capacity() - read_index; + } + } + /** + * Returns the number of empty elements, available for writing. + * + * @return the number of elements that can be written into the array. + */ + int + available_write_internal(int read_index, + int write_index) const + { + /* We substract one element here to always keep at least one sample + * free in the buffer, to distinguish between full and empty array. */ + int rv = read_index - write_index - 1; + if (write_index >= read_index) { + rv += storage_capacity(); + } + return rv; + } + /** + * Increments an index, wrapping it around the storage. + * + * @param index a reference to the index to increment. + * @param increment the number by which `index` is incremented. + * @return the new index. + */ + int + increment_index(int index, int increment) const + { + assert(increment >= 0); + return (index + increment) % storage_capacity(); + } + /** + * @brief This allows checking that enqueue (resp. dequeue) are always called + * by the right thread. + * + * @param id the id of the thread that has called the calling method first. + */ +#ifndef NDEBUG + static void assert_correct_thread(std::thread::id& id) + { + if (id == std::thread::id()) { + id = std::this_thread::get_id(); + return; + } + assert(id == std::this_thread::get_id()); + } +#endif + /** Index at which the oldest element is at, in samples. */ + std::atomic read_index_; + /** Index at which to write new elements. `write_index` is always at + * least one element ahead of `read_index_`. */ + std::atomic write_index_; + /** Maximum number of elements that can be stored in the ring buffer. */ + const int capacity_; + /** Data storage */ + std::unique_ptr data_; +#ifndef NDEBUG + /** The id of the only thread that is allowed to read from the queue. */ + mutable std::thread::id consumer_id; + /** The id of the only thread that is allowed to write from the queue. */ + mutable std::thread::id producer_id; +#endif +}; + +/** + * Adapter for `ring_buffer_base` that exposes an interface in frames. + */ +template +class audio_ring_buffer_base +{ +public: + /** + * @brief Constructor. + * + * @param channel_count Number of channels. + * @param capacity_in_frames The capacity in frames. + */ + audio_ring_buffer_base(int channel_count, int capacity_in_frames) + : channel_count(channel_count) + , ring_buffer(frames_to_samples(capacity_in_frames)) + { + assert(channel_count > 0); + } + /** + * @brief Enqueue silence. + * + * Only safely called on the producer thread. + * + * @param frame_count The number of frames of silence to enqueue. + * @return The number of frames of silence actually written to the queue. + */ + int enqueue_default(int frame_count) + { + return samples_to_frames(ring_buffer.enqueue(nullptr, frames_to_samples(frame_count))); + } + /** + * @brief Enqueue `frames_count` frames of audio. + * + * Only safely called from the producer thread. + * + * @param [in] frames If non-null, the frames to enqueue. + * Otherwise, silent frames are enqueued. + * @param frame_count The number of frames to enqueue. + * + * @return The number of frames enqueued + */ + + int enqueue(T * frames, int frame_count) + { + return samples_to_frames(ring_buffer.enqueue(frames, frames_to_samples(frame_count))); + } + + /** + * @brief Removes `frame_count` frames from the buffer, and + * write them to `frames` if it is non-null. + * + * Only safely called on the consumer thread. + * + * @param frames If non-null, the frames are copied to `frames`. + * Otherwise, they are dropped. + * @param frame_count The number of frames to remove. + * + * @return The number of frames actually dequeud. + */ + int dequeue(T * frames, int frame_count) + { + return samples_to_frames(ring_buffer.dequeue(frames, frames_to_samples(frame_count))); + } + /** + * Get the number of available frames of audio for consuming. + * + * Only safely called on the consumer thread. + * + * @return The number of available frames of audio for reading. + */ + int available_read() const + { + return samples_to_frames(ring_buffer.available_read()); + } + /** + * Get the number of available frames of audio for consuming. + * + * Only safely called on the producer thread. + * + * @return The number of empty slots in the buffer, available for writing. + */ + int available_write() const + { + return samples_to_frames(ring_buffer.available_write()); + } + /** + * Get the total capacity, for this ring buffer. + * + * Can be called safely on any thread. + * + * @return The maximum capacity of this ring buffer. + */ + int capacity() const + { + return samples_to_frames(ring_buffer.capacity()); + } +private: + /** + * @brief Frames to samples conversion. + * + * @param frames The number of frames. + * + * @return A number of samples. + */ + int frames_to_samples(int frames) const + { + return frames * channel_count; + } + /** + * @brief Samples to frames conversion. + * + * @param samples The number of samples. + * + * @return A number of frames. + */ + int samples_to_frames(int samples) const + { + return samples / channel_count; + } + /** Number of channels of audio that will stream through this ring buffer. */ + int channel_count; + /** The underlying ring buffer that is used to store the data. */ + ring_buffer_base ring_buffer; +}; + +/** + * Lock-free instantiation of the `ring_buffer_base` type. This is safe to use + * from two threads, one producer, one consumer (that never change role), + * without explicit synchronization. + */ +template +using lock_free_queue = ring_buffer_base; +/** + * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use + * from two threads, one producer, one consumer (that never change role), + * without explicit synchronization. + */ +template +using lock_free_audio_ring_buffer = audio_ring_buffer_base; + +#endif // CUBEB_RING_BUFFER_H diff --git a/Externals/cubeb/src/cubeb_sndio.c b/Externals/cubeb/src/cubeb_sndio.c new file mode 100644 index 0000000000..e6a7b987d0 --- /dev/null +++ b/Externals/cubeb/src/cubeb_sndio.c @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2011 Alexandre Ratchov + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" + +#if defined(CUBEB_SNDIO_DEBUG) +#define DPR(...) fprintf(stderr, __VA_ARGS__); +#else +#define DPR(...) do {} while(0) +#endif + +static struct cubeb_ops const sndio_ops; + +struct cubeb { + struct cubeb_ops const * ops; +}; + +struct cubeb_stream { + cubeb * context; + pthread_t th; /* to run real-time audio i/o */ + pthread_mutex_t mtx; /* protects hdl and pos */ + struct sio_hdl *hdl; /* link us to sndio */ + int active; /* cubec_start() called */ + int conv; /* need float->s16 conversion */ + unsigned char *buf; /* data is prepared here */ + unsigned int nfr; /* number of frames in buf */ + unsigned int bpf; /* bytes per frame */ + unsigned int pchan; /* number of play channels */ + uint64_t rdpos; /* frame number Joe hears right now */ + uint64_t wrpos; /* number of written frames */ + cubeb_data_callback data_cb; /* cb to preapare data */ + cubeb_state_callback state_cb; /* cb to notify about state changes */ + void *arg; /* user arg to {data,state}_cb */ +}; + +static void +float_to_s16(void *ptr, long nsamp) +{ + int16_t *dst = ptr; + float *src = ptr; + int s; + + while (nsamp-- > 0) { + s = lrintf(*(src++) * 32768); + if (s < -32768) + s = -32768; + else if (s > 32767) + s = 32767; + *(dst++) = s; + } +} + +static void +sndio_onmove(void *arg, int delta) +{ + cubeb_stream *s = (cubeb_stream *)arg; + + s->rdpos += delta * s->bpf; +} + +static void * +sndio_mainloop(void *arg) +{ +#define MAXFDS 8 + struct pollfd pfds[MAXFDS]; + cubeb_stream *s = arg; + int n, nfds, revents, state = CUBEB_STATE_STARTED; + size_t start = 0, end = 0; + long nfr; + + DPR("sndio_mainloop()\n"); + s->state_cb(s, s->arg, CUBEB_STATE_STARTED); + pthread_mutex_lock(&s->mtx); + if (!sio_start(s->hdl)) { + pthread_mutex_unlock(&s->mtx); + return NULL; + } + DPR("sndio_mainloop(), started\n"); + + start = end = s->nfr; + for (;;) { + if (!s->active) { + DPR("sndio_mainloop() stopped\n"); + state = CUBEB_STATE_STOPPED; + break; + } + if (start == end) { + if (end < s->nfr) { + DPR("sndio_mainloop() drained\n"); + state = CUBEB_STATE_DRAINED; + break; + } + pthread_mutex_unlock(&s->mtx); + nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr); + pthread_mutex_lock(&s->mtx); + if (nfr < 0) { + DPR("sndio_mainloop() cb err\n"); + state = CUBEB_STATE_ERROR; + break; + } + if (s->conv) + float_to_s16(s->buf, nfr * s->pchan); + start = 0; + end = nfr * s->bpf; + } + if (end == 0) + continue; + nfds = sio_pollfd(s->hdl, pfds, POLLOUT); + if (nfds > 0) { + pthread_mutex_unlock(&s->mtx); + n = poll(pfds, nfds, -1); + pthread_mutex_lock(&s->mtx); + if (n < 0) + continue; + } + revents = sio_revents(s->hdl, pfds); + if (revents & POLLHUP) + break; + if (revents & POLLOUT) { + n = sio_write(s->hdl, s->buf + start, end - start); + if (n == 0) { + DPR("sndio_mainloop() werr\n"); + state = CUBEB_STATE_ERROR; + break; + } + s->wrpos += n; + start += n; + } + } + sio_stop(s->hdl); + s->rdpos = s->wrpos; + pthread_mutex_unlock(&s->mtx); + s->state_cb(s, s->arg, state); + return NULL; +} + +/*static*/ int +sndio_init(cubeb **context, char const *context_name) +{ + DPR("sndio_init(%s)\n", context_name); + *context = malloc(sizeof(*context)); + (*context)->ops = &sndio_ops; + (void)context_name; + return CUBEB_OK; +} + +static char const * +sndio_get_backend_id(cubeb *context) +{ + return "sndio"; +} + +static void +sndio_destroy(cubeb *context) +{ + DPR("sndio_destroy()\n"); + free(context); +} + +static int +sndio_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void *user_ptr) +{ + cubeb_stream *s; + struct sio_par wpar, rpar; + DPR("sndio_stream_init(%s)\n", stream_name); + size_t size; + + assert(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + s = malloc(sizeof(cubeb_stream)); + if (s == NULL) + return CUBEB_ERROR; + s->context = context; + s->hdl = sio_open(NULL, SIO_PLAY, 1); + if (s->hdl == NULL) { + free(s); + DPR("sndio_stream_init(), sio_open() failed\n"); + return CUBEB_ERROR; + } + sio_initpar(&wpar); + wpar.sig = 1; + wpar.bits = 16; + switch (output_stream_params->format) { + case CUBEB_SAMPLE_S16LE: + wpar.le = 1; + break; + case CUBEB_SAMPLE_S16BE: + wpar.le = 0; + break; + case CUBEB_SAMPLE_FLOAT32NE: + wpar.le = SIO_LE_NATIVE; + break; + default: + sio_close(s->hdl); + free(s); + DPR("sndio_stream_init() unsupported format\n"); + return CUBEB_ERROR_INVALID_FORMAT; + } + wpar.rate = output_stream_params->rate; + wpar.pchan = output_stream_params->channels; + wpar.appbufsz = latency_frames; + if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { + sio_close(s->hdl); + free(s); + DPR("sndio_stream_init(), sio_setpar() failed\n"); + return CUBEB_ERROR; + } + if (rpar.bits != wpar.bits || rpar.le != wpar.le || + rpar.sig != wpar.sig || rpar.rate != wpar.rate || + rpar.pchan != wpar.pchan) { + sio_close(s->hdl); + free(s); + DPR("sndio_stream_init() unsupported params\n"); + return CUBEB_ERROR_INVALID_FORMAT; + } + sio_onmove(s->hdl, sndio_onmove, s); + s->active = 0; + s->nfr = rpar.round; + s->bpf = rpar.bps * rpar.pchan; + s->pchan = rpar.pchan; + s->data_cb = data_callback; + s->state_cb = state_callback; + s->arg = user_ptr; + s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + s->rdpos = s->wrpos = 0; + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { + s->conv = 1; + size = rpar.round * rpar.pchan * sizeof(float); + } else { + s->conv = 0; + size = rpar.round * rpar.pchan * rpar.bps; + } + s->buf = malloc(size); + if (s->buf == NULL) { + sio_close(s->hdl); + free(s); + return CUBEB_ERROR; + } + *stream = s; + DPR("sndio_stream_init() end, ok\n"); + (void)context; + (void)stream_name; + return CUBEB_OK; +} + +static int +sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + assert(ctx && max_channels); + + *max_channels = 8; + + return CUBEB_OK; +} + +static int +sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + // XXX Not yet implemented. + *rate = 44100; + + return CUBEB_OK; +} + +static int +sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) +{ + // XXX Not yet implemented. + *latency_frames = 2048; + + return CUBEB_OK; +} + +static void +sndio_stream_destroy(cubeb_stream *s) +{ + DPR("sndio_stream_destroy()\n"); + sio_close(s->hdl); + free(s); +} + +static int +sndio_stream_start(cubeb_stream *s) +{ + int err; + + DPR("sndio_stream_start()\n"); + s->active = 1; + err = pthread_create(&s->th, NULL, sndio_mainloop, s); + if (err) { + s->active = 0; + return CUBEB_ERROR; + } + return CUBEB_OK; +} + +static int +sndio_stream_stop(cubeb_stream *s) +{ + void *dummy; + + DPR("sndio_stream_stop()\n"); + if (s->active) { + s->active = 0; + pthread_join(s->th, &dummy); + } + return CUBEB_OK; +} + +static int +sndio_stream_get_position(cubeb_stream *s, uint64_t *p) +{ + pthread_mutex_lock(&s->mtx); + DPR("sndio_stream_get_position() %lld\n", s->rdpos); + *p = s->rdpos / s->bpf; + pthread_mutex_unlock(&s->mtx); + return CUBEB_OK; +} + +static int +sndio_stream_set_volume(cubeb_stream *s, float volume) +{ + DPR("sndio_stream_set_volume(%f)\n", volume); + pthread_mutex_lock(&s->mtx); + sio_setvol(s->hdl, SIO_MAXVOL * volume); + pthread_mutex_unlock(&s->mtx); + return CUBEB_OK; +} + +int +sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open + // in the "Measuring the latency and buffers usage" paragraph. + *latency = (stm->wrpos - stm->rdpos) / stm->bpf; + return CUBEB_OK; +} + +static struct cubeb_ops const sndio_ops = { + .init = sndio_init, + .get_backend_id = sndio_get_backend_id, + .get_max_channel_count = sndio_get_max_channel_count, + .get_min_latency = sndio_get_min_latency, + .get_preferred_sample_rate = sndio_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, + .enumerate_devices = NULL, + .device_collection_destroy = NULL, + .destroy = sndio_destroy, + .stream_init = sndio_stream_init, + .stream_destroy = sndio_stream_destroy, + .stream_start = sndio_stream_start, + .stream_stop = sndio_stream_stop, + .stream_get_position = sndio_stream_get_position, + .stream_get_latency = sndio_stream_get_latency, + .stream_set_volume = sndio_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; diff --git a/Externals/cubeb/src/cubeb_utils.c b/Externals/cubeb/src/cubeb_utils.c new file mode 100644 index 0000000000..6557f08967 --- /dev/null +++ b/Externals/cubeb/src/cubeb_utils.c @@ -0,0 +1,38 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include "cubeb_utils.h" +#include "cubeb_assert.h" + +#include + +static void +device_info_destroy(cubeb_device_info * info) +{ + XASSERT(info); + + free((void *) info->device_id); + free((void *) info->friendly_name); + free((void *) info->group_id); + free((void *) info->vendor_name); +} + +int +cubeb_utils_default_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) +{ + uint32_t i; + XASSERT(collection); + + (void) context; + + for (i = 0; i < collection->count; i++) + device_info_destroy(&collection->device[i]); + + free(collection->device); + return CUBEB_OK; +} diff --git a/Externals/cubeb/src/cubeb_utils.h b/Externals/cubeb/src/cubeb_utils.h new file mode 100644 index 0000000000..00011cec9a --- /dev/null +++ b/Externals/cubeb/src/cubeb_utils.h @@ -0,0 +1,352 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#if !defined(CUBEB_UTILS) +#define CUBEB_UTILS + +#include "cubeb/cubeb.h" + +#ifdef __cplusplus + +#include +#include +#include +#include +#include +#if defined(WIN32) +#include "cubeb_utils_win.h" +#else +#include "cubeb_utils_unix.h" +#endif + +/** Similar to memcpy, but accounts for the size of an element. */ +template +void PodCopy(T * destination, const T * source, size_t count) +{ + static_assert(std::is_trivial::value, "Requires trivial type"); + assert(destination && source); + memcpy(destination, source, count * sizeof(T)); +} + +/** Similar to memmove, but accounts for the size of an element. */ +template +void PodMove(T * destination, const T * source, size_t count) +{ + static_assert(std::is_trivial::value, "Requires trivial type"); + assert(destination && source); + memmove(destination, source, count * sizeof(T)); +} + +/** Similar to a memset to zero, but accounts for the size of an element. */ +template +void PodZero(T * destination, size_t count) +{ + static_assert(std::is_trivial::value, "Requires trivial type"); + assert(destination); + memset(destination, 0, count * sizeof(T)); +} + +namespace { +template +void Copy(T * destination, const T * source, size_t count, Trait) +{ + for (size_t i = 0; i < count; i++) { + destination[i] = source[i]; + } +} + +template +void Copy(T * destination, const T * source, size_t count, std::true_type) +{ + PodCopy(destination, source, count); +} +} + +/** + * This allows copying a number of elements from a `source` pointer to a + * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that + * calls the constructors and destructors otherwise. + */ +template +void Copy(T * destination, const T * source, size_t count) +{ + assert(destination && source); + Copy(destination, source, count, typename std::is_trivial::type()); +} + +namespace { +template +void ConstructDefault(T * destination, size_t count, Trait) +{ + for (size_t i = 0; i < count; i++) { + destination[i] = T(); + } +} + +template +void ConstructDefault(T * destination, + size_t count, std::true_type) +{ + PodZero(destination, count); +} +} + +/** + * This allows zeroing (using memset) or default-constructing a number of + * elements calling the constructors and destructors if necessary. + */ +template +void ConstructDefault(T * destination, size_t count) +{ + assert(destination); + ConstructDefault(destination, count, + typename std::is_arithmetic::type()); +} + +template +class auto_array +{ +public: + explicit auto_array(uint32_t capacity = 0) + : data_(capacity ? new T[capacity] : nullptr) + , capacity_(capacity) + , length_(0) + {} + + ~auto_array() + { + delete [] data_; + } + + /** Get a constant pointer to the underlying data. */ + T * data() const + { + return data_; + } + + T * end() const + { + return data_ + length_; + } + + const T& at(size_t index) const + { + assert(index < length_ && "out of range"); + return data_[index]; + } + + T& at(size_t index) + { + assert(index < length_ && "out of range"); + return data_[index]; + } + + /** Get how much underlying storage this auto_array has. */ + size_t capacity() const + { + return capacity_; + } + + /** Get how much elements this auto_array contains. */ + size_t length() const + { + return length_; + } + + /** Keeps the storage, but removes all the elements from the array. */ + void clear() + { + length_ = 0; + } + + /** Change the storage of this auto array, copying the elements to the new + * storage. + * @returns true in case of success + * @returns false if the new capacity is not big enough to accomodate for the + * elements in the array. + */ + bool reserve(size_t new_capacity) + { + if (new_capacity < length_) { + return false; + } + T * new_data = new T[new_capacity]; + if (data_ && length_) { + PodCopy(new_data, data_, length_); + } + capacity_ = new_capacity; + delete [] data_; + data_ = new_data; + + return true; + } + + /** Append `length` elements to the end of the array, resizing the array if + * needed. + * @parameter elements the elements to append to the array. + * @parameter length the number of elements to append to the array. + */ + void push(const T * elements, size_t length) + { + if (length_ + length > capacity_) { + reserve(length_ + length); + } + PodCopy(data_ + length_, elements, length); + length_ += length; + } + + /** Append `length` zero-ed elements to the end of the array, resizing the + * array if needed. + * @parameter length the number of elements to append to the array. + */ + void push_silence(size_t length) + { + if (length_ + length > capacity_) { + reserve(length + length_); + } + PodZero(data_ + length_, length); + length_ += length; + } + + /** Prepend `length` zero-ed elements to the end of the array, resizing the + * array if needed. + * @parameter length the number of elements to prepend to the array. + */ + void push_front_silence(size_t length) + { + if (length_ + length > capacity_) { + reserve(length + length_); + } + PodMove(data_ + length, data_, length_); + PodZero(data_, length); + length_ += length; + } + + /** Return the number of free elements in the array. */ + size_t available() const + { + return capacity_ - length_; + } + + /** Copies `length` elements to `elements` if it is not null, and shift + * the remaining elements of the `auto_array` to the beginning. + * @parameter elements a buffer to copy the elements to, or nullptr. + * @parameter length the number of elements to copy. + * @returns true in case of success. + * @returns false if the auto_array contains less than `length` elements. */ + bool pop(T * elements, size_t length) + { + if (length > length_) { + return false; + } + if (elements) { + PodCopy(elements, data_, length); + } + PodMove(data_, data_ + length, length_ - length); + + length_ -= length; + + return true; + } + + void set_length(size_t length) + { + assert(length <= capacity_); + length_ = length; + } + +private: + /** The underlying storage */ + T * data_; + /** The size, in number of elements, of the storage. */ + size_t capacity_; + /** The number of elements the array contains. */ + size_t length_; +}; + +struct auto_array_wrapper { + virtual void push(void * elements, size_t length) = 0; + virtual size_t length() = 0; + virtual void push_silence(size_t length) = 0; + virtual bool pop(size_t length) = 0; + virtual void * data() = 0; + virtual void * end() = 0; + virtual void clear() = 0; + virtual bool reserve(size_t capacity) = 0; + virtual void set_length(size_t length) = 0; + virtual ~auto_array_wrapper() {} +}; + +template +struct auto_array_wrapper_impl : public auto_array_wrapper { + auto_array_wrapper_impl() {} + + explicit auto_array_wrapper_impl(uint32_t size) + : ar(size) + {} + + void push(void * elements, size_t length) override { + ar.push(static_cast(elements), length); + } + + size_t length() override { + return ar.length(); + } + + void push_silence(size_t length) override { + ar.push_silence(length); + } + + bool pop(size_t length) override { + return ar.pop(nullptr, length); + } + + void * data() override { + return ar.data(); + } + + void * end() override { + return ar.end(); + } + + void clear() override { + ar.clear(); + } + + bool reserve(size_t capacity) override { + return ar.reserve(capacity); + } + + void set_length(size_t length) override { + ar.set_length(length); + } + + ~auto_array_wrapper_impl() { + ar.clear(); + } + +private: + auto_array ar; +}; + +using auto_lock = std::lock_guard; +#endif // __cplusplus + +// C language helpers + +#ifdef __cplusplus +extern "C" { +#endif + +int cubeb_utils_default_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection); + +#ifdef __cplusplus +} +#endif + +#endif /* CUBEB_UTILS */ diff --git a/Externals/cubeb/src/cubeb_utils_unix.h b/Externals/cubeb/src/cubeb_utils_unix.h new file mode 100644 index 0000000000..4876d015fe --- /dev/null +++ b/Externals/cubeb/src/cubeb_utils_unix.h @@ -0,0 +1,89 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#if !defined(CUBEB_UTILS_UNIX) +#define CUBEB_UTILS_UNIX + +#include +#include +#include + +/* This wraps a critical section to track the owner in debug mode. */ +class owned_critical_section +{ +public: + owned_critical_section() + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); +#ifndef NDEBUG + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); +#else + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); +#endif + +#ifndef NDEBUG + int r = +#endif + pthread_mutex_init(&mutex, &attr); +#ifndef NDEBUG + assert(r == 0); +#endif + + pthread_mutexattr_destroy(&attr); + } + + ~owned_critical_section() + { +#ifndef NDEBUG + int r = +#endif + pthread_mutex_destroy(&mutex); +#ifndef NDEBUG + assert(r == 0); +#endif + } + + void lock() + { +#ifndef NDEBUG + int r = +#endif + pthread_mutex_lock(&mutex); +#ifndef NDEBUG + assert(r == 0 && "Deadlock"); +#endif + } + + void unlock() + { +#ifndef NDEBUG + int r = +#endif + pthread_mutex_unlock(&mutex); +#ifndef NDEBUG + assert(r == 0 && "Unlocking unlocked mutex"); +#endif + } + + void assert_current_thread_owns() + { +#ifndef NDEBUG + int r = pthread_mutex_lock(&mutex); + assert(r == EDEADLK); +#endif + } + +private: + pthread_mutex_t mutex; + + // Disallow copy and assignment because pthread_mutex_t cannot be copied. + owned_critical_section(const owned_critical_section&); + owned_critical_section& operator=(const owned_critical_section&); +}; + +#endif /* CUBEB_UTILS_UNIX */ diff --git a/Externals/cubeb/src/cubeb_utils_win.h b/Externals/cubeb/src/cubeb_utils_win.h new file mode 100644 index 0000000000..0112ad6d3c --- /dev/null +++ b/Externals/cubeb/src/cubeb_utils_win.h @@ -0,0 +1,71 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#if !defined(CUBEB_UTILS_WIN) +#define CUBEB_UTILS_WIN + +#include +#include "cubeb-internal.h" + +/* This wraps a critical section to track the owner in debug mode, adapted from + NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */ +class owned_critical_section +{ +public: + owned_critical_section() +#ifndef NDEBUG + : owner(0) +#endif + { + InitializeCriticalSection(&critical_section); + } + + ~owned_critical_section() + { + DeleteCriticalSection(&critical_section); + } + + void lock() + { + EnterCriticalSection(&critical_section); +#ifndef NDEBUG + XASSERT(owner != GetCurrentThreadId() && "recursive locking"); + owner = GetCurrentThreadId(); +#endif + } + + void unlock() + { +#ifndef NDEBUG + /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */ + owner = 0; +#endif + LeaveCriticalSection(&critical_section); + } + + /* This is guaranteed to have the good behaviour if it succeeds. The behaviour + is undefined otherwise. */ + void assert_current_thread_owns() + { +#ifndef NDEBUG + /* This implies owner != 0, because GetCurrentThreadId cannot return 0. */ + XASSERT(owner == GetCurrentThreadId()); +#endif + } + +private: + CRITICAL_SECTION critical_section; +#ifndef NDEBUG + DWORD owner; +#endif + + // Disallow copy and assignment because CRICICAL_SECTION cannot be copied. + owned_critical_section(const owned_critical_section&); + owned_critical_section& operator=(const owned_critical_section&); +}; + +#endif /* CUBEB_UTILS_WIN */ diff --git a/Externals/cubeb/src/cubeb_wasapi.cpp b/Externals/cubeb/src/cubeb_wasapi.cpp new file mode 100644 index 0000000000..79ee86ae47 --- /dev/null +++ b/Externals/cubeb/src/cubeb_wasapi.cpp @@ -0,0 +1,2349 @@ +/* + * Copyright © 2013 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#define _WIN32_WINNT 0x0600 +#define NOMINMAX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_mixer.h" +#include "cubeb_resampler.h" +#include "cubeb_utils.h" + +#ifndef PKEY_Device_FriendlyName +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING +#endif +#ifndef PKEY_Device_InstanceId +DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); // VT_LPWSTR +#endif + +namespace { +struct com_heap_ptr_deleter { + void operator()(void * ptr) const noexcept { + CoTaskMemFree(ptr); + } +}; + +template +using com_heap_ptr = std::unique_ptr; + +template +constexpr size_t +ARRAY_LENGTH(T(&)[N]) +{ + return N; +} + +template +class no_addref_release : public T { + ULONG STDMETHODCALLTYPE AddRef() = 0; + ULONG STDMETHODCALLTYPE Release() = 0; +}; + +template +class com_ptr { +public: + com_ptr() noexcept = default; + + com_ptr(com_ptr const & other) noexcept = delete; + com_ptr & operator=(com_ptr const & other) noexcept = delete; + T ** operator&() const noexcept = delete; + + ~com_ptr() noexcept { + release(); + } + + com_ptr(com_ptr && other) noexcept + : ptr(other.ptr) + { + other.ptr = nullptr; + } + + com_ptr & operator=(com_ptr && other) noexcept { + if (ptr != other.ptr) { + release(); + ptr = other.ptr; + other.ptr = nullptr; + } + return *this; + } + + explicit operator bool() const noexcept { + return nullptr != ptr; + } + + no_addref_release * operator->() const noexcept { + return static_cast *>(ptr); + } + + T * get() const noexcept { + return ptr; + } + + T ** receive() noexcept { + XASSERT(ptr == nullptr); + return &ptr; + } + + void ** receive_vpp() noexcept { + return reinterpret_cast(receive()); + } + + com_ptr & operator=(std::nullptr_t) noexcept { + release(); + return *this; + } + + void reset(T * p = nullptr) noexcept { + release(); + ptr = p; + } + +private: + void release() noexcept { + T * temp = ptr; + + if (temp) { + ptr = nullptr; + temp->Release(); + } + } + + T * ptr = nullptr; +}; + +struct auto_com { + auto_com() { + result = CoInitializeEx(NULL, COINIT_MULTITHREADED); + } + ~auto_com() { + if (result == RPC_E_CHANGED_MODE) { + // This is not an error, COM was not initialized by this function, so it is + // not necessary to uninit it. + LOG("COM was already initialized in STA."); + } else if (result == S_FALSE) { + // This is not an error. We are allowed to call CoInitializeEx more than + // once, as long as it is matches by an CoUninitialize call. + // We do that in the dtor which is guaranteed to be called. + LOG("COM was already initialized in MTA"); + } + if (SUCCEEDED(result)) { + CoUninitialize(); + } + } + bool ok() { + return result == RPC_E_CHANGED_MODE || SUCCEEDED(result); + } +private: + HRESULT result; +}; + +extern cubeb_ops const wasapi_ops; + +int wasapi_stream_stop(cubeb_stream * stm); +int wasapi_stream_start(cubeb_stream * stm); +void close_wasapi_stream(cubeb_stream * stm); +int setup_wasapi_stream(cubeb_stream * stm); +static char const * wstr_to_utf8(wchar_t const * str); +static std::unique_ptr utf8_to_wstr(char const * str); + +} + +struct cubeb { + cubeb_ops const * ops = &wasapi_ops; +}; + +class wasapi_endpoint_notification_client; + +/* We have three possible callbacks we can use with a stream: + * - input only + * - output only + * - synchronized input and output + * + * Returns true when we should continue to play, false otherwise. + */ +typedef bool (*wasapi_refill_callback)(cubeb_stream * stm); + +struct cubeb_stream { + cubeb * context = nullptr; + /* Mixer pameters. We need to convert the input stream to this + samplerate/channel layout, as WASAPI does not resample nor upmix + itself. */ + cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + /* Stream parameters. This is what the client requested, + * and what will be presented in the callback. */ + cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; + /* The input and output device, or NULL for default. */ + std::unique_ptr input_device; + std::unique_ptr output_device; + /* The latency initially requested for this stream, in frames. */ + unsigned latency = 0; + cubeb_state_callback state_callback = nullptr; + cubeb_data_callback data_callback = nullptr; + wasapi_refill_callback refill_callback = nullptr; + void * user_ptr = nullptr; + /* Lifetime considerations: + - client, render_client, audio_clock and audio_stream_volume are interface + pointer to the IAudioClient. + - The lifetime for device_enumerator and notification_client, resampler, + mix_buffer are the same as the cubeb_stream instance. */ + + /* Main handle on the WASAPI stream. */ + com_ptr output_client; + /* Interface pointer to use the event-driven interface. */ + com_ptr render_client; + /* Interface pointer to use the volume facilities. */ + com_ptr audio_stream_volume; + /* Interface pointer to use the stream audio clock. */ + com_ptr audio_clock; + /* Frames written to the stream since it was opened. Reset on device + change. Uses mix_params.rate. */ + UINT64 frames_written = 0; + /* Frames written to the (logical) stream since it was first + created. Updated on device change. Uses stream_params.rate. */ + UINT64 total_frames_written = 0; + /* Last valid reported stream position. Used to ensure the position + reported by stream_get_position increases monotonically. */ + UINT64 prev_position = 0; + /* Device enumerator to be able to be notified when the default + device change. */ + com_ptr device_enumerator; + /* Device notification client, to be able to be notified when the default + audio device changes and route the audio to the new default audio output + device */ + com_ptr notification_client; + /* Main andle to the WASAPI capture stream. */ + com_ptr input_client; + /* Interface to use the event driven capture interface */ + com_ptr capture_client; + /* This event is set by the stream_stop and stream_destroy + function, so the render loop can exit properly. */ + HANDLE shutdown_event = 0; + /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. + The reconfiguration is handled by the render loop thread. */ + HANDLE reconfigure_event = 0; + /* This is set by WASAPI when we should refill the stream. */ + HANDLE refill_event = 0; + /* This is set by WASAPI when we should read from the input stream. In + * practice, we read from the input stream in the output callback, so + * this is not used, but it is necessary to start getting input data. */ + HANDLE input_available_event = 0; + /* Each cubeb_stream has its own thread. */ + HANDLE thread = 0; + /* The lock protects all members that are touched by the render thread or + change during a device reset, including: audio_clock, audio_stream_volume, + client, frames_written, mix_params, total_frames_written, prev_position. */ + owned_critical_section stream_reset_lock; + /* Maximum number of frames that can be passed down in a callback. */ + uint32_t input_buffer_frame_count = 0; + /* Maximum number of frames that can be requested in a callback. */ + uint32_t output_buffer_frame_count = 0; + /* Resampler instance. Resampling will only happen if necessary. */ + std::unique_ptr resampler = { nullptr, cubeb_resampler_destroy }; + /* Mixer interface */ + std::unique_ptr mixer = { nullptr, cubeb_mixer_destroy }; + /* A buffer for up/down mixing multi-channel audio. */ + std::vector mix_buffer; + /* WASAPI input works in "packets". We re-linearize the audio packets + * into this buffer before handing it to the resampler. */ + std::unique_ptr linear_input_buffer; + /* Bytes per sample. This multiplied by the number of channels is the number + * of bytes per frame. */ + size_t bytes_per_sample = 0; + /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */ + GUID waveformatextensible_sub_format = GUID_NULL; + /* Stream volume. Set via stream_set_volume and used to reset volume on + device changes. */ + float volume = 1.0; + /* True if the stream is draining. */ + bool draining = false; + /* True when we've destroyed the stream. This pointer is leaked on stream + * destruction if we could not join the thread. */ + std::atomic*> emergency_bailout; +}; + +class wasapi_endpoint_notification_client : public IMMNotificationClient +{ +public: + /* The implementation of MSCOM was copied from MSDN. */ + ULONG STDMETHODCALLTYPE + AddRef() + { + return InterlockedIncrement(&ref_count); + } + + ULONG STDMETHODCALLTYPE + Release() + { + ULONG ulRef = InterlockedDecrement(&ref_count); + if (0 == ulRef) { + delete this; + } + return ulRef; + } + + HRESULT STDMETHODCALLTYPE + QueryInterface(REFIID riid, VOID **ppvInterface) + { + if (__uuidof(IUnknown) == riid) { + AddRef(); + *ppvInterface = (IUnknown*)this; + } else if (__uuidof(IMMNotificationClient) == riid) { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } else { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + wasapi_endpoint_notification_client(HANDLE event) + : ref_count(1) + , reconfigure_event(event) + { } + + virtual ~wasapi_endpoint_notification_client() + { } + + HRESULT STDMETHODCALLTYPE + OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) + { + LOG("Audio device default changed."); + + /* we only support a single stream type for now. */ + if (flow != eRender && role != eConsole) { + return S_OK; + } + + BOOL ok = SetEvent(reconfigure_event); + if (!ok) { + LOG("SetEvent on reconfigure_event failed: %lx", GetLastError()); + } + + return S_OK; + } + + /* The remaining methods are not implemented, they simply log when called (if + log is enabled), for debugging. */ + HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) + { + LOG("Audio device added."); + return S_OK; + }; + + HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) + { + LOG("Audio device removed."); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) + { + LOG("Audio device state changed."); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) + { + LOG("Audio device property value changed."); + return S_OK; + } +private: + /* refcount for this instance, necessary to implement MSCOM semantics. */ + LONG ref_count; + HANDLE reconfigure_event; +}; + +namespace { +bool has_input(cubeb_stream * stm) +{ + return stm->input_stream_params.rate != 0; +} + +bool has_output(cubeb_stream * stm) +{ + return stm->output_stream_params.rate != 0; +} + +double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer) +{ + return double(stream.rate) / mixer.rate; +} + +/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG. + See more: https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx */ +#define MASK_DUAL_MONO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) +#define MASK_DUAL_MONO_LFE (MASK_DUAL_MONO | SPEAKER_LOW_FREQUENCY) +#define MASK_MONO (KSAUDIO_SPEAKER_MONO) +#define MASK_MONO_LFE (MASK_MONO | SPEAKER_LOW_FREQUENCY) +#define MASK_STEREO (KSAUDIO_SPEAKER_STEREO) +#define MASK_STEREO_LFE (MASK_STEREO | SPEAKER_LOW_FREQUENCY) +#define MASK_3F (MASK_STEREO | SPEAKER_FRONT_CENTER) +#define MASK_3F_LFE (MASK_3F | SPEAKER_LOW_FREQUENCY) +#define MASK_2F1 (MASK_STEREO | SPEAKER_BACK_CENTER) +#define MASK_2F1_LFE (MASK_2F1 | SPEAKER_LOW_FREQUENCY) +#define MASK_3F1 (KSAUDIO_SPEAKER_SURROUND) +#define MASK_3F1_LFE (MASK_3F1 | SPEAKER_LOW_FREQUENCY) +#define MASK_2F2 (MASK_STEREO | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) +#define MASK_2F2_LFE (MASK_2F2 | SPEAKER_LOW_FREQUENCY) +#define MASK_3F2 (MASK_3F | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) +#define MASK_3F2_LFE (KSAUDIO_SPEAKER_5POINT1_SURROUND) +#define MASK_3F3R_LFE (MASK_3F2_LFE | SPEAKER_BACK_CENTER) +#define MASK_3F4_LFE (KSAUDIO_SPEAKER_7POINT1_SURROUND) + +static DWORD +channel_layout_to_mask(cubeb_channel_layout layout) +{ + XASSERT(layout < CUBEB_LAYOUT_MAX && "invalid conversion."); + + // This variable may be used for multiple times, so we should avoid to + // allocate it in stack, or it will be created and removed repeatedly. + // Use static to allocate this local variable in data space instead of stack. + static DWORD map[CUBEB_LAYOUT_MAX] = { + 0, // CUBEB_LAYOUT_UNDEFINED + MASK_DUAL_MONO, // CUBEB_LAYOUT_DUAL_MONO + MASK_DUAL_MONO_LFE, // CUBEB_LAYOUT_DUAL_MONO_LFE + MASK_MONO, // CUBEB_LAYOUT_MONO + MASK_MONO_LFE, // CUBEB_LAYOUT_MONO_LFE + MASK_STEREO, // CUBEB_LAYOUT_STEREO + MASK_STEREO_LFE, // CUBEB_LAYOUT_STEREO_LFE + MASK_3F, // CUBEB_LAYOUT_3F + MASK_3F_LFE, // CUBEB_LAYOUT_3F_LFE + MASK_2F1, // CUBEB_LAYOUT_2F1 + MASK_2F1_LFE, // CUBEB_LAYOUT_2F1_LFE + MASK_3F1, // CUBEB_LAYOUT_3F1 + MASK_3F1_LFE, // CUBEB_LAYOUT_3F1_LFE + MASK_2F2, // CUBEB_LAYOUT_2F2 + MASK_2F2_LFE, // CUBEB_LAYOUT_2F2_LFE + MASK_3F2, // CUBEB_LAYOUT_3F2 + MASK_3F2_LFE, // CUBEB_LAYOUT_3F2_LFE + MASK_3F3R_LFE, // CUBEB_LAYOUT_3F3R_LFE + MASK_3F4_LFE, // CUBEB_LAYOUT_3F4_LFE + }; + return map[layout]; +} + +cubeb_channel_layout +mask_to_channel_layout(DWORD mask) +{ + switch (mask) { + // MASK_DUAL_MONO(_LFE) is same as STEREO(_LFE), so we skip it. + case MASK_MONO: return CUBEB_LAYOUT_MONO; + case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE; + case MASK_STEREO: return CUBEB_LAYOUT_STEREO; + case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE; + case MASK_3F: return CUBEB_LAYOUT_3F; + case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE; + case MASK_2F1: return CUBEB_LAYOUT_2F1; + case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE; + case MASK_3F1: return CUBEB_LAYOUT_3F1; + case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE; + case MASK_2F2: return CUBEB_LAYOUT_2F2; + case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE; + case MASK_3F2: return CUBEB_LAYOUT_3F2; + case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE; + case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE; + case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE; + default: return CUBEB_LAYOUT_UNDEFINED; + } +} + +uint32_t +get_rate(cubeb_stream * stm) +{ + return has_input(stm) ? stm->input_stream_params.rate + : stm->output_stream_params.rate; +} + +uint32_t +hns_to_ms(REFERENCE_TIME hns) +{ + return static_cast(hns / 10000); +} + +uint32_t +hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns) +{ + return hns_to_ms(hns * get_rate(stm)) / 1000; +} + +uint32_t +hns_to_frames(uint32_t rate, REFERENCE_TIME hns) +{ + return hns_to_ms(hns * rate) / 1000; +} + +REFERENCE_TIME +frames_to_hns(cubeb_stream * stm, uint32_t frames) +{ + return frames * 1000 / get_rate(stm); +} + +/* This returns the size of a frame in the stream, before the eventual upmix + occurs. */ +static size_t +frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames) +{ + // This is called only when we has a output client. + XASSERT(has_output(stm)); + return stm->output_stream_params.channels * stm->bytes_per_sample * frames; +} + +/* This function handles the processing of the input and output audio, + * converting it to rate and channel layout specified at initialization. + * It then calls the data callback, via the resampler. */ +long +refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, + void * output_buffer, long output_frames_needed) +{ + /* If we need to upmix after resampling, resample into the mix buffer to + avoid a copy. */ + void * dest = nullptr; + if (has_output(stm)) { + if (cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) { + dest = stm->mix_buffer.data(); + } else { + dest = output_buffer; + } + } + + long out_frames = cubeb_resampler_fill(stm->resampler.get(), + input_buffer, + &input_frames_count, + dest, + output_frames_needed); + /* TODO: Report out_frames < 0 as an error via the API. */ + XASSERT(out_frames >= 0); + + { + auto_lock lock(stm->stream_reset_lock); + stm->frames_written += out_frames; + } + + /* Go in draining mode if we got fewer frames than requested. */ + if (out_frames < output_frames_needed) { + LOG("start draining."); + stm->draining = true; + } + + /* If this is not true, there will be glitches. + It is alright to have produced less frames if we are draining, though. */ + XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm)); + + if (has_output(stm) && cubeb_should_mix(&stm->output_stream_params, &stm->output_mix_params)) { + XASSERT(dest == stm->mix_buffer.data()); + unsigned long dest_len = out_frames * stm->output_stream_params.channels; + XASSERT(dest_len <= stm->mix_buffer.size() / stm->bytes_per_sample); + unsigned long output_buffer_len = out_frames * stm->output_mix_params.channels; + cubeb_mixer_mix(stm->mixer.get(), out_frames, + dest, dest_len, output_buffer, output_buffer_len, + &stm->output_stream_params, &stm->output_mix_params); + } + + return out_frames; +} + +/* This helper grabs all the frames available from a capture client, put them in + * linear_input_buffer. linear_input_buffer should be cleared before the + * callback exits. */ +bool get_input_buffer(cubeb_stream * stm) +{ + HRESULT hr; + UINT32 padding_in; + + XASSERT(has_input(stm)); + + hr = stm->input_client->GetCurrentPadding(&padding_in); + if (FAILED(hr)) { + LOG("Failed to get padding"); + return false; + } + XASSERT(padding_in <= stm->input_buffer_frame_count); + UINT32 total_available_input = padding_in; + + BYTE * input_packet = NULL; + DWORD flags; + UINT64 dev_pos; + UINT32 next; + /* Get input packets until we have captured enough frames, and put them in a + * contiguous buffer. */ + uint32_t offset = 0; + while (offset != total_available_input) { + hr = stm->capture_client->GetNextPacketSize(&next); + if (FAILED(hr)) { + LOG("cannot get next packet size: %lx", hr); + return false; + } + /* This can happen if the capture stream has stopped. Just return in this + * case. */ + if (!next) { + break; + } + + UINT32 packet_size; + hr = stm->capture_client->GetBuffer(&input_packet, + &packet_size, + &flags, + &dev_pos, + NULL); + if (FAILED(hr)) { + LOG("GetBuffer failed for capture: %lx", hr); + return false; + } + XASSERT(packet_size == next); + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { + LOG("insert silence: ps=%u", packet_size); + stm->linear_input_buffer->push_silence(packet_size * stm->input_stream_params.channels); + } else { + if (cubeb_should_mix(&stm->input_mix_params, &stm->input_stream_params)) { + bool ok = stm->linear_input_buffer->reserve(stm->linear_input_buffer->length() + + packet_size * stm->input_stream_params.channels); + XASSERT(ok); + unsigned long input_packet_length = packet_size * stm->input_mix_params.channels; + unsigned long linear_input_buffer_length = packet_size * stm->input_stream_params.channels; + cubeb_mixer_mix(stm->mixer.get(), packet_size, + input_packet, input_packet_length, + stm->linear_input_buffer->end(), linear_input_buffer_length, + &stm->input_mix_params, + &stm->input_stream_params); + stm->linear_input_buffer->set_length(stm->linear_input_buffer->length() + linear_input_buffer_length); + } else { + stm->linear_input_buffer->push(input_packet, + packet_size * stm->input_stream_params.channels); + } + } + hr = stm->capture_client->ReleaseBuffer(packet_size); + if (FAILED(hr)) { + LOG("FAILED to release intput buffer"); + return false; + } + offset += packet_size; + } + + XASSERT(stm->linear_input_buffer->length() >= total_available_input && + offset == total_available_input); + + return true; +} + +/* Get an output buffer from the render_client. It has to be released before + * exiting the callback. */ +bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) +{ + UINT32 padding_out; + HRESULT hr; + + XASSERT(has_output(stm)); + + hr = stm->output_client->GetCurrentPadding(&padding_out); + if (FAILED(hr)) { + LOG("Failed to get padding: %lx", hr); + return false; + } + XASSERT(padding_out <= stm->output_buffer_frame_count); + + if (stm->draining) { + if (padding_out == 0) { + LOG("Draining finished."); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + return false; + } + LOG("Draining."); + return true; + } + + frame_count = stm->output_buffer_frame_count - padding_out; + BYTE * output_buffer; + + hr = stm->render_client->GetBuffer(frame_count, &output_buffer); + if (FAILED(hr)) { + LOG("cannot get render buffer"); + return false; + } + + buffer = output_buffer; + + return true; +} + +/** + * This function gets input data from a input device, and pass it along with an + * output buffer to the resamplers. */ +bool +refill_callback_duplex(cubeb_stream * stm) +{ + HRESULT hr; + void * output_buffer = nullptr; + size_t output_frames = 0; + size_t input_frames; + bool rv; + + XASSERT(has_input(stm) && has_output(stm)); + + rv = get_input_buffer(stm); + if (!rv) { + return rv; + } + + input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; + if (!input_frames) { + return true; + } + + rv = get_output_buffer(stm, output_buffer, output_frames); + if (!rv) { + hr = stm->render_client->ReleaseBuffer(output_frames, 0); + return rv; + } + + /* This can only happen when debugging, and having breakpoints set in the + * callback in a way that it makes the stream underrun. */ + if (output_frames == 0) { + return true; + } + + + ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu", + input_frames, output_frames); + + refill(stm, + stm->linear_input_buffer->data(), + input_frames, + output_buffer, + output_frames); + + stm->linear_input_buffer->clear(); + + hr = stm->render_client->ReleaseBuffer(output_frames, 0); + if (FAILED(hr)) { + LOG("failed to release buffer: %lx", hr); + return false; + } + return true; +} + +bool +refill_callback_input(cubeb_stream * stm) +{ + bool rv; + size_t input_frames; + + XASSERT(has_input(stm) && !has_output(stm)); + + rv = get_input_buffer(stm); + if (!rv) { + return rv; + } + + input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; + if (!input_frames) { + return true; + } + + ALOGV("Input callback: input frames: %Iu", input_frames); + + long read = refill(stm, + stm->linear_input_buffer->data(), + input_frames, + nullptr, + 0); + + XASSERT(read >= 0); + + stm->linear_input_buffer->clear(); + + return !stm->draining; +} + +bool +refill_callback_output(cubeb_stream * stm) +{ + bool rv; + HRESULT hr; + void * output_buffer = nullptr; + size_t output_frames = 0; + + XASSERT(!has_input(stm) && has_output(stm)); + + rv = get_output_buffer(stm, output_buffer, output_frames); + if (!rv) { + return rv; + } + + if (stm->draining || output_frames == 0) { + return true; + } + + long got = refill(stm, + nullptr, + 0, + output_buffer, + output_frames); + + ALOGV("Output callback: output frames requested: %Iu, got %ld", + output_frames, got); + + XASSERT(got >= 0); + XASSERT((unsigned long) got == output_frames || stm->draining); + + hr = stm->render_client->ReleaseBuffer(got, 0); + if (FAILED(hr)) { + LOG("failed to release buffer: %lx", hr); + return false; + } + + return (unsigned long) got == output_frames || stm->draining; +} + +static unsigned int __stdcall +wasapi_stream_render_loop(LPVOID stream) +{ + cubeb_stream * stm = static_cast(stream); + std::atomic * emergency_bailout = stm->emergency_bailout; + + bool is_playing = true; + HANDLE wait_array[4] = { + stm->shutdown_event, + stm->reconfigure_event, + stm->refill_event, + stm->input_available_event + }; + HANDLE mmcss_handle = NULL; + HRESULT hr = 0; + DWORD mmcss_task_index = 0; + auto_com com; + if (!com.ok()) { + LOG("COM initialization failed on render_loop thread."); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return 0; + } + + /* We could consider using "Pro Audio" here for WebAudio and + maybe WebRTC. */ + mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); + if (!mmcss_handle) { + /* This is not fatal, but we might glitch under heavy load. */ + LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError()); + } + + // This has already been nulled out, simply exit. + if (!emergency_bailout) { + is_playing = false; + } + + /* WaitForMultipleObjects timeout can trigger in cases where we don't want to + treat it as a timeout, such as across a system sleep/wake cycle. Trigger + the timeout error handling only when the timeout_limit is reached, which is + reset on each successful loop. */ + unsigned timeout_count = 0; + const unsigned timeout_limit = 5; + while (is_playing) { + // We want to check the emergency bailout variable before a + // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects + // is going to wait on might have been closed already. + if (*emergency_bailout) { + delete emergency_bailout; + return 0; + } + DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), + wait_array, + FALSE, + 1000); + if (*emergency_bailout) { + delete emergency_bailout; + return 0; + } + if (waitResult != WAIT_TIMEOUT) { + timeout_count = 0; + } + switch (waitResult) { + case WAIT_OBJECT_0: { /* shutdown */ + is_playing = false; + /* We don't check if the drain is actually finished here, we just want to + shutdown. */ + if (stm->draining) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + } + continue; + } + case WAIT_OBJECT_0 + 1: { /* reconfigure */ + XASSERT(stm->output_client || stm->input_client); + LOG("Reconfiguring the stream"); + /* Close the stream */ + if (stm->output_client) { + stm->output_client->Stop(); + LOG("Output stopped."); + } + if (stm->input_client) { + stm->input_client->Stop(); + LOG("Input stopped."); + } + { + auto_lock lock(stm->stream_reset_lock); + close_wasapi_stream(stm); + LOG("Stream closed."); + /* Reopen a stream and start it immediately. This will automatically pick the + new default device for this role. */ + int r = setup_wasapi_stream(stm); + if (r != CUBEB_OK) { + LOG("Error setting up the stream during reconfigure."); + /* Don't destroy the stream here, since we expect the caller to do + so after the error has propagated via the state callback. */ + is_playing = false; + hr = E_FAIL; + continue; + } + LOG("Stream setup successfuly."); + } + XASSERT(stm->output_client || stm->input_client); + if (stm->output_client) { + stm->output_client->Start(); + LOG("Output started after reconfigure."); + } + if (stm->input_client) { + stm->input_client->Start(); + LOG("Input started after reconfigure."); + } + break; + } + case WAIT_OBJECT_0 + 2: /* refill */ + XASSERT((has_input(stm) && has_output(stm)) || + (!has_input(stm) && has_output(stm))); + is_playing = stm->refill_callback(stm); + break; + case WAIT_OBJECT_0 + 3: /* input available */ + if (has_input(stm) && has_output(stm)) { continue; } + is_playing = stm->refill_callback(stm); + break; + case WAIT_TIMEOUT: + XASSERT(stm->shutdown_event == wait_array[0]); + if (++timeout_count >= timeout_limit) { + LOG("Render loop reached the timeout limit."); + is_playing = false; + hr = E_FAIL; + } + break; + default: + LOG("case %lu not handled in render loop.", waitResult); + abort(); + } + } + + if (FAILED(hr)) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + } + + if (mmcss_handle) { + AvRevertMmThreadCharacteristics(mmcss_handle); + } + + return 0; +} + +void wasapi_destroy(cubeb * context); + +HRESULT register_notification_client(cubeb_stream * stm) +{ + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(stm->device_enumerator.receive())); + if (FAILED(hr)) { + LOG("Could not get device enumerator: %lx", hr); + return hr; + } + + stm->notification_client.reset(new wasapi_endpoint_notification_client(stm->reconfigure_event)); + + hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get()); + if (FAILED(hr)) { + LOG("Could not register endpoint notification callback: %lx", hr); + stm->notification_client = nullptr; + stm->device_enumerator = nullptr; + } + + return hr; +} + +HRESULT unregister_notification_client(cubeb_stream * stm) +{ + XASSERT(stm); + HRESULT hr; + + if (!stm->device_enumerator) { + return S_OK; + } + + hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client.get()); + if (FAILED(hr)) { + // We can't really do anything here, we'll probably leak the + // notification client, but we can at least release the enumerator. + stm->device_enumerator = nullptr; + return S_OK; + } + + stm->notification_client = nullptr; + stm->device_enumerator = nullptr; + + return S_OK; +} + +HRESULT get_endpoint(com_ptr & device, LPCWSTR devid) +{ + com_ptr enumerator; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(enumerator.receive())); + if (FAILED(hr)) { + LOG("Could not get device enumerator: %lx", hr); + return hr; + } + + hr = enumerator->GetDevice(devid, device.receive()); + if (FAILED(hr)) { + LOG("Could not get device: %lx", hr); + return hr; + } + + return S_OK; +} + +HRESULT get_default_endpoint(com_ptr & device, EDataFlow direction) +{ + com_ptr enumerator; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(enumerator.receive())); + if (FAILED(hr)) { + LOG("Could not get device enumerator: %lx", hr); + return hr; + } + hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device.receive()); + if (FAILED(hr)) { + LOG("Could not get default audio endpoint: %lx", hr); + return hr; + } + + return ERROR_SUCCESS; +} + +double +current_stream_delay(cubeb_stream * stm) +{ + stm->stream_reset_lock.assert_current_thread_owns(); + + /* If the default audio endpoint went away during playback and we weren't + able to configure a new one, it's possible the caller may call this + before the error callback has propogated back. */ + if (!stm->audio_clock) { + return 0; + } + + UINT64 freq; + HRESULT hr = stm->audio_clock->GetFrequency(&freq); + if (FAILED(hr)) { + LOG("GetFrequency failed: %lx", hr); + return 0; + } + + UINT64 pos; + hr = stm->audio_clock->GetPosition(&pos, NULL); + if (FAILED(hr)) { + LOG("GetPosition failed: %lx", hr); + return 0; + } + + double cur_pos = static_cast(pos) / freq; + double max_pos = static_cast(stm->frames_written) / stm->output_mix_params.rate; + double delay = max_pos - cur_pos; + XASSERT(delay >= 0); + + return delay; +} + +int +stream_set_volume(cubeb_stream * stm, float volume) +{ + stm->stream_reset_lock.assert_current_thread_owns(); + + if (!stm->audio_stream_volume) { + return CUBEB_ERROR; + } + + uint32_t channels; + HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels); + if (hr != S_OK) { + LOG("could not get the channel count: %lx", hr); + return CUBEB_ERROR; + } + + /* up to 9.1 for now */ + if (channels > 10) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + float volumes[10]; + for (uint32_t i = 0; i < channels; i++) { + volumes[i] = volume; + } + + hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes); + if (hr != S_OK) { + LOG("could not set the channels volume: %lx", hr); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} +} // namespace anonymous + +extern "C" { +int wasapi_init(cubeb ** context, char const * context_name) +{ + HRESULT hr; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + /* We don't use the device yet, but need to make sure we can initialize one + so that this backend is not incorrectly enabled on platforms that don't + support WASAPI. */ + com_ptr device; + hr = get_default_endpoint(device, eRender); + if (FAILED(hr)) { + LOG("Could not get device: %lx", hr); + return CUBEB_ERROR; + } + + cubeb * ctx = new cubeb(); + + ctx->ops = &wasapi_ops; + + *context = ctx; + + return CUBEB_OK; +} +} + +namespace { +bool stop_and_join_render_thread(cubeb_stream * stm) +{ + bool rv = true; + LOG("Stop and join render thread."); + if (!stm->thread) { + LOG("No thread present."); + return true; + } + + // If we've already leaked the thread, just return, + // there is not much we can do. + if (!stm->emergency_bailout.load()) { + return false; + } + + BOOL ok = SetEvent(stm->shutdown_event); + if (!ok) { + LOG("Destroy SetEvent failed: %lx", GetLastError()); + } + + /* Wait five seconds for the rendering thread to return. It's supposed to + * check its event loop very often, five seconds is rather conservative. */ + DWORD r = WaitForSingleObject(stm->thread, 5000); + if (r == WAIT_TIMEOUT) { + /* Something weird happened, leak the thread and continue the shutdown + * process. */ + *(stm->emergency_bailout) = true; + // We give the ownership to the rendering thread. + stm->emergency_bailout = nullptr; + LOG("Destroy WaitForSingleObject on thread timed out," + " leaking the thread: %lx", GetLastError()); + rv = false; + } + if (r == WAIT_FAILED) { + *(stm->emergency_bailout) = true; + // We give the ownership to the rendering thread. + stm->emergency_bailout = nullptr; + LOG("Destroy WaitForSingleObject on thread failed: %lx", GetLastError()); + rv = false; + } + + + // Only attempts to close and null out the thread and event if the + // WaitForSingleObject above succeeded, so that calling this function again + // attemps to clean up the thread and event each time. + if (rv) { + LOG("Closing thread."); + CloseHandle(stm->thread); + stm->thread = NULL; + + CloseHandle(stm->shutdown_event); + stm->shutdown_event = 0; + } + + return rv; +} + +void wasapi_destroy(cubeb * context) +{ + delete context; +} + +char const * wasapi_get_backend_id(cubeb * context) +{ + return "wasapi"; +} + +int +wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + HRESULT hr; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + XASSERT(ctx && max_channels); + + com_ptr device; + hr = get_default_endpoint(device, eRender); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + com_ptr client; + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, client.receive_vpp()); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + WAVEFORMATEX * tmp = nullptr; + hr = client->GetMixFormat(&tmp); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + com_heap_ptr mix_format(tmp); + + *max_channels = mix_format->nChannels; + + return CUBEB_OK; +} + +int +wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) +{ + HRESULT hr; + REFERENCE_TIME default_period; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + if (params.format != CUBEB_SAMPLE_FLOAT32NE && params.format != CUBEB_SAMPLE_S16NE) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + com_ptr device; + hr = get_default_endpoint(device, eRender); + if (FAILED(hr)) { + LOG("Could not get default endpoint: %lx", hr); + return CUBEB_ERROR; + } + + com_ptr client; + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, client.receive_vpp()); + if (FAILED(hr)) { + LOG("Could not activate device for latency: %lx", hr); + return CUBEB_ERROR; + } + + /* The second parameter is for exclusive mode, that we don't use. */ + hr = client->GetDevicePeriod(&default_period, NULL); + if (FAILED(hr)) { + LOG("Could not get device period: %lx", hr); + return CUBEB_ERROR; + } + + LOG("default device period: %I64d", default_period); + + /* According to the docs, the best latency we can achieve is by synchronizing + the stream and the engine. + http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ + + *latency_frames = hns_to_frames(params.rate, default_period); + + LOG("Minimum latency in frames: %u", *latency_frames); + + return CUBEB_OK; +} + +int +wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + HRESULT hr; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + com_ptr device; + hr = get_default_endpoint(device, eRender); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + com_ptr client; + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, client.receive_vpp()); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + WAVEFORMATEX * tmp = nullptr; + hr = client->GetMixFormat(&tmp); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + com_heap_ptr mix_format(tmp); + + *rate = mix_format->nSamplesPerSec; + + LOG("Preferred sample rate for output: %u", *rate); + + return CUBEB_OK; +} + +int +wasapi_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout) +{ + HRESULT hr; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + com_ptr device; + hr = get_default_endpoint(device, eRender); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + com_ptr client; + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, client.receive_vpp()); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + + WAVEFORMATEX * tmp = nullptr; + hr = client->GetMixFormat(&tmp); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + com_heap_ptr mix_format(tmp); + + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); + *layout = mask_to_channel_layout(format_pcm->dwChannelMask); + + LOG("Preferred channel layout: %s", CUBEB_CHANNEL_LAYOUT_MAPS[*layout].name); + + return CUBEB_OK; +} + +void wasapi_stream_destroy(cubeb_stream * stm); + +static void +waveformatex_update_derived_properties(WAVEFORMATEX * format) +{ + format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8; + format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign; + if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(format); + format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample; + } +} + +/* Based on the mix format and the stream format, try to find a way to play + what the user requested. */ +static void +handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr & mix_format, const cubeb_stream_params * stream_params) +{ + // The CUBEB_LAYOUT_UNDEFINED can be used for input but it's not allowed for output. + XASSERT(direction == eCapture || stream_params->layout != CUBEB_LAYOUT_UNDEFINED); + com_ptr & audio_client = (direction == eRender) ? stm->output_client : stm->input_client; + XASSERT(audio_client); + /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], + so the reinterpret_cast below should be safe. In practice, this is not + true, and we just want to bail out and let the rest of the code find a good + conversion path instead of trying to make WASAPI do it by itself. + [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/ + if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { + return; + } + + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); + + /* Stash a copy of the original mix format in case we need to restore it later. */ + WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm; + + /* Get the channel mask by the channel layout. + If the layout is not supported, we will get a closest settings below. */ + format_pcm->dwChannelMask = channel_layout_to_mask(stream_params->layout); + mix_format->nChannels = stream_params->channels; + waveformatex_update_derived_properties(mix_format.get()); + + /* Check if wasapi will accept our channel layout request. */ + WAVEFORMATEX * closest; + HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, + mix_format.get(), + &closest); + if (hr == S_FALSE) { + /* Channel layout not supported, but WASAPI gives us a suggestion. Use it, + and handle the eventual upmix/downmix ourselves. Ignore the subformat of + the suggestion, since it seems to always be IEEE_FLOAT. */ + LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); + WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest); + format_pcm->dwChannelMask = closest_pcm->dwChannelMask; + mix_format->nChannels = closest->nChannels; + waveformatex_update_derived_properties(mix_format.get()); + } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { + /* Not supported, no suggestion. This should not happen, but it does in the + field with some sound cards. We restore the mix format, and let the rest + of the code figure out the right conversion path. */ + *reinterpret_cast(mix_format.get()) = hw_mix_format; + } else if (hr == S_OK) { + LOG("Requested format accepted by WASAPI."); + } else { + LOG("IsFormatSupported unhandled error: %lx", hr); + } +} + +#define DIRECTION_NAME (direction == eCapture ? "capture" : "render") + +template +int setup_wasapi_stream_one_side(cubeb_stream * stm, + cubeb_stream_params * stream_params, + wchar_t const * devid, + EDataFlow direction, + REFIID riid, + com_ptr & audio_client, + uint32_t * buffer_frame_count, + HANDLE & event, + T & render_or_capture_client, + cubeb_stream_params * mix_params) +{ + com_ptr device; + HRESULT hr; + + stm->stream_reset_lock.assert_current_thread_owns(); + bool try_again = false; + // This loops until we find a device that works, or we've exhausted all + // possibilities. + do { + if (devid) { + hr = get_endpoint(device, devid); + if (FAILED(hr)) { + LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + } else { + hr = get_default_endpoint(device, direction); + if (FAILED(hr)) { + LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + } + + /* Get a client. We will get all other interfaces we need from + * this pointer. */ + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, audio_client.receive_vpp()); + if (FAILED(hr)) { + LOG("Could not activate the device to get an audio" + " client for %s: error: %lx\n", DIRECTION_NAME, hr); + // A particular device can't be activated because it has been + // unplugged, try fall back to the default audio device. + if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { + LOG("Trying again with the default %s audio device.", DIRECTION_NAME); + devid = nullptr; + device = nullptr; + try_again = true; + } else { + return CUBEB_ERROR; + } + } else { + try_again = false; + } + } while (try_again); + + /* We have to distinguish between the format the mixer uses, + * and the format the stream we want to play uses. */ + WAVEFORMATEX * tmp = nullptr; + hr = audio_client->GetMixFormat(&tmp); + if (FAILED(hr)) { + LOG("Could not fetch current mix format from the audio" + " client for %s: error: %lx", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + com_heap_ptr mix_format(tmp); + + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); + mix_format->wBitsPerSample = stm->bytes_per_sample * 8; + format_pcm->SubFormat = stm->waveformatextensible_sub_format; + waveformatex_update_derived_properties(mix_format.get()); + /* Set channel layout only when there're more than two channels. Otherwise, + * use the default setting retrieved from the stream format of the audio + * engine's internal processing by GetMixFormat. */ + if (mix_format->nChannels > 2) { + handle_channel_layout(stm, direction ,mix_format, stream_params); + } + + mix_params->format = stream_params->format; + mix_params->rate = mix_format->nSamplesPerSec; + mix_params->channels = mix_format->nChannels; + mix_params->layout = mask_to_channel_layout(format_pcm->dwChannelMask); + if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) { + LOG("Output using undefined layout!\n"); + } else if (mix_format->nChannels != CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels) { + // The CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels may be + // different from the mix_params->channels. 6 channel ouput with stereo + // layout is acceptable in Windows. If this happens, it should not downmix + // audio according to layout. + LOG("Channel count is different from the layout standard!\n"); + } + LOG("Setup requested=[f=%d r=%u c=%u l=%s] mix=[f=%d r=%u c=%u l=%s]", + stream_params->format, stream_params->rate, stream_params->channels, + CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].name, + mix_params->format, mix_params->rate, mix_params->channels, + CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].name); + + hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST, + frames_to_hns(stm, stm->latency), + 0, + mix_format.get(), + NULL); + if (FAILED(hr)) { + LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + + hr = audio_client->GetBufferSize(buffer_frame_count); + if (FAILED(hr)) { + LOG("Could not get the buffer size from the client" + " for %s %lx.", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + // Input is up/down mixed when depacketized in get_input_buffer. + if (has_output(stm) && cubeb_should_mix(stream_params, mix_params)) { + stm->mix_buffer.resize(frames_to_bytes_before_mix(stm, *buffer_frame_count)); + } + + hr = audio_client->SetEventHandle(event); + if (FAILED(hr)) { + LOG("Could set the event handle for the %s client %lx.", + DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + + hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp()); + if (FAILED(hr)) { + LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +#undef DIRECTION_NAME + +int setup_wasapi_stream(cubeb_stream * stm) +{ + HRESULT hr; + int rv; + + stm->stream_reset_lock.assert_current_thread_owns(); + + auto_com com; + if (!com.ok()) { + LOG("Failure to initialize COM."); + return CUBEB_ERROR; + } + + XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); + + if (has_input(stm)) { + LOG("(%p) Setup capture: device=%p", stm, stm->input_device.get()); + rv = setup_wasapi_stream_one_side(stm, + &stm->input_stream_params, + stm->input_device.get(), + eCapture, + __uuidof(IAudioCaptureClient), + stm->input_client, + &stm->input_buffer_frame_count, + stm->input_available_event, + stm->capture_client, + &stm->input_mix_params); + + // We initializing an input stream, buffer ahead two buffers worth of silence. + // This delays the input side slightly, but allow to not glitch when no input + // is available when calling into the resampler to call the callback: the input + // refill event will be set shortly after to compensate for this lack of data. + // In debug, four buffers are used, to avoid tripping up assertions down the line. +#if !defined(DEBUG) + const int silent_buffer_count = 2; +#else + const int silent_buffer_count = 4; +#endif + stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count * + stm->input_stream_params.channels * + silent_buffer_count); + + if (rv != CUBEB_OK) { + LOG("Failure to open the input side."); + return rv; + } + } + + if (has_output(stm)) { + LOG("(%p) Setup render: device=%p", stm, stm->output_device.get()); + rv = setup_wasapi_stream_one_side(stm, + &stm->output_stream_params, + stm->output_device.get(), + eRender, + __uuidof(IAudioRenderClient), + stm->output_client, + &stm->output_buffer_frame_count, + stm->refill_event, + stm->render_client, + &stm->output_mix_params); + if (rv != CUBEB_OK) { + LOG("Failure to open the output side."); + return rv; + } + + hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume), + stm->audio_stream_volume.receive_vpp()); + if (FAILED(hr)) { + LOG("Could not get the IAudioStreamVolume: %lx", hr); + return CUBEB_ERROR; + } + + XASSERT(stm->frames_written == 0); + hr = stm->output_client->GetService(__uuidof(IAudioClock), + stm->audio_clock.receive_vpp()); + if (FAILED(hr)) { + LOG("Could not get the IAudioClock: %lx", hr); + return CUBEB_ERROR; + } + + /* Restore the stream volume over a device change. */ + if (stream_set_volume(stm, stm->volume) != CUBEB_OK) { + LOG("Could not set the volume."); + return CUBEB_ERROR; + } + } + + /* If we have both input and output, we resample to + * the highest sample rate available. */ + int32_t target_sample_rate; + if (has_input(stm) && has_output(stm)) { + XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate); + target_sample_rate = stm->input_stream_params.rate; + } else if (has_input(stm)) { + target_sample_rate = stm->input_stream_params.rate; + } else { + XASSERT(has_output(stm)); + target_sample_rate = stm->output_stream_params.rate; + } + + LOG("Target sample rate: %d", target_sample_rate); + + /* If we are playing/capturing a mono stream, we only resample one channel, + and copy it over, so we are always resampling the number + of channels of the stream, not the number of channels + that WASAPI wants. */ + cubeb_stream_params input_params = stm->input_mix_params; + input_params.channels = stm->input_stream_params.channels; + cubeb_stream_params output_params = stm->output_mix_params; + output_params.channels = stm->output_stream_params.channels; + + stm->resampler.reset( + cubeb_resampler_create(stm, + has_input(stm) ? &input_params : nullptr, + has_output(stm) ? &output_params : nullptr, + target_sample_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP)); + if (!stm->resampler) { + LOG("Could not get a resampler"); + return CUBEB_ERROR; + } + + XASSERT(has_input(stm) || has_output(stm)); + + if (has_input(stm) && has_output(stm)) { + stm->refill_callback = refill_callback_duplex; + } else if (has_input(stm)) { + stm->refill_callback = refill_callback_input; + } else if (has_output(stm)) { + stm->refill_callback = refill_callback_output; + } + + return CUBEB_OK; +} + +int +wasapi_stream_init(cubeb * context, cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, cubeb_data_callback data_callback, + cubeb_state_callback state_callback, void * user_ptr) +{ + HRESULT hr; + int rv; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + XASSERT(context && stream && (input_stream_params || output_stream_params)); + + if (output_stream_params && input_stream_params && + output_stream_params->format != input_stream_params->format) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + std::unique_ptr stm(new cubeb_stream(), wasapi_stream_destroy); + + stm->context = context; + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; + if (input_stream_params) { + stm->input_stream_params = *input_stream_params; + stm->input_device = utf8_to_wstr(reinterpret_cast(input_device)); + // Make sure the layout matches the channel count. + XASSERT(stm->input_stream_params.layout == CUBEB_LAYOUT_UNDEFINED || + stm->input_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->input_stream_params.layout].channels); + } + if (output_stream_params) { + stm->output_stream_params = *output_stream_params; + stm->output_device = utf8_to_wstr(reinterpret_cast(output_device)); + // Make sure the layout matches the channel count. + XASSERT(stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels); + } + + switch (output_stream_params ? output_stream_params->format : input_stream_params->format) { + case CUBEB_SAMPLE_S16NE: + stm->bytes_per_sample = sizeof(short); + stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM; + stm->linear_input_buffer.reset(new auto_array_wrapper_impl); + break; + case CUBEB_SAMPLE_FLOAT32NE: + stm->bytes_per_sample = sizeof(float); + stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + stm->linear_input_buffer.reset(new auto_array_wrapper_impl); + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } + stm->mixer.reset(cubeb_mixer_create(output_stream_params ? output_stream_params->format : + input_stream_params->format, + CUBEB_MIXER_DIRECTION_DOWNMIX | CUBEB_MIXER_DIRECTION_UPMIX)); + + stm->latency = latency_frames; + + stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->reconfigure_event) { + LOG("Can't create the reconfigure event, error: %lx", GetLastError()); + return CUBEB_ERROR; + } + + /* Unconditionally create the two events so that the wait logic is simpler. */ + stm->refill_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->refill_event) { + LOG("Can't create the refill event, error: %lx", GetLastError()); + return CUBEB_ERROR; + } + + stm->input_available_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->input_available_event) { + LOG("Can't create the input available event , error: %lx", GetLastError()); + return CUBEB_ERROR; + } + + { + /* Locking here is not strictly necessary, because we don't have a + notification client that can reset the stream yet, but it lets us + assert that the lock is held in the function. */ + auto_lock lock(stm->stream_reset_lock); + rv = setup_wasapi_stream(stm.get()); + } + if (rv != CUBEB_OK) { + return rv; + } + + hr = register_notification_client(stm.get()); + if (FAILED(hr)) { + /* this is not fatal, we can still play audio, but we won't be able + to keep using the default audio endpoint if it changes. */ + LOG("failed to register notification client, %lx", hr); + } + + *stream = stm.release(); + + return CUBEB_OK; +} + +void close_wasapi_stream(cubeb_stream * stm) +{ + XASSERT(stm); + + stm->stream_reset_lock.assert_current_thread_owns(); + + stm->output_client = nullptr; + stm->render_client = nullptr; + + stm->input_client = nullptr; + stm->capture_client = nullptr; + + stm->audio_stream_volume = nullptr; + + stm->audio_clock = nullptr; + stm->total_frames_written += static_cast(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params))); + stm->frames_written = 0; + + stm->resampler.reset(); + + stm->mix_buffer.clear(); +} + +void wasapi_stream_destroy(cubeb_stream * stm) +{ + XASSERT(stm); + + // Only free stm->emergency_bailout if we could join the thread. + // If we could not join the thread, stm->emergency_bailout is true + // and is still alive until the thread wakes up and exits cleanly. + if (stop_and_join_render_thread(stm)) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; + } + + unregister_notification_client(stm); + + CloseHandle(stm->reconfigure_event); + CloseHandle(stm->refill_event); + CloseHandle(stm->input_available_event); + + // The variables intialized in wasapi_stream_init, + // must be destroyed in wasapi_stream_destroy. + stm->mixer.reset(); + stm->linear_input_buffer.reset(); + + { + auto_lock lock(stm->stream_reset_lock); + close_wasapi_stream(stm); + } + + delete stm; +} + +enum StreamDirection { + OUTPUT, + INPUT +}; + +int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) +{ + XASSERT((dir == OUTPUT && stm->output_client) || + (dir == INPUT && stm->input_client)); + + HRESULT hr = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { + LOG("audioclient invalidated for %s device, reconfiguring", + dir == OUTPUT ? "output" : "input"); + + BOOL ok = ResetEvent(stm->reconfigure_event); + if (!ok) { + LOG("resetting reconfig event failed for %s stream: %lx", + dir == OUTPUT ? "output" : "input", GetLastError()); + } + + close_wasapi_stream(stm); + int r = setup_wasapi_stream(stm); + if (r != CUBEB_OK) { + LOG("reconfigure failed"); + return r; + } + + HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); + if (FAILED(hr2)) { + LOG("could not start the %s stream after reconfig: %lx", + dir == OUTPUT ? "output" : "input", hr); + return CUBEB_ERROR; + } + } else if (FAILED(hr)) { + LOG("could not start the %s stream: %lx.", + dir == OUTPUT ? "output" : "input", hr); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +int wasapi_stream_start(cubeb_stream * stm) +{ + auto_lock lock(stm->stream_reset_lock); + + XASSERT(stm && !stm->thread && !stm->shutdown_event); + XASSERT(stm->output_client || stm->input_client); + + stm->emergency_bailout = new std::atomic(false); + + if (stm->output_client) { + int rv = stream_start_one_side(stm, OUTPUT); + if (rv != CUBEB_OK) { + return rv; + } + } + + if (stm->input_client) { + int rv = stream_start_one_side(stm, INPUT); + if (rv != CUBEB_OK) { + return rv; + } + } + + stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->shutdown_event) { + LOG("Can't create the shutdown event, error: %lx", GetLastError()); + return CUBEB_ERROR; + } + + stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); + if (stm->thread == NULL) { + LOG("could not create WASAPI render thread."); + return CUBEB_ERROR; + } + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + + return CUBEB_OK; +} + +int wasapi_stream_stop(cubeb_stream * stm) +{ + XASSERT(stm); + HRESULT hr; + + { + auto_lock lock(stm->stream_reset_lock); + + if (stm->output_client) { + hr = stm->output_client->Stop(); + if (FAILED(hr)) { + LOG("could not stop AudioClient (output)"); + return CUBEB_ERROR; + } + } + + if (stm->input_client) { + hr = stm->input_client->Stop(); + if (FAILED(hr)) { + LOG("could not stop AudioClient (input)"); + return CUBEB_ERROR; + } + } + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + } + + if (stop_and_join_render_thread(stm)) { + // This is null if we've given the pointer to the other thread + if (stm->emergency_bailout.load()) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; + } + } else { + // If we could not join the thread, put the stream in error. + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + XASSERT(stm && position); + auto_lock lock(stm->stream_reset_lock); + + if (!has_output(stm)) { + return CUBEB_ERROR; + } + + /* Calculate how far behind the current stream head the playback cursor is. */ + uint64_t stream_delay = static_cast(current_stream_delay(stm) * stm->output_stream_params.rate); + + /* Calculate the logical stream head in frames at the stream sample rate. */ + uint64_t max_pos = stm->total_frames_written + + static_cast(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params))); + + *position = max_pos; + if (stream_delay <= *position) { + *position -= stream_delay; + } + + if (*position < stm->prev_position) { + *position = stm->prev_position; + } + stm->prev_position = *position; + + return CUBEB_OK; +} + +int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + XASSERT(stm && latency); + + if (!has_output(stm)) { + return CUBEB_ERROR; + } + + auto_lock lock(stm->stream_reset_lock); + + /* The GetStreamLatency method only works if the + AudioClient has been initialized. */ + if (!stm->output_client) { + return CUBEB_ERROR; + } + + REFERENCE_TIME latency_hns; + HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + *latency = hns_to_frames(stm, latency_hns); + + return CUBEB_OK; +} + +int wasapi_stream_set_volume(cubeb_stream * stm, float volume) +{ + auto_lock lock(stm->stream_reset_lock); + + if (!has_output(stm)) { + return CUBEB_ERROR; + } + + if (stream_set_volume(stm, volume) != CUBEB_OK) { + return CUBEB_ERROR; + } + + stm->volume = volume; + + return CUBEB_OK; +} + +static char const * +wstr_to_utf8(LPCWSTR str) +{ + int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL); + if (size <= 0) { + return nullptr; + } + + char * ret = static_cast(malloc(size)); + ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); + return ret; +} + +static std::unique_ptr +utf8_to_wstr(char const * str) +{ + int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + if (size <= 0) { + return nullptr; + } + + std::unique_ptr ret(new wchar_t[size]); + ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size); + return std::move(ret); +} + +static com_ptr +wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev) +{ + com_ptr ret; + com_ptr devtopo; + com_ptr connector; + + if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, devtopo.receive_vpp())) && + SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) { + wchar_t * tmp = nullptr; + if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) { + com_heap_ptr filterid(tmp); + if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive()))) + ret = NULL; + } + } + + return ret; +} + +static BOOL +wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, + IMMDeviceEnumerator * enumerator) +{ + BOOL ret = FALSE; + com_ptr dev; + HRESULT hr; + + hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive()); + if (SUCCEEDED(hr)) { + wchar_t * tmp = nullptr; + if (SUCCEEDED(dev->GetId(&tmp))) { + com_heap_ptr defdevid(tmp); + ret = (wcscmp(defdevid.get(), device_id) == 0); + } + } + + return ret; +} + +static int +wasapi_create_device(cubeb_device_info * ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) +{ + com_ptr endpoint; + com_ptr devnode; + com_ptr client; + EDataFlow flow; + DWORD state = DEVICE_STATE_NOTPRESENT; + com_ptr propstore; + REFERENCE_TIME def_period, min_period; + HRESULT hr; + + struct prop_variant : public PROPVARIANT { + prop_variant() { PropVariantInit(this); } + ~prop_variant() { PropVariantClear(this); } + prop_variant(prop_variant const &) = delete; + prop_variant & operator=(prop_variant const &) = delete; + }; + + hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive())); + if (FAILED(hr)) return CUBEB_ERROR; + + hr = endpoint->GetDataFlow(&flow); + if (FAILED(hr)) return CUBEB_ERROR; + + wchar_t * tmp = nullptr; + hr = dev->GetId(&tmp); + if (FAILED(hr)) return CUBEB_ERROR; + com_heap_ptr device_id(tmp); + + hr = dev->OpenPropertyStore(STGM_READ, propstore.receive()); + if (FAILED(hr)) return CUBEB_ERROR; + + hr = dev->GetState(&state); + if (FAILED(hr)) return CUBEB_ERROR; + + XASSERT(ret); + ret->device_id = wstr_to_utf8(device_id.get()); + ret->devid = reinterpret_cast(ret->device_id); + prop_variant namevar; + hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); + if (SUCCEEDED(hr)) + ret->friendly_name = wstr_to_utf8(namevar.pwszVal); + + devnode = wasapi_get_device_node(enumerator, dev); + if (devnode) { + com_ptr ps; + hr = devnode->OpenPropertyStore(STGM_READ, ps.receive()); + if (FAILED(hr)) return CUBEB_ERROR; + + prop_variant instancevar; + hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); + if (SUCCEEDED(hr)) { + ret->group_id = wstr_to_utf8(instancevar.pwszVal); + } + } + + ret->preferred = CUBEB_DEVICE_PREF_NONE; + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) + ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); + if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) + ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE); + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) + ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + + if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT; + else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT; + switch (state) { + case DEVICE_STATE_ACTIVE: + ret->state = CUBEB_DEVICE_STATE_ENABLED; + break; + case DEVICE_STATE_UNPLUGGED: + ret->state = CUBEB_DEVICE_STATE_UNPLUGGED; + break; + default: + ret->state = CUBEB_DEVICE_STATE_DISABLED; + break; + }; + + ret->format = static_cast(CUBEB_DEVICE_FMT_F32NE | CUBEB_DEVICE_FMT_S16NE); + ret->default_format = CUBEB_DEVICE_FMT_F32NE; + prop_variant fmtvar; + hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); + if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { + if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { + const PCMWAVEFORMAT * pcm = reinterpret_cast(fmtvar.blob.pBlobData); + + ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec; + ret->max_channels = pcm->wf.nChannels; + } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { + WAVEFORMATEX* wfx = reinterpret_cast(fmtvar.blob.pBlobData); + + if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || + wfx->wFormatTag == WAVE_FORMAT_PCM) { + ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec; + ret->max_channels = wfx->nChannels; + } + } + } + + if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, client.receive_vpp())) && + SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { + ret->latency_lo = hns_to_frames(ret->default_rate, min_period); + ret->latency_hi = hns_to_frames(ret->default_rate, def_period); + } else { + ret->latency_lo = 0; + ret->latency_hi = 0; + } + + return CUBEB_OK; +} + +static int +wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * out) +{ + auto_com com; + com_ptr enumerator; + com_ptr collection; + HRESULT hr; + UINT cc, i; + EDataFlow flow; + + if (!com.ok()) + return CUBEB_ERROR; + + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(enumerator.receive())); + if (FAILED(hr)) { + LOG("Could not get device enumerator: %lx", hr); + return CUBEB_ERROR; + } + + if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender; + else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture; + else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) flow = eAll; + else return CUBEB_ERROR; + + hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, collection.receive()); + if (FAILED(hr)) { + LOG("Could not enumerate audio endpoints: %lx", hr); + return CUBEB_ERROR; + } + + hr = collection->GetCount(&cc); + if (FAILED(hr)) { + LOG("IMMDeviceCollection::GetCount() failed: %lx", hr); + return CUBEB_ERROR; + } + cubeb_device_info * devices = + (cubeb_device_info *) calloc(cc, sizeof(cubeb_device_info)); + if (!devices) { + return CUBEB_ERROR; + } + out->count = 0; + for (i = 0; i < cc; i++) { + com_ptr dev; + hr = collection->Item(i, dev.receive()); + if (FAILED(hr)) { + LOG("IMMDeviceCollection::Item(%u) failed: %lx", i-1, hr); + continue; + } + auto cur = &devices[out->count]; + if (wasapi_create_device(cur, enumerator.get(), dev.get()) == CUBEB_OK) { + out->count += 1; + } + } + + out->device = devices; + return CUBEB_OK; +} + +cubeb_ops const wasapi_ops = { + /*.init =*/ wasapi_init, + /*.get_backend_id =*/ wasapi_get_backend_id, + /*.get_max_channel_count =*/ wasapi_get_max_channel_count, + /*.get_min_latency =*/ wasapi_get_min_latency, + /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, + /*.get_preferred_channel_layout =*/ wasapi_get_preferred_channel_layout, + /*.enumerate_devices =*/ wasapi_enumerate_devices, + /*.device_collection_destroy =*/ cubeb_utils_default_device_collection_destroy, + /*.destroy =*/ wasapi_destroy, + /*.stream_init =*/ wasapi_stream_init, + /*.stream_destroy =*/ wasapi_stream_destroy, + /*.stream_start =*/ wasapi_stream_start, + /*.stream_stop =*/ wasapi_stream_stop, + /*.stream_get_position =*/ wasapi_stream_get_position, + /*.stream_get_latency =*/ wasapi_stream_get_latency, + /*.stream_set_volume =*/ wasapi_stream_set_volume, + /*.stream_set_panning =*/ NULL, + /*.stream_get_current_device =*/ NULL, + /*.stream_device_destroy =*/ NULL, + /*.stream_register_device_changed_callback =*/ NULL, + /*.register_device_collection_changed =*/ NULL +}; +} // namespace anonymous diff --git a/Externals/cubeb/src/cubeb_winmm.c b/Externals/cubeb/src/cubeb_winmm.c new file mode 100644 index 0000000000..d0aab08173 --- /dev/null +++ b/Externals/cubeb/src/cubeb_winmm.c @@ -0,0 +1,1042 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#define __MSVCRT_VERSION__ 0x0700 +#undef WINVER +#define WINVER 0x0501 +#undef WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_utils.h" + +/* This is missing from the MinGW headers. Use a safe fallback. */ +#if !defined(MEMORY_ALLOCATION_ALIGNMENT) +#define MEMORY_ALLOCATION_ALIGNMENT 16 +#endif + +/**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/ +#ifndef WAVE_FORMAT_48M08 +#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */ +#endif +#ifndef WAVE_FORMAT_48M16 +#define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */ +#endif +#ifndef WAVE_FORMAT_48S08 +#define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */ +#endif +#ifndef WAVE_FORMAT_48S16 +#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ +#endif +#ifndef WAVE_FORMAT_96M08 +#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ +#endif +#ifndef WAVE_FORMAT_96M16 +#define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */ +#endif +#ifndef WAVE_FORMAT_96S08 +#define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */ +#endif +#ifndef WAVE_FORMAT_96S16 +#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ +#endif + +/**Taken from winbase.h, also not in MinGW.*/ +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only +#endif + +#ifndef DRVM_MAPPER +#define DRVM_MAPPER (0x2000) +#endif +#ifndef DRVM_MAPPER_PREFERRED_GET +#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21) +#endif +#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET +#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23) +#endif + +#define CUBEB_STREAM_MAX 32 +#define NBUFS 4 + +const GUID KSDATAFORMAT_SUBTYPE_PCM = +{ 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; +const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = +{ 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; + +struct cubeb_stream_item { + SLIST_ENTRY head; + cubeb_stream * stream; +}; + +static struct cubeb_ops const winmm_ops; + +struct cubeb { + struct cubeb_ops const * ops; + HANDLE event; + HANDLE thread; + int shutdown; + PSLIST_HEADER work; + CRITICAL_SECTION lock; + unsigned int active_streams; + unsigned int minimum_latency_ms; +}; + +struct cubeb_stream { + cubeb * context; + cubeb_stream_params params; + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + WAVEHDR buffers[NBUFS]; + size_t buffer_size; + int next_buffer; + int free_buffers; + int shutdown; + int draining; + HANDLE event; + HWAVEOUT waveout; + CRITICAL_SECTION lock; + uint64_t written; + float soft_volume; +}; + +static size_t +bytes_per_frame(cubeb_stream_params params) +{ + size_t bytes; + + switch (params.format) { + case CUBEB_SAMPLE_S16LE: + bytes = sizeof(signed short); + break; + case CUBEB_SAMPLE_FLOAT32LE: + bytes = sizeof(float); + break; + default: + XASSERT(0); + } + + return bytes * params.channels; +} + +static WAVEHDR * +winmm_get_next_buffer(cubeb_stream * stm) +{ + WAVEHDR * hdr = NULL; + + XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); + hdr = &stm->buffers[stm->next_buffer]; + XASSERT(hdr->dwFlags & WHDR_PREPARED || + (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE))); + stm->next_buffer = (stm->next_buffer + 1) % NBUFS; + stm->free_buffers -= 1; + + return hdr; +} + +static void +winmm_refill_stream(cubeb_stream * stm) +{ + WAVEHDR * hdr; + long got; + long wanted; + MMRESULT r; + + EnterCriticalSection(&stm->lock); + stm->free_buffers += 1; + XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); + + if (stm->draining) { + LeaveCriticalSection(&stm->lock); + if (stm->free_buffers == NBUFS) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + } + SetEvent(stm->event); + return; + } + + if (stm->shutdown) { + LeaveCriticalSection(&stm->lock); + SetEvent(stm->event); + return; + } + + hdr = winmm_get_next_buffer(stm); + + wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params); + + /* It is assumed that the caller is holding this lock. It must be dropped + during the callback to avoid deadlocks. */ + LeaveCriticalSection(&stm->lock); + got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted); + EnterCriticalSection(&stm->lock); + if (got < 0) { + LeaveCriticalSection(&stm->lock); + /* XXX handle this case */ + XASSERT(0); + return; + } else if (got < wanted) { + stm->draining = 1; + } + stm->written += got; + + XASSERT(hdr->dwFlags & WHDR_PREPARED); + + hdr->dwBufferLength = got * bytes_per_frame(stm->params); + XASSERT(hdr->dwBufferLength <= stm->buffer_size); + + if (stm->soft_volume != -1.0) { + if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { + float * b = (float *) hdr->lpData; + uint32_t i; + for (i = 0; i < got * stm->params.channels; i++) { + b[i] *= stm->soft_volume; + } + } else { + short * b = (short *) hdr->lpData; + uint32_t i; + for (i = 0; i < got * stm->params.channels; i++) { + b[i] = (short) (b[i] * stm->soft_volume); + } + } + } + + r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr)); + if (r != MMSYSERR_NOERROR) { + LeaveCriticalSection(&stm->lock); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return; + } + + LeaveCriticalSection(&stm->lock); +} + +static unsigned __stdcall +winmm_buffer_thread(void * user_ptr) +{ + cubeb * ctx = (cubeb *) user_ptr; + XASSERT(ctx); + + for (;;) { + DWORD r; + PSLIST_ENTRY item; + + r = WaitForSingleObject(ctx->event, INFINITE); + XASSERT(r == WAIT_OBJECT_0); + + /* Process work items in batches so that a single stream can't + starve the others by continuously adding new work to the top of + the work item stack. */ + item = InterlockedFlushSList(ctx->work); + while (item != NULL) { + PSLIST_ENTRY tmp = item; + winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream); + item = item->Next; + _aligned_free(tmp); + } + + if (ctx->shutdown) { + break; + } + } + + return 0; +} + +static void CALLBACK +winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2) +{ + cubeb_stream * stm = (cubeb_stream *) user_ptr; + struct cubeb_stream_item * item; + + if (msg != WOM_DONE) { + return; + } + + item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT); + XASSERT(item); + item->stream = stm; + InterlockedPushEntrySList(stm->context->work, &item->head); + + SetEvent(stm->context->event); +} + +static unsigned int +calculate_minimum_latency(void) +{ + OSVERSIONINFOEX osvi; + DWORDLONG mask; + + /* Running under Terminal Services results in underruns with low latency. */ + if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) { + return 500; + } + + /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */ + memset(&osvi, 0, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + osvi.dwMajorVersion = 6; + osvi.dwMinorVersion = 0; + + mask = 0; + VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL); + VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL); + + if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) { + return 200; + } + + return 100; +} + +static void winmm_destroy(cubeb * ctx); + +/*static*/ int +winmm_init(cubeb ** context, char const * context_name) +{ + cubeb * ctx; + + XASSERT(context); + *context = NULL; + + /* Don't initialize a context if there are no devices available. */ + if (waveOutGetNumDevs() == 0) { + return CUBEB_ERROR; + } + + ctx = calloc(1, sizeof(*ctx)); + XASSERT(ctx); + + ctx->ops = &winmm_ops; + + ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT); + XASSERT(ctx->work); + InitializeSListHead(ctx->work); + + ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!ctx->event) { + winmm_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); + if (!ctx->thread) { + winmm_destroy(ctx); + return CUBEB_ERROR; + } + + SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL); + + InitializeCriticalSection(&ctx->lock); + ctx->active_streams = 0; + + ctx->minimum_latency_ms = calculate_minimum_latency(); + + *context = ctx; + + return CUBEB_OK; +} + +static char const * +winmm_get_backend_id(cubeb * ctx) +{ + return "winmm"; +} + +static void +winmm_destroy(cubeb * ctx) +{ + DWORD r; + + XASSERT(ctx->active_streams == 0); + XASSERT(!InterlockedPopEntrySList(ctx->work)); + + DeleteCriticalSection(&ctx->lock); + + if (ctx->thread) { + ctx->shutdown = 1; + SetEvent(ctx->event); + r = WaitForSingleObject(ctx->thread, INFINITE); + XASSERT(r == WAIT_OBJECT_0); + CloseHandle(ctx->thread); + } + + if (ctx->event) { + CloseHandle(ctx->event); + } + + _aligned_free(ctx->work); + + free(ctx); +} + +static void winmm_stream_destroy(cubeb_stream * stm); + +static int +winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) +{ + MMRESULT r; + WAVEFORMATEXTENSIBLE wfx; + cubeb_stream * stm; + int i; + size_t bufsz; + + XASSERT(context); + XASSERT(stream); + + if (input_stream_params) { + /* Capture support not yet implemented. */ + return CUBEB_ERROR_NOT_SUPPORTED; + } + + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + *stream = NULL; + + memset(&wfx, 0, sizeof(wfx)); + if (output_stream_params->channels > 2) { + wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format); + } else { + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { + wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + } + wfx.Format.cbSize = 0; + } + wfx.Format.nChannels = output_stream_params->channels; + wfx.Format.nSamplesPerSec = output_stream_params->rate; + + /* XXX fix channel mappings */ + wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + + switch (output_stream_params->format) { + case CUBEB_SAMPLE_S16LE: + wfx.Format.wBitsPerSample = 16; + wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case CUBEB_SAMPLE_FLOAT32LE: + wfx.Format.wBitsPerSample = 32; + wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } + + wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; + wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; + wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; + + EnterCriticalSection(&context->lock); + /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when + many streams are active at once, a subset of them will not consume (via + playback) or release (via waveOutReset) their buffers. */ + if (context->active_streams >= CUBEB_STREAM_MAX) { + LeaveCriticalSection(&context->lock); + return CUBEB_ERROR; + } + context->active_streams += 1; + LeaveCriticalSection(&context->lock); + + stm = calloc(1, sizeof(*stm)); + XASSERT(stm); + + stm->context = context; + + stm->params = *output_stream_params; + + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; + stm->written = 0; + + uint32_t latency_ms = latency_frames * 1000 / output_stream_params->rate; + + if (latency_ms < context->minimum_latency_ms) { + latency_ms = context->minimum_latency_ms; + } + + bufsz = (size_t) (stm->params.rate / 1000.0 * latency_ms * bytes_per_frame(stm->params) / NBUFS); + if (bufsz % bytes_per_frame(stm->params) != 0) { + bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params)); + } + XASSERT(bufsz % bytes_per_frame(stm->params) == 0); + + stm->buffer_size = bufsz; + + InitializeCriticalSection(&stm->lock); + + stm->event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!stm->event) { + winmm_stream_destroy(stm); + return CUBEB_ERROR; + } + + stm->soft_volume = -1.0; + + /* winmm_buffer_callback will be called during waveOutOpen, so all + other initialization must be complete before calling it. */ + r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format, + (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm, + CALLBACK_FUNCTION); + if (r != MMSYSERR_NOERROR) { + winmm_stream_destroy(stm); + return CUBEB_ERROR; + } + + r = waveOutPause(stm->waveout); + if (r != MMSYSERR_NOERROR) { + winmm_stream_destroy(stm); + return CUBEB_ERROR; + } + + for (i = 0; i < NBUFS; ++i) { + WAVEHDR * hdr = &stm->buffers[i]; + + hdr->lpData = calloc(1, bufsz); + XASSERT(hdr->lpData); + hdr->dwBufferLength = bufsz; + hdr->dwFlags = 0; + + r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr)); + if (r != MMSYSERR_NOERROR) { + winmm_stream_destroy(stm); + return CUBEB_ERROR; + } + + winmm_refill_stream(stm); + } + + *stream = stm; + + return CUBEB_OK; +} + +static void +winmm_stream_destroy(cubeb_stream * stm) +{ + int i; + + if (stm->waveout) { + MMTIME time; + MMRESULT r; + int device_valid; + int enqueued; + + EnterCriticalSection(&stm->lock); + stm->shutdown = 1; + + waveOutReset(stm->waveout); + + /* Don't need this value, we just want the result to detect invalid + handle/no device errors than waveOutReset doesn't seem to report. */ + time.wType = TIME_SAMPLES; + r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); + device_valid = !(r == MMSYSERR_INVALHANDLE || r == MMSYSERR_NODRIVER); + + enqueued = NBUFS - stm->free_buffers; + LeaveCriticalSection(&stm->lock); + + /* Wait for all blocks to complete. */ + while (device_valid && enqueued > 0) { + DWORD rv = WaitForSingleObject(stm->event, INFINITE); + XASSERT(rv == WAIT_OBJECT_0); + + EnterCriticalSection(&stm->lock); + enqueued = NBUFS - stm->free_buffers; + LeaveCriticalSection(&stm->lock); + } + + EnterCriticalSection(&stm->lock); + + for (i = 0; i < NBUFS; ++i) { + if (stm->buffers[i].dwFlags & WHDR_PREPARED) { + waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i])); + } + } + + waveOutClose(stm->waveout); + + LeaveCriticalSection(&stm->lock); + } + + if (stm->event) { + CloseHandle(stm->event); + } + + DeleteCriticalSection(&stm->lock); + + for (i = 0; i < NBUFS; ++i) { + free(stm->buffers[i].lpData); + } + + EnterCriticalSection(&stm->context->lock); + XASSERT(stm->context->active_streams >= 1); + stm->context->active_streams -= 1; + LeaveCriticalSection(&stm->context->lock); + + free(stm); +} + +static int +winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + XASSERT(ctx && max_channels); + + /* We don't support more than two channels in this backend. */ + *max_channels = 2; + + return CUBEB_OK; +} + +static int +winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency) +{ + // 100ms minimum, if we are not in a bizarre configuration. + *latency = ctx->minimum_latency_ms * params.rate / 1000; + + return CUBEB_OK; +} + +static int +winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + WAVEOUTCAPS woc; + MMRESULT r; + + r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS)); + if (r != MMSYSERR_NOERROR) { + return CUBEB_ERROR; + } + + /* Check if we support 48kHz, but not 44.1kHz. */ + if (!(woc.dwFormats & WAVE_FORMAT_4S16) && + woc.dwFormats & WAVE_FORMAT_48S16) { + *rate = 48000; + return CUBEB_OK; + } + /* Prefer 44.1kHz between 44.1kHz and 48kHz. */ + *rate = 44100; + + return CUBEB_OK; +} + +static int +winmm_stream_start(cubeb_stream * stm) +{ + MMRESULT r; + + EnterCriticalSection(&stm->lock); + r = waveOutRestart(stm->waveout); + LeaveCriticalSection(&stm->lock); + + if (r != MMSYSERR_NOERROR) { + return CUBEB_ERROR; + } + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + + return CUBEB_OK; +} + +static int +winmm_stream_stop(cubeb_stream * stm) +{ + MMRESULT r; + + EnterCriticalSection(&stm->lock); + r = waveOutPause(stm->waveout); + LeaveCriticalSection(&stm->lock); + + if (r != MMSYSERR_NOERROR) { + return CUBEB_ERROR; + } + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + + return CUBEB_OK; +} + +static int +winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + MMRESULT r; + MMTIME time; + + EnterCriticalSection(&stm->lock); + time.wType = TIME_SAMPLES; + r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); + LeaveCriticalSection(&stm->lock); + + if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { + return CUBEB_ERROR; + } + + *position = time.u.sample; + + return CUBEB_OK; +} + +static int +winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + MMRESULT r; + MMTIME time; + uint64_t written; + + EnterCriticalSection(&stm->lock); + time.wType = TIME_SAMPLES; + r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); + written = stm->written; + LeaveCriticalSection(&stm->lock); + + if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { + return CUBEB_ERROR; + } + + XASSERT(written - time.u.sample <= UINT32_MAX); + *latency = (uint32_t) (written - time.u.sample); + + return CUBEB_OK; +} + +static int +winmm_stream_set_volume(cubeb_stream * stm, float volume) +{ + EnterCriticalSection(&stm->lock); + stm->soft_volume = volume; + LeaveCriticalSection(&stm->lock); + return CUBEB_OK; +} + +#define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16) +#define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16) +#define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16) +#define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16) +#define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16) +static void +winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats) +{ + if (formats & MM_11025HZ_MASK) { + info->min_rate = 11025; + info->default_rate = 11025; + info->max_rate = 11025; + } + if (formats & MM_22050HZ_MASK) { + if (info->min_rate == 0) info->min_rate = 22050; + info->max_rate = 22050; + info->default_rate = 22050; + } + if (formats & MM_44100HZ_MASK) { + if (info->min_rate == 0) info->min_rate = 44100; + info->max_rate = 44100; + info->default_rate = 44100; + } + if (formats & MM_48000HZ_MASK) { + if (info->min_rate == 0) info->min_rate = 48000; + info->max_rate = 48000; + info->default_rate = 48000; + } + if (formats & MM_96000HZ_MASK) { + if (info->min_rate == 0) { + info->min_rate = 96000; + info->default_rate = 96000; + } + info->max_rate = 96000; + } +} + +#define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \ + WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16) +static int +winmm_query_supported_formats(UINT devid, DWORD formats, + cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt) +{ + WAVEFORMATEXTENSIBLE wfx; + + if (formats & MM_S16_MASK) + *deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE; + else + *deffmt = *supfmt = 0; + + ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE)); + wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx.Format.nChannels = 2; + wfx.Format.nSamplesPerSec = 44100; + wfx.Format.wBitsPerSample = 32; + wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; + wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; + wfx.Format.cbSize = 22; + wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; + wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR) + *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE); + + return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR; +} + +static char * +guid_to_cstr(LPGUID guid) +{ + char * ret = malloc(40); + if (!ret) { + return NULL; + } + _snprintf_s(ret, 40, _TRUNCATE, + "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); + return ret; +} + +static cubeb_device_pref +winmm_query_preferred_out_device(UINT devid) +{ + DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; + cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; + + if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && + devid == mmpref) + ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; + + if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, + (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && + devid == compref) + ret |= CUBEB_DEVICE_PREF_VOICE; + + return ret; +} + +static char * +device_id_idx(UINT devid) +{ + char * ret = malloc(16); + if (!ret) { + return NULL; + } + _snprintf_s(ret, 16, _TRUNCATE, "%u", devid); + return ret; +} + +static void +winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, UINT devid) +{ + XASSERT(ret); + ret->devid = (cubeb_devid) devid; + ret->device_id = device_id_idx(devid); + ret->friendly_name = _strdup(caps->szPname); + ret->group_id = guid_to_cstr(&caps->ProductGuid); + ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid); + + ret->type = CUBEB_DEVICE_TYPE_OUTPUT; + ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret->preferred = winmm_query_preferred_out_device(devid); + + ret->max_channels = caps->wChannels; + winmm_calculate_device_rate(ret, caps->dwFormats); + winmm_query_supported_formats(devid, caps->dwFormats, + &ret->format, &ret->default_format); + + /* Hardcoded latency estimates... */ + ret->latency_lo = 100 * ret->default_rate / 1000; + ret->latency_hi = 200 * ret->default_rate / 1000; +} + +static void +winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, UINT devid) +{ + XASSERT(ret); + ret->devid = (cubeb_devid) devid; + ret->device_id = device_id_idx(devid); + ret->friendly_name = _strdup(caps->szPname); + ret->group_id = NULL; + ret->vendor_name = NULL; + + ret->type = CUBEB_DEVICE_TYPE_OUTPUT; + ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret->preferred = winmm_query_preferred_out_device(devid); + + ret->max_channels = caps->wChannels; + winmm_calculate_device_rate(ret, caps->dwFormats); + winmm_query_supported_formats(devid, caps->dwFormats, + &ret->format, &ret->default_format); + + /* Hardcoded latency estimates... */ + ret->latency_lo = 100 * ret->default_rate / 1000; + ret->latency_hi = 200 * ret->default_rate / 1000; +} + +static cubeb_device_pref +winmm_query_preferred_in_device(UINT devid) +{ + DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; + cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; + + if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && + devid == mmpref) + ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; + + if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, + (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && + devid == compref) + ret |= CUBEB_DEVICE_PREF_VOICE; + + return ret; +} + +static void +winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, UINT devid) +{ + XASSERT(ret); + ret->devid = (cubeb_devid) devid; + ret->device_id = device_id_idx(devid); + ret->friendly_name = _strdup(caps->szPname); + ret->group_id = guid_to_cstr(&caps->ProductGuid); + ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid); + + ret->type = CUBEB_DEVICE_TYPE_INPUT; + ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret->preferred = winmm_query_preferred_in_device(devid); + + ret->max_channels = caps->wChannels; + winmm_calculate_device_rate(ret, caps->dwFormats); + winmm_query_supported_formats(devid, caps->dwFormats, + &ret->format, &ret->default_format); + + /* Hardcoded latency estimates... */ + ret->latency_lo = 100 * ret->default_rate / 1000; + ret->latency_hi = 200 * ret->default_rate / 1000; +} + +static void +winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UINT devid) +{ + XASSERT(ret); + ret->devid = (cubeb_devid) devid; + ret->device_id = device_id_idx(devid); + ret->friendly_name = _strdup(caps->szPname); + ret->group_id = NULL; + ret->vendor_name = NULL; + + ret->type = CUBEB_DEVICE_TYPE_INPUT; + ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret->preferred = winmm_query_preferred_in_device(devid); + + ret->max_channels = caps->wChannels; + winmm_calculate_device_rate(ret, caps->dwFormats); + winmm_query_supported_formats(devid, caps->dwFormats, + &ret->format, &ret->default_format); + + /* Hardcoded latency estimates... */ + ret->latency_lo = 100 * ret->default_rate / 1000; + ret->latency_hi = 200 * ret->default_rate / 1000; +} + +static int +winmm_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + UINT i, incount, outcount, total; + cubeb_device_info * devices; + cubeb_device_info * dev; + + outcount = waveOutGetNumDevs(); + incount = waveInGetNumDevs(); + total = outcount + incount; + + devices = calloc(total, sizeof(cubeb_device_info)); + collection->count = 0; + + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + WAVEOUTCAPSA woc; + WAVEOUTCAPS2A woc2; + + ZeroMemory(&woc, sizeof(woc)); + ZeroMemory(&woc2, sizeof(woc2)); + + for (i = 0; i < outcount; i++) { + dev = &devices[collection->count]; + if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR) { + winmm_create_device_from_outcaps2(dev, &woc2, i); + collection->count += 1; + } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) { + winmm_create_device_from_outcaps(dev, &woc, i); + collection->count += 1; + } + } + } + + if (type & CUBEB_DEVICE_TYPE_INPUT) { + WAVEINCAPSA wic; + WAVEINCAPS2A wic2; + + ZeroMemory(&wic, sizeof(wic)); + ZeroMemory(&wic2, sizeof(wic2)); + + for (i = 0; i < incount; i++) { + dev = &devices[collection->count]; + if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR) { + winmm_create_device_from_incaps2(dev, &wic2, i); + collection->count += 1; + } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) { + winmm_create_device_from_incaps(dev, &wic, i); + collection->count += 1; + } + } + } + + collection->device = devices; + + return CUBEB_OK; +} + +static struct cubeb_ops const winmm_ops = { + /*.init =*/ winmm_init, + /*.get_backend_id =*/ winmm_get_backend_id, + /*.get_max_channel_count=*/ winmm_get_max_channel_count, + /*.get_min_latency=*/ winmm_get_min_latency, + /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, + /*.get_preferred_channel_layout =*/ NULL, + /*.enumerate_devices =*/ winmm_enumerate_devices, + /*.device_collection_destroy =*/ cubeb_utils_default_device_collection_destroy, + /*.destroy =*/ winmm_destroy, + /*.stream_init =*/ winmm_stream_init, + /*.stream_destroy =*/ winmm_stream_destroy, + /*.stream_start =*/ winmm_stream_start, + /*.stream_stop =*/ winmm_stream_stop, + /*.stream_get_position =*/ winmm_stream_get_position, + /*.stream_get_latency = */ winmm_stream_get_latency, + /*.stream_set_volume =*/ winmm_stream_set_volume, + /*.stream_set_panning =*/ NULL, + /*.stream_get_current_device =*/ NULL, + /*.stream_device_destroy =*/ NULL, + /*.stream_register_device_changed_callback=*/ NULL, + /*.register_device_collection_changed =*/ NULL +}; diff --git a/Externals/cubeb/src/speex/arch.h b/Externals/cubeb/src/speex/arch.h new file mode 100644 index 0000000000..73a45a069f --- /dev/null +++ b/Externals/cubeb/src/speex/arch.h @@ -0,0 +1,235 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file arch.h + @brief Various architecture definitions Speex +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef ARCH_H +#define ARCH_H + +/* A couple test to catch stupid option combinations */ +#ifdef FIXED_POINT + +#ifdef FLOATING_POINT +#error You cannot compile as floating point and fixed point at the same time +#endif +#ifdef _USE_SSE +#error SSE is only for floating-point +#endif +#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM)) +#error Make up your mind. What CPU do you have? +#endif +#ifdef VORBIS_PSYCHO +#error Vorbis-psy model currently not implemented in fixed-point +#endif + +#else + +#ifndef FLOATING_POINT +#error You now need to define either FIXED_POINT or FLOATING_POINT +#endif +#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) +#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? +#endif +#ifdef FIXED_POINT_DEBUG +#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" +#endif + + +#endif + +#ifndef OUTSIDE_SPEEX +#include "speex/speexdsp_types.h" +#endif + +#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ +#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ +#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */ +#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ + +#ifdef FIXED_POINT + +typedef spx_int16_t spx_word16_t; +typedef spx_int32_t spx_word32_t; +typedef spx_word32_t spx_mem_t; +typedef spx_word16_t spx_coef_t; +typedef spx_word16_t spx_lsp_t; +typedef spx_word32_t spx_sig_t; + +#define Q15ONE 32767 + +#define LPC_SCALING 8192 +#define SIG_SCALING 16384 +#define LSP_SCALING 8192. +#define GAMMA_SCALING 32768. +#define GAIN_SCALING 64 +#define GAIN_SCALING_1 0.015625 + +#define LPC_SHIFT 13 +#define LSP_SHIFT 13 +#define SIG_SHIFT 14 +#define GAIN_SHIFT 6 + +#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x))) + +#define VERY_SMALL 0 +#define VERY_LARGE32 ((spx_word32_t)2147483647) +#define VERY_LARGE16 ((spx_word16_t)32767) +#define Q15_ONE ((spx_word16_t)32767) + + +#ifdef FIXED_DEBUG +#include "fixed_debug.h" +#else + +#include "fixed_generic.h" + +#ifdef ARM5E_ASM +#include "fixed_arm5e.h" +#elif defined (ARM4_ASM) +#include "fixed_arm4.h" +#elif defined (BFIN_ASM) +#include "fixed_bfin.h" +#endif + +#endif + + +#else + +typedef float spx_mem_t; +typedef float spx_coef_t; +typedef float spx_lsp_t; +typedef float spx_sig_t; +typedef float spx_word16_t; +typedef float spx_word32_t; + +#define Q15ONE 1.0f +#define LPC_SCALING 1.f +#define SIG_SCALING 1.f +#define LSP_SCALING 1.f +#define GAMMA_SCALING 1.f +#define GAIN_SCALING 1.f +#define GAIN_SCALING_1 1.f + + +#define VERY_SMALL 1e-15f +#define VERY_LARGE32 1e15f +#define VERY_LARGE16 1e15f +#define Q15_ONE ((spx_word16_t)1.f) + +#define QCONST16(x,bits) (x) +#define QCONST32(x,bits) (x) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) (x) +#define EXTEND32(x) (x) +#define SHR16(a,shift) (a) +#define SHL16(a,shift) (a) +#define SHR32(a,shift) (a) +#define SHL32(a,shift) (a) +#define PSHR16(a,shift) (a) +#define PSHR32(a,shift) (a) +#define VSHR32(a,shift) (a) +#define SATURATE16(x,a) (x) +#define SATURATE32(x,a) (x) +#define SATURATE32PSHR(x,shift,a) (x) + +#define PSHR(a,shift) (a) +#define SHR(a,shift) (a) +#define SHL(a,shift) (a) +#define SATURATE(x,a) (x) + +#define ADD16(a,b) ((a)+(b)) +#define SUB16(a,b) ((a)-(b)) +#define ADD32(a,b) ((a)+(b)) +#define SUB32(a,b) ((a)-(b)) +#define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) +#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) + +#define MULT16_32_Q11(a,b) ((a)*(b)) +#define MULT16_32_Q13(a,b) ((a)*(b)) +#define MULT16_32_Q14(a,b) ((a)*(b)) +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_32_P15(a,b) ((a)*(b)) + +#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) + +#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_P13(c,a,b) ((c)+(a)*(b)) +#define MULT16_16_Q11_32(a,b) ((a)*(b)) +#define MULT16_16_Q13(a,b) ((a)*(b)) +#define MULT16_16_Q14(a,b) ((a)*(b)) +#define MULT16_16_Q15(a,b) ((a)*(b)) +#define MULT16_16_P15(a,b) ((a)*(b)) +#define MULT16_16_P13(a,b) ((a)*(b)) +#define MULT16_16_P14(a,b) ((a)*(b)) + +#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) +#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) + +#define WORD2INT(x) ((x) < -32767.5f ? -32768 : \ + ((x) > 32766.5f ? 32767 : (spx_int16_t)floor(.5 + (x)))) +#endif + + +#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + +/* 2 on TI C5x DSP */ +#define BYTES_PER_CHAR 2 +#define BITS_PER_CHAR 16 +#define LOG2_BITS_PER_CHAR 4 + +#else + +#define BYTES_PER_CHAR 1 +#define BITS_PER_CHAR 8 +#define LOG2_BITS_PER_CHAR 3 + +#endif + + + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + + +#endif diff --git a/Externals/cubeb/src/speex/fixed_generic.h b/Externals/cubeb/src/speex/fixed_generic.h new file mode 100644 index 0000000000..12d27aac55 --- /dev/null +++ b/Externals/cubeb/src/speex/fixed_generic.h @@ -0,0 +1,110 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file fixed_generic.h + @brief Generic fixed-point operations +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_GENERIC_H +#define FIXED_GENERIC_H + +#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) +#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) ((spx_word16_t)(x)) +#define EXTEND32(x) ((spx_word32_t)(x)) +#define SHR16(a,shift) ((a) >> (shift)) +#define SHL16(a,shift) ((a) << (shift)) +#define SHR32(a,shift) ((a) >> (shift)) +#define SHL32(a,shift) ((a) << (shift)) +#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift)) +#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) +#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) +#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +#define SATURATE32PSHR(x,shift,a) (((x)>=(SHL32(a,shift))) ? (a) : \ + (x)<=-(SHL32(a,shift)) ? -(a) : \ + (PSHR32(x, shift))) + +#define SHR(a,shift) ((a) >> (shift)) +#define SHL(a,shift) ((spx_word32_t)(a) << (shift)) +#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + + +#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b))) +#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b)) +#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b)) +#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b)) + + +/* result fits in 16 bits */ +#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b)))) + +/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */ +#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b))) + +#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) +#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12)) +#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13)) +#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14)) + +#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)) +#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))) + +#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + + +#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) +#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13))) +#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13))) + +#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) +#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) +#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) + +#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) +#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) +#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) + +#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15)) + +#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b)))) +#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b)))) +#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b))) +#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b))) + +#endif diff --git a/Externals/cubeb/src/speex/resample.c b/Externals/cubeb/src/speex/resample.c new file mode 100644 index 0000000000..aadf68517e --- /dev/null +++ b/Externals/cubeb/src/speex/resample.c @@ -0,0 +1,1237 @@ +/* Copyright (C) 2007-2008 Jean-Marc Valin + Copyright (C) 2008 Thorvald Natvig + + File: resample.c + Arbitrary resampling code + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + The design goals of this code are: + - Very fast algorithm + - SIMD-friendly algorithm + - Low memory requirement + - Good *perceptual* quality (and not best SNR) + + Warning: This resampler is relatively new. Although I think I got rid of + all the major bugs and I don't expect the API to change anymore, there + may be something I've missed. So use with caution. + + This algorithm is based on this original resampling algorithm: + Smith, Julius O. Digital Audio Resampling Home Page + Center for Computer Research in Music and Acoustics (CCRMA), + Stanford University, 2007. + Web published at http://ccrma.stanford.edu/~jos/resample/. + + There is one main difference, though. This resampler uses cubic + interpolation instead of linear interpolation in the above paper. This + makes the table much smaller and makes it possible to compute that table + on a per-stream basis. In turn, being able to tweak the table for each + stream makes it possible to both reduce complexity on simple ratios + (e.g. 2/3), and get rid of the rounding operations in the inner loop. + The latter both reduces CPU time and makes the algorithm more SIMD-friendly. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OUTSIDE_SPEEX +#include +static void *speex_alloc (int size) {return calloc(size,1);} +static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);} +static void speex_free (void *ptr) {free(ptr);} +#include "speex_resampler.h" +#include "arch.h" +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_resampler.h" +#include "arch.h" +#include "os_support.h" +#endif /* OUTSIDE_SPEEX */ + +#include "stack_alloc.h" +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define IMAX(a,b) ((a) > (b) ? (a) : (b)) +#define IMIN(a,b) ((a) < (b) ? (a) : (b)) + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef UINT32_MAX +#define UINT32_MAX 4294967296U +#endif + +#ifdef _USE_SSE +#include "resample_sse.h" +#endif + +#ifdef _USE_NEON +#include "resample_neon.h" +#endif + +/* Numer of elements to allocate on the stack */ +#ifdef VAR_ARRAYS +#define FIXED_STACK_ALLOC 8192 +#else +#define FIXED_STACK_ALLOC 1024 +#endif + +typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *); + +struct SpeexResamplerState_ { + spx_uint32_t in_rate; + spx_uint32_t out_rate; + spx_uint32_t num_rate; + spx_uint32_t den_rate; + + int quality; + spx_uint32_t nb_channels; + spx_uint32_t filt_len; + spx_uint32_t mem_alloc_size; + spx_uint32_t buffer_size; + int int_advance; + int frac_advance; + float cutoff; + spx_uint32_t oversample; + int initialised; + int started; + + /* These are per-channel */ + spx_int32_t *last_sample; + spx_uint32_t *samp_frac_num; + spx_uint32_t *magic_samples; + + spx_word16_t *mem; + spx_word16_t *sinc_table; + spx_uint32_t sinc_table_length; + resampler_basic_func resampler_ptr; + + int in_stride; + int out_stride; +} ; + +static const double kaiser12_table[68] = { + 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076, + 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014, + 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601, + 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014, + 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490, + 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546, + 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178, + 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947, + 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058, + 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438, + 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734, + 0.00001000, 0.00000000}; +/* +static const double kaiser12_table[36] = { + 0.99440475, 1.00000000, 0.99440475, 0.97779076, 0.95066529, 0.91384741, + 0.86843014, 0.81573067, 0.75723148, 0.69451601, 0.62920216, 0.56287762, + 0.49704014, 0.43304576, 0.37206735, 0.31506490, 0.26276832, 0.21567274, + 0.17404546, 0.13794294, 0.10723616, 0.08164178, 0.06075685, 0.04409466, + 0.03111947, 0.02127838, 0.01402878, 0.00886058, 0.00531256, 0.00298291, + 0.00153438, 0.00069463, 0.00025272, 0.0000527734, 0.00000500, 0.00000000}; +*/ +static const double kaiser10_table[36] = { + 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446, + 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347, + 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962, + 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451, + 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739, + 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000}; + +static const double kaiser8_table[36] = { + 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, + 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, + 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, + 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, + 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, + 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000}; + +static const double kaiser6_table[36] = { + 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, + 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, + 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, + 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, + 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, + 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000}; + +struct FuncDef { + const double *table; + int oversample; +}; + +static const struct FuncDef _KAISER12 = {kaiser12_table, 64}; +#define KAISER12 (&_KAISER12) +/*static struct FuncDef _KAISER12 = {kaiser12_table, 32}; +#define KAISER12 (&_KAISER12)*/ +static const struct FuncDef _KAISER10 = {kaiser10_table, 32}; +#define KAISER10 (&_KAISER10) +static const struct FuncDef _KAISER8 = {kaiser8_table, 32}; +#define KAISER8 (&_KAISER8) +static const struct FuncDef _KAISER6 = {kaiser6_table, 32}; +#define KAISER6 (&_KAISER6) + +struct QualityMapping { + int base_length; + int oversample; + float downsample_bandwidth; + float upsample_bandwidth; + const struct FuncDef *window_func; +}; + + +/* This table maps conversion quality to internal parameters. There are two + reasons that explain why the up-sampling bandwidth is larger than the + down-sampling bandwidth: + 1) When up-sampling, we can assume that the spectrum is already attenuated + close to the Nyquist rate (from an A/D or a previous resampling filter) + 2) Any aliasing that occurs very close to the Nyquist rate will be masked + by the sinusoids/noise just below the Nyquist rate (guaranteed only for + up-sampling). +*/ +static const struct QualityMapping quality_map[11] = { + { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */ + { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */ + { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ + { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ + { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ + { 80, 16, 0.922f, 0.940f, KAISER10}, /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */ + { 96, 16, 0.940f, 0.945f, KAISER10}, /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */ + {128, 16, 0.950f, 0.950f, KAISER10}, /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */ + {160, 16, 0.960f, 0.960f, KAISER10}, /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */ + {192, 32, 0.968f, 0.968f, KAISER12}, /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */ + {256, 32, 0.975f, 0.975f, KAISER12}, /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */ +}; +/*8,24,40,56,80,104,128,160,200,256,320*/ +static double compute_func(float x, const struct FuncDef *func) +{ + float y, frac; + double interp[4]; + int ind; + y = x*func->oversample; + ind = (int)floor(y); + frac = (y-ind); + /* CSE with handle the repeated powers */ + interp[3] = -0.1666666667*frac + 0.1666666667*(frac*frac*frac); + interp[2] = frac + 0.5*(frac*frac) - 0.5*(frac*frac*frac); + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[0] = -0.3333333333*frac + 0.5*(frac*frac) - 0.1666666667*(frac*frac*frac); + /* Just to make sure we don't have rounding problems */ + interp[1] = 1.f-interp[3]-interp[2]-interp[0]; + + /*sum = frac*accum[1] + (1-frac)*accum[2];*/ + return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3]; +} + +#if 0 +#include +int main(int argc, char **argv) +{ + int i; + for (i=0;i<256;i++) + { + printf ("%f\n", compute_func(i/256., KAISER12)); + } + return 0; +} +#endif + +#ifdef FIXED_POINT +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, const struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6f) + return WORD2INT(32768.*cutoff); + else if (fabs(x) > .5f*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return WORD2INT(32768.*cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func)); +} +#else +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, const struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6) + return cutoff; + else if (fabs(x) > .5*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func); +} +#endif + +#ifdef FIXED_POINT +static void cubic_coef(spx_word16_t x, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + spx_word16_t x2, x3; + x2 = MULT16_16_P15(x, x); + x3 = MULT16_16_P15(x, x2); + interp[0] = PSHR32(MULT16_16(QCONST16(-0.16667f, 15),x) + MULT16_16(QCONST16(0.16667f, 15),x3),15); + interp[1] = EXTRACT16(EXTEND32(x) + SHR32(SUB32(EXTEND32(x2),EXTEND32(x3)),1)); + interp[3] = PSHR32(MULT16_16(QCONST16(-0.33333f, 15),x) + MULT16_16(QCONST16(.5f,15),x2) - MULT16_16(QCONST16(0.16667f, 15),x3),15); + /* Just to make sure we don't have rounding problems */ + interp[2] = Q15_ONE-interp[0]-interp[1]-interp[3]; + if (interp[2]<32767) + interp[2]+=1; +} +#else +static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + interp[0] = -0.16667f*frac + 0.16667f*frac*frac*frac; + interp[1] = frac + 0.5f*frac*frac - 0.5f*frac*frac*frac; + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[3] = -0.33333f*frac + 0.5f*frac*frac - 0.16667f*frac*frac*frac; + /* Just to make sure we don't have rounding problems */ + interp[2] = 1.-interp[0]-interp[1]-interp[3]; +} +#endif + +static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const spx_word16_t *sinc_table = st->sinc_table; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + spx_word32_t sum; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *sinct = & sinc_table[samp_frac_num*N]; + const spx_word16_t *iptr = & in[last_sample]; + +#ifndef OVERRIDE_INNER_PRODUCT_SINGLE + int j; + sum = 0; + for(j=0;j= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_direct_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const spx_word16_t *sinc_table = st->sinc_table; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + double sum; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *sinct = & sinc_table[samp_frac_num*N]; + const spx_word16_t *iptr = & in[last_sample]; + +#ifndef OVERRIDE_INNER_PRODUCT_DOUBLE + int j; + double accum[4] = {0,0,0,0}; + + for(j=0;j= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + spx_word32_t sum; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *iptr = & in[last_sample]; + + const int offset = samp_frac_num*st->oversample/st->den_rate; +#ifdef FIXED_POINT + const spx_word16_t frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate); +#else + const spx_word16_t frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate; +#endif + spx_word16_t interp[4]; + + +#ifndef OVERRIDE_INTERPOLATE_PRODUCT_SINGLE + int j; + spx_word32_t accum[4] = {0,0,0,0}; + + for(j=0;jsinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + + cubic_coef(frac, interp); + sum = MULT16_32_Q15(interp[0],SHR32(accum[0], 1)) + MULT16_32_Q15(interp[1],SHR32(accum[1], 1)) + MULT16_32_Q15(interp[2],SHR32(accum[2], 1)) + MULT16_32_Q15(interp[3],SHR32(accum[3], 1)); + sum = SATURATE32PSHR(sum, 15, 32767); +#else + cubic_coef(frac, interp); + sum = interpolate_product_single(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp); +#endif + + out[out_stride * out_sample++] = sum; + last_sample += int_advance; + samp_frac_num += frac_advance; + if (samp_frac_num >= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_interpolate_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + const int N = st->filt_len; + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + spx_word32_t sum; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + const spx_word16_t *iptr = & in[last_sample]; + + const int offset = samp_frac_num*st->oversample/st->den_rate; +#ifdef FIXED_POINT + const spx_word16_t frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate); +#else + const spx_word16_t frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate; +#endif + spx_word16_t interp[4]; + + +#ifndef OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE + int j; + double accum[4] = {0,0,0,0}; + + for(j=0;jsinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + + cubic_coef(frac, interp); + sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]); +#else + cubic_coef(frac, interp); + sum = interpolate_product_double(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp); +#endif + + out[out_stride * out_sample++] = PSHR32(sum,15); + last_sample += int_advance; + samp_frac_num += frac_advance; + if (samp_frac_num >= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +/* This resampler is used to produce zero output in situations where memory + for the filter could not be allocated. The expected numbers of input and + output samples are still processed so that callers failing to check error + codes are not surprised, possibly getting into infinite loops. */ +static int resampler_basic_zero(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int out_sample = 0; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + const int out_stride = st->out_stride; + const int int_advance = st->int_advance; + const int frac_advance = st->frac_advance; + const spx_uint32_t den_rate = st->den_rate; + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + out[out_stride * out_sample++] = 0; + last_sample += int_advance; + samp_frac_num += frac_advance; + if (samp_frac_num >= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +static int _muldiv(spx_uint32_t *result, spx_uint32_t value, spx_uint32_t mul, spx_uint32_t div) +{ + speex_assert(result); + spx_uint32_t major = value / div; + spx_uint32_t remainder = value % div; + /* TODO: Could use 64 bits operation to check for overflow. But only guaranteed in C99+ */ + if (remainder > UINT32_MAX / mul || major > UINT32_MAX / mul + || major * mul > UINT32_MAX - remainder * mul / div) + return RESAMPLER_ERR_OVERFLOW; + *result = remainder * mul / div + major * mul; + return RESAMPLER_ERR_SUCCESS; +} + +static int update_filter(SpeexResamplerState *st) +{ + spx_uint32_t old_length = st->filt_len; + spx_uint32_t old_alloc_size = st->mem_alloc_size; + int use_direct; + spx_uint32_t min_sinc_table_length; + spx_uint32_t min_alloc_size; + + st->int_advance = st->num_rate/st->den_rate; + st->frac_advance = st->num_rate%st->den_rate; + st->oversample = quality_map[st->quality].oversample; + st->filt_len = quality_map[st->quality].base_length; + + if (st->num_rate > st->den_rate) + { + /* down-sampling */ + st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; + if (_muldiv(&st->filt_len,st->filt_len,st->num_rate,st->den_rate) != RESAMPLER_ERR_SUCCESS) + goto fail; + /* Round up to make sure we have a multiple of 8 for SSE */ + st->filt_len = ((st->filt_len-1)&(~0x7))+8; + if (2*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (4*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (8*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (16*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (st->oversample < 1) + st->oversample = 1; + } else { + /* up-sampling */ + st->cutoff = quality_map[st->quality].upsample_bandwidth; + } + + /* Choose the resampling type that requires the least amount of memory */ +#ifdef RESAMPLE_FULL_SINC_TABLE + use_direct = 1; + if (INT_MAX/sizeof(spx_word16_t)/st->den_rate < st->filt_len) + goto fail; +#else + use_direct = st->filt_len*st->den_rate <= st->filt_len*st->oversample+8 + && INT_MAX/sizeof(spx_word16_t)/st->den_rate >= st->filt_len; +#endif + if (use_direct) + { + min_sinc_table_length = st->filt_len*st->den_rate; + } else { + if ((INT_MAX/sizeof(spx_word16_t)-8)/st->oversample < st->filt_len) + goto fail; + + min_sinc_table_length = st->filt_len*st->oversample+8; + } + if (st->sinc_table_length < min_sinc_table_length) + { + spx_word16_t *sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,min_sinc_table_length*sizeof(spx_word16_t)); + if (!sinc_table) + goto fail; + + st->sinc_table = sinc_table; + st->sinc_table_length = min_sinc_table_length; + } + if (use_direct) + { + spx_uint32_t i; + for (i=0;iden_rate;i++) + { + spx_int32_t j; + for (j=0;jfilt_len;j++) + { + st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1)-((float)i)/st->den_rate), st->filt_len, quality_map[st->quality].window_func); + } + } +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_direct_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_direct_double; + else + st->resampler_ptr = resampler_basic_direct_single; +#endif + /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/ + } else { + spx_int32_t i; + for (i=-4;i<(spx_int32_t)(st->oversample*st->filt_len+4);i++) + st->sinc_table[i+4] = sinc(st->cutoff,(i/(float)st->oversample - st->filt_len/2), st->filt_len, quality_map[st->quality].window_func); +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_interpolate_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_interpolate_double; + else + st->resampler_ptr = resampler_basic_interpolate_single; +#endif + /*fprintf (stderr, "resampler uses interpolated sinc table and normalised cutoff %f\n", cutoff);*/ + } + + /* Here's the place where we update the filter memory to take into account + the change in filter length. It's probably the messiest part of the code + due to handling of lots of corner cases. */ + + /* Adding buffer_size to filt_len won't overflow here because filt_len + could be multiplied by sizeof(spx_word16_t) above. */ + min_alloc_size = st->filt_len-1 + st->buffer_size; + if (min_alloc_size > st->mem_alloc_size) + { + spx_word16_t *mem; + if (INT_MAX/sizeof(spx_word16_t)/st->nb_channels < min_alloc_size) + goto fail; + else if (!(mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*min_alloc_size * sizeof(*mem)))) + goto fail; + + st->mem = mem; + st->mem_alloc_size = min_alloc_size; + } + if (!st->started) + { + spx_uint32_t i; + for (i=0;inb_channels*st->mem_alloc_size;i++) + st->mem[i] = 0; + /*speex_warning("reinit filter");*/ + } else if (st->filt_len > old_length) + { + spx_uint32_t i; + /* Increase the filter length */ + /*speex_warning("increase filter size");*/ + for (i=st->nb_channels;i--;) + { + spx_uint32_t j; + spx_uint32_t olen = old_length; + /*if (st->magic_samples[i])*/ + { + /* Try and remove the magic samples as if nothing had happened */ + + /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */ + olen = old_length + 2*st->magic_samples[i]; + for (j=old_length-1+st->magic_samples[i];j--;) + st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; + for (j=0;jmagic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = 0; + st->magic_samples[i] = 0; + } + if (st->filt_len > olen) + { + /* If the new filter length is still bigger than the "augmented" length */ + /* Copy data going backward */ + for (j=0;jmem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)]; + /* Then put zeros for lack of anything better */ + for (;jfilt_len-1;j++) + st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0; + /* Adjust last_sample */ + st->last_sample[i] += (st->filt_len - olen)/2; + } else { + /* Put back some of the magic! */ + st->magic_samples[i] = (olen - st->filt_len)/2; + for (j=0;jfilt_len-1+st->magic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + } + } + } else if (st->filt_len < old_length) + { + spx_uint32_t i; + /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic" + samples so they can be used directly as input the next time(s) */ + for (i=0;inb_channels;i++) + { + spx_uint32_t j; + spx_uint32_t old_magic = st->magic_samples[i]; + st->magic_samples[i] = (old_length - st->filt_len)/2; + /* We must copy some of the memory that's no longer used */ + /* Copy data going backward */ + for (j=0;jfilt_len-1+st->magic_samples[i]+old_magic;j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + st->magic_samples[i] += old_magic; + } + } + return RESAMPLER_ERR_SUCCESS; + +fail: + st->resampler_ptr = resampler_basic_zero; + /* st->mem may still contain consumed input samples for the filter. + Restore filt_len so that filt_len - 1 still points to the position after + the last of these samples. */ + st->filt_len = old_length; + return RESAMPLER_ERR_ALLOC_FAILED; +} + +EXPORT SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err); +} + +EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + spx_uint32_t i; + SpeexResamplerState *st; + int filter_err; + + if (quality > 10 || quality < 0) + { + if (err) + *err = RESAMPLER_ERR_INVALID_ARG; + return NULL; + } + st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState)); + if (!st) + { + if (err) + *err = RESAMPLER_ERR_ALLOC_FAILED; + return NULL; + } + st->initialised = 0; + st->started = 0; + st->in_rate = 0; + st->out_rate = 0; + st->num_rate = 0; + st->den_rate = 0; + st->quality = -1; + st->sinc_table_length = 0; + st->mem_alloc_size = 0; + st->filt_len = 0; + st->mem = 0; + st->resampler_ptr = 0; + + st->cutoff = 1.f; + st->nb_channels = nb_channels; + st->in_stride = 1; + st->out_stride = 1; + + st->buffer_size = 160; + + /* Per channel data */ + if (!(st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(spx_int32_t)))) + goto fail; + if (!(st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t)))) + goto fail; + if (!(st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t)))) + goto fail; + + speex_resampler_set_quality(st, quality); + speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate); + + filter_err = update_filter(st); + if (filter_err == RESAMPLER_ERR_SUCCESS) + { + st->initialised = 1; + } else { + speex_resampler_destroy(st); + st = NULL; + } + if (err) + *err = filter_err; + + return st; + +fail: + if (err) + *err = RESAMPLER_ERR_ALLOC_FAILED; + speex_resampler_destroy(st); + return NULL; +} + +EXPORT void speex_resampler_destroy(SpeexResamplerState *st) +{ + speex_free(st->mem); + speex_free(st->sinc_table); + speex_free(st->last_sample); + speex_free(st->magic_samples); + speex_free(st->samp_frac_num); + speex_free(st); +} + +static int speex_resampler_process_native(SpeexResamplerState *st, spx_uint32_t channel_index, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int j=0; + const int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size; + spx_uint32_t ilen; + + st->started = 1; + + /* Call the right resampler through the function ptr */ + out_sample = st->resampler_ptr(st, channel_index, mem, in_len, out, out_len); + + if (st->last_sample[channel_index] < (spx_int32_t)*in_len) + *in_len = st->last_sample[channel_index]; + *out_len = out_sample; + st->last_sample[channel_index] -= *in_len; + + ilen = *in_len; + + for(j=0;jmagic_samples[channel_index]; + spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size; + const int N = st->filt_len; + + speex_resampler_process_native(st, channel_index, &tmp_in_len, *out, &out_len); + + st->magic_samples[channel_index] -= tmp_in_len; + + /* If we couldn't process all "magic" input samples, save the rest for next time */ + if (st->magic_samples[channel_index]) + { + spx_uint32_t i; + for (i=0;imagic_samples[channel_index];i++) + mem[N-1+i]=mem[N-1+i+tmp_in_len]; + } + *out += out_len*st->out_stride; + return out_len; +} + +#ifdef FIXED_POINT +EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +#else +EXPORT int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +#endif +{ + int j; + spx_uint32_t ilen = *in_len; + spx_uint32_t olen = *out_len; + spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size; + const int filt_offs = st->filt_len - 1; + const spx_uint32_t xlen = st->mem_alloc_size - filt_offs; + const int istride = st->in_stride; + + if (st->magic_samples[channel_index]) + olen -= speex_resampler_magic(st, channel_index, &out, olen); + if (! st->magic_samples[channel_index]) { + while (ilen && olen) { + spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen; + spx_uint32_t ochunk = olen; + + if (in) { + for(j=0;jout_stride; + if (in) + in += ichunk * istride; + } + } + *in_len -= ilen; + *out_len -= olen; + return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS; +} + +#ifdef FIXED_POINT +EXPORT int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +#else +EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +#endif +{ + int j; + const int istride_save = st->in_stride; + const int ostride_save = st->out_stride; + spx_uint32_t ilen = *in_len; + spx_uint32_t olen = *out_len; + spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size; + const spx_uint32_t xlen = st->mem_alloc_size - (st->filt_len - 1); +#ifdef VAR_ARRAYS + const unsigned int ylen = (olen < FIXED_STACK_ALLOC) ? olen : FIXED_STACK_ALLOC; + VARDECL(spx_word16_t *ystack); + ALLOC(ystack, ylen, spx_word16_t); +#else + const unsigned int ylen = FIXED_STACK_ALLOC; + spx_word16_t ystack[FIXED_STACK_ALLOC]; +#endif + + st->out_stride = 1; + + while (ilen && olen) { + spx_word16_t *y = ystack; + spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen; + spx_uint32_t ochunk = (olen > ylen) ? ylen : olen; + spx_uint32_t omagic = 0; + + if (st->magic_samples[channel_index]) { + omagic = speex_resampler_magic(st, channel_index, &y, ochunk); + ochunk -= omagic; + olen -= omagic; + } + if (! st->magic_samples[channel_index]) { + if (in) { + for(j=0;jfilt_len-1]=WORD2INT(in[j*istride_save]); +#else + x[j+st->filt_len-1]=in[j*istride_save]; +#endif + } else { + for(j=0;jfilt_len-1]=0; + } + + speex_resampler_process_native(st, channel_index, &ichunk, y, &ochunk); + } else { + ichunk = 0; + ochunk = 0; + } + + for (j=0;jout_stride = ostride_save; + *in_len -= ilen; + *out_len -= olen; + + return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_out_len = *out_len; + spx_uint32_t bak_in_len = *in_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;inb_channels;i++) + { + *out_len = bak_out_len; + *in_len = bak_in_len; + if (in != NULL) + speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len); + else + speex_resampler_process_float(st, i, NULL, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_out_len = *out_len; + spx_uint32_t bak_in_len = *in_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;inb_channels;i++) + { + *out_len = bak_out_len; + *in_len = bak_in_len; + if (in != NULL) + speex_resampler_process_int(st, i, in+i, in_len, out+i, out_len); + else + speex_resampler_process_int(st, i, NULL, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate); +} + +EXPORT void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate) +{ + *in_rate = st->in_rate; + *out_rate = st->out_rate; +} + +static inline spx_uint32_t _gcd(spx_uint32_t a, spx_uint32_t b) +{ + while (b != 0) + { + spx_uint32_t temp = a; + + a = b; + b = temp % b; + } + return a; +} + +EXPORT int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + spx_uint32_t fact; + spx_uint32_t old_den; + spx_uint32_t i; + if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den) + return RESAMPLER_ERR_SUCCESS; + + old_den = st->den_rate; + st->in_rate = in_rate; + st->out_rate = out_rate; + st->num_rate = ratio_num; + st->den_rate = ratio_den; + + fact = _gcd (st->num_rate, st->den_rate); + + st->num_rate /= fact; + st->den_rate /= fact; + + if (old_den > 0) + { + for (i=0;inb_channels;i++) + { + if (_muldiv(&st->samp_frac_num[i],st->samp_frac_num[i],st->den_rate,old_den) != RESAMPLER_ERR_SUCCESS) + return RESAMPLER_ERR_OVERFLOW; + /* Safety net */ + if (st->samp_frac_num[i] >= st->den_rate) + st->samp_frac_num[i] = st->den_rate-1; + } + } + + if (st->initialised) + return update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den) +{ + *ratio_num = st->num_rate; + *ratio_den = st->den_rate; +} + +EXPORT int speex_resampler_set_quality(SpeexResamplerState *st, int quality) +{ + if (quality > 10 || quality < 0) + return RESAMPLER_ERR_INVALID_ARG; + if (st->quality == quality) + return RESAMPLER_ERR_SUCCESS; + st->quality = quality; + if (st->initialised) + return update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT void speex_resampler_get_quality(SpeexResamplerState *st, int *quality) +{ + *quality = st->quality; +} + +EXPORT void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->in_stride = stride; +} + +EXPORT void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->in_stride; +} + +EXPORT void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->out_stride = stride; +} + +EXPORT void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->out_stride; +} + +EXPORT int speex_resampler_get_input_latency(SpeexResamplerState *st) +{ + return st->filt_len / 2; +} + +EXPORT int speex_resampler_get_output_latency(SpeexResamplerState *st) +{ + return ((st->filt_len / 2) * st->den_rate + (st->num_rate >> 1)) / st->num_rate; +} + +EXPORT int speex_resampler_skip_zeros(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels;i++) + st->last_sample[i] = st->filt_len/2; + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT int speex_resampler_reset_mem(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels;i++) + { + st->last_sample[i] = 0; + st->magic_samples[i] = 0; + st->samp_frac_num[i] = 0; + } + for (i=0;inb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + return RESAMPLER_ERR_SUCCESS; +} + +EXPORT const char *speex_resampler_strerror(int err) +{ + switch (err) + { + case RESAMPLER_ERR_SUCCESS: + return "Success."; + case RESAMPLER_ERR_ALLOC_FAILED: + return "Memory allocation failed."; + case RESAMPLER_ERR_BAD_STATE: + return "Bad resampler state."; + case RESAMPLER_ERR_INVALID_ARG: + return "Invalid argument."; + case RESAMPLER_ERR_PTR_OVERLAP: + return "Input and output buffers overlap."; + default: + return "Unknown error. Bad error code or strange version mismatch."; + } +} diff --git a/Externals/cubeb/src/speex/resample_neon.h b/Externals/cubeb/src/speex/resample_neon.h new file mode 100644 index 0000000000..0acbd27b9a --- /dev/null +++ b/Externals/cubeb/src/speex/resample_neon.h @@ -0,0 +1,201 @@ +/* Copyright (C) 2007-2008 Jean-Marc Valin + * Copyright (C) 2008 Thorvald Natvig + * Copyright (C) 2011 Texas Instruments + * author Jyri Sarha + */ +/** + @file resample_neon.h + @brief Resampler functions (NEON version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#ifdef FIXED_POINT +#ifdef __thumb2__ +static inline int32_t saturate_32bit_to_16bit(int32_t a) { + int32_t ret; + asm ("ssat %[ret], #16, %[a]" + : [ret] "=&r" (ret) + : [a] "r" (a) + : ); + return ret; +} +#else +static inline int32_t saturate_32bit_to_16bit(int32_t a) { + int32_t ret; + asm ("vmov.s32 d0[0], %[a]\n" + "vqmovn.s32 d0, q0\n" + "vmov.s16 %[ret], d0[0]\n" + : [ret] "=&r" (ret) + : [a] "r" (a) + : "q0"); + return ret; +} +#endif +#undef WORD2INT +#define WORD2INT(x) (saturate_32bit_to_16bit(x)) + +#define OVERRIDE_INNER_PRODUCT_SINGLE +/* Only works when len % 4 == 0 */ +static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len) +{ + int32_t ret; + uint32_t remainder = len % 16; + len = len - remainder; + + asm volatile (" cmp %[len], #0\n" + " bne 1f\n" + " vld1.16 {d16}, [%[b]]!\n" + " vld1.16 {d20}, [%[a]]!\n" + " subs %[remainder], %[remainder], #4\n" + " vmull.s16 q0, d16, d20\n" + " beq 5f\n" + " b 4f\n" + "1:" + " vld1.16 {d16, d17, d18, d19}, [%[b]]!\n" + " vld1.16 {d20, d21, d22, d23}, [%[a]]!\n" + " subs %[len], %[len], #16\n" + " vmull.s16 q0, d16, d20\n" + " vmlal.s16 q0, d17, d21\n" + " vmlal.s16 q0, d18, d22\n" + " vmlal.s16 q0, d19, d23\n" + " beq 3f\n" + "2:" + " vld1.16 {d16, d17, d18, d19}, [%[b]]!\n" + " vld1.16 {d20, d21, d22, d23}, [%[a]]!\n" + " subs %[len], %[len], #16\n" + " vmlal.s16 q0, d16, d20\n" + " vmlal.s16 q0, d17, d21\n" + " vmlal.s16 q0, d18, d22\n" + " vmlal.s16 q0, d19, d23\n" + " bne 2b\n" + "3:" + " cmp %[remainder], #0\n" + " beq 5f\n" + "4:" + " vld1.16 {d16}, [%[b]]!\n" + " vld1.16 {d20}, [%[a]]!\n" + " subs %[remainder], %[remainder], #4\n" + " vmlal.s16 q0, d16, d20\n" + " bne 4b\n" + "5:" + " vaddl.s32 q0, d0, d1\n" + " vadd.s64 d0, d0, d1\n" + " vqmovn.s64 d0, q0\n" + " vqrshrn.s32 d0, q0, #15\n" + " vmov.s16 %[ret], d0[0]\n" + : [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b), + [len] "+r" (len), [remainder] "+r" (remainder) + : + : "cc", "q0", + "d16", "d17", "d18", "d19", + "d20", "d21", "d22", "d23"); + + return ret; +} +#elif defined(FLOATING_POINT) + +static inline int32_t saturate_float_to_16bit(float a) { + int32_t ret; + asm ("vmov.f32 d0[0], %[a]\n" + "vcvt.s32.f32 d0, d0, #15\n" + "vqrshrn.s32 d0, q0, #15\n" + "vmov.s16 %[ret], d0[0]\n" + : [ret] "=&r" (ret) + : [a] "r" (a) + : "q0"); + return ret; +} +#undef WORD2INT +#define WORD2INT(x) (saturate_float_to_16bit(x)) + +#define OVERRIDE_INNER_PRODUCT_SINGLE +/* Only works when len % 4 == 0 */ +static inline float inner_product_single(const float *a, const float *b, unsigned int len) +{ + float ret; + uint32_t remainder = len % 16; + len = len - remainder; + + asm volatile (" cmp %[len], #0\n" + " bne 1f\n" + " vld1.32 {q4}, [%[b]]!\n" + " vld1.32 {q8}, [%[a]]!\n" + " subs %[remainder], %[remainder], #4\n" + " vmul.f32 q0, q4, q8\n" + " bne 4f\n" + " b 5f\n" + "1:" + " vld1.32 {q4, q5}, [%[b]]!\n" + " vld1.32 {q8, q9}, [%[a]]!\n" + " vld1.32 {q6, q7}, [%[b]]!\n" + " vld1.32 {q10, q11}, [%[a]]!\n" + " subs %[len], %[len], #16\n" + " vmul.f32 q0, q4, q8\n" + " vmul.f32 q1, q5, q9\n" + " vmul.f32 q2, q6, q10\n" + " vmul.f32 q3, q7, q11\n" + " beq 3f\n" + "2:" + " vld1.32 {q4, q5}, [%[b]]!\n" + " vld1.32 {q8, q9}, [%[a]]!\n" + " vld1.32 {q6, q7}, [%[b]]!\n" + " vld1.32 {q10, q11}, [%[a]]!\n" + " subs %[len], %[len], #16\n" + " vmla.f32 q0, q4, q8\n" + " vmla.f32 q1, q5, q9\n" + " vmla.f32 q2, q6, q10\n" + " vmla.f32 q3, q7, q11\n" + " bne 2b\n" + "3:" + " vadd.f32 q4, q0, q1\n" + " vadd.f32 q5, q2, q3\n" + " cmp %[remainder], #0\n" + " vadd.f32 q0, q4, q5\n" + " beq 5f\n" + "4:" + " vld1.32 {q6}, [%[b]]!\n" + " vld1.32 {q10}, [%[a]]!\n" + " subs %[remainder], %[remainder], #4\n" + " vmla.f32 q0, q6, q10\n" + " bne 4b\n" + "5:" + " vadd.f32 d0, d0, d1\n" + " vpadd.f32 d0, d0, d0\n" + " vmov.f32 %[ret], d0[0]\n" + : [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b), + [len] "+l" (len), [remainder] "+l" (remainder) + : + : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", + "q9", "q10", "q11"); + return ret; +} +#endif diff --git a/Externals/cubeb/src/speex/resample_sse.h b/Externals/cubeb/src/speex/resample_sse.h new file mode 100644 index 0000000000..fed5b8276f --- /dev/null +++ b/Externals/cubeb/src/speex/resample_sse.h @@ -0,0 +1,128 @@ +/* Copyright (C) 2007-2008 Jean-Marc Valin + * Copyright (C) 2008 Thorvald Natvig + */ +/** + @file resample_sse.h + @brief Resampler functions (SSE version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#define OVERRIDE_INNER_PRODUCT_SINGLE +static inline float inner_product_single(const float *a, const float *b, unsigned int len) +{ + int i; + float ret; + __m128 sum = _mm_setzero_ps(); + for (i=0;i +#define OVERRIDE_INNER_PRODUCT_DOUBLE + +static inline double inner_product_double(const float *a, const float *b, unsigned int len) +{ + int i; + double ret; + __m128d sum = _mm_setzero_pd(); + __m128 t; + for (i=0;i +# else +# ifdef HAVE_ALLOCA_H +# include +# else +# include +# endif +# endif +#endif + +/** + * @def ALIGN(stack, size) + * + * Aligns the stack to a 'size' boundary + * + * @param stack Stack + * @param size New size boundary + */ + +/** + * @def PUSH(stack, size, type) + * + * Allocates 'size' elements of type 'type' on the stack + * + * @param stack Stack + * @param size Number of elements + * @param type Type of element + */ + +/** + * @def VARDECL(var) + * + * Declare variable on stack + * + * @param var Variable to declare + */ + +/** + * @def ALLOC(var, size, type) + * + * Allocate 'size' elements of 'type' on stack + * + * @param var Name of variable to allocate + * @param size Number of elements + * @param type Type of element + */ + +#ifdef ENABLE_VALGRIND + +#include + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) + +#define PUSH(stack, size, type) (VALGRIND_MAKE_NOACCESS(stack, 1000),ALIGN((stack),sizeof(type)),VALGRIND_MAKE_WRITABLE(stack, ((size)*sizeof(type))),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) + +#else + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) + +#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) + +#endif + +#if defined(VAR_ARRAYS) +#define VARDECL(var) +#define ALLOC(var, size, type) type var[size] +#elif defined(USE_ALLOCA) +#define VARDECL(var) var +#define ALLOC(var, size, type) var = alloca(sizeof(type)*(size)) +#else +#define VARDECL(var) var +#define ALLOC(var, size, type) var = PUSH(stack, size, type) +#endif + + +#endif diff --git a/Externals/licenses.md b/Externals/licenses.md index 3bc79fc5a3..014f3ad82b 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -6,6 +6,8 @@ Dolphin includes or links code of the following third-party software projects: [LGPLv2.1+](https://git.kernel.org/cgit/bluetooth/bluez.git/tree/COPYING.LIB) - [Bochs](http://bochs.sourceforge.net/): [LGPLv2.1+](http://bochs.sourceforge.net/cgi-bin/lxr/source/COPYING) +- [cubeb](https://github.com/kinetiknz/cubeb): + [ISC](https://github.com/kinetiknz/cubeb/blob/master/LICENSE) - [ENet](http://enet.bespin.org/): [MIT](http://enet.bespin.org/License.html) - [GCEmu](http://sourceforge.net/projects/gcemu-project/):