From 1b948aab62da6227d5d970dde9d06d007eb17b81 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 24 Nov 2023 21:14:39 +1000 Subject: [PATCH] dep/cubeb: Minimize and update to 54217bc --- dep/cubeb/CMakeLists.txt | 257 +-- dep/cubeb/Config.cmake.in | 4 - dep/cubeb/cmake/toolchain-cross-mingw.cmake | 14 - dep/cubeb/cubeb.supp | 36 - dep/cubeb/cubeb.vcxproj | 1 - dep/cubeb/cubeb.vcxproj.filters | 1 - dep/cubeb/include/cubeb/cubeb.h | 6 +- .../src/android/audiotrack_definitions.h | 85 - dep/cubeb/src/android/cubeb-output-latency.h | 76 - dep/cubeb/src/android/cubeb_media_library.h | 64 - dep/cubeb/src/android/sles_definitions.h | 104 - dep/cubeb/src/cubeb-jni.h | 8 + dep/cubeb/src/cubeb-sles.h | 38 - dep/cubeb/src/cubeb.c | 114 +- dep/cubeb/src/cubeb_aaudio.cpp | 1506 -------------- dep/cubeb/src/cubeb_alsa.c | 12 +- dep/cubeb/src/cubeb_android.h | 17 - dep/cubeb/src/cubeb_array_queue.h | 99 - dep/cubeb/src/cubeb_audiotrack.c | 472 ----- dep/cubeb/src/cubeb_jack.cpp | 6 +- dep/cubeb/src/cubeb_kai.c | 369 ---- dep/cubeb/src/cubeb_log.cpp | 139 +- dep/cubeb/src/cubeb_log.h | 35 +- dep/cubeb/src/cubeb_mixer.cpp | 2 +- dep/cubeb/src/cubeb_opensl.c | 1796 ----------------- dep/cubeb/src/cubeb_oss.c | 6 + dep/cubeb/src/cubeb_pulse.c | 7 +- dep/cubeb/src/cubeb_ring_array.h | 23 +- dep/cubeb/src/cubeb_sndio.c | 45 +- dep/cubeb/src/cubeb_sun.c | 733 ------- dep/cubeb/src/cubeb_triple_buffer.h | 80 + dep/cubeb/src/cubeb_wasapi.cpp | 406 ++-- dep/cubeb/src/cubeb_winmm.c | 67 +- .../flatpak/org.duckstation.DuckStation.json | 3 +- 34 files changed, 562 insertions(+), 6069 deletions(-) delete mode 100644 dep/cubeb/Config.cmake.in delete mode 100644 dep/cubeb/cmake/toolchain-cross-mingw.cmake delete mode 100644 dep/cubeb/cubeb.supp delete mode 100644 dep/cubeb/src/android/audiotrack_definitions.h delete mode 100644 dep/cubeb/src/android/cubeb-output-latency.h delete mode 100644 dep/cubeb/src/android/cubeb_media_library.h delete mode 100644 dep/cubeb/src/android/sles_definitions.h delete mode 100644 dep/cubeb/src/cubeb-sles.h delete mode 100644 dep/cubeb/src/cubeb_aaudio.cpp delete mode 100644 dep/cubeb/src/cubeb_android.h delete mode 100644 dep/cubeb/src/cubeb_array_queue.h delete mode 100644 dep/cubeb/src/cubeb_audiotrack.c delete mode 100644 dep/cubeb/src/cubeb_kai.c delete mode 100644 dep/cubeb/src/cubeb_opensl.c delete mode 100644 dep/cubeb/src/cubeb_sun.c create mode 100644 dep/cubeb/src/cubeb_triple_buffer.h diff --git a/dep/cubeb/CMakeLists.txt b/dep/cubeb/CMakeLists.txt index 447ef7f9e..cca61d5cf 100644 --- a/dep/cubeb/CMakeLists.txt +++ b/dep/cubeb/CMakeLists.txt @@ -2,10 +2,8 @@ # - backend selection via command line, rather than simply detecting headers. cmake_minimum_required(VERSION 3.14 FATAL_ERROR) -project(cubeb - VERSION 0.0.0) +project(cubeb C CXX) -option(BUILD_RUST_LIBS "Build rust backends" OFF) option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON) if(NOT CMAKE_BUILD_TYPE) @@ -14,25 +12,17 @@ if(NOT CMAKE_BUILD_TYPE) endif() set(CMAKE_C_STANDARD 99) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) - -if (BUILD_RUST_LIBS) - if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs") - set(USE_PULSE_RUST 1) - endif() - if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs") - set(USE_AUDIOUNIT_RUST 1) - endif() -endif() +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 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 -fno-exceptions -fno-rtti") else() - string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI - string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable Exceptions + string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable RTTI + string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable Exceptions endif() add_library(cubeb @@ -46,43 +36,15 @@ add_library(cubeb target_include_directories(cubeb PUBLIC $ $ ) -set_target_properties(cubeb PROPERTIES - VERSION ${cubeb_VERSION} - SOVERSION ${cubeb_VERSION_MAJOR} -) - -include(CMakePackageConfigHelpers) -write_basic_package_version_file( - "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" - COMPATIBILITY SameMajorVersion -) - -configure_package_config_file( - "Config.cmake.in" - "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" -) - -install( - FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" -) - -install(TARGETS cubeb EXPORT "${PROJECT_NAME}Targets") -install( - EXPORT "${PROJECT_NAME}Targets" - NAMESPACE "${PROJECT_NAME}::" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" -) add_library(speex OBJECT subprojects/speex/resample.c) set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) target_include_directories(speex INTERFACE subprojects) target_compile_definitions(speex PUBLIC -OUTSIDE_SPEEX -FLOATING_POINT -EXPORT= -RANDOM_PREFIX=speex + OUTSIDE_SPEEX + FLOATING_POINT + EXPORT= + RANDOM_PREFIX=speex ) # $ required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415 @@ -96,17 +58,19 @@ find_package(Threads) target_link_libraries(cubeb PRIVATE Threads::Threads) if(LAZY_LOAD_LIBS) - check_include_files(pulse/pulseaudio.h USE_PULSE) - check_include_files(alsa/asoundlib.h USE_ALSA) - check_include_files(jack/jack.h USE_JACK) - check_include_files(sndio.h USE_SNDIO) - check_include_files(aaudio/AAudio.h USE_AAUDIO) + if(NOT APPLE AND NOT WIN32) + # Skip checks on MacOS because it takes ages in XCode. + check_include_files(pulse/pulseaudio.h USE_PULSE) + check_include_files(alsa/asoundlib.h USE_ALSA) + check_include_files(jack/jack.h USE_JACK) + check_include_files(sndio.h USE_SNDIO) - if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO OR USE_AAUDIO) - target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS}) + if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO) + target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS}) + endif() endif() -else() +elseif(NOT APPLE AND NOT WIN32) find_package(PkgConfig REQUIRED) @@ -136,12 +100,6 @@ else() target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN) target_link_libraries(cubeb PRIVATE sndio) endif() - - check_include_files(aaudio/AAudio.h USE_AAUDIO) - if(USE_AAUDIO) - target_compile_definitions(cubeb PRIVATE DISABLE_LIBAAUDIO_DLOPEN) - target_link_libraries(cubeb PRIVATE aaudio) - endif() endif() if(USE_PULSE) @@ -164,138 +122,57 @@ if(USE_SNDIO) target_compile_definitions(cubeb PRIVATE USE_SNDIO) endif() -if(USE_AAUDIO) - target_sources(cubeb PRIVATE src/cubeb_aaudio.cpp) - target_compile_definitions(cubeb PRIVATE USE_AAUDIO) - - # set this definition to enable low latency mode. Possibly bad for battery - target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY) - - # set this definition to enable power saving mode. Possibly resulting - # in high latency - # target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_POWER_SAVING) - - # set this mode to make the backend use an exclusive stream. - # will decrease latency. - # target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_EXCLUSIVE_STREAM) -endif() - -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(audioclient.h USE_WASAPI) -if(USE_WASAPI) - target_sources(cubeb PRIVATE - src/cubeb_wasapi.cpp) - target_compile_definitions(cubeb PRIVATE USE_WASAPI) - target_link_libraries(cubeb PRIVATE avrt ole32 ksuser) -endif() - -check_include_files("windows.h;mmsystem.h" USE_WINMM) -if(USE_WINMM) - target_sources(cubeb PRIVATE - src/cubeb_winmm.c) - target_compile_definitions(cubeb PRIVATE USE_WINMM) - target_link_libraries(cubeb PRIVATE winmm) -endif() - -check_include_files(SLES/OpenSLES.h USE_OPENSL) -if(USE_OPENSL) - target_sources(cubeb PRIVATE - src/cubeb_opensl.c - src/cubeb-jni.cpp) - target_compile_definitions(cubeb PRIVATE USE_OPENSL) - target_link_libraries(cubeb PRIVATE OpenSLES) -endif() - -check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H) -if(HAVE_SYS_SOUNDCARD_H) - try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests" - ${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c) - if(USE_OSS) - # strlcpy is not available on BSD systems that use glibc, - # like Debian kfreebsd, so try using libbsd if available - include(CheckSymbolExists) - check_symbol_exists(strlcpy string.h HAVE_STRLCPY) - if(NOT HAVE_STRLCPY) - pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay) - if(libbsd-overlay_FOUND) - target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay) - set(HAVE_STRLCPY true) - endif() - endif() - if (HAVE_STRLCPY) - target_sources(cubeb PRIVATE - src/cubeb_oss.c) - target_compile_definitions(cubeb PRIVATE USE_OSS) - endif() +if(APPLE) + 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() 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) +if(WIN32) + check_include_files(audioclient.h USE_WASAPI) + if(USE_WASAPI) + target_sources(cubeb PRIVATE + src/cubeb_wasapi.cpp) + target_compile_definitions(cubeb PRIVATE USE_WASAPI) + target_link_libraries(cubeb PRIVATE avrt ole32 ksuser) + 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() endif() -check_include_files(sys/audioio.h USE_SUN) -if(USE_SUN) - target_sources(cubeb PRIVATE - src/cubeb_sun.c) - target_compile_definitions(cubeb PRIVATE USE_SUN) +if(NOT WIN32 AND NOT APPLE) + check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H) + if(HAVE_SYS_SOUNDCARD_H) + try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests" + ${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c) + if(USE_OSS) + # strlcpy is not available on BSD systems that use glibc, + # like Debian kfreebsd, so try using libbsd if available + include(CheckSymbolExists) + check_symbol_exists(strlcpy string.h HAVE_STRLCPY) + if(NOT HAVE_STRLCPY) + pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay) + if(libbsd-overlay_FOUND) + target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay) + set(HAVE_STRLCPY true) + endif() + endif() + if (HAVE_STRLCPY) + target_sources(cubeb PRIVATE + src/cubeb_oss.c) + target_compile_definitions(cubeb PRIVATE USE_OSS) + endif() + endif() + endif() endif() - -check_include_files(kai.h USE_KAI) -if(USE_KAI) - target_sources(cubeb PRIVATE - src/cubeb_kai.c) - target_compile_definitions(cubeb PRIVATE USE_KAI) - target_link_libraries(cubeb PRIVATE kai) -endif() - -if(USE_PULSE AND USE_PULSE_RUST) - include(ExternalProject) - set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust) - ExternalProject_Add( - cubeb_pulse_rs - DOWNLOAD_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND cargo build COMMAND cargo build --release - BUILD_ALWAYS ON - BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs" - INSTALL_COMMAND "" - LOG_BUILD ON) - add_dependencies(cubeb cubeb_pulse_rs) - target_compile_definitions(cubeb PRIVATE USE_PULSE_RUST) - target_link_libraries(cubeb PRIVATE - debug "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/debug/libcubeb_pulse.a" - optimized "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/release/libcubeb_pulse.a" pulse) -endif() - -if(USE_AUDIOUNIT AND USE_AUDIOUNIT_RUST) - include(ExternalProject) - set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust) - ExternalProject_Add( - cubeb_coreaudio_rs - DOWNLOAD_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND cargo build COMMAND cargo build --release - BUILD_ALWAYS ON - BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs" - INSTALL_COMMAND "" - LOG_BUILD ON) - add_dependencies(cubeb cubeb_coreaudio_rs) - target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT_RUST) - target_link_libraries(cubeb PRIVATE - debug "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/debug/libcubeb_coreaudio.a" - optimized "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/release/libcubeb_coreaudio.a") -endif() - diff --git a/dep/cubeb/Config.cmake.in b/dep/cubeb/Config.cmake.in deleted file mode 100644 index be464aa49..000000000 --- a/dep/cubeb/Config.cmake.in +++ /dev/null @@ -1,4 +0,0 @@ -@PACKAGE_INIT@ - -include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake") -check_required_components(cubeb) diff --git a/dep/cubeb/cmake/toolchain-cross-mingw.cmake b/dep/cubeb/cmake/toolchain-cross-mingw.cmake deleted file mode 100644 index 6d29fe240..000000000 --- a/dep/cubeb/cmake/toolchain-cross-mingw.cmake +++ /dev/null @@ -1,14 +0,0 @@ -SET(CMAKE_SYSTEM_NAME Windows) - -set(COMPILER_PREFIX "i686-w64-mingw32") - -find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres) -find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc-posix) -find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++-posix) - -SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX}) - -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - diff --git a/dep/cubeb/cubeb.supp b/dep/cubeb/cubeb.supp deleted file mode 100644 index 0012ea51e..000000000 --- a/dep/cubeb/cubeb.supp +++ /dev/null @@ -1,36 +0,0 @@ -{ - 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/dep/cubeb/cubeb.vcxproj b/dep/cubeb/cubeb.vcxproj index 9a46ae6d1..cbf5b6efb 100644 --- a/dep/cubeb/cubeb.vcxproj +++ b/dep/cubeb/cubeb.vcxproj @@ -6,7 +6,6 @@ - diff --git a/dep/cubeb/cubeb.vcxproj.filters b/dep/cubeb/cubeb.vcxproj.filters index fb1f35919..223265d04 100644 --- a/dep/cubeb/cubeb.vcxproj.filters +++ b/dep/cubeb/cubeb.vcxproj.filters @@ -5,7 +5,6 @@ - diff --git a/dep/cubeb/include/cubeb/cubeb.h b/dep/cubeb/include/cubeb/cubeb.h index a3b229944..9c7e607ad 100644 --- a/dep/cubeb/include/cubeb/cubeb.h +++ b/dep/cubeb/include/cubeb/cubeb.h @@ -163,6 +163,7 @@ typedef enum { implications. */ } cubeb_log_level; +/// A single channel position, to be used in a bitmask. typedef enum { CHANNEL_UNKNOWN = 0, CHANNEL_FRONT_LEFT = 1 << 0, @@ -185,6 +186,9 @@ typedef enum { CHANNEL_TOP_BACK_RIGHT = 1 << 17 } cubeb_channel; +/// A bitmask representing the channel layout of a cubeb stream. This is +/// bit-compatible with WAVEFORMATEXENSIBLE and in the same order as the SMPTE +/// ordering. typedef uint32_t cubeb_channel_layout; // Some common layout definitions. enum { @@ -433,7 +437,7 @@ typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr, /** * User supplied callback called when the underlying device changed. - * @param user The pointer passed to cubeb_stream_init. */ + * @param user_ptr The pointer passed to cubeb_stream_init. */ typedef void (*cubeb_device_changed_callback)(void * user_ptr); /** diff --git a/dep/cubeb/src/android/audiotrack_definitions.h b/dep/cubeb/src/android/audiotrack_definitions.h deleted file mode 100644 index f6b6931fa..000000000 --- a/dep/cubeb/src/android/audiotrack_definitions.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -/* - * 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/dep/cubeb/src/android/cubeb-output-latency.h b/dep/cubeb/src/android/cubeb-output-latency.h deleted file mode 100644 index 870a884a3..000000000 --- a/dep/cubeb/src/android/cubeb-output-latency.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef _CUBEB_OUTPUT_LATENCY_H_ -#define _CUBEB_OUTPUT_LATENCY_H_ - -#include "../cubeb-jni.h" -#include "cubeb_media_library.h" -#include - -struct output_latency_function { - media_lib * from_lib; - cubeb_jni * from_jni; - int version; -}; - -typedef struct output_latency_function output_latency_function; - -const int ANDROID_JELLY_BEAN_MR1_4_2 = 17; - -output_latency_function * -cubeb_output_latency_load_method(int version) -{ - output_latency_function * ol = NULL; - ol = calloc(1, sizeof(output_latency_function)); - - ol->version = version; - - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) { - ol->from_jni = cubeb_jni_init(); - return ol; - } - - ol->from_lib = cubeb_load_media_library(); - return ol; -} - -bool -cubeb_output_latency_method_is_loaded(output_latency_function * ol) -{ - assert(ol); - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) { - return !!ol->from_jni; - } - - return !!ol->from_lib; -} - -void -cubeb_output_latency_unload_method(output_latency_function * ol) -{ - if (!ol) { - return; - } - - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) { - cubeb_jni_destroy(ol->from_jni); - } - - if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) { - cubeb_close_media_library(ol->from_lib); - } - - free(ol); -} - -uint32_t -cubeb_get_output_latency(output_latency_function * ol) -{ - assert(cubeb_output_latency_method_is_loaded(ol)); - - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) { - return cubeb_get_output_latency_from_jni(ol->from_jni); - } - - return cubeb_get_output_latency_from_media_library(ol->from_lib); -} - -#endif // _CUBEB_OUTPUT_LATENCY_H_ diff --git a/dep/cubeb/src/android/cubeb_media_library.h b/dep/cubeb/src/android/cubeb_media_library.h deleted file mode 100644 index 27fbc86ec..000000000 --- a/dep/cubeb/src/android/cubeb_media_library.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef _CUBEB_MEDIA_LIBRARY_H_ -#define _CUBEB_MEDIA_LIBRARY_H_ - -struct media_lib { - void * libmedia; - int32_t (*get_output_latency)(uint32_t * latency, int stream_type); -}; - -typedef struct media_lib media_lib; - -media_lib * -cubeb_load_media_library() -{ - media_lib ml = {0}; - ml.libmedia = dlopen("libmedia.so", RTLD_LAZY); - if (!ml.libmedia) { - return NULL; - } - - // Get the latency, in ms, from AudioFlinger. First, try the most recent - // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency, - // audio_stream_type_t streamType) - ml.get_output_latency = dlsym( - ml.libmedia, - "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); - if (!ml.get_output_latency) { - // In case of failure, try the signature from legacy version. - // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) - ml.get_output_latency = - dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); - if (!ml.get_output_latency) { - return NULL; - } - } - - media_lib * rv = NULL; - rv = calloc(1, sizeof(media_lib)); - assert(rv); - *rv = ml; - return rv; -} - -void -cubeb_close_media_library(media_lib * ml) -{ - dlclose(ml->libmedia); - ml->libmedia = NULL; - ml->get_output_latency = NULL; - free(ml); -} - -uint32_t -cubeb_get_output_latency_from_media_library(media_lib * ml) -{ - uint32_t latency = 0; - const int audio_stream_type_music = 3; - int32_t r = ml->get_output_latency(&latency, audio_stream_type_music); - if (r) { - return 0; - } - return latency; -} - -#endif // _CUBEB_MEDIA_LIBRARY_H_ diff --git a/dep/cubeb/src/android/sles_definitions.h b/dep/cubeb/src/android/sles_definitions.h deleted file mode 100644 index b107003d1..000000000 --- a/dep/cubeb/src/android/sles_definitions.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in - * the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep - * using a C compiler in cubeb. - */ - -#ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_ -#define OPENSL_ES_ANDROIDCONFIGURATION_H_ - -/*---------------------------------------------------------------------------*/ -/* Android AudioRecorder configuration */ -/*---------------------------------------------------------------------------*/ - -/** Audio recording preset */ -/** Audio recording preset key */ -#define SL_ANDROID_KEY_RECORDING_PRESET \ - ((const SLchar *)"androidRecordingPreset") -/** Audio recording preset values */ -/** preset "none" cannot be set, it is used to indicate the current settings - * do not match any of the presets. */ -#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000) -/** generic recording configuration on the platform */ -#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001) -/** uses the microphone audio source with the same orientation as the camera - * if available, the main device microphone otherwise */ -#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002) -/** uses the main microphone tuned for voice recognition */ -#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003) -/** uses the main microphone tuned for audio communications */ -#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004) -/** uses the main microphone unprocessed */ -#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005) - -/*---------------------------------------------------------------------------*/ -/* Android AudioPlayer configuration */ -/*---------------------------------------------------------------------------*/ - -/** Audio playback stream type */ -/** Audio playback stream type key */ -#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType") - -/** Audio playback stream type values */ -/* same as android.media.AudioManager.STREAM_VOICE_CALL */ -#define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000) -/* same as android.media.AudioManager.STREAM_SYSTEM */ -#define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001) -/* same as android.media.AudioManager.STREAM_RING */ -#define SL_ANDROID_STREAM_RING ((SLint32)0x00000002) -/* same as android.media.AudioManager.STREAM_MUSIC */ -#define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003) -/* same as android.media.AudioManager.STREAM_ALARM */ -#define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004) -/* same as android.media.AudioManager.STREAM_NOTIFICATION */ -#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005) - -/*---------------------------------------------------------------------------*/ -/* Android AudioPlayer and AudioRecorder configuration */ -/*---------------------------------------------------------------------------*/ - -/** Audio Performance mode. - * Performance mode tells the framework how to configure the audio path - * for a player or recorder according to application performance and - * functional requirements. - * It affects the output or input latency based on acceptable tradeoffs on - * battery drain and use of pre or post processing effects. - * Performance mode should be set before realizing the object and should be - * read after realizing the object to check if the requested mode could be - * granted or not. - */ -/** Audio Performance mode key */ -#define SL_ANDROID_KEY_PERFORMANCE_MODE \ - ((const SLchar *)"androidPerformanceMode") - -/** Audio performance values */ -/* No specific performance requirement. Allows HW and SW pre/post - * processing. */ -#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000) -/* Priority given to latency. No HW or software pre/post processing. - * This is the default if no performance mode is specified. */ -#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001) -/* Priority given to latency while still allowing HW pre and post - * processing. */ -#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002) -/* Priority given to power saving if latency is not a concern. - * Allows HW and SW pre/post processing. */ -#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003) - -#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */ diff --git a/dep/cubeb/src/cubeb-jni.h b/dep/cubeb/src/cubeb-jni.h index c4a712a06..d63629fb9 100644 --- a/dep/cubeb/src/cubeb-jni.h +++ b/dep/cubeb/src/cubeb-jni.h @@ -3,6 +3,10 @@ typedef struct cubeb_jni cubeb_jni; +#ifdef __cplusplus +extern "C" { +#endif + cubeb_jni * cubeb_jni_init(); int @@ -10,4 +14,8 @@ cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); +#ifdef __cplusplus +}; +#endif + #endif // _CUBEB_JNI_H_ diff --git a/dep/cubeb/src/cubeb-sles.h b/dep/cubeb/src/cubeb-sles.h deleted file mode 100644 index ca93543c0..000000000 --- a/dep/cubeb/src/cubeb-sles.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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/dep/cubeb/src/cubeb.c b/dep/cubeb/src/cubeb.c index d5627f1d8..d4a2b868c 100644 --- a/dep/cubeb/src/cubeb.c +++ b/dep/cubeb/src/cubeb.c @@ -31,10 +31,6 @@ struct cubeb_stream { 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); @@ -47,10 +43,6 @@ alsa_init(cubeb ** context, char const * context_name); int audiounit_init(cubeb ** context, char const * context_name); #endif -#if defined(USE_AUDIOUNIT_RUST) -int -audiounit_rust_init(cubeb ** contet, char const * context_name); -#endif #if defined(USE_WINMM) int winmm_init(cubeb ** context, char const * context_name); @@ -63,30 +55,10 @@ wasapi_init(cubeb ** context, char const * context_name); int sndio_init(cubeb ** context, char const * context_name); #endif -#if defined(USE_SUN) -int -sun_init(cubeb ** context, char const * context_name); -#endif -#if defined(USE_OPENSL) -int -opensl_init(cubeb ** context, char const * context_name); -#endif #if defined(USE_OSS) int oss_init(cubeb ** context, char const * context_name); #endif -#if defined(USE_AAUDIO) -int -aaudio_init(cubeb ** context, char const * context_name); -#endif -#if defined(USE_AUDIOTRACK) -int -audiotrack_init(cubeb ** context, char const * context_name); -#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, @@ -151,10 +123,6 @@ cubeb_init(cubeb ** context, char const * context_name, 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) @@ -167,10 +135,6 @@ cubeb_init(cubeb ** context, char const * context_name, } else if (!strcmp(backend_name, "audiounit")) { #if defined(USE_AUDIOUNIT) init_oneshot = audiounit_init; -#endif - } else if (!strcmp(backend_name, "audiounit-rust")) { -#if defined(USE_AUDIOUNIT_RUST) - init_oneshot = audiounit_rust_init; #endif } else if (!strcmp(backend_name, "wasapi")) { #if defined(USE_WASAPI) @@ -183,30 +147,10 @@ cubeb_init(cubeb ** context, char const * context_name, } else if (!strcmp(backend_name, "sndio")) { #if defined(USE_SNDIO) init_oneshot = sndio_init; -#endif - } else if (!strcmp(backend_name, "sun")) { -#if defined(USE_SUN) - init_oneshot = sun_init; -#endif - } else if (!strcmp(backend_name, "opensl")) { -#if defined(USE_OPENSL) - init_oneshot = opensl_init; #endif } else if (!strcmp(backend_name, "oss")) { #if defined(USE_OSS) init_oneshot = oss_init; -#endif - } else if (!strcmp(backend_name, "aaudio")) { -#if defined(USE_AAUDIO) - init_oneshot = aaudio_init; -#endif - } else if (!strcmp(backend_name, "audiotrack")) { -#if defined(USE_AUDIOTRACK) - init_oneshot = audiotrack_init; -#endif - } else if (!strcmp(backend_name, "kai")) { -#if defined(USE_KAI) - init_oneshot = kai_init; #endif } else { /* Already set */ @@ -219,9 +163,6 @@ cubeb_init(cubeb ** context, char const * context_name, * to override all other choices */ init_oneshot, -#if defined(USE_PULSE_RUST) - pulse_rust_init, -#endif #if defined(USE_PULSE) pulse_init, #endif @@ -237,9 +178,6 @@ cubeb_init(cubeb ** context, char const * context_name, #if defined(USE_OSS) oss_init, #endif -#if defined(USE_AUDIOUNIT_RUST) - audiounit_rust_init, -#endif #if defined(USE_AUDIOUNIT) audiounit_init, #endif @@ -251,20 +189,6 @@ cubeb_init(cubeb ** context, char const * context_name, #endif #if defined(USE_SUN) sun_init, -#endif -#if defined(USE_OPENSL) - opensl_init, -#endif - // TODO: should probably be preferred over OpenSLES when available. - // Initialization will fail on old android devices. -#if defined(USE_AAUDIO) - aaudio_init, -#endif -#if defined(USE_AUDIOTRACK) - audiotrack_init, -#endif -#if defined(USE_KAI) - kai_init, #endif }; int i; @@ -297,9 +221,6 @@ cubeb_get_backend_names() #if defined(USE_PULSE) "pulse", #endif -#if defined(USE_PULSE_RUST) - "pulse-rust", -#endif #if defined(USE_JACK) "jack", #endif @@ -309,9 +230,6 @@ cubeb_get_backend_names() #if defined(USE_AUDIOUNIT) "audiounit", #endif -#if defined(USE_AUDIOUNIT_RUST) - "audiounit-rust", -#endif #if defined(USE_WASAPI) "wasapi", #endif @@ -324,20 +242,8 @@ cubeb_get_backend_names() #if defined(USE_SUN) "sun", #endif -#if defined(USE_OPENSL) - "opensl", -#endif #if defined(USE_OSS) "oss", -#endif -#if defined(USE_AAUDIO) - "aaudio", -#endif -#if defined(USE_AUDIOTRACK) - "audiotrack", -#endif -#if defined(USE_KAI) - "kai", #endif NULL, }; @@ -406,6 +312,8 @@ cubeb_destroy(cubeb * context) } context->ops->destroy(context); + + cubeb_set_log_callback(CUBEB_LOG_DISABLED, NULL); } int @@ -687,14 +595,14 @@ cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, int rv; if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) return CUBEB_ERROR_INVALID_PARAMETER; - if (collection == NULL) + if (context == NULL || 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) { + if (cubeb_log_get_callback()) { for (size_t i = 0; i < collection->count; i++) { log_device(&collection->device[i]); } @@ -756,21 +664,11 @@ cubeb_set_log_callback(cubeb_log_level log_level, return CUBEB_ERROR_INVALID_PARAMETER; } - if (g_cubeb_log_callback && log_callback) { + if (cubeb_log_get_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"); - } + cubeb_log_set(log_level, log_callback); return CUBEB_OK; } diff --git a/dep/cubeb/src/cubeb_aaudio.cpp b/dep/cubeb/src/cubeb_aaudio.cpp deleted file mode 100644 index 6076f1d21..000000000 --- a/dep/cubeb/src/cubeb_aaudio.cpp +++ /dev/null @@ -1,1506 +0,0 @@ -/* ex: set tabstop=2 shiftwidth=2 expandtab: - * Copyright © 2019 Jan Kelling - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ -#include "cubeb-internal.h" -#include "cubeb/cubeb.h" -#include "cubeb_android.h" -#include "cubeb_log.h" -#include "cubeb_resampler.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DISABLE_LIBAAUDIO_DLOPEN -#define WRAP(x) x -#else -#define WRAP(x) (*cubeb_##x) -#define LIBAAUDIO_API_VISIT(X) \ - X(AAudio_convertResultToText) \ - X(AAudio_convertStreamStateToText) \ - X(AAudio_createStreamBuilder) \ - X(AAudioStreamBuilder_openStream) \ - X(AAudioStreamBuilder_setChannelCount) \ - X(AAudioStreamBuilder_setBufferCapacityInFrames) \ - X(AAudioStreamBuilder_setDirection) \ - X(AAudioStreamBuilder_setFormat) \ - X(AAudioStreamBuilder_setSharingMode) \ - X(AAudioStreamBuilder_setPerformanceMode) \ - X(AAudioStreamBuilder_setSampleRate) \ - X(AAudioStreamBuilder_delete) \ - X(AAudioStreamBuilder_setDataCallback) \ - X(AAudioStreamBuilder_setErrorCallback) \ - X(AAudioStream_close) \ - X(AAudioStream_read) \ - X(AAudioStream_requestStart) \ - X(AAudioStream_requestPause) \ - X(AAudioStream_setBufferSizeInFrames) \ - X(AAudioStream_getTimestamp) \ - X(AAudioStream_requestFlush) \ - X(AAudioStream_requestStop) \ - X(AAudioStream_getPerformanceMode) \ - X(AAudioStream_getSharingMode) \ - X(AAudioStream_getBufferSizeInFrames) \ - X(AAudioStream_getBufferCapacityInFrames) \ - X(AAudioStream_getSampleRate) \ - X(AAudioStream_waitForStateChange) \ - X(AAudioStream_getFramesRead) \ - X(AAudioStream_getState) \ - X(AAudioStream_getFramesWritten) \ - X(AAudioStream_getFramesPerBurst) \ - X(AAudioStreamBuilder_setInputPreset) \ - X(AAudioStreamBuilder_setUsage) - -// not needed or added later on -// X(AAudioStreamBuilder_setFramesPerDataCallback) \ - // X(AAudioStreamBuilder_setDeviceId) \ - // X(AAudioStreamBuilder_setSamplesPerFrame) \ - // X(AAudioStream_getSamplesPerFrame) \ - // X(AAudioStream_getDeviceId) \ - // X(AAudioStream_write) \ - // X(AAudioStream_getChannelCount) \ - // X(AAudioStream_getFormat) \ - // X(AAudioStream_getXRunCount) \ - // X(AAudioStream_isMMapUsed) \ - // X(AAudioStreamBuilder_setContentType) \ - // X(AAudioStreamBuilder_setSessionId) \ - // X(AAudioStream_getUsage) \ - // X(AAudioStream_getContentType) \ - // X(AAudioStream_getInputPreset) \ - // X(AAudioStream_getSessionId) \ -// END: not needed or added later on - -#define MAKE_TYPEDEF(x) static decltype(x) * cubeb_##x; -LIBAAUDIO_API_VISIT(MAKE_TYPEDEF) -#undef MAKE_TYPEDEF -#endif - -const uint8_t MAX_STREAMS = 16; - -using unique_lock = std::unique_lock; -using lock_guard = std::lock_guard; - -enum class stream_state { - INIT = 0, - STOPPED, - STOPPING, - STARTED, - STARTING, - DRAINING, - ERROR, - SHUTDOWN, -}; - -struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ - cubeb * context{}; - void * user_ptr{}; - - std::atomic in_use{false}; - std::atomic state{stream_state::INIT}; - - AAudioStream * ostream{}; - AAudioStream * istream{}; - cubeb_data_callback data_callback{}; - cubeb_state_callback state_callback{}; - cubeb_resampler * resampler{}; - - // mutex synchronizes access to the stream from the state thread - // and user-called functions. Everything that is accessed in the - // aaudio data (or error) callback is synchronized only via atomics. - std::mutex mutex; - - std::unique_ptr in_buf; - unsigned in_frame_size{}; // size of one input frame - - cubeb_sample_format out_format{}; - std::atomic volume{1.f}; - unsigned out_channels{}; - unsigned out_frame_size{}; - int64_t latest_output_latency = 0; - int64_t latest_input_latency = 0; - bool voice_input; - bool voice_output; - uint64_t previous_clock; -}; - -struct cubeb { - struct cubeb_ops const * ops{}; - void * libaaudio{}; - - struct { - // The state thread: it waits for state changes and stops - // drained streams. - std::thread thread; - std::thread notifier; - std::mutex mutex; - std::condition_variable cond; - std::atomic join{false}; - std::atomic waiting{false}; - } state; - - // streams[i].in_use signals whether a stream is used - struct cubeb_stream streams[MAX_STREAMS]; -}; - -// Only allowed from state thread, while mutex on stm is locked -static void -shutdown(cubeb_stream * stm) -{ - if (stm->istream) { - WRAP(AAudioStream_requestStop)(stm->istream); - } - if (stm->ostream) { - WRAP(AAudioStream_requestStop)(stm->ostream); - } - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - stm->state.store(stream_state::SHUTDOWN); -} - -// Returns whether the given state is one in which we wait for -// an asynchronous change -static bool -waiting_state(stream_state state) -{ - switch (state) { - case stream_state::DRAINING: - case stream_state::STARTING: - case stream_state::STOPPING: - return true; - default: - return false; - } -} - -static void -update_state(cubeb_stream * stm) -{ - // Fast path for streams that don't wait for state change or are invalid - enum stream_state old_state = stm->state.load(); - if (old_state == stream_state::INIT || old_state == stream_state::STARTED || - old_state == stream_state::STOPPED || - old_state == stream_state::SHUTDOWN) { - return; - } - - // If the main thread currently operates on this thread, we don't - // have to wait for it - unique_lock lock(stm->mutex, std::try_to_lock); - if (!lock.owns_lock()) { - return; - } - - // check again: if this is true now, the stream was destroyed or - // changed between our fast path check and locking the mutex - old_state = stm->state.load(); - if (old_state == stream_state::INIT || old_state == stream_state::STARTED || - old_state == stream_state::STOPPED || - old_state == stream_state::SHUTDOWN) { - return; - } - - // We compute the new state the stream has and then compare_exchange it - // if it has changed. This way we will never just overwrite state - // changes that were set from the audio thread in the meantime, - // such as a DRAINING or error state. - enum stream_state new_state; - do { - if (old_state == stream_state::SHUTDOWN) { - return; - } - - if (old_state == stream_state::ERROR) { - shutdown(stm); - return; - } - - new_state = old_state; - - aaudio_stream_state_t istate = 0; - aaudio_stream_state_t ostate = 0; - - // We use waitForStateChange (with zero timeout) instead of just - // getState since only the former internally updates the state. - // See the docs of aaudio getState/waitForStateChange for details, - // why we are passing STATE_UNKNOWN. - aaudio_result_t res; - if (stm->istream) { - res = WRAP(AAudioStream_waitForStateChange)( - stm->istream, AAUDIO_STREAM_STATE_UNKNOWN, &istate, 0); - if (res != AAUDIO_OK) { - LOG("AAudioStream_waitForStateChanged: %s", - WRAP(AAudio_convertResultToText)(res)); - return; - } - assert(istate); - } - - if (stm->ostream) { - res = WRAP(AAudioStream_waitForStateChange)( - stm->ostream, AAUDIO_STREAM_STATE_UNKNOWN, &ostate, 0); - if (res != AAUDIO_OK) { - LOG("AAudioStream_waitForStateChanged: %s", - WRAP(AAudio_convertResultToText)(res)); - return; - } - assert(ostate); - } - - // handle invalid stream states - if (istate == AAUDIO_STREAM_STATE_PAUSING || - istate == AAUDIO_STREAM_STATE_PAUSED || - istate == AAUDIO_STREAM_STATE_FLUSHING || - istate == AAUDIO_STREAM_STATE_FLUSHED || - istate == AAUDIO_STREAM_STATE_UNKNOWN || - istate == AAUDIO_STREAM_STATE_DISCONNECTED) { - const char * name = WRAP(AAudio_convertStreamStateToText)(istate); - LOG("Unexpected android input stream state %s", name); - shutdown(stm); - return; - } - - if (ostate == AAUDIO_STREAM_STATE_PAUSING || - ostate == AAUDIO_STREAM_STATE_PAUSED || - ostate == AAUDIO_STREAM_STATE_FLUSHING || - ostate == AAUDIO_STREAM_STATE_FLUSHED || - ostate == AAUDIO_STREAM_STATE_UNKNOWN || - ostate == AAUDIO_STREAM_STATE_DISCONNECTED) { - const char * name = WRAP(AAudio_convertStreamStateToText)(istate); - LOG("Unexpected android output stream state %s", name); - shutdown(stm); - return; - } - - switch (old_state) { - case stream_state::STARTING: - if ((!istate || istate == AAUDIO_STREAM_STATE_STARTED) && - (!ostate || ostate == AAUDIO_STREAM_STATE_STARTED)) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - new_state = stream_state::STARTED; - } - break; - case stream_state::DRAINING: - // The DRAINING state means that we want to stop the streams but - // may not have done so yet. - // The aaudio docs state that returning STOP from the callback isn't - // enough, the stream has to be stopped from another thread - // afterwards. - // No callbacks are triggered anymore when requestStop returns. - // That is important as we otherwise might read from a closed istream - // for a duplex stream. - // Therefor it is important to close ostream first. - if (ostate && ostate != AAUDIO_STREAM_STATE_STOPPING && - ostate != AAUDIO_STREAM_STATE_STOPPED) { - res = WRAP(AAudioStream_requestStop)(stm->ostream); - if (res != AAUDIO_OK) { - LOG("AAudioStream_requestStop: %s", - WRAP(AAudio_convertResultToText)(res)); - return; - } - } - if (istate && istate != AAUDIO_STREAM_STATE_STOPPING && - istate != AAUDIO_STREAM_STATE_STOPPED) { - res = WRAP(AAudioStream_requestStop)(stm->istream); - if (res != AAUDIO_OK) { - LOG("AAudioStream_requestStop: %s", - WRAP(AAudio_convertResultToText)(res)); - return; - } - } - - // we always wait until both streams are stopped until we - // send CUBEB_STATE_DRAINED. Then we can directly transition - // our logical state to STOPPED, not triggering - // an additional CUBEB_STATE_STOPPED callback (which might - // be unexpected for the user). - if ((!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED) && - (!istate || istate == AAUDIO_STREAM_STATE_STOPPED)) { - new_state = stream_state::STOPPED; - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - } - break; - case stream_state::STOPPING: - assert(!istate || istate == AAUDIO_STREAM_STATE_STOPPING || - istate == AAUDIO_STREAM_STATE_STOPPED); - assert(!ostate || ostate == AAUDIO_STREAM_STATE_STOPPING || - ostate == AAUDIO_STREAM_STATE_STOPPED); - if ((!istate || istate == AAUDIO_STREAM_STATE_STOPPED) && - (!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED)) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - new_state = stream_state::STOPPED; - } - break; - default: - assert(false && "Unreachable: invalid state"); - } - } while (old_state != new_state && - !stm->state.compare_exchange_strong(old_state, new_state)); -} - -// See https://nyorain.github.io/lock-free-wakeup.html for a note -// why this is needed. The audio thread notifies the state thread about -// state changes and must not block. The state thread on the other hand should -// sleep until there is work to be done. So we need a lockfree producer -// and blocking producer. This can only be achieved safely with a new thread -// that only serves as notifier backup (in case the notification happens -// right between the state thread checking and going to sleep in which case -// this thread will kick in and signal it right again). -static void -notifier_thread(cubeb * ctx) -{ - unique_lock lock(ctx->state.mutex); - - while (!ctx->state.join.load()) { - ctx->state.cond.wait(lock); - if (ctx->state.waiting.load()) { - // This must signal our state thread since there is no other - // thread currently waiting on the condition variable. - // The state change thread is guaranteed to be waiting since - // we hold the mutex it locks when awake. - ctx->state.cond.notify_one(); - } - } - - // make sure other thread joins as well - ctx->state.cond.notify_one(); - LOG("Exiting notifier thread"); -} - -static void -state_thread(cubeb * ctx) -{ - unique_lock lock(ctx->state.mutex); - - bool waiting = false; - while (!ctx->state.join.load()) { - waiting |= ctx->state.waiting.load(); - if (waiting) { - ctx->state.waiting.store(false); - waiting = false; - for (unsigned i = 0u; i < MAX_STREAMS; ++i) { - cubeb_stream * stm = &ctx->streams[i]; - update_state(stm); - waiting |= waiting_state(atomic_load(&stm->state)); - } - - // state changed from another thread, update again immediately - if (ctx->state.waiting.load()) { - waiting = true; - continue; - } - - // Not waiting for any change anymore: we can wait on the - // condition variable without timeout - if (!waiting) { - continue; - } - - // while any stream is waiting for state change we sleep with regular - // timeouts. But we wake up immediately if signaled. - // This might seem like a poor man's implementation of state change - // waiting but (as of october 2020), the implementation of - // AAudioStream_waitForStateChange is just sleeping with regular - // timeouts as well: - // https://android.googlesource.com/platform/frameworks/av/+/refs/heads/master/media/libaaudio/src/core/AudioStream.cpp - auto dur = std::chrono::milliseconds(5); - ctx->state.cond.wait_for(lock, dur); - } else { - ctx->state.cond.wait(lock); - } - } - - // make sure other thread joins as well - ctx->state.cond.notify_one(); - LOG("Exiting state thread"); -} - -static char const * -aaudio_get_backend_id(cubeb * /* ctx */) -{ - return "aaudio"; -} - -static int -aaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) -{ - assert(ctx && max_channels); - // NOTE: we might get more, AAudio docs don't specify anything. - *max_channels = 2; - return CUBEB_OK; -} - -static void -aaudio_destroy(cubeb * ctx) -{ - assert(ctx); - -#ifndef NDEBUG - // make sure all streams were destroyed - for (unsigned i = 0u; i < MAX_STREAMS; ++i) { - assert(!ctx->streams[i].in_use.load()); - } -#endif - - // broadcast joining to both threads - // they will additionally signal each other before joining - ctx->state.join.store(true); - ctx->state.cond.notify_all(); - - if (ctx->state.thread.joinable()) { - ctx->state.thread.join(); - } - if (ctx->state.notifier.joinable()) { - ctx->state.notifier.join(); - } - - if (ctx->libaaudio) { - dlclose(ctx->libaaudio); - } - delete ctx; -} - -static void -apply_volume(cubeb_stream * stm, void * audio_data, uint32_t num_frames) -{ - float volume = stm->volume.load(); - // optimization: we don't have to change anything in this case - if (volume == 1.f) { - return; - } - - switch (stm->out_format) { - case CUBEB_SAMPLE_S16NE: - for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) { - (static_cast(audio_data))[i] *= volume; - } - break; - case CUBEB_SAMPLE_FLOAT32NE: - for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) { - (static_cast(audio_data))[i] *= volume; - } - break; - default: - assert(false && "Unreachable: invalid stream out_format"); - } -} - -// Returning AAUDIO_CALLBACK_RESULT_STOP seems to put the stream in -// an invalid state. Seems like an AAudio bug/bad documentation. -// We therefore only return it on error. - -static aaudio_data_callback_result_t -aaudio_duplex_data_cb(AAudioStream * astream, void * user_data, - void * audio_data, int32_t num_frames) -{ - cubeb_stream * stm = (cubeb_stream *)user_data; - assert(stm->ostream == astream); - assert(stm->istream); - assert(num_frames >= 0); - - stream_state state = atomic_load(&stm->state); - // int istate = WRAP(AAudioStream_getState)(stm->istream); - // int ostate = WRAP(AAudioStream_getState)(stm->ostream); - // ALOGV("aaudio duplex data cb on stream %p: state %ld (in: %d, out: %d), - // num_frames: %ld", - // (void*) stm, state, istate, ostate, num_frames); - - // all other states may happen since the callback might be called - // from within requestStart - assert(state != stream_state::SHUTDOWN); - - // This might happen when we started draining but not yet actually - // stopped the stream from the state thread. - if (state == stream_state::DRAINING) { - std::memset(audio_data, 0x0, num_frames * stm->out_frame_size); - return AAUDIO_CALLBACK_RESULT_CONTINUE; - } - - // The aaudio docs state that AAudioStream_read must not be called on - // the stream associated with a callback. But we call it on the input stream - // while this callback is for the output stream so this is ok. - // We also pass timeout 0, giving us strong non-blocking guarantees. - // This is exactly how it's done in the aaudio duplex example code snippet. - long in_num_frames = - WRAP(AAudioStream_read)(stm->istream, stm->in_buf.get(), num_frames, 0); - if (in_num_frames < 0) { // error - stm->state.store(stream_state::ERROR); - LOG("AAudioStream_read: %s", - WRAP(AAudio_convertResultToText)(in_num_frames)); - return AAUDIO_CALLBACK_RESULT_STOP; - } - - // This can happen shortly after starting the stream. AAudio might immediately - // begin to buffer output but not have any input ready yet. We could - // block AAudioStream_read (passing a timeout > 0) but that leads to issues - // since blocking in this callback is a bad idea in general and it might break - // the stream when it is stopped by another thread shortly after being - // started. We therefore simply send silent input to the application, as shown - // in the AAudio duplex stream code example. - if (in_num_frames < num_frames) { - // LOG("AAudioStream_read returned not enough frames: %ld instead of %d", - // in_num_frames, num_frames); - unsigned left = num_frames - in_num_frames; - char * buf = stm->in_buf.get() + in_num_frames * stm->in_frame_size; - std::memset(buf, 0x0, left * stm->in_frame_size); - in_num_frames = num_frames; - } - - long done_frames = - cubeb_resampler_fill(stm->resampler, stm->in_buf.get(), &in_num_frames, - audio_data, num_frames); - - if (done_frames < 0 || done_frames > num_frames) { - LOG("Error in data callback or resampler: %ld", done_frames); - stm->state.store(stream_state::ERROR); - return AAUDIO_CALLBACK_RESULT_STOP; - } else if (done_frames < num_frames) { - stm->state.store(stream_state::DRAINING); - stm->context->state.waiting.store(true); - stm->context->state.cond.notify_one(); - - char * begin = - static_cast(audio_data) + done_frames * stm->out_frame_size; - std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size); - } - - apply_volume(stm, audio_data, done_frames); - return AAUDIO_CALLBACK_RESULT_CONTINUE; -} - -static aaudio_data_callback_result_t -aaudio_output_data_cb(AAudioStream * astream, void * user_data, - void * audio_data, int32_t num_frames) -{ - cubeb_stream * stm = (cubeb_stream *)user_data; - assert(stm->ostream == astream); - assert(!stm->istream); - assert(num_frames >= 0); - - stream_state state = stm->state.load(); - // int ostate = WRAP(AAudioStream_getState)(stm->ostream); - // ALOGV("aaudio output data cb on stream %p: state %ld (%d), num_frames: - // %ld", - // (void*) stm, state, ostate, num_frames); - - // all other states may happen since the callback might be called - // from within requestStart - assert(state != stream_state::SHUTDOWN); - - // This might happen when we started draining but not yet actually - // stopped the stream from the state thread. - if (state == stream_state::DRAINING) { - std::memset(audio_data, 0x0, num_frames * stm->out_frame_size); - return AAUDIO_CALLBACK_RESULT_CONTINUE; - } - - long done_frames = - cubeb_resampler_fill(stm->resampler, NULL, NULL, audio_data, num_frames); - if (done_frames < 0 || done_frames > num_frames) { - LOG("Error in data callback or resampler: %ld", done_frames); - stm->state.store(stream_state::ERROR); - return AAUDIO_CALLBACK_RESULT_STOP; - } else if (done_frames < num_frames) { - stm->state.store(stream_state::DRAINING); - stm->context->state.waiting.store(true); - stm->context->state.cond.notify_one(); - - char * begin = - static_cast(audio_data) + done_frames * stm->out_frame_size; - std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size); - } - - apply_volume(stm, audio_data, done_frames); - return AAUDIO_CALLBACK_RESULT_CONTINUE; -} - -static aaudio_data_callback_result_t -aaudio_input_data_cb(AAudioStream * astream, void * user_data, - void * audio_data, int32_t num_frames) -{ - cubeb_stream * stm = (cubeb_stream *)user_data; - assert(stm->istream == astream); - assert(!stm->ostream); - assert(num_frames >= 0); - - stream_state state = stm->state.load(); - // int istate = WRAP(AAudioStream_getState)(stm->istream); - // ALOGV("aaudio input data cb on stream %p: state %ld (%d), num_frames: %ld", - // (void*) stm, state, istate, num_frames); - - // all other states may happen since the callback might be called - // from within requestStart - assert(state != stream_state::SHUTDOWN); - - // This might happen when we started draining but not yet actually - // STOPPED the stream from the state thread. - if (state == stream_state::DRAINING) { - return AAUDIO_CALLBACK_RESULT_CONTINUE; - } - - long input_frame_count = num_frames; - long done_frames = cubeb_resampler_fill(stm->resampler, audio_data, - &input_frame_count, NULL, 0); - if (done_frames < 0 || done_frames > num_frames) { - LOG("Error in data callback or resampler: %ld", done_frames); - stm->state.store(stream_state::ERROR); - return AAUDIO_CALLBACK_RESULT_STOP; - } else if (done_frames < input_frame_count) { - // we don't really drain an input stream, just have to - // stop it from the state thread. That is signaled via the - // DRAINING state. - stm->state.store(stream_state::DRAINING); - stm->context->state.waiting.store(true); - stm->context->state.cond.notify_one(); - } - - return AAUDIO_CALLBACK_RESULT_CONTINUE; -} - -static void -aaudio_error_cb(AAudioStream * astream, void * user_data, aaudio_result_t error) -{ - cubeb_stream * stm = static_cast(user_data); - assert(stm->ostream == astream || stm->istream == astream); - LOG("AAudio error callback: %s", WRAP(AAudio_convertResultToText)(error)); - stm->state.store(stream_state::ERROR); -} - -static int -realize_stream(AAudioStreamBuilder * sb, const cubeb_stream_params * params, - AAudioStream ** stream, unsigned * frame_size) -{ - aaudio_result_t res; - assert(params->rate); - assert(params->channels); - - WRAP(AAudioStreamBuilder_setSampleRate)(sb, params->rate); - WRAP(AAudioStreamBuilder_setChannelCount)(sb, params->channels); - - aaudio_format_t fmt; - switch (params->format) { - case CUBEB_SAMPLE_S16NE: - fmt = AAUDIO_FORMAT_PCM_I16; - *frame_size = sizeof(int16_t) * params->channels; - break; - case CUBEB_SAMPLE_FLOAT32NE: - fmt = AAUDIO_FORMAT_PCM_FLOAT; - *frame_size = sizeof(float) * params->channels; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - - WRAP(AAudioStreamBuilder_setFormat)(sb, fmt); - res = WRAP(AAudioStreamBuilder_openStream)(sb, stream); - if (res == AAUDIO_ERROR_INVALID_FORMAT) { - LOG("AAudio device doesn't support output format %d", fmt); - return CUBEB_ERROR_INVALID_FORMAT; - } else if (params->rate && res == AAUDIO_ERROR_INVALID_RATE) { - // The requested rate is not supported. - // Just try again with default rate, we create a resampler anyways - WRAP(AAudioStreamBuilder_setSampleRate)(sb, AAUDIO_UNSPECIFIED); - res = WRAP(AAudioStreamBuilder_openStream)(sb, stream); - LOG("Requested rate of %u is not supported, inserting resampler", - params->rate); - } - - // When the app has no permission to record audio - // (android.permission.RECORD_AUDIO) but requested and input stream, this will - // return INVALID_ARGUMENT. - if (res != AAUDIO_OK) { - LOG("AAudioStreamBuilder_openStream: %s", - WRAP(AAudio_convertResultToText)(res)); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static void -aaudio_stream_destroy(cubeb_stream * stm) -{ - lock_guard lock(stm->mutex); - assert(stm->state == stream_state::STOPPED || - stm->state == stream_state::STOPPING || - stm->state == stream_state::INIT || - stm->state == stream_state::DRAINING || - stm->state == stream_state::ERROR || - stm->state == stream_state::SHUTDOWN); - - aaudio_result_t res; - - // No callbacks are triggered anymore when requestStop returns. - // That is important as we otherwise might read from a closed istream - // for a duplex stream. - if (stm->ostream) { - if (stm->state != stream_state::STOPPED && - stm->state != stream_state::STOPPING && - stm->state != stream_state::SHUTDOWN) { - res = WRAP(AAudioStream_requestStop)(stm->ostream); - if (res != AAUDIO_OK) { - LOG("AAudioStreamBuilder_requestStop: %s", - WRAP(AAudio_convertResultToText)(res)); - } - } - - WRAP(AAudioStream_close)(stm->ostream); - stm->ostream = NULL; - } - - if (stm->istream) { - if (stm->state != stream_state::STOPPED && - stm->state != stream_state::STOPPING && - stm->state != stream_state::SHUTDOWN) { - res = WRAP(AAudioStream_requestStop)(stm->istream); - if (res != AAUDIO_OK) { - LOG("AAudioStreamBuilder_requestStop: %s", - WRAP(AAudio_convertResultToText)(res)); - } - } - - WRAP(AAudioStream_close)(stm->istream); - stm->istream = NULL; - } - - if (stm->resampler) { - cubeb_resampler_destroy(stm->resampler); - stm->resampler = NULL; - } - - stm->in_buf = {}; - stm->in_frame_size = {}; - stm->out_format = {}; - stm->out_channels = {}; - stm->out_frame_size = {}; - - stm->state.store(stream_state::INIT); - stm->in_use.store(false); -} - -static int -aaudio_stream_init_impl(cubeb_stream * stm, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames) -{ - assert(stm->state.load() == stream_state::INIT); - stm->in_use.store(true); - - aaudio_result_t res; - AAudioStreamBuilder * sb; - res = WRAP(AAudio_createStreamBuilder)(&sb); - if (res != AAUDIO_OK) { - LOG("AAudio_createStreamBuilder: %s", - WRAP(AAudio_convertResultToText)(res)); - return CUBEB_ERROR; - } - - // make sure the builder is always destroyed - struct StreamBuilderDestructor { - void operator()(AAudioStreamBuilder * sb) - { - WRAP(AAudioStreamBuilder_delete)(sb); - } - }; - - std::unique_ptr sbPtr(sb); - - WRAP(AAudioStreamBuilder_setErrorCallback)(sb, aaudio_error_cb, stm); - WRAP(AAudioStreamBuilder_setBufferCapacityInFrames)(sb, latency_frames); - - AAudioStream_dataCallback in_data_callback{}; - AAudioStream_dataCallback out_data_callback{}; - if (output_stream_params && input_stream_params) { - out_data_callback = aaudio_duplex_data_cb; - in_data_callback = NULL; - } else if (input_stream_params) { - in_data_callback = aaudio_input_data_cb; - } else if (output_stream_params) { - out_data_callback = aaudio_output_data_cb; - } else { - LOG("Tried to open stream without input or output parameters"); - return CUBEB_ERROR; - } - -#ifdef CUBEB_AAUDIO_EXCLUSIVE_STREAM - LOG("AAudio setting exclusive share mode for stream"); - WRAP(AAudioStreamBuilder_setSharingMode)(sb, AAUDIO_SHARING_MODE_EXCLUSIVE); -#endif - - if (latency_frames <= POWERSAVE_LATENCY_FRAMES_THRESHOLD) { - LOG("AAudio setting low latency mode for stream"); - WRAP(AAudioStreamBuilder_setPerformanceMode) - (sb, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); - } else { - LOG("AAudio setting power saving mode for stream"); - WRAP(AAudioStreamBuilder_setPerformanceMode) - (sb, AAUDIO_PERFORMANCE_MODE_POWER_SAVING); - } - - unsigned frame_size; - - // initialize streams - // output - uint32_t target_sample_rate = 0; - cubeb_stream_params out_params; - if (output_stream_params) { - int output_preset = stm->voice_output ? AAUDIO_USAGE_VOICE_COMMUNICATION - : AAUDIO_USAGE_MEDIA; - WRAP(AAudioStreamBuilder_setUsage)(sb, output_preset); - WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_OUTPUT); - WRAP(AAudioStreamBuilder_setDataCallback)(sb, out_data_callback, stm); - int res_err = - realize_stream(sb, output_stream_params, &stm->ostream, &frame_size); - if (res_err) { - return res_err; - } - - // output debug information - aaudio_sharing_mode_t sm = WRAP(AAudioStream_getSharingMode)(stm->ostream); - aaudio_performance_mode_t pm = - WRAP(AAudioStream_getPerformanceMode)(stm->ostream); - int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->ostream); - int bsize = WRAP(AAudioStream_getBufferSizeInFrames)(stm->ostream); - int rate = WRAP(AAudioStream_getSampleRate)(stm->ostream); - LOG("AAudio output stream sharing mode: %d", sm); - LOG("AAudio output stream performance mode: %d", pm); - LOG("AAudio output stream buffer capacity: %d", bcap); - LOG("AAudio output stream buffer size: %d", bsize); - LOG("AAudio output stream buffer rate: %d", rate); - - target_sample_rate = output_stream_params->rate; - out_params = *output_stream_params; - out_params.rate = rate; - - stm->out_channels = output_stream_params->channels; - stm->out_format = output_stream_params->format; - stm->out_frame_size = frame_size; - stm->volume.store(1.f); - } - - // input - cubeb_stream_params in_params; - if (input_stream_params) { - // Match what the OpenSL backend does for now, we could use UNPROCESSED and - // VOICE_COMMUNICATION here, but we'd need to make it clear that - // application-level AEC and other voice processing should be disabled - // there. - int input_preset = stm->voice_input ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION - : AAUDIO_INPUT_PRESET_CAMCORDER; - WRAP(AAudioStreamBuilder_setInputPreset)(sb, input_preset); - WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_INPUT); - WRAP(AAudioStreamBuilder_setDataCallback)(sb, in_data_callback, stm); - int res_err = - realize_stream(sb, input_stream_params, &stm->istream, &frame_size); - if (res_err) { - return res_err; - } - - // output debug information - aaudio_sharing_mode_t sm = WRAP(AAudioStream_getSharingMode)(stm->istream); - aaudio_performance_mode_t pm = - WRAP(AAudioStream_getPerformanceMode)(stm->istream); - int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->istream); - int bsize = WRAP(AAudioStream_getBufferSizeInFrames)(stm->istream); - int rate = WRAP(AAudioStream_getSampleRate)(stm->istream); - LOG("AAudio input stream sharing mode: %d", sm); - LOG("AAudio input stream performance mode: %d", pm); - LOG("AAudio input stream buffer capacity: %d", bcap); - LOG("AAudio input stream buffer size: %d", bsize); - LOG("AAudio input stream buffer rate: %d", rate); - - stm->in_buf.reset(new char[bcap * frame_size]()); - assert(!target_sample_rate || - target_sample_rate == input_stream_params->rate); - - target_sample_rate = input_stream_params->rate; - in_params = *input_stream_params; - in_params.rate = rate; - stm->in_frame_size = frame_size; - } - - // initialize resampler - stm->resampler = cubeb_resampler_create( - stm, input_stream_params ? &in_params : NULL, - output_stream_params ? &out_params : NULL, target_sample_rate, - stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT, - CUBEB_RESAMPLER_RECLOCK_NONE); - - if (!stm->resampler) { - LOG("Failed to create resampler"); - return CUBEB_ERROR; - } - - // the stream isn't started initially. We don't need to differentiate - // between a stream that was just initialized and one that played - // already but was stopped. - stm->state.store(stream_state::STOPPED); - LOG("Cubeb stream (%p) INIT success", (void *)stm); - return CUBEB_OK; -} - -static int -aaudio_stream_init(cubeb * ctx, cubeb_stream ** stream, - char const * /* stream_name */, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) -{ - assert(!input_device); - assert(!output_device); - - // atomically find a free stream. - cubeb_stream * stm = NULL; - unique_lock lock; - for (unsigned i = 0u; i < MAX_STREAMS; ++i) { - // This check is only an optimization, we don't strictly need it - // since we check again after locking the mutex. - if (ctx->streams[i].in_use.load()) { - continue; - } - - // if this fails, another thread initialized this stream - // between our check of in_use and this. - lock = unique_lock(ctx->streams[i].mutex, std::try_to_lock); - if (!lock.owns_lock()) { - continue; - } - - if (ctx->streams[i].in_use.load()) { - lock = {}; - continue; - } - - stm = &ctx->streams[i]; - break; - } - - if (!stm) { - LOG("Error: maximum number of streams reached"); - return CUBEB_ERROR; - } - - stm->context = ctx; - stm->user_ptr = user_ptr; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->voice_input = input_stream_params && - !!(input_stream_params->prefs & CUBEB_STREAM_PREF_VOICE); - stm->voice_output = output_stream_params && - !!(output_stream_params->prefs & CUBEB_STREAM_PREF_VOICE); - stm->previous_clock = 0; - - LOG("cubeb stream prefs: voice_input: %s voice_output: %s", - stm->voice_input ? "true" : "false", - stm->voice_output ? "true" : "false"); - - int err = aaudio_stream_init_impl(stm, input_device, input_stream_params, - output_device, output_stream_params, - latency_frames); - if (err != CUBEB_OK) { - // This is needed since aaudio_stream_destroy will lock the mutex again. - // It's no problem that there is a gap in between as the stream isn't - // actually in u se. - lock.unlock(); - aaudio_stream_destroy(stm); - return err; - } - - *stream = stm; - return CUBEB_OK; -} - -static int -aaudio_stream_start(cubeb_stream * stm) -{ - assert(stm && stm->in_use.load()); - lock_guard lock(stm->mutex); - - stream_state state = stm->state.load(); - int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0; - int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0; - LOGV("STARTING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate); - - switch (state) { - case stream_state::STARTED: - case stream_state::STARTING: - LOG("cubeb stream %p already STARTING/STARTED", (void *)stm); - return CUBEB_OK; - case stream_state::ERROR: - case stream_state::SHUTDOWN: - return CUBEB_ERROR; - case stream_state::INIT: - assert(false && "Invalid stream"); - return CUBEB_ERROR; - case stream_state::STOPPED: - case stream_state::STOPPING: - case stream_state::DRAINING: - break; - } - - aaudio_result_t res; - - // Important to start istream before ostream. - // As soon as we start ostream, the callbacks might be triggered an we - // might read from istream (on duplex). If istream wasn't started yet - // this is a problem. - if (stm->istream) { - res = WRAP(AAudioStream_requestStart)(stm->istream); - if (res != AAUDIO_OK) { - LOG("AAudioStream_requestStart (istream): %s", - WRAP(AAudio_convertResultToText)(res)); - stm->state.store(stream_state::ERROR); - return CUBEB_ERROR; - } - } - - if (stm->ostream) { - res = WRAP(AAudioStream_requestStart)(stm->ostream); - if (res != AAUDIO_OK) { - LOG("AAudioStream_requestStart (ostream): %s", - WRAP(AAudio_convertResultToText)(res)); - stm->state.store(stream_state::ERROR); - return CUBEB_ERROR; - } - } - - int ret = CUBEB_OK; - bool success; - - while (!(success = stm->state.compare_exchange_strong( - state, stream_state::STARTING))) { - // we land here only if the state has changed in the meantime - switch (state) { - // If an error ocurred in the meantime, we can't change that. - // The stream will be stopped when shut down. - case stream_state::ERROR: - ret = CUBEB_ERROR; - break; - // The only situation in which the state could have switched to draining - // is if the callback was already fired and requested draining. Don't - // overwrite that. It's not an error either though. - case stream_state::DRAINING: - break; - - // If the state switched [DRAINING -> STOPPING] or [DRAINING/STOPPING -> - // STOPPED] in the meantime, we can simply overwrite that since we restarted - // the stream. - case stream_state::STOPPING: - case stream_state::STOPPED: - continue; - - // There is no situation in which the state could have been valid before - // but now in shutdown mode, since we hold the streams mutex. - // There is also no way that it switched *into* STARTING or - // STARTED mode. - default: - assert(false && "Invalid state change"); - ret = CUBEB_ERROR; - break; - } - - break; - } - - if (success) { - stm->context->state.waiting.store(true); - stm->context->state.cond.notify_one(); - } - - return ret; -} - -static int -aaudio_stream_stop(cubeb_stream * stm) -{ - assert(stm && stm->in_use.load()); - lock_guard lock(stm->mutex); - - stream_state state = stm->state.load(); - int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0; - int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0; - LOGV("STOPPING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate); - - switch (state) { - case stream_state::STOPPED: - case stream_state::STOPPING: - case stream_state::DRAINING: - LOG("cubeb stream %p already STOPPING/STOPPED", (void *)stm); - return CUBEB_OK; - case stream_state::ERROR: - case stream_state::SHUTDOWN: - return CUBEB_ERROR; - case stream_state::INIT: - assert(false && "Invalid stream"); - return CUBEB_ERROR; - case stream_state::STARTED: - case stream_state::STARTING: - break; - } - - aaudio_result_t res; - - // No callbacks are triggered anymore when requestStop returns. - // That is important as we otherwise might read from a closed istream - // for a duplex stream. - // Therefor it is important to close ostream first. - if (stm->ostream) { - // Could use pause + flush here as well, the public cubeb interface - // doesn't state behavior. - res = WRAP(AAudioStream_requestStop)(stm->ostream); - if (res != AAUDIO_OK) { - LOG("AAudioStream_requestStop (ostream): %s", - WRAP(AAudio_convertResultToText)(res)); - stm->state.store(stream_state::ERROR); - return CUBEB_ERROR; - } - } - - if (stm->istream) { - res = WRAP(AAudioStream_requestStop)(stm->istream); - if (res != AAUDIO_OK) { - LOG("AAudioStream_requestStop (istream): %s", - WRAP(AAudio_convertResultToText)(res)); - stm->state.store(stream_state::ERROR); - return CUBEB_ERROR; - } - } - - int ret = CUBEB_OK; - bool success; - while (!(success = atomic_compare_exchange_strong(&stm->state, &state, - stream_state::STOPPING))) { - // we land here only if the state has changed in the meantime - switch (state) { - // If an error ocurred in the meantime, we can't change that. - // The stream will be STOPPED when shut down. - case stream_state::ERROR: - ret = CUBEB_ERROR; - break; - // If it was switched to DRAINING in the meantime, it was or - // will be STOPPED soon anyways. We don't interfere with - // the DRAINING process, no matter in which state. - // Not an error - case stream_state::DRAINING: - case stream_state::STOPPING: - case stream_state::STOPPED: - break; - - // If the state switched from STARTING to STARTED in the meantime - // we can simply overwrite that since we just STOPPED it. - case stream_state::STARTED: - continue; - - // There is no situation in which the state could have been valid before - // but now in shutdown mode, since we hold the streams mutex. - // There is also no way that it switched *into* STARTING mode. - default: - assert(false && "Invalid state change"); - ret = CUBEB_ERROR; - break; - } - - break; - } - - if (success) { - stm->context->state.waiting.store(true); - stm->context->state.cond.notify_one(); - } - - return ret; -} - -static int -aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) -{ - assert(stm && stm->in_use.load()); - lock_guard lock(stm->mutex); - - stream_state state = stm->state.load(); - AAudioStream * stream = stm->ostream ? stm->ostream : stm->istream; - switch (state) { - case stream_state::ERROR: - case stream_state::SHUTDOWN: - return CUBEB_ERROR; - case stream_state::DRAINING: - case stream_state::STOPPED: - case stream_state::STOPPING: - // getTimestamp is only valid when the stream is playing. - // Simply return the number of frames passed to aaudio - *position = WRAP(AAudioStream_getFramesRead)(stream); - if (*position < stm->previous_clock) { - *position = stm->previous_clock; - } else { - stm->previous_clock = *position; - } - return CUBEB_OK; - case stream_state::INIT: - assert(false && "Invalid stream"); - return CUBEB_ERROR; - case stream_state::STARTED: - case stream_state::STARTING: - break; - } - - int64_t pos; - int64_t ns; - aaudio_result_t res; - res = WRAP(AAudioStream_getTimestamp)(stream, CLOCK_MONOTONIC, &pos, &ns); - if (res != AAUDIO_OK) { - // When the audio stream is not running, invalid_state is returned and we - // simply fall back to the method we use for non-playing streams. - if (res == AAUDIO_ERROR_INVALID_STATE) { - *position = WRAP(AAudioStream_getFramesRead)(stream); - if (*position < stm->previous_clock) { - *position = stm->previous_clock; - } else { - stm->previous_clock = *position; - } - return CUBEB_OK; - } - - LOG("AAudioStream_getTimestamp: %s", WRAP(AAudio_convertResultToText)(res)); - return CUBEB_ERROR; - } - - *position = pos; - if (*position < stm->previous_clock) { - *position = stm->previous_clock; - } else { - stm->previous_clock = *position; - } - return CUBEB_OK; -} - -static int -aaudio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) -{ - int64_t pos; - int64_t ns; - aaudio_result_t res; - - if (!stm->ostream) { - LOG("error: aaudio_stream_get_latency on input-only stream"); - return CUBEB_ERROR; - } - - res = - WRAP(AAudioStream_getTimestamp)(stm->ostream, CLOCK_MONOTONIC, &pos, &ns); - if (res != AAUDIO_OK) { - LOG("aaudio_stream_get_latency, AAudioStream_getTimestamp: %s, returning " - "memoized value", - WRAP(AAudio_convertResultToText)(res)); - // Expected when the stream is paused. - *latency = stm->latest_output_latency; - return CUBEB_OK; - } - - int64_t read = WRAP(AAudioStream_getFramesRead)(stm->ostream); - - *latency = stm->latest_output_latency = read - pos; - LOG("aaudio_stream_get_latency, %u", *latency); - - return CUBEB_OK; -} - -static int -aaudio_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency) -{ - int64_t pos; - int64_t ns; - aaudio_result_t res; - - if (!stm->istream) { - LOG("error: aaudio_stream_get_input_latency on an ouput-only stream"); - return CUBEB_ERROR; - } - - res = - WRAP(AAudioStream_getTimestamp)(stm->istream, CLOCK_MONOTONIC, &pos, &ns); - if (res != AAUDIO_OK) { - // Expected when the stream is paused. - LOG("aaudio_stream_get_input_latency, AAudioStream_getTimestamp: %s, " - "returning memoized value", - WRAP(AAudio_convertResultToText)(res)); - *latency = stm->latest_input_latency; - return CUBEB_OK; - } - - int64_t written = WRAP(AAudioStream_getFramesWritten)(stm->istream); - - *latency = stm->latest_input_latency = written - pos; - LOG("aaudio_stream_get_input_latency, %u", *latency); - - return CUBEB_OK; -} - -static int -aaudio_stream_set_volume(cubeb_stream * stm, float volume) -{ - assert(stm && stm->in_use.load() && stm->ostream); - stm->volume.store(volume); - return CUBEB_OK; -} - -aaudio_data_callback_result_t -dummy_callback(AAudioStream * stream, void * userData, void * audioData, - int32_t numFrames) -{ - return AAUDIO_CALLBACK_RESULT_STOP; -} - -// Returns a dummy stream with all default settings -static AAudioStream * -init_dummy_stream() -{ - AAudioStreamBuilder * streamBuilder; - aaudio_result_t res; - res = WRAP(AAudio_createStreamBuilder)(&streamBuilder); - if (res != AAUDIO_OK) { - LOG("init_dummy_stream: AAudio_createStreamBuilder: %s", - WRAP(AAudio_convertResultToText)(res)); - return nullptr; - } - WRAP(AAudioStreamBuilder_setDataCallback) - (streamBuilder, dummy_callback, nullptr); - WRAP(AAudioStreamBuilder_setPerformanceMode) - (streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); - - AAudioStream * stream; - res = WRAP(AAudioStreamBuilder_openStream)(streamBuilder, &stream); - if (res != AAUDIO_OK) { - LOG("init_dummy_stream: AAudioStreamBuilder_openStream %s", - WRAP(AAudio_convertResultToText)(res)); - return nullptr; - } - WRAP(AAudioStreamBuilder_delete)(streamBuilder); - - return stream; -} - -static void -destroy_dummy_stream(AAudioStream * stream) -{ - WRAP(AAudioStream_close)(stream); -} - -static int -aaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, - uint32_t * latency_frames) -{ - AAudioStream * stream = init_dummy_stream(); - - if (!stream) { - return CUBEB_ERROR; - } - - // https://android.googlesource.com/platform/compatibility/cdd/+/refs/heads/master/5_multimedia/5_6_audio-latency.md - *latency_frames = WRAP(AAudioStream_getFramesPerBurst)(stream); - - LOG("aaudio_get_min_latency: %u frames", *latency_frames); - - destroy_dummy_stream(stream); - - return CUBEB_OK; -} - -int -aaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) -{ - AAudioStream * stream = init_dummy_stream(); - - if (!stream) { - return CUBEB_ERROR; - } - - *rate = WRAP(AAudioStream_getSampleRate)(stream); - - LOG("aaudio_get_preferred_sample_rate %uHz", *rate); - - destroy_dummy_stream(stream); - - return CUBEB_OK; -} - -extern "C" int -aaudio_init(cubeb ** context, char const * context_name); - -const static struct cubeb_ops aaudio_ops = { - /*.init =*/aaudio_init, - /*.get_backend_id =*/aaudio_get_backend_id, - /*.get_max_channel_count =*/aaudio_get_max_channel_count, - /* .get_min_latency =*/aaudio_get_min_latency, - /*.get_preferred_sample_rate =*/aaudio_get_preferred_sample_rate, - /*.enumerate_devices =*/NULL, - /*.device_collection_destroy =*/NULL, - /*.destroy =*/aaudio_destroy, - /*.stream_init =*/aaudio_stream_init, - /*.stream_destroy =*/aaudio_stream_destroy, - /*.stream_start =*/aaudio_stream_start, - /*.stream_stop =*/aaudio_stream_stop, - /*.stream_get_position =*/aaudio_stream_get_position, - /*.stream_get_latency =*/aaudio_stream_get_latency, - /*.stream_get_input_latency =*/aaudio_stream_get_input_latency, - /*.stream_set_volume =*/aaudio_stream_set_volume, - /*.stream_set_name =*/NULL, - /*.stream_get_current_device =*/NULL, - /*.stream_device_destroy =*/NULL, - /*.stream_register_device_changed_callback =*/NULL, - /*.register_device_collection_changed =*/NULL}; - -extern "C" /*static*/ int -aaudio_init(cubeb ** context, char const * /* context_name */) -{ - // load api - void * libaaudio = NULL; -#ifndef DISABLE_LIBAAUDIO_DLOPEN - libaaudio = dlopen("libaaudio.so", RTLD_NOW); - if (!libaaudio) { - return CUBEB_ERROR; - } - -#define LOAD(x) \ - { \ - cubeb_##x = (decltype(x) *)(dlsym(libaaudio, #x)); \ - if (!WRAP(x)) { \ - LOG("AAudio: Failed to load %s", #x); \ - dlclose(libaaudio); \ - return CUBEB_ERROR; \ - } \ - } - - LIBAAUDIO_API_VISIT(LOAD); -#undef LOAD -#endif - - cubeb * ctx = new cubeb; - ctx->ops = &aaudio_ops; - ctx->libaaudio = libaaudio; - - ctx->state.thread = std::thread(state_thread, ctx); - - // NOTE: using platform-specific APIs we could set the priority of the - // notifier thread lower than the priority of the state thread. - // This way, it's more likely that the state thread will be woken up - // by the condition variable signal when both are currently waiting - ctx->state.notifier = std::thread(notifier_thread, ctx); - - *context = ctx; - return CUBEB_OK; -} diff --git a/dep/cubeb/src/cubeb_alsa.c b/dep/cubeb/src/cubeb_alsa.c index b1464d154..6b53df087 100644 --- a/dep/cubeb/src/cubeb_alsa.c +++ b/dep/cubeb/src/cubeb_alsa.c @@ -7,9 +7,13 @@ #undef NDEBUG #define _DEFAULT_SOURCE #define _BSD_SOURCE +#if defined(__NetBSD__) +#define _NETBSD_SOURCE /* timersub() */ +#endif #define _XOPEN_SOURCE 500 #include "cubeb-internal.h" #include "cubeb/cubeb.h" +#include "cubeb_tracing.h" #include #include #include @@ -579,10 +583,14 @@ alsa_run_thread(void * context) cubeb * ctx = context; int r; + CUBEB_REGISTER_THREAD("cubeb rendering thread"); + do { r = alsa_run(ctx); } while (r >= 0); + CUBEB_UNREGISTER_THREAD(); + return NULL; } @@ -957,11 +965,11 @@ alsa_destroy(cubeb * ctx) WRAP(snd_config_delete)(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); } - +#ifndef DISABLE_LIBASOUND_DLOPEN if (ctx->libasound) { dlclose(ctx->libasound); } - +#endif free(ctx); } diff --git a/dep/cubeb/src/cubeb_android.h b/dep/cubeb/src/cubeb_android.h deleted file mode 100644 index c21a941ab..000000000 --- a/dep/cubeb/src/cubeb_android.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef CUBEB_ANDROID_H -#define CUBEB_ANDROID_H - -#ifdef __cplusplus -extern "C" { -#endif -// If the latency requested is above this threshold, this stream is considered -// intended for playback (vs. real-time). Tell Android it should favor saving -// power over performance or latency. -// This is around 100ms at 44100 or 48000 -const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000; - -#ifdef __cplusplus -}; -#endif - -#endif // CUBEB_ANDROID_H diff --git a/dep/cubeb/src/cubeb_array_queue.h b/dep/cubeb/src/cubeb_array_queue.h deleted file mode 100644 index d6d958132..000000000 --- a/dep/cubeb/src/cubeb_array_queue.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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/dep/cubeb/src/cubeb_audiotrack.c b/dep/cubeb/src/cubeb_audiotrack.c deleted file mode 100644 index 59deba148..000000000 --- a/dep/cubeb/src/cubeb_audiotrack.c +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright © 2013 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#if !defined(NDEBUG) -#define NDEBUG -#endif -#include -#include -#include -#include -#include -#include - -#include "android/audiotrack_definitions.h" -#include "cubeb-internal.h" -#include "cubeb/cubeb.h" - -#ifndef ALOG -#if defined(DEBUG) || defined(FORCE_ALOG) -#define ALOG(args...) \ - __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb", ##args) -#else -#define ALOG(args...) -#endif -#endif - -/** - * A lot of bytes for safety. It should be possible to bring this down a bit. */ -#define SIZE_AUDIOTRACK_INSTANCE 256 - -/** - * call dlsym to get the symbol |mangled_name|, handle the error and store the - * pointer in |pointer|. Because depending on Android version, we want different - * symbols, not finding a symbol is not an error. */ -#define DLSYM_DLERROR(mangled_name, pointer, lib) \ - do { \ - pointer = dlsym(lib, mangled_name); \ - if (!pointer) { \ - ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \ - } else { \ - ALOG("%stm: OK", mangled_name); \ - } \ - } while (0); - -static struct cubeb_ops const audiotrack_ops; -void -audiotrack_destroy(cubeb * context); -void -audiotrack_stream_destroy(cubeb_stream * stream); - -struct AudioTrack { - /* only available on ICS and later. The second int paramter is in fact of type - * audio_stream_type_t. */ - /* static */ status_t (*get_min_frame_count)(int * frame_count, - int stream_type, uint32_t rate); - /* if we have a recent ctor, but can't find the above symbol, we - * can get the minimum frame count with this signature, and we are - * running gingerbread. */ - /* static */ status_t (*get_min_frame_count_gingerbread)(int * frame_count, - int stream_type, - uint32_t rate); - void * (*ctor)(void * instance, int, unsigned int, int, int, int, - unsigned int, void (*)(int, void *, void *), void *, int, int); - void * (*dtor)(void * instance); - void (*start)(void * instance); - void (*pause)(void * instance); - uint32_t (*latency)(void * instance); - status_t (*check)(void * instance); - status_t (*get_position)(void * instance, uint32_t * position); - /* static */ int (*get_output_samplingrate)(int * samplerate, int stream); - status_t (*set_marker_position)(void * instance, unsigned int); - status_t (*set_volume)(void * instance, float left, float right); -}; - -struct cubeb { - struct cubeb_ops const * ops; - void * library; - struct AudioTrack klass; -}; - -struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ - cubeb * context; - void * user_ptr; - /**/ - cubeb_stream_params params; - cubeb_data_callback data_callback; - cubeb_state_callback state_callback; - void * instance; - /* Number of frames that have been passed to the AudioTrack callback */ - long unsigned written; - int draining; -}; - -static void -audiotrack_refill(int event, void * user, void * info) -{ - cubeb_stream * stream = user; - switch (event) { - case EVENT_MORE_DATA: { - long got = 0; - struct Buffer * b = (struct Buffer *)info; - - if (stream->draining) { - return; - } - - got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, - b->frameCount); - - stream->written += got; - - if (got != (long)b->frameCount) { - stream->draining = 1; - /* set a marker so we are notified when the are done draining, that is, - * when every frame has been played by android. */ - stream->context->klass.set_marker_position(stream->instance, - stream->written); - } - - break; - } - case EVENT_UNDERRUN: - ALOG("underrun in cubeb backend."); - break; - case EVENT_LOOP_END: - assert(0 && "We don't support the loop feature of audiotrack."); - break; - case EVENT_MARKER: - assert(stream->draining); - stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); - break; - case EVENT_NEW_POS: - assert( - 0 && - "We don't support the setPositionUpdatePeriod feature of audiotrack."); - break; - case EVENT_BUFFER_END: - assert(0 && "Should not happen."); - break; - } -} - -/* We are running on gingerbread if we found the gingerbread signature for - * getMinFrameCount */ -static int -audiotrack_version_is_gingerbread(cubeb * ctx) -{ - return ctx->klass.get_min_frame_count_gingerbread != NULL; -} - -int -audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, - int * min_frame_count) -{ - status_t status; - /* Recent Android have a getMinFrameCount method. */ - if (!audiotrack_version_is_gingerbread(ctx)) { - status = ctx->klass.get_min_frame_count( - min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); - } else { - status = ctx->klass.get_min_frame_count_gingerbread( - min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); - } - if (status != 0) { - ALOG("error getting the min frame count"); - return CUBEB_ERROR; - } - return CUBEB_OK; -} - -int -audiotrack_init(cubeb ** context, char const * context_name) -{ - cubeb * ctx; - struct AudioTrack * c; - - assert(context); - *context = NULL; - - ctx = calloc(1, sizeof(*ctx)); - assert(ctx); - - /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android - * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on - * the first call to a dlsym'ed function. Somehow this does not happen when - * using only the name of the library. */ - ctx->library = dlopen("libmedia.so", RTLD_LAZY); - if (!ctx->library) { - ALOG("dlopen error: %s.", dlerror()); - free(ctx); - return CUBEB_ERROR; - } - - /* Recent Android first, then Gingerbread. */ - DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", - ctx->klass.ctor, ctx->library); - DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library); - - DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, - ctx->library); - DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, - ctx->library); - - DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", - ctx->klass.get_output_samplingrate, ctx->library); - - /* |getMinFrameCount| is available on gingerbread and ICS with different - * signatures. */ - DLSYM_DLERROR( - "_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", - ctx->klass.get_min_frame_count, ctx->library); - if (!ctx->klass.get_min_frame_count) { - DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", - ctx->klass.get_min_frame_count_gingerbread, ctx->library); - } - - DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, - ctx->library); - DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, - ctx->library); - DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", - ctx->klass.get_position, ctx->library); - DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", - ctx->klass.set_marker_position, ctx->library); - DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, - ctx->library); - - /* check that we have a combination of symbol that makes sense */ - c = &ctx->klass; - if (!(c->ctor && c->dtor && c->latency && c->check && - /* at least one way to get the minimum frame count to request. */ - (c->get_min_frame_count || c->get_min_frame_count_gingerbread) && - c->start && c->pause && c->get_position && c->set_marker_position)) { - ALOG("Could not find all the symbols we need."); - audiotrack_destroy(ctx); - return CUBEB_ERROR; - } - - ctx->ops = &audiotrack_ops; - - *context = ctx; - - return CUBEB_OK; -} - -char const * -audiotrack_get_backend_id(cubeb * context) -{ - return "audiotrack"; -} - -static int -audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) -{ - assert(ctx && max_channels); - - /* The android mixer handles up to two channels, see - http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 - */ - *max_channels = 2; - - return CUBEB_OK; -} - -static int -audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, - uint32_t * latency_ms) -{ - /* We always use the lowest latency possible when using this backend (see - * audiotrack_stream_init), so this value is not going to be used. */ - int r; - - r = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms); - if (r != CUBEB_OK) { - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) -{ - status_t r; - - r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */); - - return r == 0 ? CUBEB_OK : CUBEB_ERROR; -} - -void -audiotrack_destroy(cubeb * context) -{ - assert(context); - - dlclose(context->library); - - free(context); -} - -int -audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, - char const * stream_name, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency, cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) -{ - cubeb_stream * stm; - int32_t channels; - uint32_t min_frame_count; - - assert(ctx && stream); - - assert(!input_stream_params && "not supported"); - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; - } - - if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE || - output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) { - return CUBEB_ERROR_INVALID_FORMAT; - } - - if (audiotrack_get_min_frame_count(ctx, output_stream_params, - (int *)&min_frame_count)) { - return CUBEB_ERROR; - } - - stm = calloc(1, sizeof(*stm)); - assert(stm); - - stm->context = ctx; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; - stm->params = *output_stream_params; - - stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1); - (*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = - 0xbaadbaad; - assert(stm->instance && "cubeb: EOM"); - - /* gingerbread uses old channel layout enum */ - if (audiotrack_version_is_gingerbread(ctx)) { - channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy - : AUDIO_CHANNEL_OUT_MONO_Legacy; - } else { - channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS - : AUDIO_CHANNEL_OUT_MONO_ICS; - } - - ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate, - AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0, - audiotrack_refill, stm, 0, 0); - - assert((*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - - 4)) == 0xbaadbaad); - - if (ctx->klass.check(stm->instance)) { - ALOG("stream not initialized properly."); - audiotrack_stream_destroy(stm); - return CUBEB_ERROR; - } - - *stream = stm; - - return CUBEB_OK; -} - -void -audiotrack_stream_destroy(cubeb_stream * stream) -{ - assert(stream->context); - - stream->context->klass.dtor(stream->instance); - - free(stream->instance); - stream->instance = NULL; - free(stream); -} - -int -audiotrack_stream_start(cubeb_stream * stream) -{ - assert(stream->instance); - - stream->context->klass.start(stream->instance); - stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED); - - return CUBEB_OK; -} - -int -audiotrack_stream_stop(cubeb_stream * stream) -{ - assert(stream->instance); - - stream->context->klass.pause(stream->instance); - stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED); - - return CUBEB_OK; -} - -int -audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position) -{ - uint32_t p; - - assert(stream->instance && position); - stream->context->klass.get_position(stream->instance, &p); - *position = p; - - return CUBEB_OK; -} - -int -audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency) -{ - assert(stream->instance && latency); - - /* Android returns the latency in ms, we want it in frames. */ - *latency = stream->context->klass.latency(stream->instance); - /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */ - *latency = (*latency * stream->params.rate) / 1000; - - return 0; -} - -int -audiotrack_stream_set_volume(cubeb_stream * stream, float volume) -{ - status_t status; - - status = stream->context->klass.set_volume(stream->instance, volume, volume); - - if (status) { - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static struct cubeb_ops const audiotrack_ops = { - .init = audiotrack_init, - .get_backend_id = audiotrack_get_backend_id, - .get_max_channel_count = audiotrack_get_max_channel_count, - .get_min_latency = audiotrack_get_min_latency, - .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, - .enumerate_devices = NULL, - .device_collection_destroy = NULL, - .destroy = audiotrack_destroy, - .stream_init = audiotrack_stream_init, - .stream_destroy = audiotrack_stream_destroy, - .stream_start = audiotrack_stream_start, - .stream_stop = audiotrack_stream_stop, - .stream_get_position = audiotrack_stream_get_position, - .stream_get_latency = audiotrack_stream_get_latency, - .stream_get_input_latency = NULL, - .stream_set_volume = audiotrack_stream_set_volume, - .stream_set_name = NULL, - .stream_get_current_device = NULL, - .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL, - .register_device_collection_changed = NULL}; diff --git a/dep/cubeb/src/cubeb_jack.cpp b/dep/cubeb/src/cubeb_jack.cpp index 3b3056c64..6f921d607 100644 --- a/dep/cubeb/src/cubeb_jack.cpp +++ b/dep/cubeb/src/cubeb_jack.cpp @@ -8,7 +8,7 @@ */ #define _DEFAULT_SOURCE #define _BSD_SOURCE -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__NetBSD__) #define _POSIX_SOURCE #endif #include "cubeb-internal.h" @@ -788,10 +788,10 @@ cbjack_destroy(cubeb * context) if (context->jack_client != NULL) WRAP(jack_client_close)(context->jack_client); - +#ifndef DISABLE_LIBJACK_DLOPEN if (context->libjack) dlclose(context->libjack); - +#endif free(context); } diff --git a/dep/cubeb/src/cubeb_kai.c b/dep/cubeb/src/cubeb_kai.c deleted file mode 100644 index 0a0d67661..000000000 --- a/dep/cubeb/src/cubeb_kai.c +++ /dev/null @@ -1,369 +0,0 @@ -/* - * 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-internal.h" -#include "cubeb/cubeb.h" - -/* We don't support more than 2 channels in KAI */ -#define MAX_CHANNELS 2 - -#define NBUFS 2 -#define FRAME_SIZE 2048 - -struct cubeb_stream_item { - cubeb_stream * stream; -}; - -static struct cubeb_ops const kai_ops; - -struct cubeb { - struct cubeb_ops const * ops; -}; - -struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ - cubeb * context; - void * user_ptr; - /**/ - cubeb_stream_params params; - cubeb_data_callback data_callback; - cubeb_state_callback state_callback; - - HKAI hkai; - KAISPEC spec; - uint64_t total_frames; - float soft_volume; - _fmutex mutex; - float float_buffer[FRAME_SIZE * MAX_CHANNELS]; -}; - -static inline long -frames_to_bytes(long frames, cubeb_stream_params params) -{ - return frames * 2 * params.channels; /* 2 bytes per frame */ -} - -static inline long -bytes_to_frames(long bytes, cubeb_stream_params params) -{ - return bytes / 2 / params.channels; /* 2 bytes per frame */ -} - -static void -kai_destroy(cubeb * ctx); - -/*static*/ int -kai_init(cubeb ** context, char const * context_name) -{ - cubeb * ctx; - - XASSERT(context); - *context = NULL; - - if (kaiInit(KAIM_AUTO)) - return CUBEB_ERROR; - - ctx = calloc(1, sizeof(*ctx)); - XASSERT(ctx); - - ctx->ops = &kai_ops; - - *context = ctx; - - return CUBEB_OK; -} - -static char const * -kai_get_backend_id(cubeb * ctx) -{ - return "kai"; -} - -static void -kai_destroy(cubeb * ctx) -{ - kaiDone(); - - free(ctx); -} - -static void -float_to_s16ne(int16_t * dst, float * src, size_t n) -{ - long l; - - while (n--) { - l = lrintf(*src++ * 0x8000); - if (l > 32767) - l = 32767; - if (l < -32768) - l = -32768; - *dst++ = (int16_t)l; - } -} - -static ULONG APIENTRY -kai_callback(PVOID cbdata, PVOID buffer, ULONG len) -{ - cubeb_stream * stm = cbdata; - void * p; - long wanted_frames; - long frames; - float soft_volume; - int elements = len / sizeof(int16_t); - - p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE ? stm->float_buffer : buffer; - - wanted_frames = bytes_to_frames(len, stm->params); - frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames); - - _fmutex_request(&stm->mutex, 0); - stm->total_frames += frames; - soft_volume = stm->soft_volume; - _fmutex_release(&stm->mutex); - - if (frames < wanted_frames) - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - - if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) - float_to_s16ne(buffer, p, elements); - - if (soft_volume != -1.0f) { - int16_t * b = buffer; - int i; - - for (i = 0; i < elements; i++) - *b++ *= soft_volume; - } - - return frames_to_bytes(frames, stm->params); -} - -static void -kai_stream_destroy(cubeb_stream * stm); - -static int -kai_stream_init(cubeb * context, cubeb_stream ** stream, - char const * stream_name, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency, cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) -{ - cubeb_stream * stm; - KAISPEC wanted_spec; - - XASSERT(!input_stream_params && "not supported."); - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; - } - - if (!output_stream_params) - return CUBEB_ERROR_INVALID_PARAMETER; - - // Loopback is unsupported - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - return CUBEB_ERROR_NOT_SUPPORTED; - } - - if (output_stream_params->channels < 1 || - output_stream_params->channels > MAX_CHANNELS) - return CUBEB_ERROR_INVALID_FORMAT; - - XASSERT(context); - XASSERT(stream); - - *stream = NULL; - - stm = calloc(1, sizeof(*stm)); - XASSERT(stm); - - stm->context = context; - stm->params = *output_stream_params; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; - stm->soft_volume = -1.0f; - - if (_fmutex_create(&stm->mutex, 0)) { - free(stm); - return CUBEB_ERROR; - } - - wanted_spec.usDeviceIndex = 0; - wanted_spec.ulType = KAIT_PLAY; - wanted_spec.ulBitsPerSample = BPS_16; - wanted_spec.ulSamplingRate = stm->params.rate; - wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM; - wanted_spec.ulChannels = stm->params.channels; - wanted_spec.ulNumBuffers = NBUFS; - wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params); - wanted_spec.fShareable = TRUE; - wanted_spec.pfnCallBack = kai_callback; - wanted_spec.pCallBackData = stm; - - if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) { - _fmutex_close(&stm->mutex); - free(stm); - return CUBEB_ERROR; - } - - *stream = stm; - - return CUBEB_OK; -} - -static void -kai_stream_destroy(cubeb_stream * stm) -{ - kaiClose(stm->hkai); - _fmutex_close(&stm->mutex); - free(stm); -} - -static int -kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) -{ - XASSERT(ctx && max_channels); - - *max_channels = MAX_CHANNELS; - - return CUBEB_OK; -} - -static int -kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency) -{ - /* We have at least two buffers. One is being played, the other one is being - filled. So there is as much latency as one buffer. */ - *latency = FRAME_SIZE; - - return CUBEB_OK; -} - -static int -kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) -{ - cubeb_stream_params params; - KAISPEC wanted_spec; - KAISPEC spec; - HKAI hkai; - - params.format = CUBEB_SAMPLE_S16NE; - params.rate = 48000; - params.channels = 2; - - wanted_spec.usDeviceIndex = 0; - wanted_spec.ulType = KAIT_PLAY; - wanted_spec.ulBitsPerSample = BPS_16; - wanted_spec.ulSamplingRate = params.rate; - wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM; - wanted_spec.ulChannels = params.channels; - wanted_spec.ulNumBuffers = NBUFS; - wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params); - wanted_spec.fShareable = TRUE; - wanted_spec.pfnCallBack = kai_callback; - wanted_spec.pCallBackData = NULL; - - /* Test 48KHz */ - if (kaiOpen(&wanted_spec, &spec, &hkai)) { - /* Not supported. Fall back to 44.1KHz */ - params.rate = 44100; - } else { - /* Supported. Use 48KHz */ - kaiClose(hkai); - } - - *rate = params.rate; - - return CUBEB_OK; -} - -static int -kai_stream_start(cubeb_stream * stm) -{ - if (kaiPlay(stm->hkai)) - return CUBEB_ERROR; - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - - return CUBEB_OK; -} - -static int -kai_stream_stop(cubeb_stream * stm) -{ - if (kaiStop(stm->hkai)) - return CUBEB_ERROR; - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - - return CUBEB_OK; -} - -static int -kai_stream_get_position(cubeb_stream * stm, uint64_t * position) -{ - _fmutex_request(&stm->mutex, 0); - *position = stm->total_frames; - _fmutex_release(&stm->mutex); - - return CUBEB_OK; -} - -static int -kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency) -{ - /* Out of buffers, one is being played, the others are being filled. - So there is as much latency as total buffers - 1. */ - *latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) * - (stm->spec.ulNumBuffers - 1); - - return CUBEB_OK; -} - -static int -kai_stream_set_volume(cubeb_stream * stm, float volume) -{ - _fmutex_request(&stm->mutex, 0); - stm->soft_volume = volume; - _fmutex_release(&stm->mutex); - - return CUBEB_OK; -} - -static struct cubeb_ops const kai_ops = { - /*.init =*/kai_init, - /*.get_backend_id =*/kai_get_backend_id, - /*.get_max_channel_count=*/kai_get_max_channel_count, - /*.get_min_latency=*/kai_get_min_latency, - /*.get_preferred_sample_rate =*/kai_get_preferred_sample_rate, - /*.get_preferred_channel_layout =*/NULL, - /*.enumerate_devices =*/NULL, - /*.device_collection_destroy =*/NULL, - /*.destroy =*/kai_destroy, - /*.stream_init =*/kai_stream_init, - /*.stream_destroy =*/kai_stream_destroy, - /*.stream_start =*/kai_stream_start, - /*.stream_stop =*/kai_stream_stop, - /*.stream_get_position =*/kai_stream_get_position, - /*.stream_get_latency = */ kai_stream_get_latency, - /*.stream_get_input_latency = */ NULL, - /*.stream_set_volume =*/kai_stream_set_volume, - /*.stream_set_name =*/NULL, - /*.stream_get_current_device =*/NULL, - /*.stream_device_destroy =*/NULL, - /*.stream_register_device_changed_callback=*/NULL, - /*.register_device_collection_changed=*/NULL}; diff --git a/dep/cubeb/src/cubeb_log.cpp b/dep/cubeb/src/cubeb_log.cpp index 9bd4c9b9e..9a1c54edf 100644 --- a/dep/cubeb/src/cubeb_log.cpp +++ b/dep/cubeb/src/cubeb_log.cpp @@ -16,8 +16,8 @@ #include #endif -cubeb_log_level g_cubeb_log_level; -cubeb_log_callback g_cubeb_log_callback; +static std::atomic g_cubeb_log_level; +static std::atomic g_cubeb_log_callback; /** The maximum size of a log message, after having been formatted. */ const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; @@ -25,111 +25,56 @@ const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; * 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]() { - CUBEB_REGISTER_THREAD("cubeb_log"); - while (true) { - cubeb_log_message msg; - while (msg_queue.dequeue(&msg, 1)) { - LOG_INTERNAL_NO_FORMAT(CUBEB_LOG_NORMAL, "%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 - } - CUBEB_UNREGISTER_THREAD(); - }).detach(); - } - // Tell the underlying queue the producer thread has changed, so it does not - // assert in debug. This should be called with the thread stopped. - void reset_producer_thread() { msg_queue.reset_thread_ids(); } - -private: -#ifndef _WIN32 - const struct timespec sleep_for = { - CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000, - (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000}; -#endif - cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); } - /** This is quite a big data structure, but is only instantiated if the - * asynchronous logger is used.*/ - lock_free_queue msg_queue; -}; +const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10; void -cubeb_async_log(char const * fmt, ...) +cubeb_noop_log_callback(char const * /* fmt */, ...) +{ +} + +void +cubeb_log_internal(char const * file, uint32_t line, 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); + g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg); } void -cubeb_async_log_reset_threads(void) +cubeb_log_internal_no_format(const char * msg) { - if (!g_cubeb_log_callback) { - return; - } - cubeb_async_logger::get().reset_producer_thread(); + g_cubeb_log_callback.load()(msg); +} + +void +cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback) +{ + g_cubeb_log_level = log_level; + // Once a callback has a been set, `g_cubeb_log_callback` is never set back to + // nullptr, to prevent a TOCTOU race between checking the pointer + if (log_callback && log_level != CUBEB_LOG_DISABLED) { + g_cubeb_log_callback = log_callback; + } else if (!log_callback || CUBEB_LOG_DISABLED) { + g_cubeb_log_callback = cubeb_noop_log_callback; + } else { + assert(false && "Incorrect parameters passed to cubeb_log_set"); + } +} + +cubeb_log_level +cubeb_log_get_level() +{ + return g_cubeb_log_level; +} + +cubeb_log_callback +cubeb_log_get_callback() +{ + if (g_cubeb_log_callback == cubeb_noop_log_callback) { + return nullptr; + } + return g_cubeb_log_callback; } diff --git a/dep/cubeb/src/cubeb_log.h b/dep/cubeb/src/cubeb_log.h index fcc9c89d1..d42a0b77d 100644 --- a/dep/cubeb/src/cubeb_log.h +++ b/dep/cubeb/src/cubeb_log.h @@ -30,12 +30,16 @@ extern "C" { (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #endif -extern cubeb_log_level g_cubeb_log_level; -extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2); void -cubeb_async_log(const char * fmt, ...); +cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback); +cubeb_log_level +cubeb_log_get_level(void); +cubeb_log_callback +cubeb_log_get_callback(void); void -cubeb_async_log_reset_threads(void); +cubeb_log_internal_no_format(const char * msg); +void +cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...); #ifdef __cplusplus } @@ -44,31 +48,16 @@ cubeb_async_log_reset_threads(void); #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_NO_FORMAT(level, fmt, ...) \ - do { \ - if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ - g_cubeb_log_callback(fmt, __VA_ARGS__); \ - } \ - } while (0) - #define LOG_INTERNAL(level, fmt, ...) \ do { \ - if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ - g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \ - ##__VA_ARGS__); \ - } \ - } while (0) - -#define ALOG_INTERNAL(level, fmt, ...) \ - do { \ - if (level <= g_cubeb_log_level) { \ - cubeb_async_log(fmt, ##__VA_ARGS__); \ + if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \ + cubeb_log_internal(__FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \ } \ } while (0) /* Asynchronous logging macros to log in real-time callbacks. */ /* Should not be used on android due to the use of global/static variables. */ -#define ALOGV(msg, ...) ALOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) -#define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) +#define ALOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) +#define ALOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #endif // CUBEB_LOG diff --git a/dep/cubeb/src/cubeb_mixer.cpp b/dep/cubeb/src/cubeb_mixer.cpp index 74bab7139..7f87571f5 100644 --- a/dep/cubeb/src/cubeb_mixer.cpp +++ b/dep/cubeb/src/cubeb_mixer.cpp @@ -183,7 +183,7 @@ MixerContext::auto_matrix() { double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}}; double maxcoef = 0; - float maxval; + double maxval; cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout); cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout); diff --git a/dep/cubeb/src/cubeb_opensl.c b/dep/cubeb/src/cubeb_opensl.c deleted file mode 100644 index e5969984b..000000000 --- a/dep/cubeb/src/cubeb_opensl.c +++ /dev/null @@ -1,1796 +0,0 @@ -/* - * 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 "android/sles_definitions.h" -#include -#include -#include -#include -#include -#endif -#include "android/cubeb-output-latency.h" -#include "cubeb-internal.h" -#include "cubeb-sles.h" -#include "cubeb/cubeb.h" -#include "cubeb_android.h" -#include "cubeb_array_queue.h" -#include "cubeb_resampler.h" - -#if defined(__ANDROID__) -#ifdef LOG -#undef LOG -#endif -//#define LOGGING_ENABLED -#ifdef LOGGING_ENABLED -#define LOG(args...) \ - __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL", ##args) -#else -#define LOG(...) -#endif - -//#define TIMESTAMP_ENABLED -#ifdef TIMESTAMP_ENABLED -#define FILENAME \ - (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#define LOG_TS(args...) \ - __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)", \ - ##args) -#define TIMESTAMP(msg) \ - do { \ - struct timeval timestamp; \ - int ts_ret = gettimeofday(×tamp, NULL); \ - if (ts_ret == 0) { \ - LOG_TS("%lld: %s (%s %s:%d)", \ - timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, \ - __FUNCTION__, FILENAME, __LINE__); \ - } else { \ - LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, \ - __LINE__); \ - } \ - } while (0) -#else -#define TIMESTAMP(...) -#endif - -#define ANDROID_VERSION_GINGERBREAD_MR1 10 -#define ANDROID_VERSION_JELLY_BEAN 18 -#define ANDROID_VERSION_LOLLIPOP 21 -#define ANDROID_VERSION_MARSHMALLOW 23 -#define ANDROID_VERSION_N_MR1 25 -#endif - -#define DEFAULT_SAMPLE_RATE 48000 -#define DEFAULT_NUM_OF_FRAMES 480 - -static struct cubeb_ops const opensl_ops; - -struct cubeb { - struct cubeb_ops const * ops; - void * lib; - SLInterfaceID SL_IID_BUFFERQUEUE; - SLInterfaceID SL_IID_PLAY; -#if defined(__ANDROID__) - SLInterfaceID SL_IID_ANDROIDCONFIGURATION; - SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; -#endif - SLInterfaceID SL_IID_VOLUME; - SLInterfaceID SL_IID_RECORD; - SLObjectItf engObj; - SLEngineItf eng; - SLObjectItf outmixObj; - output_latency_function * p_output_latency_function; -}; - -#define NELEMS(A) (sizeof(A) / sizeof A[0]) -#define NBUFS 2 - -struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ - cubeb * context; - void * user_ptr; - /**/ - pthread_mutex_t mutex; - SLObjectItf playerObj; - SLPlayItf play; - SLBufferQueueItf bufq; - SLVolumeItf volume; - void ** queuebuf; - uint32_t queuebuf_capacity; - int queuebuf_idx; - long queuebuf_len; - long bytespersec; - long framesize; - /* Total number of played frames. - * Synchronized by stream::mutex lock. */ - long written; - /* Flag indicating draining. Synchronized - * by stream::mutex lock. */ - int draining; - /* Flags to determine in/out.*/ - uint32_t input_enabled; - uint32_t output_enabled; - /* Recorder abstract object. */ - SLObjectItf recorderObj; - /* Recorder Itf for input capture. */ - SLRecordItf recorderItf; - /* Buffer queue for input capture. */ - SLAndroidSimpleBufferQueueItf recorderBufferQueueItf; - /* Store input buffers. */ - void ** input_buffer_array; - /* The capacity of the array. - * On capture only can be small (4). - * On full duplex is calculated to - * store 1 sec of data buffers. */ - uint32_t input_array_capacity; - /* Current filled index of input buffer array. - * It is initiated to -1 indicating buffering - * have not started yet. */ - int input_buffer_index; - /* Length of input buffer.*/ - uint32_t input_buffer_length; - /* Input frame size */ - uint32_t input_frame_size; - /* Device sampling rate. If user rate is not - * accepted an compatible rate is set. If it is - * accepted this is equal to params.rate. */ - uint32_t input_device_rate; - /* Exchange input buffers between input - * and full duplex threads. */ - array_queue * input_queue; - /* Silent input buffer used on full duplex. */ - void * input_silent_buffer; - /* Number of input frames from the start of the stream*/ - uint32_t input_total_frames; - /* Flag to stop the execution of user callback and - * close all working threads. Synchronized by - * stream::mutex lock. */ - uint32_t shutdown; - /* Store user callback. */ - cubeb_data_callback data_callback; - /* Store state callback. */ - cubeb_state_callback state_callback; - - cubeb_resampler * resampler; - unsigned int user_output_rate; - unsigned int output_configured_rate; - unsigned int buffer_size_frames; - // Audio output latency used in cubeb_stream_get_position(). - unsigned int output_latency_ms; - int64_t lastPosition; - int64_t lastPositionTimeStamp; - int64_t lastCompensativePosition; - int voice_input; - int voice_output; -}; - -/* Forward declaration. */ -static int -opensl_stop_player(cubeb_stream * stm); -static int -opensl_stop_recorder(cubeb_stream * stm); - -static int -opensl_get_draining(cubeb_stream * stm) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - assert((r == EDEADLK || r == EBUSY) && - "get_draining: mutex should be locked but it's not."); -#endif - return stm->draining; -} - -static void -opensl_set_draining(cubeb_stream * stm, int value) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - LOG("set draining try r = %d", r); - assert((r == EDEADLK || r == EBUSY) && - "set_draining: mutex should be locked but it's not."); -#endif - assert(value == 0 || value == 1); - stm->draining = value; -} - -static void -opensl_notify_drained(cubeb_stream * stm) -{ - assert(stm); - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->play) { - LOG("stop player in play_callback"); - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - } -} - -static uint32_t -opensl_get_shutdown(cubeb_stream * stm) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - assert((r == EDEADLK || r == EBUSY) && - "get_shutdown: mutex should be locked but it's not."); -#endif - return stm->shutdown; -} - -static void -opensl_set_shutdown(cubeb_stream * stm, uint32_t value) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - LOG("set shutdown try r = %d", r); - assert((r == EDEADLK || r == EBUSY) && - "set_shutdown: mutex should be locked but it's not."); -#endif - assert(value == 0 || value == 1); - stm->shutdown = value; -} - -static void -play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) -{ - cubeb_stream * stm = user_ptr; - assert(stm); - switch (event) { - case SL_PLAYEVENT_HEADATMARKER: - opensl_notify_drained(stm); - break; - default: - break; - } -} - -static void -recorder_marker_callback(SLRecordItf caller, void * pContext, SLuint32 event) -{ - cubeb_stream * stm = pContext; - assert(stm); - - if (event == SL_RECORDEVENT_HEADATMARKER) { - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - if (stm->play) { - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } - } - } -} - -static void -bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) -{ - cubeb_stream * stm = user_ptr; - assert(stm); - SLBufferQueueState state; - SLresult res; - long written = 0; - - res = (*stm->bufq)->GetState(stm->bufq, &state); - assert(res == SL_RESULT_SUCCESS); - - if (state.count > 1) { - return; - } - - uint8_t * buf = stm->queuebuf[stm->queuebuf_idx]; - written = 0; - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (!draining && !shutdown) { - written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf, - stm->queuebuf_len / stm->framesize); - LOG("bufferqueue_callback: resampler fill returned %ld frames", written); - if (written < 0 || written * stm->framesize > stm->queuebuf_len) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - opensl_stop_player(stm); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return; - } - } - - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - assert(stm->queuebuf_len >= written * stm->framesize); - memset(buf + written * stm->framesize, 0, - stm->queuebuf_len - written * stm->framesize); - res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; - - if (written > 0) { - pthread_mutex_lock(&stm->mutex); - stm->written += written; - pthread_mutex_unlock(&stm->mutex); - } - - if (!draining && written * stm->framesize < stm->queuebuf_len) { - LOG("bufferqueue_callback draining"); - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int64_t written_duration = - INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (written_duration == 0) { - // since we didn't write any sample, it's not possible to reach the marker - // time and trigger the callback. We should initiative notify drained. - opensl_notify_drained(stm); - } else { - // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf - // to make sure all the data has been processed. - (*stm->play) - ->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); - } - return; - } -} - -static int -opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer) -{ - assert(stm); - - int current_index = stm->input_buffer_index; - void * last_buffer = NULL; - - if (current_index < 0) { - // This is the first enqueue - current_index = 0; - } else { - // The current index hold the last filled buffer get it before advance - // index. - last_buffer = stm->input_buffer_array[current_index]; - // Advance to get next available buffer - current_index = (current_index + 1) % stm->input_array_capacity; - } - // enqueue next empty buffer to be filled by the recorder - SLresult res = (*stm->recorderBufferQueueItf) - ->Enqueue(stm->recorderBufferQueueItf, - stm->input_buffer_array[current_index], - stm->input_buffer_length); - if (res != SL_RESULT_SUCCESS) { - LOG("Enqueue recorder failed. Error code: %lu", res); - return CUBEB_ERROR; - } - // All good, update buffer and index. - stm->input_buffer_index = current_index; - if (last_filled_buffer) { - *last_filled_buffer = last_buffer; - } - return CUBEB_OK; -} - -// input data callback -void -recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context) -{ - assert(context); - cubeb_stream * stm = context; - assert(stm->recorderBufferQueueItf); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - uint32_t shutdown = opensl_get_shutdown(stm); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf - // page 184, on transition to the SL_RECORDSTATE_STOPPED state, - // the application should continue to enqueue buffers onto the queue - // to retrieve the residual recorded data in the system. - r = opensl_enqueue_recorder(stm, NULL); - assert(r == CUBEB_OK); - return; - } - - // Enqueue next available buffer and get the last filled buffer. - void * input_buffer = NULL; - r = opensl_enqueue_recorder(stm, &input_buffer); - assert(r == CUBEB_OK); - assert(input_buffer); - // Fill resampler with last input - long input_frame_count = stm->input_buffer_length / stm->input_frame_size; - long got = cubeb_resampler_fill(stm->resampler, input_buffer, - &input_frame_count, NULL, 0); - // Error case - if (got < 0 || got > input_frame_count) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - } - - // Advance total stream frames - stm->input_total_frames += got; - - if (got < input_frame_count) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - int64_t duration = - INT64_C(1000) * stm->input_total_frames / stm->input_device_rate; - (*stm->recorderItf) - ->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration); - return; - } -} - -void -recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context) -{ - assert(context); - cubeb_stream * stm = context; - assert(stm->recorderBufferQueueItf); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - /* On draining and shutdown the recorder should have been stoped from - * the one set the flags. Accordint to the doc, on transition to - * the SL_RECORDSTATE_STOPPED state, the application should - * continue to enqueue buffers onto the queue to retrieve the residual - * recorded data in the system. */ - LOG("Input shutdown %d or drain %d", shutdown, draining); - int r = opensl_enqueue_recorder(stm, NULL); - assert(r == CUBEB_OK); - return; - } - - // Enqueue next available buffer and get the last filled buffer. - void * input_buffer = NULL; - r = opensl_enqueue_recorder(stm, &input_buffer); - assert(r == CUBEB_OK); - assert(input_buffer); - - assert(stm->input_queue); - r = array_queue_push(stm->input_queue, input_buffer); - if (r == -1) { - LOG("Input queue is full, drop input ..."); - return; - } - - LOG("Input pushed in the queue, input array %zu", - array_queue_get_size(stm->input_queue)); -} - -static void -player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr) -{ - TIMESTAMP("ENTER"); - cubeb_stream * stm = user_ptr; - assert(stm); - SLresult res; - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - // Get output - void * output_buffer = NULL; - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - output_buffer = stm->queuebuf[stm->queuebuf_idx]; - // Advance the output buffer queue index - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - LOG("Shutdown/draining, send silent"); - // Set silent on buffer - memset(output_buffer, 0, stm->queuebuf_len); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - return; - } - - // Get input. - void * input_buffer = array_queue_pop(stm->input_queue); - long input_frame_count = stm->input_buffer_length / stm->input_frame_size; - long frames_needed = stm->queuebuf_len / stm->framesize; - if (!input_buffer) { - LOG("Input hole set silent input buffer"); - input_buffer = stm->input_silent_buffer; - } - - long written = 0; - // Trigger user callback through resampler - written = - cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count, - output_buffer, frames_needed); - - LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written, - frames_needed, array_queue_get_size(stm->input_queue)); - - if (written < 0 || written > frames_needed) { - // Error case - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - opensl_stop_player(stm); - opensl_stop_recorder(stm); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - memset(output_buffer, 0, stm->queuebuf_len); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - return; - } - - // Advance total out written frames counter - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - stm->written += written; - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (written < frames_needed) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int64_t written_duration = - INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf - // to make sure all the data has been processed. - (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); - } - - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - memset((uint8_t *)output_buffer + written * stm->framesize, 0, - stm->queuebuf_len - written * stm->framesize); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - TIMESTAMP("EXIT"); -} - -static void -opensl_destroy(cubeb * ctx); - -#if defined(__ANDROID__) -#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) -typedef int(system_property_get)(const char *, char *); - -static int -wrap_system_property_get(const char * name, char * value) -{ - void * libc = dlopen("libc.so", RTLD_LAZY); - if (!libc) { - LOG("Failed to open libc.so"); - return -1; - } - system_property_get * func = - (system_property_get *)dlsym(libc, "__system_property_get"); - int ret = -1; - if (func) { - ret = func(name, value); - } - dlclose(libc); - return ret; -} -#endif - -static int -get_android_version(void) -{ - char version_string[PROP_VALUE_MAX]; - - memset(version_string, 0, PROP_VALUE_MAX); - -#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) - int len = wrap_system_property_get("ro.build.version.sdk", version_string); -#else - int len = __system_property_get("ro.build.version.sdk", version_string); -#endif - if (len <= 0) { - LOG("Failed to get Android version!\n"); - return len; - } - - int version = (int)strtol(version_string, NULL, 10); - LOG("Android version %d", version); - return version; -} -#endif - -/*static*/ int -opensl_init(cubeb ** context, char const * context_name) -{ - cubeb * ctx; - -#if defined(__ANDROID__) - int android_version = get_android_version(); - if (android_version > 0 && - android_version <= ANDROID_VERSION_GINGERBREAD_MR1) { - // Don't even attempt to run on Gingerbread and lower - return CUBEB_ERROR; - } -#endif - - *context = NULL; - - ctx = calloc(1, sizeof(*ctx)); - assert(ctx); - - ctx->ops = &opensl_ops; - - ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); - if (!ctx->lib) { - free(ctx); - return CUBEB_ERROR; - } - - typedef SLresult (*slCreateEngine_t)( - SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32, - const SLInterfaceID *, const SLboolean *); - slCreateEngine_t f_slCreateEngine = - (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); - SLInterfaceID SL_IID_ENGINE = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); - SLInterfaceID SL_IID_OUTPUTMIX = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); - ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME"); - ctx->SL_IID_BUFFERQUEUE = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); -#if defined(__ANDROID__) - ctx->SL_IID_ANDROIDCONFIGURATION = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); - ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); -#endif - ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); - ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD"); - - if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX || - !ctx->SL_IID_BUFFERQUEUE || -#if defined(__ANDROID__) - !ctx->SL_IID_ANDROIDCONFIGURATION || - !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE || -#endif - !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}}; - - SLresult res; - res = cubeb_get_sles_engine(&ctx->engObj, 1, opt, 0, NULL, NULL); - - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - res = cubeb_realize_sles_engine(ctx->engObj); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX}; - const SLboolean reqom[] = {SL_BOOLEAN_TRUE}; - res = - (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - ctx->p_output_latency_function = - cubeb_output_latency_load_method(android_version); - if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) { - LOG("Warning: output latency is not available, cubeb_stream_get_position() " - "is not supported"); - } - - *context = ctx; - - LOG("Cubeb init (%p) success", ctx); - return CUBEB_OK; -} - -static char const * -opensl_get_backend_id(cubeb * ctx) -{ - return "opensl"; -} - -static int -opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) -{ - assert(ctx && max_channels); - /* The android mixer handles up to two channels, see - http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 - */ - *max_channels = 2; - - return CUBEB_OK; -} - -static void -opensl_destroy(cubeb * ctx) -{ - if (ctx->outmixObj) - (*ctx->outmixObj)->Destroy(ctx->outmixObj); - if (ctx->engObj) - cubeb_destroy_sles_engine(&ctx->engObj); - dlclose(ctx->lib); - if (ctx->p_output_latency_function) - cubeb_output_latency_unload_method(ctx->p_output_latency_function); - free(ctx); -} - -static void -opensl_stream_destroy(cubeb_stream * stm); - -#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) -static int -opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, - cubeb_stream_params * params) -{ - assert(format); - assert(params); - - format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - format->numChannels = params->channels; - // sampleRate is in milliHertz - format->sampleRate = params->rate * 1000; - format->channelMask = params->channels == 1 - ? SL_SPEAKER_FRONT_CENTER - : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_S16BE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - case CUBEB_SAMPLE_FLOAT32LE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; - format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_FLOAT32BE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; - format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - return CUBEB_OK; -} -#endif - -static int -opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params) -{ - assert(format); - assert(params); - - format->formatType = SL_DATAFORMAT_PCM; - format->numChannels = params->channels; - // samplesPerSec is in milliHertz - format->samplesPerSec = params->rate * 1000; - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->channelMask = params->channels == 1 - ? SL_SPEAKER_FRONT_CENTER - : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_S16BE: - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - return CUBEB_OK; -} - -static int -opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) -{ - assert(stm); - assert(params); - - SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut; - lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - lDataLocatorOut.numBuffers = NBUFS; - - SLDataFormat_PCM lDataFormat; - int r = opensl_set_format(&lDataFormat, params); - if (r != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } - - /* For now set device rate to params rate. */ - stm->input_device_rate = params->rate; - - SLDataSink lDataSink; - lDataSink.pLocator = &lDataLocatorOut; - lDataSink.pFormat = &lDataFormat; - - SLDataLocator_IODevice lDataLocatorIn; - lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE; - lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT; - lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; - lDataLocatorIn.device = NULL; - - SLDataSource lDataSource; - lDataSource.pLocator = &lDataLocatorIn; - lDataSource.pFormat = NULL; - - const SLInterfaceID lSoundRecorderIIDs[] = { - stm->context->SL_IID_RECORD, - stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - stm->context->SL_IID_ANDROIDCONFIGURATION}; - - const SLboolean lSoundRecorderReqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, - SL_BOOLEAN_TRUE}; - // create the audio recorder abstract object - SLresult res = (*stm->context->eng) - ->CreateAudioRecorder( - stm->context->eng, &stm->recorderObj, &lDataSource, - &lDataSink, NELEMS(lSoundRecorderIIDs), - lSoundRecorderIIDs, lSoundRecorderReqs); - // Sample rate not supported. Try again with default sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED) { - if (stm->output_enabled && stm->output_configured_rate != 0) { - // Set the same with the player. Since there is no - // api for input device this is a safe choice. - stm->input_device_rate = stm->output_configured_rate; - } else { - // The output preferred rate is used for an input only scenario. - // The default rate expected to be supported from all android devices. - stm->input_device_rate = DEFAULT_SAMPLE_RATE; - } - lDataFormat.samplesPerSec = stm->input_device_rate * 1000; - res = (*stm->context->eng) - ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj, - &lDataSource, &lDataSink, - NELEMS(lSoundRecorderIIDs), - lSoundRecorderIIDs, lSoundRecorderReqs); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - } - - if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { - SLAndroidConfigurationItf recorderConfig; - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &recorderConfig); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get the android configuration interface for recorder. " - "Error " - "code: %lu", - res); - return CUBEB_ERROR; - } - - // Voice recognition is the lowest latency, according to the docs. Camcorder - // uses a microphone that is in the same direction as the camera. - SLint32 streamType = stm->voice_input - ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION - : SL_ANDROID_RECORDING_PRESET_CAMCORDER; - - res = - (*recorderConfig) - ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, - &streamType, sizeof(SLint32)); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set the android configuration to VOICE for the recorder. " - "Error code: %lu", - res); - return CUBEB_ERROR; - } - } - // realize the audio recorder - res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - // get the record interface - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD, - &stm->recorderItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get recorder interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->recorderItf) - ->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register recorder marker callback. Error code: %lu", res); - return CUBEB_ERROR; - } - - (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); - - res = (*stm->recorderItf) - ->SetCallbackEventsMask(stm->recorderItf, - (SLuint32)SL_RECORDEVENT_HEADATMARKER); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set headatmarker event mask. Error code: %lu", res); - return CUBEB_ERROR; - } - // get the simple android buffer queue interface - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, - stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &stm->recorderBufferQueueItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get recorder (android) buffer queue interface. Error code: " - "%lu", - res); - return CUBEB_ERROR; - } - - // register callback on record (input) buffer queue - slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback; - if (stm->output_enabled) { - // Register full duplex callback instead. - rec_callback = recorder_fullduplex_callback; - } - res = (*stm->recorderBufferQueueItf) - ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register recorder buffer queue callback. Error code: %lu", - res); - return CUBEB_ERROR; - } - - // Calculate length of input buffer according to requested latency - stm->input_frame_size = params->channels * sizeof(int16_t); - stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames); - - // Calculate the capacity of input array - stm->input_array_capacity = NBUFS; - if (stm->output_enabled) { - // Full duplex, update capacity to hold 1 sec of data - stm->input_array_capacity = - 1 * stm->input_device_rate / stm->input_buffer_length; - } - // Allocate input array - stm->input_buffer_array = - (void **)calloc(1, sizeof(void *) * stm->input_array_capacity); - // Buffering has not started yet. - stm->input_buffer_index = -1; - // Prepare input buffers - for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { - stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length); - } - - // On full duplex allocate input queue and silent buffer - if (stm->output_enabled) { - stm->input_queue = array_queue_create(stm->input_array_capacity); - assert(stm->input_queue); - stm->input_silent_buffer = calloc(1, stm->input_buffer_length); - assert(stm->input_silent_buffer); - } - - // Enqueue buffer to start rolling once recorder started - r = opensl_enqueue_recorder(stm, NULL); - if (r != CUBEB_OK) { - return r; - } - - LOG("Cubeb stream init recorder success"); - - return CUBEB_OK; -} - -static int -opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) -{ - assert(stm); - assert(params); - - stm->user_output_rate = params->rate; - if (params->format == CUBEB_SAMPLE_S16NE || - params->format == CUBEB_SAMPLE_S16BE) { - stm->framesize = params->channels * sizeof(int16_t); - } else if (params->format == CUBEB_SAMPLE_FLOAT32NE || - params->format == CUBEB_SAMPLE_FLOAT32BE) { - stm->framesize = params->channels * sizeof(float); - } - stm->lastPosition = -1; - stm->lastPositionTimeStamp = 0; - stm->lastCompensativePosition = -1; - - void * format = NULL; - SLuint32 * format_sample_rate = NULL; - -#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) - SLAndroidDataFormat_PCM_EX pcm_ext_format; - if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) { - if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } - format = &pcm_ext_format; - format_sample_rate = &pcm_ext_format.sampleRate; - } -#endif - - SLDataFormat_PCM pcm_format; - if (!format) { - if (opensl_set_format(&pcm_format, params) != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } - format = &pcm_format; - format_sample_rate = &pcm_format.samplesPerSec; - } - - SLDataLocator_BufferQueue loc_bufq; - loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; - loc_bufq.numBuffers = NBUFS; - SLDataSource source; - source.pLocator = &loc_bufq; - source.pFormat = format; - - SLDataLocator_OutputMix loc_outmix; - loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = stm->context->outmixObj; - SLDataSink sink; - sink.pLocator = &loc_outmix; - sink.pFormat = NULL; - -#if defined(__ANDROID__) - const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE, - stm->context->SL_IID_VOLUME, - stm->context->SL_IID_ANDROIDCONFIGURATION}; - const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; -#else - const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME}; - const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; -#endif - assert(NELEMS(ids) == NELEMS(req)); - - uint32_t preferred_sampling_rate = stm->user_output_rate; - SLresult res = SL_RESULT_CONTENT_UNSUPPORTED; - if (preferred_sampling_rate) { - res = (*stm->context->eng) - ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source, - &sink, NELEMS(ids), ids, req); - } - - // Sample rate not supported? Try again with primary sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED && - preferred_sampling_rate != DEFAULT_SAMPLE_RATE) { - preferred_sampling_rate = DEFAULT_SAMPLE_RATE; - *format_sample_rate = preferred_sampling_rate * 1000; - res = (*stm->context->eng) - ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source, - &sink, NELEMS(ids), ids, req); - } - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create audio player. Error code: %lu", res); - return CUBEB_ERROR; - } - - stm->output_configured_rate = preferred_sampling_rate; - stm->bytespersec = stm->output_configured_rate * stm->framesize; - stm->queuebuf_len = stm->framesize * stm->buffer_size_frames; - - // Calculate the capacity of input array - stm->queuebuf_capacity = NBUFS; - if (stm->output_enabled) { - // Full duplex, update capacity to hold 1 sec of data - stm->queuebuf_capacity = - 1 * stm->output_configured_rate / stm->queuebuf_len; - } - // Allocate input array - stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity); - for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { - stm->queuebuf[i] = calloc(1, stm->queuebuf_len); - assert(stm->queuebuf[i]); - } - - SLAndroidConfigurationItf playerConfig = NULL; - - if (get_android_version() >= ANDROID_VERSION_N_MR1) { - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &playerConfig); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get Android configuration interface. Error code: %lu", - res); - return CUBEB_ERROR; - } - - SLint32 streamType = SL_ANDROID_STREAM_MEDIA; - if (stm->voice_output) { - streamType = SL_ANDROID_STREAM_VOICE; - } - res = (*playerConfig) - ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, - &streamType, sizeof(streamType)); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android configuration to %d Error code: %lu", - streamType, res); - } - - SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY; - if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) { - performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; - } - - res = (*playerConfig) - ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE, - &performanceMode, sizeof(performanceMode)); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android performance mode to %d Error code: %lu. This " - "is" - " not fatal", - performanceMode, res); - } - } - - res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize player object. Error code: %lu", res); - return CUBEB_ERROR; - } - - // There are two ways of getting the audio output latency: - // - a configuration value, only available on some devices (notably devices - // running FireOS) - // - A Java method, that we call using JNI. - // - // The first method is prefered, if available, because it can account for more - // latency causes, and is more precise. - - // Latency has to be queried after the realization of the interface, when - // using SL_IID_ANDROIDCONFIGURATION. - SLuint32 audioLatency = 0; - SLuint32 paramSize = sizeof(SLuint32); - // The reported latency is in milliseconds. - if (playerConfig) { - res = (*playerConfig) - ->GetConfiguration(playerConfig, - (const SLchar *)"androidGetAudioLatency", - ¶mSize, &audioLatency); - if (res == SL_RESULT_SUCCESS) { - LOG("Got playback latency using android configuration extension"); - stm->output_latency_ms = audioLatency; - } - } - // `playerConfig` is available, but the above failed, or `playerConfig` is not - // available. In both cases, we need to acquire the output latency by an other - // mean. - if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) { - if (cubeb_output_latency_method_is_loaded( - stm->context->p_output_latency_function)) { - LOG("Got playback latency using JNI"); - stm->output_latency_ms = - cubeb_get_output_latency(stm->context->p_output_latency_function); - } else { - LOG("No alternate latency querying method loaded, A/V sync will be off."); - stm->output_latency_ms = 0; - } - } - - LOG("Audio output latency: %dms", stm->output_latency_ms); - - res = - (*stm->playerObj) - ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get play interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE, - &stm->bufq); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get bufferqueue interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME, - &stm->volume); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get volume interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register play callback. Error code: %lu", res); - return CUBEB_ERROR; - } - - // Work around wilhelm/AudioTrack badness, bug 1221228 - (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0); - - res = (*stm->play) - ->SetCallbackEventsMask(stm->play, - (SLuint32)SL_PLAYEVENT_HEADATMARKER); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set headatmarker event mask. Error code: %lu", res); - return CUBEB_ERROR; - } - - slBufferQueueCallback player_callback = bufferqueue_callback; - if (stm->input_enabled) { - player_callback = player_fullduplex_callback; - } - res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register bufferqueue callback. Error code: %lu", res); - return CUBEB_ERROR; - } - - { - // Enqueue a silent frame so once the player becomes playing, the frame - // will be consumed and kick off the buffer queue callback. - // Note the duration of a single frame is less than 1ms. We don't bother - // adjusting the playback position. - uint8_t * buf = stm->queuebuf[stm->queuebuf_idx++]; - memset(buf, 0, stm->framesize); - res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize); - assert(res == SL_RESULT_SUCCESS); - } - - LOG("Cubeb stream init playback success"); - return CUBEB_OK; -} - -static int -opensl_validate_stream_param(cubeb_stream_params * stream_params) -{ - if ((stream_params && - (stream_params->channels < 1 || stream_params->channels > 32))) { - return CUBEB_ERROR_INVALID_FORMAT; - } - if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { - LOG("Loopback is not supported"); - return CUBEB_ERROR_NOT_SUPPORTED; - } - return CUBEB_OK; -} - -int -has_pref_set(cubeb_stream_params * input_params, - cubeb_stream_params * output_params, cubeb_stream_prefs pref) -{ - return (input_params && input_params->prefs & pref) || - (output_params && output_params->prefs & pref); -} - -static int -opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, - char const * stream_name, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) -{ - cubeb_stream * stm; - - assert(ctx); - if (input_device || output_device) { - LOG("Device selection is not supported in Android. The default will be " - "used"); - } - - *stream = NULL; - - int r = opensl_validate_stream_param(output_stream_params); - if (r != CUBEB_OK) { - LOG("Output stream params not valid"); - return r; - } - r = opensl_validate_stream_param(input_stream_params); - if (r != CUBEB_OK) { - LOG("Input stream params not valid"); - return r; - } - - stm = calloc(1, sizeof(*stm)); - assert(stm); - - stm->context = ctx; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; - stm->buffer_size_frames = - latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES; - stm->input_enabled = (input_stream_params) ? 1 : 0; - stm->output_enabled = (output_stream_params) ? 1 : 0; - stm->shutdown = 1; - stm->voice_input = - has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE); - stm->voice_output = - has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE); - - LOG("cubeb stream prefs: voice_input: %s voice_output: %s", - stm->voice_input ? "true" : "false", - stm->voice_output ? "true" : "false"); - -#ifdef DEBUG - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); - r = pthread_mutex_init(&stm->mutex, &attr); -#else - r = pthread_mutex_init(&stm->mutex, NULL); -#endif - assert(r == 0); - - if (output_stream_params) { - LOG("Playback params: Rate %d, channels %d, format %d, latency in frames " - "%d.", - output_stream_params->rate, output_stream_params->channels, - output_stream_params->format, stm->buffer_size_frames); - r = opensl_configure_playback(stm, output_stream_params); - if (r != CUBEB_OK) { - opensl_stream_destroy(stm); - return r; - } - } - - if (input_stream_params) { - LOG("Capture params: Rate %d, channels %d, format %d, latency in frames " - "%d.", - input_stream_params->rate, input_stream_params->channels, - input_stream_params->format, stm->buffer_size_frames); - r = opensl_configure_capture(stm, input_stream_params); - if (r != CUBEB_OK) { - opensl_stream_destroy(stm); - return r; - } - } - - /* Configure resampler*/ - uint32_t target_sample_rate; - if (input_stream_params) { - target_sample_rate = input_stream_params->rate; - } else { - assert(output_stream_params); - target_sample_rate = output_stream_params->rate; - } - - // Use the actual configured rates for input - // and output. - cubeb_stream_params input_params; - if (input_stream_params) { - input_params = *input_stream_params; - input_params.rate = stm->input_device_rate; - } - cubeb_stream_params output_params; - if (output_stream_params) { - output_params = *output_stream_params; - output_params.rate = stm->output_configured_rate; - } - - stm->resampler = cubeb_resampler_create( - stm, input_stream_params ? &input_params : NULL, - output_stream_params ? &output_params : NULL, target_sample_rate, - data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT, - CUBEB_RESAMPLER_RECLOCK_NONE); - if (!stm->resampler) { - LOG("Failed to create resampler"); - opensl_stream_destroy(stm); - return CUBEB_ERROR; - } - - *stream = stm; - LOG("Cubeb stream (%p) init success", stm); - return CUBEB_OK; -} - -static int -opensl_start_player(cubeb_stream * stm) -{ - assert(stm->playerObj); - SLuint32 playerState; - (*stm->playerObj)->GetState(stm->playerObj, &playerState); - if (playerState == SL_OBJECT_STATE_REALIZED) { - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to start player. Error code: %lu", res); - return CUBEB_ERROR; - } - } - return CUBEB_OK; -} - -static int -opensl_start_recorder(cubeb_stream * stm) -{ - assert(stm->recorderObj); - SLuint32 recorderState; - (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState); - if (recorderState == SL_OBJECT_STATE_REALIZED) { - SLresult res = - (*stm->recorderItf) - ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to start recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - } - return CUBEB_OK; -} - -static int -opensl_stream_start(cubeb_stream * stm) -{ - assert(stm); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 0); - opensl_set_draining(stm, 0); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (stm->playerObj) { - r = opensl_start_player(stm); - if (r != CUBEB_OK) { - return r; - } - } - - if (stm->recorderObj) { - int r = opensl_start_recorder(stm); - if (r != CUBEB_OK) { - return r; - } - } - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - LOG("Cubeb stream (%p) started", stm); - return CUBEB_OK; -} - -static int -opensl_stop_player(cubeb_stream * stm) -{ - assert(stm->playerObj); - assert(stm->shutdown || stm->draining); - - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to stop player. Error code: %lu", res); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -opensl_stop_recorder(cubeb_stream * stm) -{ - assert(stm->recorderObj); - assert(stm->shutdown || stm->draining); - - SLresult res = (*stm->recorderItf) - ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to stop recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -opensl_stream_stop(cubeb_stream * stm) -{ - assert(stm); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (stm->playerObj) { - r = opensl_stop_player(stm); - if (r != CUBEB_OK) { - return r; - } - } - - if (stm->recorderObj) { - int r = opensl_stop_recorder(stm); - if (r != CUBEB_OK) { - return r; - } - } - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - LOG("Cubeb stream (%p) stopped", stm); - return CUBEB_OK; -} - -static int -opensl_destroy_recorder(cubeb_stream * stm) -{ - assert(stm); - assert(stm->recorderObj); - - if (stm->recorderBufferQueueItf) { - SLresult res = - (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to clear recorder buffer queue. Error code: %lu", res); - return CUBEB_ERROR; - } - stm->recorderBufferQueueItf = NULL; - for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { - free(stm->input_buffer_array[i]); - } - } - - (*stm->recorderObj)->Destroy(stm->recorderObj); - stm->recorderObj = NULL; - stm->recorderItf = NULL; - - if (stm->input_queue) { - array_queue_destroy(stm->input_queue); - } - free(stm->input_silent_buffer); - - return CUBEB_OK; -} - -static void -opensl_stream_destroy(cubeb_stream * stm) -{ - assert(stm->draining || stm->shutdown); - - if (stm->playerObj) { - (*stm->playerObj)->Destroy(stm->playerObj); - stm->playerObj = NULL; - stm->play = NULL; - stm->bufq = NULL; - for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { - free(stm->queuebuf[i]); - } - } - - if (stm->recorderObj) { - int r = opensl_destroy_recorder(stm); - assert(r == CUBEB_OK); - } - - if (stm->resampler) { - cubeb_resampler_destroy(stm->resampler); - } - - pthread_mutex_destroy(&stm->mutex); - - LOG("Cubeb stream (%p) destroyed", stm); - free(stm); -} - -static int -opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) -{ - SLmillisecond msec; - uint32_t compensation_msec = 0; - SLresult res; - - res = (*stm->play)->GetPosition(stm->play, &msec); - if (res != SL_RESULT_SUCCESS) - return CUBEB_ERROR; - - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - if (stm->lastPosition == msec) { - compensation_msec = - (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / - 1000000; - } else { - stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec; - stm->lastPosition = msec; - } - - uint64_t samplerate = stm->user_output_rate; - uint32_t output_latency = stm->output_latency_ms; - - pthread_mutex_lock(&stm->mutex); - int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / - stm->output_configured_rate; - pthread_mutex_unlock(&stm->mutex); - assert(maximum_position >= 0); - - if (msec > output_latency) { - int64_t unadjusted_position; - if (stm->lastCompensativePosition > msec + compensation_msec) { - // Over compensation, use lastCompensativePosition. - unadjusted_position = - samplerate * (stm->lastCompensativePosition - output_latency) / 1000; - } else { - unadjusted_position = - samplerate * (msec - output_latency + compensation_msec) / 1000; - stm->lastCompensativePosition = msec + compensation_msec; - } - *position = unadjusted_position < maximum_position ? unadjusted_position - : maximum_position; - } else { - *position = 0; - } - return CUBEB_OK; -} - -static int -opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) -{ - assert(stm); - assert(latency); - - uint32_t stream_latency_frames = - stm->user_output_rate * stm->output_latency_ms / 1000; - - return stream_latency_frames + cubeb_resampler_latency(stm->resampler); -} - -int -opensl_stream_set_volume(cubeb_stream * stm, float volume) -{ - SLresult res; - SLmillibel max_level, millibels; - float unclamped_millibels; - - res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level); - - if (res != SL_RESULT_SUCCESS) { - return CUBEB_ERROR; - } - - /* millibels are 100*dB, so the conversion from the volume's linear amplitude - * is 100 * 20 * log(volume). However we clamp the resulting value before - * passing it to lroundf() in order to prevent it from silently returning an - * erroneous value when the unclamped value exceeds the size of a long. */ - unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f)); - unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN); - unclamped_millibels = fminf(unclamped_millibels, max_level); - - millibels = lroundf(unclamped_millibels); - - res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels); - - if (res != SL_RESULT_SUCCESS) { - return CUBEB_ERROR; - } - return CUBEB_OK; -} - -static struct cubeb_ops const opensl_ops = { - .init = opensl_init, - .get_backend_id = opensl_get_backend_id, - .get_max_channel_count = opensl_get_max_channel_count, - .get_min_latency = NULL, - .get_preferred_sample_rate = NULL, - .enumerate_devices = NULL, - .device_collection_destroy = NULL, - .destroy = opensl_destroy, - .stream_init = opensl_stream_init, - .stream_destroy = opensl_stream_destroy, - .stream_start = opensl_stream_start, - .stream_stop = opensl_stream_stop, - .stream_get_position = opensl_stream_get_position, - .stream_get_latency = opensl_stream_get_latency, - .stream_get_input_latency = NULL, - .stream_set_volume = opensl_stream_set_volume, - .stream_set_name = NULL, - .stream_get_current_device = NULL, - .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL, - .register_device_collection_changed = NULL}; diff --git a/dep/cubeb/src/cubeb_oss.c b/dep/cubeb/src/cubeb_oss.c index 88f8582a0..8718fa306 100644 --- a/dep/cubeb/src/cubeb_oss.c +++ b/dep/cubeb/src/cubeb_oss.c @@ -14,6 +14,7 @@ #include "cubeb/cubeb.h" #include "cubeb_mixer.h" #include "cubeb_strings.h" +#include "cubeb_tracing.h" #include #include #include @@ -975,6 +976,8 @@ oss_io_routine(void * arg) cubeb_state new_state; int stopped; + CUBEB_REGISTER_THREAD("cubeb rendering thread"); + do { pthread_mutex_lock(&s->mtx); if (s->destroying) { @@ -1005,6 +1008,9 @@ oss_io_routine(void * arg) pthread_mutex_lock(&s->mtx); s->thread_created = false; pthread_mutex_unlock(&s->mtx); + + CUBEB_UNREGISTER_THREAD(); + return NULL; } diff --git a/dep/cubeb/src/cubeb_pulse.c b/dep/cubeb/src/cubeb_pulse.c index 13f679164..686640525 100644 --- a/dep/cubeb/src/cubeb_pulse.c +++ b/dep/cubeb/src/cubeb_pulse.c @@ -804,10 +804,11 @@ pulse_destroy(cubeb * ctx) if (ctx->device_ids) { cubeb_strings_destroy(ctx->device_ids); } - +#ifndef DISABLE_LIBPULSE_DLOPEN if (ctx->libpulse) { dlclose(ctx->libpulse); } +#endif free(ctx->default_sink_info); free(ctx); } @@ -1024,7 +1025,7 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream, return CUBEB_ERROR; } - if (g_cubeb_log_level) { + if (cubeb_log_get_level()) { if (output_stream_params) { const pa_buffer_attr * output_att; output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); @@ -1577,7 +1578,7 @@ pulse_subscribe_callback(pa_context * ctx, pa_subscription_event_type_t t, case PA_SUBSCRIPTION_EVENT_SOURCE: case PA_SUBSCRIPTION_EVENT_SINK: - if (g_cubeb_log_level) { + if (cubeb_log_get_level()) { if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == diff --git a/dep/cubeb/src/cubeb_ring_array.h b/dep/cubeb/src/cubeb_ring_array.h index 05a8fe962..331d0471c 100644 --- a/dep/cubeb/src/cubeb_ring_array.h +++ b/dep/cubeb/src/cubeb_ring_array.h @@ -9,6 +9,7 @@ #define CUBEB_RING_ARRAY_H #include "cubeb_utils.h" +#include /** Ring array of pointers is used to hold buffers. In case that asynchronous producer/consumer callbacks do not arrive in a @@ -46,7 +47,7 @@ single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame, /** Initialize the ring array. @param ra The ring_array pointer of allocated structure. @retval 0 on success. */ -int +static int ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame, uint32_t channelsPerFrame, uint32_t framesPerBuffer) { @@ -78,7 +79,7 @@ ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame, /** Destroy the ring array. @param ra The ring_array pointer.*/ -void +static void ring_array_destroy(ring_array * ra) { assert(ra); @@ -97,7 +98,7 @@ ring_array_destroy(ring_array * ra) @param ra The ring_array pointer. @retval Pointer of the allocated space to be stored with fresh data or NULL if full. */ -AudioBuffer * +static AudioBuffer * ring_array_get_free_buffer(ring_array * ra) { assert(ra && ra->buffer_array); @@ -118,7 +119,7 @@ ring_array_get_free_buffer(ring_array * ra) /** 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 * +static AudioBuffer * ring_array_get_data_buffer(ring_array * ra) { assert(ra && ra->buffer_array); @@ -138,18 +139,4 @@ ring_array_get_data_buffer(ring_array * ra) 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/dep/cubeb/src/cubeb_sndio.c b/dep/cubeb/src/cubeb_sndio.c index 5e11725ec..944c28d42 100644 --- a/dep/cubeb/src/cubeb_sndio.c +++ b/dep/cubeb/src/cubeb_sndio.c @@ -6,6 +6,7 @@ */ #include "cubeb-internal.h" #include "cubeb/cubeb.h" +#include "cubeb_tracing.h" #include #include #include @@ -67,7 +68,7 @@ struct cubeb_stream { struct sio_hdl * hdl; /* link us to sndio */ int mode; /* bitmap of SIO_{PLAY,REC} */ int active; /* cubec_start() called */ - int conv; /* need float->s16 conversion */ + int conv; /* need float->s24 conversion */ unsigned char * rbuf; /* rec data consumed from here */ unsigned char * pbuf; /* play data is prepared here */ unsigned int nfr; /* number of frames in ibuf and obuf */ @@ -98,33 +99,33 @@ s16_setvol(void * ptr, long nsamp, float volume) } static void -float_to_s16(void * ptr, long nsamp, float volume) +float_to_s24(void * ptr, long nsamp, float volume) { - int16_t * dst = ptr; + int32_t * dst = ptr; float * src = ptr; - float mult = volume * 32768; + float mult = volume * 8388608; int s; while (nsamp-- > 0) { s = lrintf(*(src++) * mult); - if (s < -32768) - s = -32768; - else if (s > 32767) - s = 32767; + if (s < -8388608) + s = -8388608; + else if (s > 8388607) + s = 8388607; *(dst++) = s; } } static void -s16_to_float(void * ptr, long nsamp) +s24_to_float(void * ptr, long nsamp) { - int16_t * src = ptr; + int32_t * src = ptr; float * dst = ptr; src += nsamp; dst += nsamp; while (nsamp-- > 0) - *(--dst) = (1. / 32768) * *(--src); + *(--dst) = (1. / 8388608) * *(--src); } static const char * @@ -161,10 +162,14 @@ sndio_mainloop(void * arg) size_t pstart = 0, pend = 0, rstart = 0, rend = 0; long nfr; + CUBEB_REGISTER_THREAD("cubeb rendering thread"); + nfds = WRAP(sio_nfds)(s->hdl); pfds = calloc(nfds, sizeof(struct pollfd)); - if (pfds == NULL) + if (pfds == NULL) { + CUBEB_UNREGISTER_THREAD(); return NULL; + } DPR("sndio_mainloop()\n"); s->state_cb(s, s->arg, CUBEB_STATE_STARTED); @@ -172,6 +177,7 @@ sndio_mainloop(void * arg) if (!WRAP(sio_start)(s->hdl)) { pthread_mutex_unlock(&s->mtx); free(pfds); + CUBEB_UNREGISTER_THREAD(); return NULL; } DPR("sndio_mainloop(), started\n"); @@ -207,7 +213,7 @@ sndio_mainloop(void * arg) } if ((s->mode & SIO_REC) && s->conv) - s16_to_float(s->rbuf, s->nfr * s->rchan); + s24_to_float(s->rbuf, s->nfr * s->rchan); /* invoke call-back, it returns less that s->nfr if done */ pthread_mutex_unlock(&s->mtx); @@ -238,7 +244,7 @@ sndio_mainloop(void * arg) if (s->mode & SIO_PLAY) { if (s->conv) - float_to_s16(s->pbuf, nfr * s->pchan, s->volume); + float_to_s24(s->pbuf, nfr * s->pchan, s->volume); else s16_setvol(s->pbuf, nfr * s->pchan, s->volume); } @@ -300,6 +306,7 @@ sndio_mainloop(void * arg) pthread_mutex_unlock(&s->mtx); s->state_cb(s, s->arg, state); free(pfds); + CUBEB_UNREGISTER_THREAD(); return NULL; } @@ -362,8 +369,10 @@ static void sndio_destroy(cubeb * context) { DPR("sndio_destroy()\n"); +#ifndef DISABLE_LIBSNDIO_DLOPEN if (context->libsndio) dlclose(context->libsndio); +#endif free(context); } @@ -420,21 +429,25 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream, } WRAP(sio_initpar)(&wpar); wpar.sig = 1; - wpar.bits = 16; switch (format) { case CUBEB_SAMPLE_S16LE: wpar.le = 1; + wpar.bits = 16; break; case CUBEB_SAMPLE_S16BE: wpar.le = 0; + wpar.bits = 16; break; case CUBEB_SAMPLE_FLOAT32NE: wpar.le = SIO_LE_NATIVE; + wpar.bits = 24; + wpar.msb = 0; break; default: DPR("sndio_stream_init() unsupported format\n"); goto err; } + wpar.bps = SIO_BPS(wpar.bits); wpar.rate = rate; if (s->mode & SIO_REC) wpar.rchan = input_stream_params->channels; @@ -446,6 +459,8 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream, goto err; } if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig || + rpar.bps != wpar.bps || + (wpar.bits < 8 * wpar.bps && rpar.msb != wpar.msb) || rpar.rate != wpar.rate || ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { diff --git a/dep/cubeb/src/cubeb_sun.c b/dep/cubeb/src/cubeb_sun.c deleted file mode 100644 index 3b7bef71d..000000000 --- a/dep/cubeb/src/cubeb_sun.c +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright © 2019-2020 Nia Alarie - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ -#include "cubeb-internal.h" -#include "cubeb/cubeb.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Default to 4 + 1 for the default device. */ -#ifndef SUN_DEVICE_COUNT -#define SUN_DEVICE_COUNT (5) -#endif - -/* Supported well by most hardware. */ -#ifndef SUN_PREFER_RATE -#define SUN_PREFER_RATE (48000) -#endif - -/* Standard acceptable minimum. */ -#ifndef SUN_LATENCY_MS -#define SUN_LATENCY_MS (40) -#endif - -#ifndef SUN_DEFAULT_DEVICE -#define SUN_DEFAULT_DEVICE "/dev/audio" -#endif - -#ifndef SUN_BUFFER_FRAMES -#define SUN_BUFFER_FRAMES (32) -#endif - -/* - * Supported on NetBSD regardless of hardware. - */ - -#ifndef SUN_MAX_CHANNELS -#ifdef __NetBSD__ -#define SUN_MAX_CHANNELS (12) -#else -#define SUN_MAX_CHANNELS (2) -#endif -#endif - -#ifndef SUN_MIN_RATE -#define SUN_MIN_RATE (1000) -#endif - -#ifndef SUN_MAX_RATE -#define SUN_MAX_RATE (192000) -#endif - -static struct cubeb_ops const sun_ops; - -struct cubeb { - struct cubeb_ops const * ops; -}; - -struct sun_stream { - char name[32]; - int fd; - void * buf; - struct audio_info info; - unsigned frame_size; /* precision in bytes * channels */ - bool floating; -}; - -struct cubeb_stream { - struct cubeb * context; - void * user_ptr; - pthread_t thread; - pthread_mutex_t mutex; /* protects running, volume, frames_written */ - bool running; - float volume; - struct sun_stream play; - struct sun_stream record; - cubeb_data_callback data_cb; - cubeb_state_callback state_cb; - uint64_t frames_written; - uint64_t blocks_written; -}; - -int -sun_init(cubeb ** context, char const * context_name) -{ - cubeb * c; - - (void)context_name; - if ((c = calloc(1, sizeof(cubeb))) == NULL) { - return CUBEB_ERROR; - } - c->ops = &sun_ops; - *context = c; - return CUBEB_OK; -} - -static void -sun_destroy(cubeb * context) -{ - free(context); -} - -static char const * -sun_get_backend_id(cubeb * context) -{ - return "sun"; -} - -static int -sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate) -{ - (void)context; - - *rate = SUN_PREFER_RATE; - return CUBEB_OK; -} - -static int -sun_get_max_channel_count(cubeb * context, uint32_t * max_channels) -{ - (void)context; - - *max_channels = SUN_MAX_CHANNELS; - return CUBEB_OK; -} - -static int -sun_get_min_latency(cubeb * context, cubeb_stream_params params, - uint32_t * latency_frames) -{ - (void)context; - - *latency_frames = SUN_LATENCY_MS * params.rate / 1000; - return CUBEB_OK; -} - -static int -sun_get_hwinfo(const char * device, struct audio_info * format, int * props, - struct audio_device * dev) -{ - int fd = -1; - - if ((fd = open(device, O_RDONLY)) == -1) { - goto error; - } -#ifdef AUDIO_GETFORMAT - if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) { - goto error; - } -#endif -#ifdef AUDIO_GETPROPS - if (ioctl(fd, AUDIO_GETPROPS, props) != 0) { - goto error; - } -#endif - if (ioctl(fd, AUDIO_GETDEV, dev) != 0) { - goto error; - } - close(fd); - return CUBEB_OK; -error: - if (fd != -1) { - close(fd); - } - return CUBEB_ERROR; -} - -/* - * XXX: PR kern/54264 - */ -static int -sun_prinfo_verify_sanity(struct audio_prinfo * prinfo) -{ - return prinfo->precision >= 8 && prinfo->precision <= 32 && - prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS && - prinfo->sample_rate < SUN_MAX_RATE && - prinfo->sample_rate > SUN_MIN_RATE; -} - -static int -sun_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) -{ - unsigned i; - cubeb_device_info device = {0}; - char dev[16] = SUN_DEFAULT_DEVICE; - char dev_friendly[64]; - struct audio_info hwfmt; - struct audio_device hwname; - struct audio_prinfo * prinfo = NULL; - int hwprops; - - collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info)); - if (collection->device == NULL) { - return CUBEB_ERROR; - } - collection->count = 0; - - for (i = 0; i < SUN_DEVICE_COUNT; ++i) { - if (i > 0) { - (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1); - } - if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) { - continue; - } -#ifdef AUDIO_GETPROPS - device.type = 0; - if ((hwprops & AUDIO_PROP_CAPTURE) != 0 && - sun_prinfo_verify_sanity(&hwfmt.record)) { - /* the device supports recording, probably */ - device.type |= CUBEB_DEVICE_TYPE_INPUT; - } - if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 && - sun_prinfo_verify_sanity(&hwfmt.play)) { - /* the device supports playback, probably */ - device.type |= CUBEB_DEVICE_TYPE_OUTPUT; - } - switch (device.type) { - case 0: - /* device doesn't do input or output, aliens probably involved */ - continue; - case CUBEB_DEVICE_TYPE_INPUT: - if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) { - /* this device is input only, not scanning for those, skip it */ - continue; - } - break; - case CUBEB_DEVICE_TYPE_OUTPUT: - if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) { - /* this device is output only, not scanning for those, skip it */ - continue; - } - break; - } - if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) { - prinfo = &hwfmt.record; - } - if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) { - prinfo = &hwfmt.play; - } -#endif - if (i > 0) { - (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)", - hwname.name, hwname.version, hwname.config, i - 1); - } else { - (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)", - hwname.name, hwname.version, hwname.config); - } - device.devid = (void *)(uintptr_t)i; - device.device_id = strdup(dev); - device.friendly_name = strdup(dev_friendly); - device.group_id = strdup(dev); - device.vendor_name = strdup(hwname.name); - device.type = type; - device.state = CUBEB_DEVICE_STATE_ENABLED; - device.preferred = - (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; -#ifdef AUDIO_GETFORMAT - device.max_channels = prinfo->channels; - device.default_rate = prinfo->sample_rate; -#else - device.max_channels = 2; - device.default_rate = SUN_PREFER_RATE; -#endif - device.default_format = CUBEB_DEVICE_FMT_S16NE; - device.format = CUBEB_DEVICE_FMT_S16NE; - device.min_rate = SUN_MIN_RATE; - device.max_rate = SUN_MAX_RATE; - device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000; - device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000; - collection->device[collection->count++] = device; - } - return CUBEB_OK; -} - -static int -sun_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) -{ - unsigned i; - - for (i = 0; i < collection->count; ++i) { - free((char *)collection->device[i].device_id); - free((char *)collection->device[i].friendly_name); - free((char *)collection->device[i].group_id); - free((char *)collection->device[i].vendor_name); - } - free(collection->device); - return CUBEB_OK; -} - -static int -sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params, - struct audio_info * info, struct audio_prinfo * prinfo) -{ - prinfo->channels = params->channels; - prinfo->sample_rate = params->rate; -#ifdef AUDIO_ENCODING_SLINEAR_LE - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE; - prinfo->precision = 16; - break; - case CUBEB_SAMPLE_S16BE: - prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE; - prinfo->precision = 16; - break; - case CUBEB_SAMPLE_FLOAT32NE: - prinfo->encoding = AUDIO_ENCODING_SLINEAR; - prinfo->precision = 32; - break; - default: - LOG("Unsupported format"); - return CUBEB_ERROR_INVALID_FORMAT; - } -#else - switch (params->format) { - case CUBEB_SAMPLE_S16NE: - prinfo->encoding = AUDIO_ENCODING_LINEAR; - prinfo->precision = 16; - break; - case CUBEB_SAMPLE_FLOAT32NE: - prinfo->encoding = AUDIO_ENCODING_LINEAR; - prinfo->precision = 32; - break; - default: - LOG("Unsupported format"); - return CUBEB_ERROR_INVALID_FORMAT; - } -#endif - if (ioctl(fd, AUDIO_SETINFO, info) == -1) { - return CUBEB_ERROR; - } - if (ioctl(fd, AUDIO_GETINFO, info) == -1) { - return CUBEB_ERROR; - } - return CUBEB_OK; -} - -static int -sun_stream_stop(cubeb_stream * s) -{ - pthread_mutex_lock(&s->mutex); - if (s->running) { - s->running = false; - pthread_mutex_unlock(&s->mutex); - pthread_join(s->thread, NULL); - } else { - pthread_mutex_unlock(&s->mutex); - } - return CUBEB_OK; -} - -static void -sun_stream_destroy(cubeb_stream * s) -{ - sun_stream_stop(s); - pthread_mutex_destroy(&s->mutex); - if (s->play.fd != -1) { - close(s->play.fd); - } - if (s->record.fd != -1) { - close(s->record.fd); - } - free(s->play.buf); - free(s->record.buf); - free(s); -} - -static void -sun_float_to_linear32(void * buf, unsigned sample_count, float vol) -{ - float * in = buf; - int32_t * out = buf; - int32_t * tail = out + sample_count; - - while (out < tail) { - float f = *(in++) * vol; - if (f < -1.0) - f = -1.0; - else if (f > 1.0) - f = 1.0; - *(out++) = f * (float)INT32_MAX; - } -} - -static void -sun_linear32_to_float(void * buf, unsigned sample_count) -{ - int32_t * in = buf; - float * out = buf; - float * tail = out + sample_count; - - while (out < tail) { - *(out++) = (1.0 / 0x80000000) * *(in++); - } -} - -static void -sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol) -{ - unsigned i; - int32_t multiplier = vol * 0x8000; - - for (i = 0; i < sample_count; ++i) { - buf[i] = (buf[i] * multiplier) >> 15; - } -} - -static void * -sun_io_routine(void * arg) -{ - cubeb_stream * s = arg; - cubeb_state state = CUBEB_STATE_STARTED; - size_t to_read = 0; - long to_write = 0; - size_t write_ofs = 0; - size_t read_ofs = 0; - int drain = 0; - - s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED); - while (state != CUBEB_STATE_ERROR) { - pthread_mutex_lock(&s->mutex); - if (!s->running) { - pthread_mutex_unlock(&s->mutex); - state = CUBEB_STATE_STOPPED; - break; - } - pthread_mutex_unlock(&s->mutex); - if (s->record.fd != -1 && s->record.floating) { - sun_linear32_to_float(s->record.buf, - s->record.info.record.channels * SUN_BUFFER_FRAMES); - } - to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, - SUN_BUFFER_FRAMES); - if (to_write == CUBEB_ERROR) { - state = CUBEB_STATE_ERROR; - break; - } - if (s->play.fd != -1) { - float vol; - - pthread_mutex_lock(&s->mutex); - vol = s->volume; - pthread_mutex_unlock(&s->mutex); - - if (s->play.floating) { - sun_float_to_linear32(s->play.buf, - s->play.info.play.channels * to_write, vol); - } else { - sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write, - vol); - } - } - if (to_write < SUN_BUFFER_FRAMES) { - drain = 1; - } - to_write = s->play.fd != -1 ? to_write : 0; - to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0; - write_ofs = 0; - read_ofs = 0; - while (to_write > 0 || to_read > 0) { - size_t bytes; - ssize_t n, frames; - - if (to_write > 0) { - bytes = to_write * s->play.frame_size; - if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) < - 0) { - state = CUBEB_STATE_ERROR; - break; - } - frames = n / s->play.frame_size; - pthread_mutex_lock(&s->mutex); - s->frames_written += frames; - pthread_mutex_unlock(&s->mutex); - to_write -= frames; - write_ofs += n; - } - if (to_read > 0) { - bytes = to_read * s->record.frame_size; - if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, - bytes)) < 0) { - state = CUBEB_STATE_ERROR; - break; - } - frames = n / s->record.frame_size; - to_read -= frames; - read_ofs += n; - } - } - if (drain && state != CUBEB_STATE_ERROR) { - state = CUBEB_STATE_DRAINED; - break; - } - } - s->state_cb(s, s->user_ptr, state); - return NULL; -} - -static int -sun_stream_init(cubeb * context, cubeb_stream ** stream, - char const * stream_name, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned latency_frames, cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) -{ - int ret = CUBEB_OK; - cubeb_stream * s = NULL; - - (void)stream_name; - (void)latency_frames; - if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - s->record.fd = -1; - s->play.fd = -1; - if (input_device != 0) { - snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu", - (uintptr_t)input_device - 1); - } else { - snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE); - } - if (output_device != 0) { - snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu", - (uintptr_t)output_device - 1); - } else { - snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE); - } - if (input_stream_params != NULL) { - if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - LOG("Loopback not supported"); - ret = CUBEB_ERROR_NOT_SUPPORTED; - goto error; - } - if (s->record.fd == -1) { - if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) { - LOG("Audio device could not be opened as read-only"); - ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; - goto error; - } - } - AUDIO_INITINFO(&s->record.info); -#ifdef AUMODE_RECORD - s->record.info.mode = AUMODE_RECORD; -#endif - if ((ret = sun_copy_params(s->record.fd, s, input_stream_params, - &s->record.info, &s->record.info.record)) != - CUBEB_OK) { - LOG("Setting record params failed"); - goto error; - } - s->record.floating = - (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); - } - if (output_stream_params != NULL) { - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - LOG("Loopback not supported"); - ret = CUBEB_ERROR_NOT_SUPPORTED; - goto error; - } - if (s->play.fd == -1) { - if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) { - LOG("Audio device could not be opened as write-only"); - ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; - goto error; - } - } - AUDIO_INITINFO(&s->play.info); -#ifdef AUMODE_PLAY - s->play.info.mode = AUMODE_PLAY; -#endif - if ((ret = sun_copy_params(s->play.fd, s, output_stream_params, - &s->play.info, &s->play.info.play)) != - CUBEB_OK) { - LOG("Setting play params failed"); - goto error; - } - s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); - } - s->context = context; - s->volume = 1.0; - s->state_cb = state_callback; - s->data_cb = data_callback; - s->user_ptr = user_ptr; - if (pthread_mutex_init(&s->mutex, NULL) != 0) { - LOG("Failed to create mutex"); - goto error; - } - s->play.frame_size = - s->play.info.play.channels * (s->play.info.play.precision / 8); - if (s->play.fd != -1 && - (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - s->record.frame_size = - s->record.info.record.channels * (s->record.info.record.precision / 8); - if (s->record.fd != -1 && - (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == - NULL) { - ret = CUBEB_ERROR; - goto error; - } - *stream = s; - return CUBEB_OK; -error: - if (s != NULL) { - sun_stream_destroy(s); - } - return ret; -} - -static int -sun_stream_start(cubeb_stream * s) -{ - s->running = true; - if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) { - LOG("Couldn't create thread"); - return CUBEB_ERROR; - } - return CUBEB_OK; -} - -static int -sun_stream_get_position(cubeb_stream * s, uint64_t * position) -{ -#ifdef AUDIO_GETOOFFS - struct audio_offset offset; - - if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) { - return CUBEB_ERROR; - } - s->blocks_written += offset.deltablks; - *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size; - return CUBEB_OK; -#else - pthread_mutex_lock(&s->mutex); - *position = s->frames_written; - pthread_mutex_unlock(&s->mutex); - return CUBEB_OK; -#endif -} - -static int -sun_stream_get_latency(cubeb_stream * s, uint32_t * latency) -{ -#ifdef AUDIO_GETBUFINFO - struct audio_info info; - - if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) { - return CUBEB_ERROR; - } - - *latency = (info.play.seek + info.blocksize) / s->play.frame_size; - return CUBEB_OK; -#else - cubeb_stream_params params; - - params.rate = s->play.info.play.sample_rate; - - return sun_get_min_latency(NULL, params, latency); -#endif -} - -static int -sun_stream_set_volume(cubeb_stream * stream, float volume) -{ - pthread_mutex_lock(&stream->mutex); - stream->volume = volume; - pthread_mutex_unlock(&stream->mutex); - return CUBEB_OK; -} - -static int -sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device) -{ - *device = calloc(1, sizeof(cubeb_device)); - if (*device == NULL) { - return CUBEB_ERROR; - } - (*device)->input_name = - stream->record.fd != -1 ? strdup(stream->record.name) : NULL; - (*device)->output_name = - stream->play.fd != -1 ? strdup(stream->play.name) : NULL; - return CUBEB_OK; -} - -static int -sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device) -{ - (void)stream; - free(device->input_name); - free(device->output_name); - free(device); - return CUBEB_OK; -} - -static struct cubeb_ops const sun_ops = { - .init = sun_init, - .get_backend_id = sun_get_backend_id, - .get_max_channel_count = sun_get_max_channel_count, - .get_min_latency = sun_get_min_latency, - .get_preferred_sample_rate = sun_get_preferred_sample_rate, - .enumerate_devices = sun_enumerate_devices, - .device_collection_destroy = sun_device_collection_destroy, - .destroy = sun_destroy, - .stream_init = sun_stream_init, - .stream_destroy = sun_stream_destroy, - .stream_start = sun_stream_start, - .stream_stop = sun_stream_stop, - .stream_get_position = sun_stream_get_position, - .stream_get_latency = sun_stream_get_latency, - .stream_get_input_latency = NULL, - .stream_set_volume = sun_stream_set_volume, - .stream_set_name = NULL, - .stream_get_current_device = sun_get_current_device, - .stream_device_destroy = sun_stream_device_destroy, - .stream_register_device_changed_callback = NULL, - .register_device_collection_changed = NULL}; diff --git a/dep/cubeb/src/cubeb_triple_buffer.h b/dep/cubeb/src/cubeb_triple_buffer.h new file mode 100644 index 000000000..a5a5978fb --- /dev/null +++ b/dep/cubeb/src/cubeb_triple_buffer.h @@ -0,0 +1,80 @@ +/* + * Copyright © 2022 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +/** + * Adapted and ported to C++ from https://crates.io/crates/triple_buffer + */ + +#ifndef CUBEB_TRIPLE_BUFFER +#define CUBEB_TRIPLE_BUFFER + +#include + +// Single producer / single consumer wait-free triple buffering +// implementation, for when a producer wants to publish data to a consumer +// without blocking, but when a queue is wastefull, because it's OK for the +// consumer to miss data updates. +template class triple_buffer { +public: + // Write a new value into the triple buffer. Returns true if a value was + // overwritten. + // Producer-side only. + bool write(T & input) + { + storage[input_idx] = input; + return publish(); + } + // Get the latest value from the triple buffer. + // Consumer-side only. + T & read() + { + update(); + return storage[output_idx]; + } + // Returns true if a new value has been published by the consumer without + // having been consumed yet. + // Consumer-side only. + bool updated() + { + return (shared_state.load(std::memory_order_relaxed) & BACK_DIRTY_BIT) != 0; + } + +private: + // Publish a value to the consumer. Returns true if the data was overwritten + // without having been read. + bool publish() + { + auto former_back_idx = shared_state.exchange(input_idx | BACK_DIRTY_BIT, + std::memory_order_acq_rel); + input_idx = former_back_idx & BACK_INDEX_MASK; + return (former_back_idx & BACK_DIRTY_BIT) != 0; + } + // Get a new value from the producer, if a new value has been produced. + bool update() + { + bool was_updated = updated(); + if (was_updated) { + auto former_back_idx = + shared_state.exchange(output_idx, std::memory_order_acq_rel); + output_idx = former_back_idx & BACK_INDEX_MASK; + } + return was_updated; + } + T storage[3]; + // Mask used to extract back-buffer index + const uint8_t BACK_INDEX_MASK = 0b11; + // Bit set by producer to signal updates + const uint8_t BACK_DIRTY_BIT = 0b100; + // Shared state: a dirty bit, and an index. + std::atomic shared_state = {0}; + // Output index, private to the consumer. + uint8_t output_idx = 1; + // Input index, private to the producer. + uint8_t input_idx = 2; +}; + +#endif // CUBEB_TRIPLE_BUFFER diff --git a/dep/cubeb/src/cubeb_wasapi.cpp b/dep/cubeb/src/cubeb_wasapi.cpp index 039ad76c0..c2b4045de 100644 --- a/dep/cubeb/src/cubeb_wasapi.cpp +++ b/dep/cubeb/src/cubeb_wasapi.cpp @@ -24,12 +24,17 @@ #include #include #include +/* clang-format off */ +/* These need to be included after windows.h */ +#include +/* clang-format on */ #include "cubeb-internal.h" #include "cubeb/cubeb.h" #include "cubeb_mixer.h" #include "cubeb_resampler.h" #include "cubeb_strings.h" +#include "cubeb_tracing.h" #include "cubeb_utils.h" // Windows 10 exposes the IAudioClient3 interface to create low-latency streams. @@ -96,6 +101,8 @@ namespace { const int64_t LATENCY_NOT_AVAILABLE_YET = -1; +const DWORD DEVICE_CHANGE_DEBOUNCE_MS = 250; + struct com_heap_ptr_deleter { void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); } }; @@ -103,7 +110,9 @@ struct com_heap_ptr_deleter { template using com_heap_ptr = std::unique_ptr; -template constexpr size_t ARRAY_LENGTH(T (&)[N]) +template +constexpr size_t +ARRAY_LENGTH(T (&)[N]) { return N; } @@ -181,6 +190,20 @@ private: T * ptr = nullptr; }; +LONG +wasapi_stream_add_ref(cubeb_stream * stm); +LONG +wasapi_stream_release(cubeb_stream * stm); + +struct auto_stream_ref { + auto_stream_ref(cubeb_stream * stm_) : stm(stm_) + { + wasapi_stream_add_ref(stm); + } + ~auto_stream_ref() { wasapi_stream_release(stm); } + cubeb_stream * stm; +}; + extern cubeb_ops const wasapi_ops; static com_heap_ptr @@ -223,6 +246,11 @@ private: com_heap_ptr capture_comms_id; }; +struct AutoRegisterThread { + AutoRegisterThread(const char * name) { CUBEB_REGISTER_THREAD(name); } + ~AutoRegisterThread() { CUBEB_UNREGISTER_THREAD(); } +}; + int wasapi_stream_stop(cubeb_stream * stm); int @@ -365,8 +393,8 @@ struct cubeb_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. */ + /* This event is set by the 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. */ @@ -410,17 +438,23 @@ struct cubeb_stream { float volume = 1.0; /* True if the stream is draining. */ bool draining = false; - /* If the render thread fails to stop, this is set to true and ownership of - * the stm is "leaked" to the render thread for later cleanup. */ - std::atomic emergency_bailout{false}; /* This needs an active audio input stream to be known, and is updated in the * first audio input callback. */ std::atomic input_latency_hns{LATENCY_NOT_AVAILABLE_YET}; - /* Those attributes count the number of frames requested (resp. received) by the OS, to be able to detect drifts. This is only used for logging for now. */ size_t total_input_frames = 0; size_t total_output_frames = 0; + /* This is set by the render loop thread once it has obtained a reference to + * COM and this stream object. */ + HANDLE thread_ready_event = 0; + /* Keep a ref count on this stream object. After both stream_destroy has been + * called and the render loop thread has exited, destroy this stream object. + */ + LONG ref_count = 0; + + /* True if the stream is active, false if inactive. */ + bool active = false; }; class monitor_device_notifications { @@ -463,6 +497,7 @@ public: private: static unsigned int __stdcall thread_proc(LPVOID args) { + AutoRegisterThread raii("WASAPI device notification thread"); XASSERT(args); auto mdn = static_cast(args); mdn->notification_thread_loop(); @@ -689,7 +724,8 @@ public: } wasapi_endpoint_notification_client(HANDLE event, ERole role) - : ref_count(1), reconfigure_event(event), role(role) + : ref_count(1), reconfigure_event(event), role(role), + last_device_change(timeGetTime()) { } @@ -698,17 +734,32 @@ public: HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) { - LOG("endpoint: Audio device default changed."); + LOG("endpoint: Audio device default changed flow=%d role=%d " + "new_device_id=%ws.", + flow, role, device_id); /* we only support a single stream type for now. */ - if (flow != eRender && role != this->role) { + if (flow != eRender || role != this->role) { return S_OK; } - BOOL ok = SetEvent(reconfigure_event); - if (!ok) { - LOG("endpoint: SetEvent on reconfigure_event failed: %lx", - GetLastError()); + DWORD last_change_ms = timeGetTime() - last_device_change; + bool same_device = default_device_id && device_id && + wcscmp(default_device_id.get(), device_id) == 0; + LOG("endpoint: Audio device default changed last_change=%u same_device=%d", + last_change_ms, same_device); + if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) { + if (device_id) { + default_device_id.reset(_wcsdup(device_id)); + } else { + default_device_id.reset(); + } + BOOL ok = SetEvent(reconfigure_event); + LOG("endpoint: Audio device default changed: trigger reconfig"); + if (!ok) { + LOG("endpoint: SetEvent on reconfigure_event failed: %lx", + GetLastError()); + } } return S_OK; @@ -747,6 +798,8 @@ private: LONG ref_count; HANDLE reconfigure_event; ERole role; + std::unique_ptr default_device_id; + DWORD last_device_change; }; namespace { @@ -756,9 +809,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr, void const * input_buffer, void * output_buffer, long nframes) { - if (stm->emergency_bailout) { - return CUBEB_ERROR; - } return stm->data_callback(stm, user_ptr, input_buffer, output_buffer, nframes); } @@ -766,9 +816,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr, void wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state) { - if (stm->emergency_bailout) { - return; - } return stm->state_callback(stm, user_ptr, state); } @@ -1303,8 +1350,10 @@ refill_callback_output(cubeb_stream * stm) long got = refill(stm, nullptr, 0, output_buffer, output_frames); - ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames, - got); + if (got != output_frames) { + ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames, + got); + } if (got < 0) { return false; } @@ -1322,29 +1371,12 @@ refill_callback_output(cubeb_stream * stm) void wasapi_stream_destroy(cubeb_stream * stm); -static void -handle_emergency_bailout(cubeb_stream * stm) -{ - if (stm->emergency_bailout) { - CloseHandle(stm->thread); - stm->thread = NULL; - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; - wasapi_stream_destroy(stm); - _endthreadex(0); - } -} - static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) { + AutoRegisterThread raii("cubeb rendering thread"); cubeb_stream * stm = static_cast(stream); - 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_stream_ref stream_ref(stm); struct auto_com { auto_com() { @@ -1354,6 +1386,21 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) ~auto_com() { CoUninitialize(); } } com; + 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; + + // Signal wasapi_stream_start that we've initialized COM and incremented + // the stream's ref_count. + BOOL ok = SetEvent(stm->thread_ready_event); + if (!ok) { + LOG("thread_ready SetEvent failed: %lx", GetLastError()); + return 0; + } + /* We could consider using "Pro Audio" here for WebAudio and maybe WebRTC. */ mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); @@ -1363,20 +1410,9 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) GetLastError()); } - /* 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 = 3; while (is_playing) { - handle_emergency_bailout(stm); DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), - wait_array, FALSE, 1000); - handle_emergency_bailout(stm); - if (waitResult != WAIT_TIMEOUT) { - timeout_count = 0; - } + wait_array, FALSE, INFINITE); switch (waitResult) { case WAIT_OBJECT_0: { /* shutdown */ is_playing = false; @@ -1388,36 +1424,40 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) continue; } case WAIT_OBJECT_0 + 1: { /* reconfigure */ + auto_lock lock(stm->stream_reset_lock); + if (!stm->active) { + /* Avoid reconfiguring, stream start will handle it. */ + LOG("Stream is not active, ignoring reconfigure."); + continue; + } XASSERT(stm->output_client || stm->input_client); LOG("Reconfiguring the stream"); /* Close the stream */ + bool was_running = false; if (stm->output_client) { - stm->output_client->Stop(); + was_running = stm->output_client->Stop() == S_OK; LOG("Output stopped."); } if (stm->input_client) { - stm->input_client->Stop(); + was_running = stm->input_client->Stop() == S_OK; 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."); + 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) { + if (was_running && stm->output_client) { hr = stm->output_client->Start(); if (FAILED(hr)) { LOG("Error starting output after reconfigure, error: %lx", hr); @@ -1426,7 +1466,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) } LOG("Output started after reconfigure."); } - if (stm->input_client) { + if (was_running && stm->input_client) { hr = stm->input_client->Start(); if (FAILED(hr)) { LOG("Error starting input after reconfiguring, error: %lx", hr); @@ -1455,14 +1495,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) 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); XASSERT(false); @@ -1482,8 +1514,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) AvRevertMmThreadCharacteristics(mmcss_handle); } - handle_emergency_bailout(stm); - if (FAILED(hr)) { wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); } @@ -1739,24 +1769,16 @@ namespace { enum ShutdownPhase { OnStop, OnDestroy }; bool -stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase) +stop_and_join_render_thread(cubeb_stream * stm) { - // Only safe to transfer `stm` ownership to the render thread when - // the stream is being destroyed by the caller. - bool bailout = phase == OnDestroy; - - LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread, - stm->emergency_bailout.load(), static_cast(phase)); + LOG("%p: Stop and join render thread: %p", stm, stm->thread); if (!stm->thread) { return true; } - XASSERT(!stm->emergency_bailout); - BOOL ok = SetEvent(stm->shutdown_event); if (!ok) { LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError()); - stm->emergency_bailout = bailout; return false; } @@ -1774,31 +1796,23 @@ stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase) LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: " "%lx, %lx", r, GetLastError()); - stm->emergency_bailout = bailout; return false; } - // Only attempt to close and null out the thread and event if the - // WaitForSingleObject above succeeded. - LOG("stop_and_join_render_thread: Closing thread."); - CloseHandle(stm->thread); - stm->thread = NULL; - - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; - return true; } void wasapi_destroy(cubeb * context) { - auto_lock lock(context->lock); - XASSERT(!context->device_collection_enumerator && - !context->collection_notification_client); + { + auto_lock lock(context->lock); + XASSERT(!context->device_collection_enumerator && + !context->collection_notification_client); - if (context->device_ids) { - cubeb_strings_destroy(context->device_ids); + if (context->device_ids) { + cubeb_strings_destroy(context->device_ids); + } } delete context; @@ -2469,8 +2483,8 @@ setup_wasapi_stream(cubeb_stream * stm) std::unique_ptr selected_output_device_id; if (stm->output_device_id) { if (std::unique_ptr tmp = - move(copy_wide_string(stm->output_device_id.get()))) { - selected_output_device_id = move(tmp); + copy_wide_string(stm->output_device_id.get())) { + selected_output_device_id = std::move(tmp); } else { LOG("Failed to copy output device identifier."); return CUBEB_ERROR; @@ -2512,7 +2526,7 @@ setup_wasapi_stream(cubeb_stream * stm) cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm); if (matched) { selected_output_device_id = - move(utf8_to_wstr(reinterpret_cast(matched))); + utf8_to_wstr(reinterpret_cast(matched)); } } } @@ -2528,9 +2542,9 @@ setup_wasapi_stream(cubeb_stream * stm) stm->output_stream_params.layout = stm->input_stream_params.layout; if (stm->input_device_id) { if (std::unique_ptr tmp = - move(copy_wide_string(stm->input_device_id.get()))) { + copy_wide_string(stm->input_device_id.get())) { XASSERT(!selected_output_device_id); - selected_output_device_id = move(tmp); + selected_output_device_id = std::move(tmp); } else { LOG("Failed to copy device identifier while copying input stream " "configuration to output stream configuration to drive loopback."); @@ -2690,8 +2704,8 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, return CUBEB_ERROR_INVALID_FORMAT; } - std::unique_ptr stm( - new cubeb_stream(), wasapi_stream_destroy); + cubeb_stream * stm = new cubeb_stream(); + auto_stream_ref stream_ref(stm); stm->context = context; stm->data_callback = data_callback; @@ -2763,12 +2777,24 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, return CUBEB_ERROR; } + 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_ready_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->thread_ready_event) { + LOG("Can't create the thread ready event, error: %lx", GetLastError()); + return CUBEB_ERROR; + } + { /* 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()); + rv = setup_wasapi_stream(stm); } if (rv != CUBEB_OK) { return rv; @@ -2783,7 +2809,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, !(output_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) { LOG("Follow the system default input or/and output devices"); - HRESULT hr = register_notification_client(stm.get()); + HRESULT hr = register_notification_client(stm); if (FAILED(hr)) { /* this is not fatal, we can still play audio, but we won't be able to keep using the default audio endpoint if it changes. */ @@ -2791,9 +2817,25 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, } } - *stream = stm.release(); + 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; + } - LOG("Stream init succesfull (%p)", *stream); + // Wait for the wasapi_stream_render_loop thread to signal that COM has been + // initialized and the stream's ref_count has been incremented. + hr = WaitForSingleObject(stm->thread_ready_event, INFINITE); + XASSERT(hr == WAIT_OBJECT_0); + CloseHandle(stm->thread_ready_event); + stm->thread_ready_event = 0; + + wasapi_stream_add_ref(stm); + *stream = stm; + + LOG("Stream init successful (%p)", *stream); return CUBEB_OK; } @@ -2804,20 +2846,18 @@ close_wasapi_stream(cubeb_stream * 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->output_device = nullptr; - stm->input_device = nullptr; - #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME stm->audio_stream_volume = nullptr; #endif - stm->audio_clock = nullptr; + stm->render_client = nullptr; + stm->output_client = nullptr; + stm->output_device = nullptr; + + stm->capture_client = nullptr; + stm->input_client = nullptr; + stm->input_device = nullptr; + stm->total_frames_written += static_cast( round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, @@ -2833,32 +2873,59 @@ close_wasapi_stream(cubeb_stream * stm) } } +LONG +wasapi_stream_add_ref(cubeb_stream * stm) +{ + XASSERT(stm); + LONG result = InterlockedIncrement(&stm->ref_count); + LOGV("Stream ref count incremented = %i (%p)", result, stm); + return result; +} + +LONG +wasapi_stream_release(cubeb_stream * stm) +{ + XASSERT(stm); + + LONG result = InterlockedDecrement(&stm->ref_count); + LOGV("Stream ref count decremented = %i (%p)", result, stm); + if (result == 0) { + LOG("Stream ref count hit zero, destroying (%p)", stm); + + if (stm->notification_client) { + unregister_notification_client(stm); + } + + CloseHandle(stm->shutdown_event); + CloseHandle(stm->reconfigure_event); + CloseHandle(stm->refill_event); + CloseHandle(stm->input_available_event); + + CloseHandle(stm->thread); + + // The variables intialized in wasapi_stream_init, + // must be destroyed in wasapi_stream_release. + stm->linear_input_buffer.reset(); + + { + auto_lock lock(stm->stream_reset_lock); + close_wasapi_stream(stm); + } + + delete stm; + } + + return result; +} + void wasapi_stream_destroy(cubeb_stream * stm) { XASSERT(stm); - LOG("Stream destroy (%p)", stm); + LOG("Stream destroy called, decrementing ref count (%p)", stm); - if (!stop_and_join_render_thread(stm, OnDestroy)) { - // Emergency bailout: render thread becomes responsible for calling - // wasapi_stream_destroy. - return; - } - - if (stm->notification_client) { - unregister_notification_client(stm); - } - - { - auto_lock lock(stm->stream_reset_lock); - close_wasapi_stream(stm); - } - - CloseHandle(stm->reconfigure_event); - CloseHandle(stm->refill_event); - CloseHandle(stm->input_available_event); - - delete stm; + stop_and_join_render_thread(stm); + wasapi_stream_release(stm); } enum StreamDirection { OUTPUT, INPUT }; @@ -2866,6 +2933,7 @@ enum StreamDirection { OUTPUT, INPUT }; int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) { + XASSERT(stm); XASSERT((dir == OUTPUT && stm->output_client) || (dir == INPUT && stm->input_client)); @@ -2909,7 +2977,7 @@ wasapi_stream_start(cubeb_stream * stm) { auto_lock lock(stm->stream_reset_lock); - XASSERT(stm && !stm->thread && !stm->shutdown_event); + XASSERT(stm); XASSERT(stm->output_client || stm->input_client); if (stm->output_client) { @@ -2926,24 +2994,9 @@ wasapi_stream_start(cubeb_stream * stm) } } - stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); - if (!stm->shutdown_event) { - LOG("Can't create the shutdown event, error: %lx", GetLastError()); - return CUBEB_ERROR; - } + stm->active = true; - cubeb_async_log_reset_threads(); - stm->thread = - (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, - STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); - if (stm->thread == NULL) { - LOG("could not create WASAPI render thread."); - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; - return CUBEB_ERROR; - } - - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); return CUBEB_OK; } @@ -2973,13 +3026,9 @@ wasapi_stream_stop(cubeb_stream * stm) } } - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - } + stm->active = false; - if (!stop_and_join_render_thread(stm, OnStop)) { - // If we could not join the thread, put the stream in error. - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return CUBEB_ERROR; + wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); } return CUBEB_OK; @@ -3114,8 +3163,9 @@ wstr_to_utf8(LPCWSTR str) return ret; } -static std::unique_ptr -utf8_to_wstr(char const * str) { +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; @@ -3126,8 +3176,8 @@ utf8_to_wstr(char const * str) { return ret; } -static com_ptr wasapi_get_device_node( - IMMDeviceEnumerator * enumerator, IMMDevice * dev) +static com_ptr +wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev) { com_ptr ret; com_ptr devtopo; diff --git a/dep/cubeb/src/cubeb_winmm.c b/dep/cubeb/src/cubeb_winmm.c index 44aec86d2..9aa176ead 100644 --- a/dep/cubeb/src/cubeb_winmm.c +++ b/dep/cubeb/src/cubeb_winmm.c @@ -105,10 +105,13 @@ struct cubeb_stream { int free_buffers; int shutdown; int draining; + int error; HANDLE event; HWAVEOUT waveout; CRITICAL_SECTION lock; uint64_t written; + /* number of frames written during preroll */ + uint64_t position_base; float soft_volume; /* For position wrap-around handling: */ size_t frame_size; @@ -150,6 +153,14 @@ winmm_get_next_buffer(cubeb_stream * stm) return hdr; } +static long +preroll_callback(cubeb_stream * stream, void * user, const void * inputbuffer, + void * outputbuffer, long nframes) +{ + memset((uint8_t *)outputbuffer, 0, nframes * bytes_per_frame(stream->params)); + return nframes; +} + static void winmm_refill_stream(cubeb_stream * stm) { @@ -158,13 +169,20 @@ winmm_refill_stream(cubeb_stream * stm) long wanted; MMRESULT r; + ALOG("winmm_refill_stream"); + EnterCriticalSection(&stm->lock); + if (stm->error) { + LeaveCriticalSection(&stm->lock); + return; + } stm->free_buffers += 1; XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); if (stm->draining) { LeaveCriticalSection(&stm->lock); if (stm->free_buffers == NBUFS) { + ALOG("winmm_refill_stream draining"); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); } SetEvent(stm->event); @@ -187,9 +205,10 @@ winmm_refill_stream(cubeb_stream * stm) got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted); EnterCriticalSection(&stm->lock); if (got < 0) { + stm->error = 1; LeaveCriticalSection(&stm->lock); - /* XXX handle this case */ - XASSERT(0); + SetEvent(stm->event); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return; } else if (got < wanted) { stm->draining = 1; @@ -224,6 +243,8 @@ winmm_refill_stream(cubeb_stream * stm) return; } + ALOG("winmm_refill_stream %ld frames", got); + LeaveCriticalSection(&stm->lock); } @@ -486,7 +507,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, stm->params = *output_stream_params; - stm->data_callback = data_callback; + // Data callback is set to the user-provided data callback after + // the initialization and potential preroll callback calls are done, because + // cubeb users don't expect the data callback to be called during + // initialization. + stm->data_callback = preroll_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->written = 0; @@ -553,9 +578,18 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, stm->frame_size = bytes_per_frame(stm->params); stm->prev_pos_lo_dword = 0; stm->pos_hi_dword = 0; + // Set the user data callback now that preroll has finished. + stm->data_callback = data_callback; + stm->position_base = 0; + + // Offset the position by the number of frames written during preroll. + stm->position_base = stm->written; + stm->written = 0; *stream = stm; + LOG("winmm_stream_init OK"); + return CUBEB_OK; } @@ -585,7 +619,7 @@ winmm_stream_destroy(cubeb_stream * stm) LeaveCriticalSection(&stm->lock); /* Wait for all blocks to complete. */ - while (device_valid && enqueued > 0) { + while (device_valid && enqueued > 0 && !stm->error) { DWORD rv = WaitForSingleObject(stm->event, INFINITE); XASSERT(rv == WAIT_OBJECT_0); @@ -774,7 +808,17 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_ERROR; } - *position = update_64bit_position(stm, time.u.cb) / stm->frame_size; + uint64_t position_not_adjusted = + update_64bit_position(stm, time.u.cb) / stm->frame_size; + + // Subtract the number of frames that were written while prerolling, during + // initialization. + if (position_not_adjusted < stm->position_base) { + *position = 0; + } else { + *position = position_not_adjusted - stm->position_base; + } + LeaveCriticalSection(&stm->lock); return CUBEB_OK; @@ -787,17 +831,12 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency) MMTIME time; uint64_t written, position; - EnterCriticalSection(&stm->lock); - /* See the long comment above for why not just use TIME_SAMPLES here. */ - time.wType = TIME_BYTES; - r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); - - if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) { - LeaveCriticalSection(&stm->lock); - return CUBEB_ERROR; + int rv = winmm_stream_get_position(stm, &position); + if (rv != CUBEB_OK) { + return rv; } - position = update_64bit_position(stm, time.u.cb); + EnterCriticalSection(&stm->lock); written = stm->written; LeaveCriticalSection(&stm->lock); diff --git a/scripts/flatpak/org.duckstation.DuckStation.json b/scripts/flatpak/org.duckstation.DuckStation.json index ca70ff1e1..07aedc82e 100644 --- a/scripts/flatpak/org.duckstation.DuckStation.json +++ b/scripts/flatpak/org.duckstation.DuckStation.json @@ -22,7 +22,8 @@ "modules/21-libbacktrace.json", { "name": "duckstation", - "buildsystem": "cmake", + "buildsystem": "cmake-ninja", + "no-make-install": true, "build-options": { "strip": false, "no-debuginfo": true,