├── .gitignore ├── src ├── cubeb-speex-resampler.h ├── cubeb-jni.h ├── cubeb_android.h ├── cubeb_utils.cpp ├── cubeb_osx_run_loop.h ├── cubeb_tracing.h ├── cubeb-jni-instances.h ├── cubeb_assert.h ├── cubeb_mixer.h ├── cubeb_osx_run_loop.cpp ├── cubeb_strings.h ├── cubeb_utils_win.h ├── android │ ├── cubeb-output-latency.h │ ├── cubeb_media_library.h │ ├── audiotrack_definitions.h │ └── sles_definitions.h ├── cubeb_utils_unix.h ├── cubeb_array_queue.h ├── cubeb_triple_buffer.h ├── cubeb_log.h ├── cubeb-jni.cpp ├── cubeb-internal.h ├── cubeb_strings.c ├── cubeb_audio_dump.h ├── cubeb_resampler.h ├── cubeb_ring_array.h ├── cubeb_audio_dump.cpp ├── cubeb_log.cpp ├── cubeb_utils.h └── cubeb_kai.c ├── Config.cmake.in ├── cmake ├── compile_tests │ └── oss_is_v4.c └── toolchain-cross-mingw.cmake ├── subprojects └── speex │ ├── speex_config_types.h │ ├── resample_sse.h │ ├── fixed_generic.h │ ├── arch.h │ └── resample_neon.h ├── .clang-format ├── libcubeb.pc.in ├── scan-build-install.sh ├── docs └── Doxyfile.in ├── .gitmodules ├── test ├── README.md ├── test_latency.cpp ├── test_triple_buffer.cpp ├── test_utils.cpp ├── test_audio_dump.cpp ├── test_ring_array.cpp ├── test_overload_callback.cpp ├── test_record.cpp ├── test_tone.cpp ├── test_device_changed_callback.cpp ├── common.h ├── test_logging.cpp ├── test_ring_buffer.cpp ├── test_audio.cpp ├── test_callback_ret.cpp ├── test_devices.cpp └── test_duplex.cpp ├── cubeb.supp ├── scripts └── run-clang-format.sh ├── AUTHORS ├── LICENSE ├── INSTALL.md ├── .github └── workflows │ ├── docs.yml │ └── build.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .cache/ 3 | build/ 4 | -------------------------------------------------------------------------------- /src/cubeb-speex-resampler.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake") 4 | check_required_components(cubeb) 5 | -------------------------------------------------------------------------------- /cmake/compile_tests/oss_is_v4.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if SOUND_VERSION < 0x040000 4 | # error "OSSv4 is not available in sys/soundcard.h" 5 | #endif 6 | 7 | int main() 8 | { 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /subprojects/speex/speex_config_types.h: -------------------------------------------------------------------------------- 1 | #ifndef __SPEEX_TYPES_H__ 2 | #define __SPEEX_TYPES_H__ 3 | 4 | /* these are filled in by configure */ 5 | typedef int16_t spx_int16_t; 6 | typedef uint16_t spx_uint16_t; 7 | typedef int32_t spx_int32_t; 8 | typedef uint32_t spx_uint32_t; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | IndentWidth: 2 2 | UseTab: Never 3 | ReflowComments: true 4 | PointerAlignment: Middle 5 | AlignAfterOpenBracket: Align 6 | AlwaysBreakAfterReturnType: TopLevel 7 | ColumnLimit: 80 8 | BreakBeforeBraces: Custom 9 | BraceWrapping: 10 | AfterFunction: true 11 | AfterControlStatement: Never 12 | SpaceBeforeParens: ControlStatements 13 | BreakBeforeBinaryOperators: None 14 | -------------------------------------------------------------------------------- /libcubeb.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@CUBEB_PC_LIBDIR@ 4 | includedir=@CUBEB_PC_INCLUDEDIR@ 5 | 6 | Name: libcubeb 7 | Description: Cross platform audio library 8 | Version: @PROJECT_VERSION@ 9 | Requires.private: @CUBEB_PC_PRIVATE_REQUIRES@ 10 | Libs: -L${libdir} -lcubeb 11 | Libs.private: @CUBEB_PC_PRIVATE_LIBS@ 12 | Cflags: -I${includedir} 13 | -------------------------------------------------------------------------------- /scan-build-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | 3 | CLANG_CHECKER_NAME=checker-278 4 | 5 | cd ~ 6 | 7 | if [ ! -d ~/$CLANG_CHECKER_NAME ] 8 | then 9 | curl http://clang-analyzer.llvm.org/downloads/$CLANG_CHECKER_NAME.tar.bz2 -o ~/$CLANG_CHECKER_NAME.tar.bz2 10 | tar -xf ~/$CLANG_CHECKER_NAME.tar.bz2 11 | fi 12 | 13 | export SCAN_BUILD_PATH=~/$CLANG_CHECKER_NAME/bin/scan-build 14 | 15 | cd - 16 | -------------------------------------------------------------------------------- /src/cubeb-jni.h: -------------------------------------------------------------------------------- 1 | #ifndef _CUBEB_JNI_H_ 2 | #define _CUBEB_JNI_H_ 3 | 4 | typedef struct cubeb_jni cubeb_jni; 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | cubeb_jni * 11 | cubeb_jni_init(); 12 | int 13 | cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); 14 | void 15 | cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); 16 | 17 | #ifdef __cplusplus 18 | }; 19 | #endif 20 | 21 | #endif // _CUBEB_JNI_H_ 22 | -------------------------------------------------------------------------------- /docs/Doxyfile.in: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = @PROJECT_NAME@ 2 | PROJECT_NUMBER = @PROJECT_VERSION@ 3 | OUTPUT_DIRECTORY = . 4 | JAVADOC_AUTOBRIEF = YES 5 | OPTIMIZE_OUTPUT_FOR_C = YES 6 | CASE_SENSE_NAMES = NO 7 | SORT_MEMBER_DOCS = NO 8 | QUIET = YES 9 | WARN_NO_PARAMDOC = YES 10 | INPUT = @CMAKE_CURRENT_SOURCE_DIR@/include/cubeb 11 | GENERATE_HTML = YES 12 | GENERATE_LATEX = NO 13 | -------------------------------------------------------------------------------- /src/cubeb_android.h: -------------------------------------------------------------------------------- 1 | #ifndef CUBEB_ANDROID_H 2 | #define CUBEB_ANDROID_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | // If the latency requested is above this threshold, this stream is considered 8 | // intended for playback (vs. real-time). Tell Android it should favor saving 9 | // power over performance or latency. 10 | // This is around 100ms at 44100 or 48000 11 | const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000; 12 | 13 | #ifdef __cplusplus 14 | }; 15 | #endif 16 | 17 | #endif // CUBEB_ANDROID_H 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "googletest"] 2 | path = googletest 3 | url = https://github.com/google/googletest 4 | [submodule "cmake/sanitizers-cmake"] 5 | path = cmake/sanitizers-cmake 6 | url = https://github.com/arsenm/sanitizers-cmake 7 | [submodule "src/cubeb-coreaudio-rs"] 8 | path = src/cubeb-coreaudio-rs 9 | url = https://github.com/mozilla/cubeb-coreaudio-rs 10 | branch = trailblazer 11 | [submodule "src/cubeb-pulse-rs"] 12 | path = src/cubeb-pulse-rs 13 | url = https://github.com/mozilla/cubeb-pulse-rs 14 | branch = dev 15 | -------------------------------------------------------------------------------- /cmake/toolchain-cross-mingw.cmake: -------------------------------------------------------------------------------- 1 | SET(CMAKE_SYSTEM_NAME Windows) 2 | 3 | set(COMPILER_PREFIX "i686-w64-mingw32") 4 | 5 | find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres) 6 | find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc-posix) 7 | find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++-posix) 8 | 9 | SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX}) 10 | 11 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 12 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 13 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 14 | 15 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | Notes on writing tests. 2 | 3 | The googletest submodule is currently at 1.6 rather than the latest, and should 4 | only be updated to track the version used in Gecko to make test compatibility 5 | easier. 6 | 7 | Always #include "gtest/gtest.h" before *anything* else. 8 | 9 | All tests should be part of the "cubeb" test case, e.g. TEST(cubeb, my_test). 10 | 11 | Tests are built stand-alone in cubeb, but built as a single unit in Gecko, so 12 | you must use unique names for globally visible items in each test, e.g. rather 13 | than state_cb use state_cb_my_test. 14 | -------------------------------------------------------------------------------- /cubeb.supp: -------------------------------------------------------------------------------- 1 | { 2 | snd_config_update-malloc 3 | Memcheck:Leak 4 | fun:malloc 5 | ... 6 | fun:snd_config_update_r 7 | } 8 | { 9 | snd1_dlobj_cache_get-malloc 10 | Memcheck:Leak 11 | fun:malloc 12 | ... 13 | fun:snd1_dlobj_cache_get 14 | } 15 | { 16 | parse_defs-malloc 17 | Memcheck:Leak 18 | fun:malloc 19 | ... 20 | fun:parse_defs 21 | } 22 | { 23 | parse_defs-calloc 24 | Memcheck:Leak 25 | fun:calloc 26 | ... 27 | fun:parse_defs 28 | } 29 | { 30 | pa_client_conf_from_x11-malloc 31 | Memcheck:Leak 32 | fun:malloc 33 | ... 34 | fun:pa_client_conf_from_x11 35 | } 36 | 37 | -------------------------------------------------------------------------------- /scripts/run-clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if there are uncommitted changes (excluding rust backends) 4 | if [ -n "$(git status --porcelain | egrep -v '(cubeb-coreaudio-rs|cubeb-pulse-rs)')" ]; then 5 | echo "Not running clang-format -- commit changes and try again" 6 | exit 0 7 | fi 8 | 9 | # Find and format all C/C++ files 10 | find "$1/src" "$1/include" "$1/test" \ 11 | -type f \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) \ 12 | -not -path "*/subprojects/speex/*" \ 13 | -not -path "*/src/cubeb-coreaudio-rs/*" \ 14 | -not -path "*/src/cubeb-pulse-rs/*" \ 15 | -print0 | xargs -0 "${2:-clang-format}" -Werror -i 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Matthew Gregan 2 | Alexandre Ratchov 3 | Michael Wu 4 | Paul Adenot 5 | David Richards 6 | Sebastien Alaiwan 7 | KO Myung-Hun 8 | Haakon Sporsheim 9 | Alex Chronopoulos 10 | Jan Beich 11 | Vito Caputo 12 | Landry Breuil 13 | Jacek Caban 14 | Paul Hancock 15 | Ted Mielczarek 16 | Chun-Min Chang 17 | -------------------------------------------------------------------------------- /src/cubeb_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2018 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #include "cubeb_utils.h" 9 | 10 | size_t 11 | cubeb_sample_size(cubeb_sample_format format) 12 | { 13 | switch (format) { 14 | case CUBEB_SAMPLE_S16LE: 15 | case CUBEB_SAMPLE_S16BE: 16 | return sizeof(int16_t); 17 | case CUBEB_SAMPLE_FLOAT32LE: 18 | case CUBEB_SAMPLE_FLOAT32BE: 19 | return sizeof(float); 20 | default: 21 | // should never happen as all cases are handled above. 22 | assert(false); 23 | return 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cubeb_osx_run_loop.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* On OSX 10.6 and after, the notification callbacks from the audio hardware are 9 | * called on the main thread. Setting the kAudioHardwarePropertyRunLoop property 10 | * to null tells the OSX to use a separate thread for that. 11 | * 12 | * This has to be called only once per process, so it is in a separate header 13 | * for easy integration in other code bases. */ 14 | #if defined(__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | void 19 | cubeb_set_coreaudio_notification_runloop(); 20 | 21 | #if defined(__cplusplus) 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2011 Mozilla Foundation 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /src/cubeb_tracing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2022 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_TRACING_H 9 | #define CUBEB_TRACING_H 10 | 11 | /* Empty header to allow hooking up a frame profiler. */ 12 | 13 | // To be called once on a thread to register for tracing. 14 | #define CUBEB_REGISTER_THREAD(name) 15 | // To be called once before a registered threads exits. 16 | #define CUBEB_UNREGISTER_THREAD() 17 | // Insert a tracing marker, with a particular name. 18 | // Phase can be 'x': instant marker, start time but no duration 19 | // 'b': beginning of a marker with a duration 20 | // 'e': end of a marker with a duration 21 | #define CUBEB_TRACE(name, phase) 22 | 23 | #endif // CUBEB_TRACING_H 24 | -------------------------------------------------------------------------------- /src/cubeb-jni-instances.h: -------------------------------------------------------------------------------- 1 | #ifndef _CUBEB_JNI_INSTANCES_H_ 2 | #define _CUBEB_JNI_INSTANCES_H_ 3 | 4 | /* 5 | * The methods in this file offer a way to pass in the required 6 | * JNI instances in the cubeb library. By default they return NULL. 7 | * In this case part of the cubeb API that depends on JNI 8 | * will return CUBEB_ERROR_NOT_SUPPORTED. Currently only one 9 | * method depends on that: 10 | * 11 | * cubeb_stream_get_position() 12 | * 13 | * Users that want to use that cubeb API method must "override" 14 | * the methods bellow to return a valid instance of JavaVM 15 | * and application's Context object. 16 | * */ 17 | 18 | JNIEnv * 19 | cubeb_get_jni_env_for_thread() 20 | { 21 | return nullptr; 22 | } 23 | 24 | jobject 25 | cubeb_jni_get_context_instance() 26 | { 27 | return nullptr; 28 | } 29 | 30 | #endif //_CUBEB_JNI_INSTANCES_H_ 31 | -------------------------------------------------------------------------------- /src/cubeb_assert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_ASSERT 9 | #define CUBEB_ASSERT 10 | 11 | #include 12 | #include 13 | 14 | /** 15 | * This allow using an external release assert method. This file should only 16 | * export a function or macro called XASSERT that aborts the program. 17 | */ 18 | 19 | #define XASSERT(expr) \ 20 | do { \ 21 | if (!(expr)) { \ 22 | fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \ 23 | abort(); \ 24 | } \ 25 | } while (0) 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/cubeb_mixer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_MIXER 9 | #define CUBEB_MIXER 10 | 11 | #include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params. 12 | 13 | #if defined(__cplusplus) 14 | extern "C" { 15 | #endif 16 | 17 | typedef struct cubeb_mixer cubeb_mixer; 18 | cubeb_mixer * 19 | cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels, 20 | cubeb_channel_layout in_layout, uint32_t out_channels, 21 | cubeb_channel_layout out_layout); 22 | void 23 | cubeb_mixer_destroy(cubeb_mixer * mixer); 24 | int 25 | cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer, 26 | size_t input_buffer_size, void * output_buffer, 27 | size_t output_buffer_size); 28 | 29 | unsigned int 30 | cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout); 31 | 32 | #if defined(__cplusplus) 33 | } 34 | #endif 35 | 36 | #endif // CUBEB_MIXER 37 | -------------------------------------------------------------------------------- /src/cubeb_osx_run_loop.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #include "cubeb_osx_run_loop.h" 9 | #include "cubeb_log.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void 17 | cubeb_set_coreaudio_notification_runloop() 18 | { 19 | /* This is needed so that AudioUnit listeners get called on this thread, and 20 | * not the main thread. If we don't do that, they are not called, or a crash 21 | * occur, depending on the OSX version. */ 22 | AudioObjectPropertyAddress runloop_address = { 23 | kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, 24 | kAudioObjectPropertyElementMaster}; 25 | 26 | CFRunLoopRef run_loop = nullptr; 27 | 28 | OSStatus r; 29 | r = AudioObjectSetPropertyData(kAudioObjectSystemObject, &runloop_address, 0, 30 | NULL, sizeof(CFRunLoopRef), &run_loop); 31 | if (r != noErr) { 32 | LOG("Could not make global CoreAudio notifications use their own thread."); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/test_latency.cpp: -------------------------------------------------------------------------------- 1 | #include "cubeb/cubeb.h" 2 | #include "gtest/gtest.h" 3 | #include 4 | #include 5 | // #define ENABLE_NORMAL_LOG 6 | // #define ENABLE_VERBOSE_LOG 7 | #include "common.h" 8 | 9 | TEST(cubeb, latency) 10 | { 11 | cubeb * ctx = NULL; 12 | int r; 13 | uint32_t max_channels; 14 | uint32_t preferred_rate; 15 | uint32_t latency_frames; 16 | 17 | r = common_init(&ctx, "Cubeb audio test"); 18 | ASSERT_EQ(r, CUBEB_OK); 19 | 20 | std::unique_ptr cleanup_cubeb_at_exit( 21 | ctx, cubeb_destroy); 22 | 23 | r = cubeb_get_max_channel_count(ctx, &max_channels); 24 | ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); 25 | if (r == CUBEB_OK) { 26 | ASSERT_GT(max_channels, 0u); 27 | } 28 | 29 | r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate); 30 | ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); 31 | if (r == CUBEB_OK) { 32 | ASSERT_GT(preferred_rate, 0u); 33 | } 34 | 35 | cubeb_stream_params params = { 36 | CUBEB_SAMPLE_FLOAT32NE, preferred_rate, 37 | max_channels, CUBEB_LAYOUT_UNDEFINED, 38 | CUBEB_STREAM_PREF_NONE, CUBEB_INPUT_PROCESSING_PARAM_NONE}; 39 | r = cubeb_get_min_latency(ctx, ¶ms, &latency_frames); 40 | ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); 41 | if (r == CUBEB_OK) { 42 | ASSERT_GT(latency_frames, 0u); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/cubeb_strings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2011 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_STRINGS_H 9 | #define CUBEB_STRINGS_H 10 | 11 | #include "cubeb/cubeb.h" 12 | 13 | #if defined(__cplusplus) 14 | extern "C" { 15 | #endif 16 | 17 | /** Opaque handle referencing interned string storage. */ 18 | typedef struct cubeb_strings cubeb_strings; 19 | 20 | /** Initialize an interned string structure. 21 | @param strings An out param where an opaque pointer to the 22 | interned string storage will be returned. 23 | @retval CUBEB_OK in case of success. 24 | @retval CUBEB_ERROR in case of error. */ 25 | CUBEB_EXPORT int 26 | cubeb_strings_init(cubeb_strings ** strings); 27 | 28 | /** Destroy an interned string structure freeing all associated memory. 29 | @param strings An opaque pointer to the interned string storage to 30 | destroy. */ 31 | CUBEB_EXPORT void 32 | cubeb_strings_destroy(cubeb_strings * strings); 33 | 34 | /** Add string to internal storage. 35 | @param strings Opaque pointer to interned string storage. 36 | @param s String to add to storage. 37 | @retval CUBEB_OK 38 | @retval CUBEB_ERROR 39 | */ 40 | CUBEB_EXPORT char const * 41 | cubeb_strings_intern(cubeb_strings * strings, char const * s); 42 | 43 | #if defined(__cplusplus) 44 | } 45 | #endif 46 | 47 | #endif // !CUBEB_STRINGS_H 48 | -------------------------------------------------------------------------------- /test/test_triple_buffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2022 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* cubeb_triple_buffer test */ 9 | #include "gtest/gtest.h" 10 | #if !defined(_XOPEN_SOURCE) 11 | #define _XOPEN_SOURCE 600 12 | #endif 13 | #include "cubeb/cubeb.h" 14 | #include "cubeb_triple_buffer.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "common.h" 23 | 24 | TEST(cubeb, triple_buffer) 25 | { 26 | struct AB { 27 | uint64_t a; 28 | uint64_t b; 29 | }; 30 | triple_buffer buffer; 31 | 32 | std::atomic finished = {false}; 33 | 34 | ASSERT_TRUE(!buffer.updated()); 35 | 36 | auto t = std::thread([&finished, &buffer] { 37 | AB ab; 38 | ab.a = 0; 39 | ab.b = UINT64_MAX; 40 | uint64_t counter = 0; 41 | do { 42 | buffer.write(ab); 43 | ab.a++; 44 | ab.b--; 45 | } while (counter++ < 1e6 && ab.a <= UINT64_MAX && ab.b != 0); 46 | finished.store(true); 47 | }); 48 | 49 | AB ab; 50 | AB old_ab; 51 | old_ab.a = 0; 52 | old_ab.b = UINT64_MAX; 53 | 54 | // Wait to have at least one value produced. 55 | while (!buffer.updated()) { 56 | } 57 | 58 | // Check that the values are increasing (resp. descreasing) monotonically. 59 | while (!finished) { 60 | ab = buffer.read(); 61 | ASSERT_GE(ab.a, old_ab.a); 62 | ASSERT_LE(ab.b, old_ab.b); 63 | old_ab = ab; 64 | } 65 | 66 | t.join(); 67 | 68 | buffer.invalidate(); 69 | ASSERT_FALSE(buffer.updated()); 70 | } 71 | -------------------------------------------------------------------------------- /test/test_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "cubeb_utils.h" 2 | #include "gtest/gtest.h" 3 | 4 | TEST(cubeb, auto_array) 5 | { 6 | auto_array array; 7 | auto_array array2(10); 8 | uint32_t a[10]; 9 | 10 | ASSERT_EQ(array2.length(), 0u); 11 | ASSERT_EQ(array2.capacity(), 10u); 12 | 13 | for (uint32_t i = 0; i < 10; i++) { 14 | a[i] = i; 15 | } 16 | 17 | ASSERT_EQ(array.capacity(), 0u); 18 | ASSERT_EQ(array.length(), 0u); 19 | 20 | array.push(a, 10); 21 | 22 | ASSERT_TRUE(!array.reserve(9)); 23 | 24 | for (uint32_t i = 0; i < 10; i++) { 25 | ASSERT_EQ(array.data()[i], i); 26 | } 27 | 28 | ASSERT_EQ(array.capacity(), 10u); 29 | ASSERT_EQ(array.length(), 10u); 30 | 31 | uint32_t b[10]; 32 | 33 | array.pop(b, 5); 34 | 35 | ASSERT_EQ(array.capacity(), 10u); 36 | ASSERT_EQ(array.length(), 5u); 37 | for (uint32_t i = 0; i < 5; i++) { 38 | ASSERT_EQ(b[i], i); 39 | ASSERT_EQ(array.data()[i], 5 + i); 40 | } 41 | uint32_t * bb = b + 5; 42 | array.pop(bb, 5); 43 | 44 | ASSERT_EQ(array.capacity(), 10u); 45 | ASSERT_EQ(array.length(), 0u); 46 | for (uint32_t i = 0; i < 5; i++) { 47 | ASSERT_EQ(bb[i], 5 + i); 48 | } 49 | 50 | ASSERT_TRUE(!array.pop(nullptr, 1)); 51 | 52 | array.push(a, 10); 53 | array.push(a, 10); 54 | 55 | for (uint32_t j = 0; j < 2; j++) { 56 | for (uint32_t i = 0; i < 10; i++) { 57 | ASSERT_EQ(array.data()[10 * j + i], i); 58 | } 59 | } 60 | ASSERT_EQ(array.length(), 20u); 61 | ASSERT_EQ(array.capacity(), 20u); 62 | array.pop(nullptr, 5); 63 | 64 | for (uint32_t i = 0; i < 5; i++) { 65 | ASSERT_EQ(array.data()[i], 5 + i); 66 | } 67 | 68 | ASSERT_EQ(array.length(), 15u); 69 | ASSERT_EQ(array.capacity(), 20u); 70 | } 71 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Build instructions for libcubeb 2 | 3 | You must have CMake v3.14 or later installed. 4 | 5 | 1. `git clone --recursive https://github.com/mozilla/cubeb.git` 6 | 2. `cd cubeb` 7 | 3. `cmake -B ./build .` 8 | 4. `cmake --build ./build` 9 | 5. `cd build && ctest` 10 | 11 | # Windows build notes 12 | 13 | Windows builds can use Microsoft Visual Studio 2015, Microsoft Visual Studio 14 | 2017, or MinGW-w64 with Win32 threads (by passing `cmake -G` to generate the 15 | appropriate build configuration). 16 | 17 | ## Microsoft Visual Studio 2015 or 2017 Command Line 18 | 19 | CMake can be used from the command line by following the build steps at the top 20 | of this file. CMake will select a default generator based on the environment, 21 | or one can be specified with the `-G` argument. 22 | 23 | ## Microsoft Visual Studio 2017 IDE 24 | 25 | Visual Studio 2017 adds in built support for CMake. CMake can be used from 26 | within the IDE via the following steps: 27 | 28 | - Navigate to `File -> Open -> Cmake...` 29 | - Open `CMakeLists.txt` file in the root of the project. 30 | 31 | Note, to generate the build in the cubeb dir CMake settings need to be updated 32 | via: `CMake -> Change CMake Settings -> CMakeLists.txt`. The default 33 | configuration used by Visual Studio will place the build in a different location 34 | than the steps detailed at the top of this file. 35 | 36 | ## MinGW-w64 37 | 38 | To build with MinGW-w64, install the following items: 39 | 40 | - Download and install MinGW-w64 with Win32 threads. 41 | - Download and install CMake. 42 | - Run MinGW-w64 Terminal from the Start Menu. 43 | - Follow the build steps at the top of this file, but at step 4 run: 44 | `cmake -G "MinGW Makefiles" ../cubeb` 45 | - Continue the build steps at the top of this file. 46 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate and Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | docs: 11 | runs-on: ubuntu-24.04 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | submodules: true 17 | 18 | - name: Install Dependencies 19 | run: | 20 | sudo apt-get update 21 | sudo apt-get install -y doxygen cmake 22 | 23 | - name: Configure CMake 24 | run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 25 | 26 | - name: Generate Documentation 27 | run: | 28 | cd build 29 | make doc 30 | 31 | - name: Deploy to GitHub Pages 32 | if: github.ref == 'refs/heads/master' && github.event_name == 'push' 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: | 36 | # Configure git 37 | git config --global user.name "github-actions[bot]" 38 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 39 | 40 | # Create a new orphan branch for gh-pages 41 | git checkout --orphan gh-pages 42 | 43 | # Remove all files from the current branch 44 | git rm -rf . 45 | 46 | # Copy documentation files to root 47 | cp -r build/docs/html/* . 48 | 49 | # Create .nojekyll file to disable Jekyll processing 50 | touch .nojekyll 51 | 52 | # Add and commit files 53 | git add . 54 | git commit -m "Deploy documentation to GitHub Pages" 55 | 56 | # Push to gh-pages branch using authenticated URL 57 | git push --force https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git gh-pages 58 | -------------------------------------------------------------------------------- /src/cubeb_utils_win.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #if !defined(CUBEB_UTILS_WIN) 9 | #define CUBEB_UTILS_WIN 10 | 11 | #include "cubeb-internal.h" 12 | #include 13 | 14 | /* This wraps an SRWLock to track the owner in debug mode, adapted from 15 | NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx 16 | */ 17 | class owned_critical_section { 18 | public: 19 | owned_critical_section() 20 | : srwlock(SRWLOCK_INIT) 21 | #ifndef NDEBUG 22 | , 23 | owner(0) 24 | #endif 25 | { 26 | } 27 | 28 | void lock() 29 | { 30 | AcquireSRWLockExclusive(&srwlock); 31 | #ifndef NDEBUG 32 | XASSERT(owner != GetCurrentThreadId() && "recursive locking"); 33 | owner = GetCurrentThreadId(); 34 | #endif 35 | } 36 | 37 | void unlock() 38 | { 39 | #ifndef NDEBUG 40 | /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */ 41 | owner = 0; 42 | #endif 43 | ReleaseSRWLockExclusive(&srwlock); 44 | } 45 | 46 | /* This is guaranteed to have the good behaviour if it succeeds. The behaviour 47 | is undefined otherwise. */ 48 | void assert_current_thread_owns() 49 | { 50 | #ifndef NDEBUG 51 | /* This implies owner != 0, because GetCurrentThreadId cannot return 0. */ 52 | XASSERT(owner == GetCurrentThreadId()); 53 | #endif 54 | } 55 | 56 | private: 57 | SRWLOCK srwlock; 58 | #ifndef NDEBUG 59 | DWORD owner; 60 | #endif 61 | 62 | // Disallow copy and assignment because SRWLock cannot be copied. 63 | owned_critical_section(const owned_critical_section &); 64 | owned_critical_section & operator=(const owned_critical_section &); 65 | }; 66 | 67 | #endif /* CUBEB_UTILS_WIN */ 68 | -------------------------------------------------------------------------------- /src/android/cubeb-output-latency.h: -------------------------------------------------------------------------------- 1 | #ifndef _CUBEB_OUTPUT_LATENCY_H_ 2 | #define _CUBEB_OUTPUT_LATENCY_H_ 3 | 4 | #include "../cubeb-jni.h" 5 | #include "cubeb_media_library.h" 6 | #include 7 | 8 | struct output_latency_function { 9 | media_lib * from_lib; 10 | cubeb_jni * from_jni; 11 | int version; 12 | }; 13 | 14 | typedef struct output_latency_function output_latency_function; 15 | 16 | const int ANDROID_JELLY_BEAN_MR1_4_2 = 17; 17 | 18 | output_latency_function * 19 | cubeb_output_latency_load_method(int version) 20 | { 21 | output_latency_function * ol = NULL; 22 | ol = (output_latency_function *)calloc(1, sizeof(output_latency_function)); 23 | 24 | ol->version = version; 25 | 26 | if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) { 27 | ol->from_jni = cubeb_jni_init(); 28 | return ol; 29 | } 30 | 31 | ol->from_lib = cubeb_load_media_library(); 32 | return ol; 33 | } 34 | 35 | bool 36 | cubeb_output_latency_method_is_loaded(output_latency_function * ol) 37 | { 38 | assert(ol); 39 | if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) { 40 | return !!ol->from_jni; 41 | } 42 | 43 | return !!ol->from_lib; 44 | } 45 | 46 | void 47 | cubeb_output_latency_unload_method(output_latency_function * ol) 48 | { 49 | if (!ol) { 50 | return; 51 | } 52 | 53 | if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) { 54 | cubeb_jni_destroy(ol->from_jni); 55 | } 56 | 57 | if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) { 58 | cubeb_close_media_library(ol->from_lib); 59 | } 60 | 61 | free(ol); 62 | } 63 | 64 | extern "C" { 65 | 66 | uint32_t 67 | cubeb_get_output_latency(output_latency_function * ol) 68 | { 69 | assert(cubeb_output_latency_method_is_loaded(ol)); 70 | 71 | if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) { 72 | return cubeb_get_output_latency_from_jni(ol->from_jni); 73 | } 74 | 75 | return cubeb_get_output_latency_from_media_library(ol->from_lib); 76 | } 77 | } 78 | 79 | #endif // _CUBEB_OUTPUT_LATENCY_H_ 80 | -------------------------------------------------------------------------------- /src/cubeb_utils_unix.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #if !defined(CUBEB_UTILS_UNIX) 9 | #define CUBEB_UTILS_UNIX 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | /* This wraps a critical section to track the owner in debug mode. */ 16 | class owned_critical_section { 17 | public: 18 | owned_critical_section() 19 | { 20 | pthread_mutexattr_t attr; 21 | pthread_mutexattr_init(&attr); 22 | #ifndef NDEBUG 23 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); 24 | #else 25 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); 26 | #endif 27 | 28 | #ifndef NDEBUG 29 | int r = 30 | #endif 31 | pthread_mutex_init(&mutex, &attr); 32 | #ifndef NDEBUG 33 | assert(r == 0); 34 | #endif 35 | 36 | pthread_mutexattr_destroy(&attr); 37 | } 38 | 39 | ~owned_critical_section() 40 | { 41 | #ifndef NDEBUG 42 | int r = 43 | #endif 44 | pthread_mutex_destroy(&mutex); 45 | #ifndef NDEBUG 46 | assert(r == 0); 47 | #endif 48 | } 49 | 50 | void lock() 51 | { 52 | #ifndef NDEBUG 53 | int r = 54 | #endif 55 | pthread_mutex_lock(&mutex); 56 | #ifndef NDEBUG 57 | assert(r == 0 && "Deadlock"); 58 | #endif 59 | } 60 | 61 | void unlock() 62 | { 63 | #ifndef NDEBUG 64 | int r = 65 | #endif 66 | pthread_mutex_unlock(&mutex); 67 | #ifndef NDEBUG 68 | assert(r == 0 && "Unlocking unlocked mutex"); 69 | #endif 70 | } 71 | 72 | void assert_current_thread_owns() 73 | { 74 | #ifndef NDEBUG 75 | int r = pthread_mutex_lock(&mutex); 76 | assert(r == EDEADLK); 77 | #endif 78 | } 79 | 80 | private: 81 | pthread_mutex_t mutex; 82 | 83 | // Disallow copy and assignment because pthread_mutex_t cannot be copied. 84 | owned_critical_section(const owned_critical_section &); 85 | owned_critical_section & operator=(const owned_critical_section &); 86 | }; 87 | 88 | #endif /* CUBEB_UTILS_UNIX */ 89 | -------------------------------------------------------------------------------- /src/android/cubeb_media_library.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #ifndef _CUBEB_MEDIA_LIBRARY_H_ 6 | #define _CUBEB_MEDIA_LIBRARY_H_ 7 | 8 | typedef int32_t (*get_output_latency_ptr)(uint32_t * latency, int stream_type); 9 | 10 | struct media_lib { 11 | void * libmedia; 12 | get_output_latency_ptr get_output_latency; 13 | }; 14 | 15 | typedef struct media_lib media_lib; 16 | 17 | media_lib * 18 | cubeb_load_media_library() 19 | { 20 | media_lib ml = {}; 21 | ml.libmedia = dlopen("libmedia.so", RTLD_LAZY); 22 | if (!ml.libmedia) { 23 | return nullptr; 24 | } 25 | 26 | // Get the latency, in ms, from AudioFlinger. First, try the most recent 27 | // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency, 28 | // audio_stream_type_t streamType) 29 | ml.get_output_latency = (get_output_latency_ptr)dlsym( 30 | ml.libmedia, 31 | "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); 32 | if (!ml.get_output_latency) { 33 | // In case of failure, try the signature from legacy version. 34 | // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) 35 | ml.get_output_latency = (get_output_latency_ptr)dlsym( 36 | ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); 37 | if (!ml.get_output_latency) { 38 | return nullptr; 39 | } 40 | } 41 | 42 | media_lib * rv = nullptr; 43 | rv = (media_lib *)calloc(1, sizeof(media_lib)); 44 | assert(rv); 45 | *rv = ml; 46 | return rv; 47 | } 48 | 49 | void 50 | cubeb_close_media_library(media_lib * ml) 51 | { 52 | dlclose(ml->libmedia); 53 | ml->libmedia = NULL; 54 | ml->get_output_latency = NULL; 55 | free(ml); 56 | } 57 | 58 | uint32_t 59 | cubeb_get_output_latency_from_media_library(media_lib * ml) 60 | { 61 | uint32_t latency = 0; 62 | const int audio_stream_type_music = 3; 63 | int32_t r = ml->get_output_latency(&latency, audio_stream_type_music); 64 | if (r) { 65 | return 0; 66 | } 67 | return latency; 68 | } 69 | 70 | #endif // _CUBEB_MEDIA_LIBRARY_H_ 71 | -------------------------------------------------------------------------------- /test/test_audio_dump.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #define NOMINMAX 9 | #define _USE_MATH_DEFINES 10 | 11 | #include "cubeb/cubeb.h" 12 | #include 13 | 14 | #include "cubeb_audio_dump.h" 15 | #include "gtest/gtest.h" 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | TEST(cubeb, audio_dump) 23 | { 24 | cubeb_audio_dump_session_t session; 25 | int rv = cubeb_audio_dump_init(&session); 26 | ASSERT_EQ(rv, 0); 27 | 28 | cubeb_stream_params params; 29 | params.rate = 44100; 30 | params.channels = 2; 31 | params.format = CUBEB_SAMPLE_FLOAT32NE; 32 | 33 | cubeb_audio_dump_stream_t dump_stream; 34 | rv = cubeb_audio_dump_stream_init(session, &dump_stream, params, "test.wav"); 35 | ASSERT_EQ(rv, 0); 36 | 37 | rv = cubeb_audio_dump_start(session); 38 | ASSERT_EQ(rv, 0); 39 | 40 | float phase = 0; 41 | const size_t buf_sz = 2 * 44100 / 50; 42 | float buf[buf_sz]; 43 | for (uint32_t iteration = 0; iteration < 50; iteration++) { 44 | uint32_t write_idx = 0; 45 | for (uint32_t i = 0; i < buf_sz / params.channels; i++) { 46 | for (uint32_t j = 0; j < params.channels; j++) { 47 | buf[write_idx++] = sin(phase); 48 | } 49 | phase += 440 * M_PI * 2 / 44100; 50 | if (phase > 2 * M_PI) { 51 | phase -= 2 * M_PI; 52 | } 53 | } 54 | rv = cubeb_audio_dump_write(dump_stream, buf, 2 * 44100 / 50); 55 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 56 | ASSERT_EQ(rv, 0); 57 | } 58 | 59 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 60 | 61 | rv = cubeb_audio_dump_stop(session); 62 | ASSERT_EQ(rv, 0); 63 | 64 | rv = cubeb_audio_dump_stream_shutdown(session, dump_stream); 65 | ASSERT_EQ(rv, 0); 66 | 67 | rv = cubeb_audio_dump_shutdown(session); 68 | ASSERT_EQ(rv, 0); 69 | 70 | std::ifstream file("test.wav"); 71 | ASSERT_TRUE(file.good()); 72 | } 73 | 74 | #undef NOMINMAX 75 | -------------------------------------------------------------------------------- /src/cubeb_array_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_ARRAY_QUEUE_H 9 | #define CUBEB_ARRAY_QUEUE_H 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #if defined(__cplusplus) 16 | extern "C" { 17 | #endif 18 | 19 | typedef struct { 20 | void ** buf; 21 | size_t num; 22 | size_t writePos; 23 | size_t readPos; 24 | pthread_mutex_t mutex; 25 | } array_queue; 26 | 27 | array_queue * 28 | array_queue_create(size_t num) 29 | { 30 | assert(num != 0); 31 | array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue)); 32 | new_queue->buf = (void **)calloc(1, sizeof(void *) * num); 33 | new_queue->readPos = 0; 34 | new_queue->writePos = 0; 35 | new_queue->num = num; 36 | 37 | pthread_mutex_init(&new_queue->mutex, NULL); 38 | 39 | return new_queue; 40 | } 41 | 42 | void 43 | array_queue_destroy(array_queue * aq) 44 | { 45 | assert(aq); 46 | 47 | free(aq->buf); 48 | pthread_mutex_destroy(&aq->mutex); 49 | free(aq); 50 | } 51 | 52 | int 53 | array_queue_push(array_queue * aq, void * item) 54 | { 55 | assert(item); 56 | 57 | pthread_mutex_lock(&aq->mutex); 58 | int ret = -1; 59 | if (aq->buf[aq->writePos % aq->num] == NULL) { 60 | aq->buf[aq->writePos % aq->num] = item; 61 | aq->writePos = (aq->writePos + 1) % aq->num; 62 | ret = 0; 63 | } 64 | // else queue is full 65 | pthread_mutex_unlock(&aq->mutex); 66 | return ret; 67 | } 68 | 69 | void * 70 | array_queue_pop(array_queue * aq) 71 | { 72 | pthread_mutex_lock(&aq->mutex); 73 | void * value = aq->buf[aq->readPos % aq->num]; 74 | if (value) { 75 | aq->buf[aq->readPos % aq->num] = NULL; 76 | aq->readPos = (aq->readPos + 1) % aq->num; 77 | } 78 | pthread_mutex_unlock(&aq->mutex); 79 | return value; 80 | } 81 | 82 | size_t 83 | array_queue_get_size(array_queue * aq) 84 | { 85 | pthread_mutex_lock(&aq->mutex); 86 | ssize_t r = aq->writePos - aq->readPos; 87 | if (r < 0) { 88 | r = aq->num + r; 89 | assert(r >= 0); 90 | } 91 | pthread_mutex_unlock(&aq->mutex); 92 | return (size_t)r; 93 | } 94 | 95 | #if defined(__cplusplus) 96 | } 97 | #endif 98 | 99 | #endif // CUBE_ARRAY_QUEUE_H 100 | -------------------------------------------------------------------------------- /test/test_ring_array.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #ifdef __APPLE__ 3 | #include "cubeb/cubeb.h" 4 | #include "cubeb_ring_array.h" 5 | #include 6 | #include 7 | #include 8 | 9 | TEST(cubeb, ring_array) 10 | { 11 | ring_array ra; 12 | 13 | ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER); 14 | ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER); 15 | 16 | unsigned int capacity = 8; 17 | ring_array_init(&ra, capacity, sizeof(int), 1, 1); 18 | int verify_data[capacity]; // {1,2,3,4,5,6,7,8}; 19 | AudioBuffer * p_data = NULL; 20 | 21 | for (unsigned int i = 0; i < capacity; ++i) { 22 | verify_data[i] = i; // in case capacity change value 23 | *(int *)ra.buffer_array[i].mData = i; 24 | ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int)); 25 | ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u); 26 | } 27 | 28 | /* Get store buffers*/ 29 | for (unsigned int i = 0; i < capacity; ++i) { 30 | p_data = ring_array_get_free_buffer(&ra); 31 | ASSERT_NE(p_data, nullptr); 32 | ASSERT_EQ(*(int *)p_data->mData, verify_data[i]); 33 | } 34 | /*Now array is full extra store should give NULL*/ 35 | ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr); 36 | /* Get fetch buffers*/ 37 | for (unsigned int i = 0; i < capacity; ++i) { 38 | p_data = ring_array_get_data_buffer(&ra); 39 | ASSERT_NE(p_data, nullptr); 40 | ASSERT_EQ(*(int *)p_data->mData, verify_data[i]); 41 | } 42 | /*Now array is empty extra fetch should give NULL*/ 43 | ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr); 44 | 45 | p_data = NULL; 46 | /* Repeated store fetch should can go for ever*/ 47 | for (unsigned int i = 0; i < 2 * capacity; ++i) { 48 | p_data = ring_array_get_free_buffer(&ra); 49 | ASSERT_NE(p_data, nullptr); 50 | ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data); 51 | } 52 | 53 | p_data = NULL; 54 | /* Verify/modify buffer data*/ 55 | for (unsigned int i = 0; i < capacity; ++i) { 56 | p_data = ring_array_get_free_buffer(&ra); 57 | ASSERT_NE(p_data, nullptr); 58 | ASSERT_EQ(*((int *)p_data->mData), verify_data[i]); 59 | (*((int *)p_data->mData))++; // Modify data 60 | } 61 | for (unsigned int i = 0; i < capacity; ++i) { 62 | p_data = ring_array_get_data_buffer(&ra); 63 | ASSERT_NE(p_data, nullptr); 64 | ASSERT_EQ(*((int *)p_data->mData), 65 | verify_data[i] + 1); // Verify modified data 66 | } 67 | 68 | ring_array_destroy(&ra); 69 | } 70 | #else 71 | TEST(cubeb, DISABLED_ring_array) {} 72 | #endif 73 | -------------------------------------------------------------------------------- /src/android/audiotrack_definitions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | /* 20 | * The following definitions are copied from the android sources. Only the 21 | * relevant enum member and values needed are copied. 22 | */ 23 | 24 | /* 25 | * From 26 | * https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h 27 | */ 28 | typedef int32_t status_t; 29 | 30 | /* 31 | * From 32 | * https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h 33 | */ 34 | struct Buffer { 35 | uint32_t flags; 36 | int channelCount; 37 | int format; 38 | size_t frameCount; 39 | size_t size; 40 | union { 41 | void * raw; 42 | short * i16; 43 | int8_t * i8; 44 | }; 45 | }; 46 | 47 | enum event_type { 48 | EVENT_MORE_DATA = 0, 49 | EVENT_UNDERRUN = 1, 50 | EVENT_LOOP_END = 2, 51 | EVENT_MARKER = 3, 52 | EVENT_NEW_POS = 4, 53 | EVENT_BUFFER_END = 5 54 | }; 55 | 56 | /** 57 | * From 58 | * https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h 59 | * and 60 | * https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h 61 | */ 62 | 63 | #define AUDIO_STREAM_TYPE_MUSIC 3 64 | 65 | enum { 66 | AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1, 67 | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2, 68 | AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS, 69 | AUDIO_CHANNEL_OUT_STEREO_ICS = 70 | (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS) 71 | } AudioTrack_ChannelMapping_ICS; 72 | 73 | enum { 74 | AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4, 75 | AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8, 76 | AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy, 77 | AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy | 78 | AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy) 79 | } AudioTrack_ChannelMapping_Legacy; 80 | 81 | typedef enum { 82 | AUDIO_FORMAT_PCM = 0x00000000, 83 | AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, 84 | AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT), 85 | } AudioTrack_SampleType; 86 | -------------------------------------------------------------------------------- /src/cubeb_triple_buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2022 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /** 9 | * Adapted and ported to C++ from https://crates.io/crates/triple_buffer 10 | */ 11 | 12 | #ifndef CUBEB_TRIPLE_BUFFER 13 | #define CUBEB_TRIPLE_BUFFER 14 | 15 | #include 16 | 17 | // Single producer / single consumer wait-free triple buffering 18 | // implementation, for when a producer wants to publish data to a consumer 19 | // without blocking, but when a queue is wastefull, because it's OK for the 20 | // consumer to miss data updates. 21 | template class triple_buffer { 22 | public: 23 | // Write a new value into the triple buffer. Returns true if a value was 24 | // overwritten. 25 | // Producer-side only. 26 | bool write(T & input) 27 | { 28 | storage[input_idx] = input; 29 | return publish(); 30 | } 31 | // Get the latest value from the triple buffer. 32 | // Consumer-side only. 33 | T & read() 34 | { 35 | update(); 36 | return storage[output_idx]; 37 | } 38 | // Returns true if a new value has been published by the consumer without 39 | // having been consumed yet. 40 | // Consumer-side only. 41 | bool updated() 42 | { 43 | return (shared_state.load(std::memory_order_relaxed) & BACK_DIRTY_BIT) != 0; 44 | } 45 | // Reset state and indices to initial values. 46 | void invalidate() 47 | { 48 | shared_state.store(0, std::memory_order_release); 49 | input_idx = 1; 50 | output_idx = 2; 51 | } 52 | 53 | private: 54 | // Publish a value to the consumer. Returns true if the data was overwritten 55 | // without having been read. 56 | bool publish() 57 | { 58 | auto former_back_idx = shared_state.exchange(input_idx | BACK_DIRTY_BIT, 59 | std::memory_order_acq_rel); 60 | input_idx = former_back_idx & BACK_INDEX_MASK; 61 | return (former_back_idx & BACK_DIRTY_BIT) != 0; 62 | } 63 | // Get a new value from the producer, if a new value has been produced. 64 | bool update() 65 | { 66 | bool was_updated = updated(); 67 | if (was_updated) { 68 | auto former_back_idx = 69 | shared_state.exchange(output_idx, std::memory_order_acq_rel); 70 | output_idx = former_back_idx & BACK_INDEX_MASK; 71 | } 72 | return was_updated; 73 | } 74 | T storage[3]; 75 | // Mask used to extract back-buffer index 76 | const uint8_t BACK_INDEX_MASK = 0b11; 77 | // Bit set by producer to signal updates 78 | const uint8_t BACK_DIRTY_BIT = 0b100; 79 | // Shared state: a dirty bit, and an index. 80 | std::atomic shared_state = {0}; 81 | // Output index, private to the consumer. 82 | uint8_t output_idx = 1; 83 | // Input index, private to the producer. 84 | uint8_t input_idx = 2; 85 | }; 86 | 87 | #endif // CUBEB_TRIPLE_BUFFER 88 | -------------------------------------------------------------------------------- /src/cubeb_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_LOG 9 | #define CUBEB_LOG 10 | 11 | #include "cubeb/cubeb.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #if defined(__GNUC__) || defined(__clang__) 18 | #define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args))) 19 | #if defined(__FILE_NAME__) 20 | #define __FILENAME__ __FILE_NAME__ 21 | #else 22 | #define __FILENAME__ \ 23 | (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \ 24 | : __FILE__) 25 | #endif 26 | #else 27 | #define PRINTF_FORMAT(fmt, args) 28 | #include 29 | #define __FILENAME__ \ 30 | (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) 31 | #endif 32 | 33 | void 34 | cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback); 35 | cubeb_log_level 36 | cubeb_log_get_level(void); 37 | cubeb_log_callback 38 | cubeb_log_get_callback(void); 39 | void 40 | cubeb_log_internal_no_format(const char * msg); 41 | void 42 | cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...) 43 | PRINTF_FORMAT(3, 4); 44 | void 45 | cubeb_async_log(const char * fmt, ...) PRINTF_FORMAT(1, 2); 46 | void 47 | cubeb_async_log_reset_threads(void); 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | 53 | #define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) 54 | #define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) 55 | 56 | #define LOG_INTERNAL(level, fmt, ...) \ 57 | do { \ 58 | if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \ 59 | cubeb_log_internal(__FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \ 60 | } \ 61 | } while (0) 62 | 63 | #define ALOG_INTERNAL(level, fmt, ...) \ 64 | do { \ 65 | if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \ 66 | cubeb_async_log(fmt, ##__VA_ARGS__); \ 67 | } \ 68 | } while (0) 69 | 70 | /* Asynchronous logging macros to log in real-time callbacks. */ 71 | /* Should not be used on android due to the use of global/static variables. */ 72 | #define ALOGV(msg, ...) ALOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) 73 | #define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) 74 | 75 | #endif // CUBEB_LOG 76 | -------------------------------------------------------------------------------- /src/cubeb-jni.cpp: -------------------------------------------------------------------------------- 1 | /* clang-format off */ 2 | #include "jni.h" 3 | #include 4 | #include "cubeb-jni-instances.h" 5 | /* clang-format on */ 6 | 7 | #define AUDIO_STREAM_TYPE_MUSIC 3 8 | 9 | struct cubeb_jni { 10 | jobject s_audio_manager_obj = nullptr; 11 | jclass s_audio_manager_class = nullptr; 12 | jmethodID s_get_output_latency_id = nullptr; 13 | }; 14 | 15 | extern "C" cubeb_jni * 16 | cubeb_jni_init() 17 | { 18 | jobject ctx_obj = cubeb_jni_get_context_instance(); 19 | JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); 20 | if (!jni_env || !ctx_obj) { 21 | return nullptr; 22 | } 23 | 24 | cubeb_jni * cubeb_jni_ptr = new cubeb_jni; 25 | assert(cubeb_jni_ptr); 26 | 27 | // Find the audio manager object and make it global to call it from another 28 | // method 29 | jclass context_class = jni_env->FindClass("android/content/Context"); 30 | jfieldID audio_service_field = jni_env->GetStaticFieldID( 31 | context_class, "AUDIO_SERVICE", "Ljava/lang/String;"); 32 | jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, 33 | audio_service_field); 34 | jmethodID get_system_service_id = 35 | jni_env->GetMethodID(context_class, "getSystemService", 36 | "(Ljava/lang/String;)Ljava/lang/Object;"); 37 | jobject audio_manager_obj = 38 | jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr); 39 | cubeb_jni_ptr->s_audio_manager_obj = 40 | reinterpret_cast(jni_env->NewGlobalRef(audio_manager_obj)); 41 | 42 | // Make the audio manager class a global reference in order to preserve method 43 | // id 44 | jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager"); 45 | cubeb_jni_ptr->s_audio_manager_class = 46 | reinterpret_cast(jni_env->NewGlobalRef(audio_manager_class)); 47 | cubeb_jni_ptr->s_get_output_latency_id = 48 | jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I"); 49 | 50 | jni_env->DeleteLocalRef(ctx_obj); 51 | jni_env->DeleteLocalRef(context_class); 52 | jni_env->DeleteLocalRef(jstr); 53 | jni_env->DeleteLocalRef(audio_manager_obj); 54 | jni_env->DeleteLocalRef(audio_manager_class); 55 | 56 | return cubeb_jni_ptr; 57 | } 58 | 59 | extern "C" int 60 | cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr) 61 | { 62 | assert(cubeb_jni_ptr); 63 | JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); 64 | return jni_env->CallIntMethod( 65 | cubeb_jni_ptr->s_audio_manager_obj, 66 | cubeb_jni_ptr->s_get_output_latency_id, 67 | AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC 68 | } 69 | 70 | extern "C" void 71 | cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr) 72 | { 73 | assert(cubeb_jni_ptr); 74 | 75 | JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); 76 | assert(jni_env); 77 | 78 | jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj); 79 | jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class); 80 | 81 | delete cubeb_jni_ptr; 82 | } 83 | -------------------------------------------------------------------------------- /test/test_overload_callback.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #include "gtest/gtest.h" 9 | #if !defined(_XOPEN_SOURCE) 10 | #define _XOPEN_SOURCE 600 11 | #endif 12 | #include "cubeb/cubeb.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | // #define ENABLE_NORMAL_LOG 19 | // #define ENABLE_VERBOSE_LOG 20 | #include "common.h" 21 | 22 | #define SAMPLE_FREQUENCY 48000 23 | #define STREAM_FORMAT CUBEB_SAMPLE_S16LE 24 | 25 | std::atomic load_callback{false}; 26 | 27 | static long 28 | data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, 29 | void * outputbuffer, long nframes) 30 | { 31 | if (load_callback) { 32 | fprintf(stderr, "Sleeping...\n"); 33 | delay(100000); 34 | fprintf(stderr, "Sleeping done\n"); 35 | } 36 | return nframes; 37 | } 38 | 39 | static void 40 | state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state) 41 | { 42 | ASSERT_TRUE(!!stream); 43 | 44 | switch (state) { 45 | case CUBEB_STATE_STARTED: 46 | fprintf(stderr, "stream started\n"); 47 | break; 48 | case CUBEB_STATE_STOPPED: 49 | fprintf(stderr, "stream stopped\n"); 50 | break; 51 | case CUBEB_STATE_DRAINED: 52 | FAIL() << "this test is not supposed to drain"; 53 | break; 54 | case CUBEB_STATE_ERROR: 55 | fprintf(stderr, "stream error\n"); 56 | break; 57 | default: 58 | FAIL() << "this test is not supposed to have a weird state"; 59 | break; 60 | } 61 | } 62 | 63 | TEST(cubeb, overload_callback) 64 | { 65 | cubeb * ctx; 66 | cubeb_stream * stream; 67 | cubeb_stream_params output_params; 68 | int r; 69 | uint32_t latency_frames = 0; 70 | 71 | r = common_init(&ctx, "Cubeb callback overload"); 72 | ASSERT_EQ(r, CUBEB_OK); 73 | 74 | std::unique_ptr cleanup_cubeb_at_exit( 75 | ctx, cubeb_destroy); 76 | 77 | // This test is specifically designed to test a behaviour of the WASAPI 78 | // backend in a specific scenario. 79 | if (strcmp(cubeb_get_backend_id(ctx), "wasapi") != 0) { 80 | return; 81 | } 82 | 83 | output_params.format = STREAM_FORMAT; 84 | output_params.rate = 48000; 85 | output_params.channels = 2; 86 | output_params.layout = CUBEB_LAYOUT_STEREO; 87 | output_params.prefs = CUBEB_STREAM_PREF_NONE; 88 | 89 | r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 90 | ASSERT_EQ(r, CUBEB_OK); 91 | 92 | r = cubeb_stream_init(ctx, &stream, "Cubeb", NULL, NULL, NULL, &output_params, 93 | latency_frames, data_cb, state_cb, NULL); 94 | ASSERT_EQ(r, CUBEB_OK); 95 | 96 | std::unique_ptr 97 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 98 | 99 | cubeb_stream_start(stream); 100 | delay(500); 101 | // This causes the callback to sleep for a large number of seconds. 102 | load_callback = true; 103 | delay(500); 104 | cubeb_stream_stop(stream); 105 | } 106 | 107 | #undef SAMPLE_FREQUENCY 108 | #undef STREAM_FORMAT 109 | -------------------------------------------------------------------------------- /src/cubeb-internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | #if !defined(CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5) 8 | #define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 9 | 10 | #include "cubeb/cubeb.h" 11 | #include "cubeb_assert.h" 12 | #include "cubeb_log.h" 13 | #include 14 | #include 15 | 16 | #ifdef __clang__ 17 | #ifndef CLANG_ANALYZER_NORETURN 18 | #if __has_feature(attribute_analyzer_noreturn) 19 | #define CLANG_ANALYZER_NORETURN __attribute__((analyzer_noreturn)) 20 | #else 21 | #define CLANG_ANALYZER_NORETURN 22 | #endif // ifndef CLANG_ANALYZER_NORETURN 23 | #endif // __has_feature(attribute_analyzer_noreturn) 24 | #else // __clang__ 25 | #define CLANG_ANALYZER_NORETURN 26 | #endif 27 | 28 | #if defined(__cplusplus) 29 | extern "C" { 30 | #endif 31 | 32 | #if defined(__cplusplus) 33 | } 34 | #endif 35 | 36 | struct cubeb_ops { 37 | int (*init)(cubeb ** context, char const * context_name); 38 | char const * (*get_backend_id)(cubeb * context); 39 | int (*get_max_channel_count)(cubeb * context, uint32_t * max_channels); 40 | int (*get_min_latency)(cubeb * context, cubeb_stream_params params, 41 | uint32_t * latency_ms); 42 | int (*get_preferred_sample_rate)(cubeb * context, uint32_t * rate); 43 | int (*get_supported_input_processing_params)( 44 | cubeb * context, cubeb_input_processing_params * params); 45 | int (*enumerate_devices)(cubeb * context, cubeb_device_type type, 46 | cubeb_device_collection * collection); 47 | int (*device_collection_destroy)(cubeb * context, 48 | cubeb_device_collection * collection); 49 | void (*destroy)(cubeb * context); 50 | int (*stream_init)(cubeb * context, cubeb_stream ** stream, 51 | char const * stream_name, cubeb_devid input_device, 52 | cubeb_stream_params * input_stream_params, 53 | cubeb_devid output_device, 54 | cubeb_stream_params * output_stream_params, 55 | unsigned int latency, cubeb_data_callback data_callback, 56 | cubeb_state_callback state_callback, void * user_ptr); 57 | void (*stream_destroy)(cubeb_stream * stream); 58 | int (*stream_start)(cubeb_stream * stream); 59 | int (*stream_stop)(cubeb_stream * stream); 60 | int (*stream_get_position)(cubeb_stream * stream, uint64_t * position); 61 | int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency); 62 | int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency); 63 | int (*stream_set_volume)(cubeb_stream * stream, float volumes); 64 | int (*stream_set_name)(cubeb_stream * stream, char const * stream_name); 65 | int (*stream_get_current_device)(cubeb_stream * stream, 66 | cubeb_device ** const device); 67 | int (*stream_set_input_mute)(cubeb_stream * stream, int mute); 68 | int (*stream_set_input_processing_params)( 69 | cubeb_stream * stream, cubeb_input_processing_params params); 70 | int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device); 71 | int (*stream_register_device_changed_callback)( 72 | cubeb_stream * stream, 73 | cubeb_device_changed_callback device_changed_callback); 74 | int (*register_device_collection_changed)( 75 | cubeb * context, cubeb_device_type devtype, 76 | cubeb_device_collection_changed_callback callback, void * user_ptr); 77 | }; 78 | 79 | #endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */ 80 | -------------------------------------------------------------------------------- /src/cubeb_strings.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2011 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #include "cubeb_strings.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define CUBEB_STRINGS_INLINE_COUNT 4 15 | 16 | struct cubeb_strings { 17 | uint32_t size; 18 | uint32_t count; 19 | char ** data; 20 | char * small_store[CUBEB_STRINGS_INLINE_COUNT]; 21 | }; 22 | 23 | int 24 | cubeb_strings_init(cubeb_strings ** strings) 25 | { 26 | cubeb_strings * strs = NULL; 27 | 28 | if (!strings) { 29 | return CUBEB_ERROR; 30 | } 31 | 32 | strs = calloc(1, sizeof(cubeb_strings)); 33 | assert(strs); 34 | 35 | if (!strs) { 36 | return CUBEB_ERROR; 37 | } 38 | 39 | strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]); 40 | strs->count = 0; 41 | strs->data = strs->small_store; 42 | 43 | *strings = strs; 44 | 45 | return CUBEB_OK; 46 | } 47 | 48 | void 49 | cubeb_strings_destroy(cubeb_strings * strings) 50 | { 51 | char ** sp = NULL; 52 | char ** se = NULL; 53 | 54 | if (!strings) { 55 | return; 56 | } 57 | 58 | sp = strings->data; 59 | se = sp + strings->count; 60 | 61 | for (; sp != se; sp++) { 62 | if (*sp) { 63 | free(*sp); 64 | } 65 | } 66 | 67 | if (strings->data != strings->small_store) { 68 | free(strings->data); 69 | } 70 | 71 | free(strings); 72 | } 73 | 74 | /** Look for string in string storage. 75 | @param strings Opaque pointer to interned string storage. 76 | @param s String to look up. 77 | @retval Read-only string or NULL if not found. */ 78 | static char const * 79 | cubeb_strings_lookup(cubeb_strings * strings, char const * s) 80 | { 81 | char ** sp = NULL; 82 | char ** se = NULL; 83 | 84 | if (!strings || !s) { 85 | return NULL; 86 | } 87 | 88 | sp = strings->data; 89 | se = sp + strings->count; 90 | 91 | for (; sp != se; sp++) { 92 | if (*sp && strcmp(*sp, s) == 0) { 93 | return *sp; 94 | } 95 | } 96 | 97 | return NULL; 98 | } 99 | 100 | static char const * 101 | cubeb_strings_push(cubeb_strings * strings, char const * s) 102 | { 103 | char * is = NULL; 104 | 105 | if (strings->count == strings->size) { 106 | char ** new_data; 107 | uint32_t value_size = sizeof(char const *); 108 | uint32_t new_size = strings->size * 2; 109 | if (!new_size || value_size > (uint32_t)-1 / new_size) { 110 | // overflow 111 | return NULL; 112 | } 113 | 114 | if (strings->small_store == strings->data) { 115 | // First time heap allocation. 116 | new_data = malloc(new_size * value_size); 117 | if (new_data) { 118 | memcpy(new_data, strings->small_store, sizeof(strings->small_store)); 119 | } 120 | } else { 121 | new_data = realloc(strings->data, new_size * value_size); 122 | } 123 | 124 | if (!new_data) { 125 | // out of memory 126 | return NULL; 127 | } 128 | 129 | strings->size = new_size; 130 | strings->data = new_data; 131 | } 132 | 133 | is = strdup(s); 134 | strings->data[strings->count++] = is; 135 | 136 | return is; 137 | } 138 | 139 | char const * 140 | cubeb_strings_intern(cubeb_strings * strings, char const * s) 141 | { 142 | char const * is = NULL; 143 | 144 | if (!strings || !s) { 145 | return NULL; 146 | } 147 | 148 | is = cubeb_strings_lookup(strings, s); 149 | if (is) { 150 | return is; 151 | } 152 | 153 | return cubeb_strings_push(strings, s); 154 | } 155 | -------------------------------------------------------------------------------- /test/test_record.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb api/function test. Record the mic and check there is sound. */ 9 | #include "gtest/gtest.h" 10 | #if !defined(_XOPEN_SOURCE) 11 | #define _XOPEN_SOURCE 600 12 | #endif 13 | #include "cubeb/cubeb.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // #define ENABLE_NORMAL_LOG 21 | // #define ENABLE_VERBOSE_LOG 22 | #include "common.h" 23 | 24 | #define SAMPLE_FREQUENCY 48000 25 | #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE 26 | 27 | struct user_state_record { 28 | std::atomic invalid_audio_value{0}; 29 | }; 30 | 31 | long 32 | data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, 33 | void * outputbuffer, long nframes) 34 | { 35 | user_state_record * u = reinterpret_cast(user); 36 | float * b = (float *)inputbuffer; 37 | 38 | if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) { 39 | return CUBEB_ERROR; 40 | } 41 | 42 | for (long i = 0; i < nframes; i++) { 43 | if (b[i] <= -1.0 || b[i] >= 1.0) { 44 | u->invalid_audio_value = 1; 45 | break; 46 | } 47 | } 48 | 49 | return nframes; 50 | } 51 | 52 | void 53 | state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state) 54 | { 55 | if (stream == NULL) 56 | return; 57 | 58 | switch (state) { 59 | case CUBEB_STATE_STARTED: 60 | fprintf(stderr, "stream started\n"); 61 | break; 62 | case CUBEB_STATE_STOPPED: 63 | fprintf(stderr, "stream stopped\n"); 64 | break; 65 | case CUBEB_STATE_DRAINED: 66 | fprintf(stderr, "stream drained\n"); 67 | break; 68 | default: 69 | fprintf(stderr, "unknown stream state %d\n", state); 70 | } 71 | 72 | return; 73 | } 74 | 75 | TEST(cubeb, record) 76 | { 77 | if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != 78 | CUBEB_OK) { 79 | fprintf(stderr, "Set log callback failed\n"); 80 | } 81 | cubeb * ctx; 82 | cubeb_stream * stream; 83 | cubeb_stream_params params; 84 | int r; 85 | user_state_record stream_state; 86 | 87 | r = common_init(&ctx, "Cubeb record example"); 88 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 89 | 90 | std::unique_ptr cleanup_cubeb_at_exit( 91 | ctx, cubeb_destroy); 92 | 93 | /* This test needs an available input device, skip it if this host does not 94 | * have one. */ 95 | if (!can_run_audio_input_test(ctx)) { 96 | return; 97 | } 98 | 99 | params.format = STREAM_FORMAT; 100 | params.rate = SAMPLE_FREQUENCY; 101 | params.channels = 1; 102 | params.layout = CUBEB_LAYOUT_UNDEFINED; 103 | params.prefs = CUBEB_STREAM_PREF_NONE; 104 | 105 | r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, ¶ms, 106 | NULL, nullptr, 4096, data_cb_record, state_cb_record, 107 | &stream_state); 108 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 109 | 110 | std::unique_ptr 111 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 112 | 113 | cubeb_stream_start(stream); 114 | delay(500); 115 | cubeb_stream_stop(stream); 116 | 117 | #ifdef __linux__ 118 | // user callback does not arrive in Linux, silence the error 119 | fprintf(stderr, "Check is disabled in Linux\n"); 120 | #else 121 | ASSERT_FALSE(stream_state.invalid_audio_value.load()); 122 | #endif 123 | } 124 | 125 | #undef SAMPLE_FREQUENCY 126 | #undef STREAM_FORMAT 127 | -------------------------------------------------------------------------------- /src/cubeb_audio_dump.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_AUDIO_DUMP 9 | #define CUBEB_AUDIO_DUMP 10 | 11 | #include "cubeb/cubeb.h" 12 | 13 | #if defined(__cplusplus) 14 | extern "C" { 15 | #endif 16 | 17 | typedef struct cubeb_audio_dump_stream * cubeb_audio_dump_stream_t; 18 | typedef struct cubeb_audio_dump_session * cubeb_audio_dump_session_t; 19 | 20 | // Start audio dumping session 21 | // This can only be called if the other API functions 22 | // aren't currently being called: synchronized externally. 23 | // This is not real-time safe. 24 | // 25 | // This is generally called when deciding to start logging some audio. 26 | // 27 | // Returns 0 in case of success. 28 | int 29 | cubeb_audio_dump_init(cubeb_audio_dump_session_t * session); 30 | 31 | // End audio dumping session 32 | // This can only be called if the other API functions 33 | // aren't currently being called: synchronized externally. 34 | // 35 | // This is generally called when deciding to stop logging some audio. 36 | // 37 | // This is not real-time safe. 38 | // Returns 0 in case of success. 39 | int 40 | cubeb_audio_dump_shutdown(cubeb_audio_dump_session_t session); 41 | 42 | // Register a stream for dumping to a file 43 | // This can only be called if cubeb_audio_dump_write 44 | // isn't currently being called: synchronized externally. 45 | // 46 | // This is generally called when setting up a system-level stream side (either 47 | // input or output). 48 | // 49 | // This is not real-time safe. 50 | // Returns 0 in case of success. 51 | int 52 | cubeb_audio_dump_stream_init(cubeb_audio_dump_session_t session, 53 | cubeb_audio_dump_stream_t * stream, 54 | cubeb_stream_params stream_params, 55 | const char * name); 56 | 57 | // Unregister a stream for dumping to a file 58 | // This can only be called if cubeb_audio_dump_write 59 | // isn't currently being called: synchronized externally. 60 | // 61 | // This is generally called when a system-level audio stream side 62 | // (input/output) has been stopped and drained, and the audio callback isn't 63 | // going to be called. 64 | // 65 | // This is not real-time safe. 66 | // Returns 0 in case of success. 67 | int 68 | cubeb_audio_dump_stream_shutdown(cubeb_audio_dump_session_t session, 69 | cubeb_audio_dump_stream_t stream); 70 | 71 | // Start dumping. 72 | // cubeb_audio_dump_write can now be called. 73 | // 74 | // This starts dumping the audio to disk. Generally this is called when 75 | // cubeb_stream_start is caled is called, but can be called at the beginning of 76 | // the application. 77 | // 78 | // This is not real-time safe. 79 | // Returns 0 in case of success. 80 | int 81 | cubeb_audio_dump_start(cubeb_audio_dump_session_t session); 82 | 83 | // Stop dumping. 84 | // cubeb_audio_dump_write can't be called at this point. 85 | // 86 | // This stops dumping the audio to disk cubeb_stream_stop is caled is called, 87 | // but can be called before exiting the application. 88 | // 89 | // This is not real-time safe. 90 | // Returns 0 in case of success. 91 | int 92 | cubeb_audio_dump_stop(cubeb_audio_dump_session_t session); 93 | 94 | // Dump some audio samples for audio stream id. 95 | // 96 | // This is generally called from the real-time audio callback. 97 | // 98 | // This is real-time safe. 99 | // Returns 0 in case of success. 100 | int 101 | cubeb_audio_dump_write(cubeb_audio_dump_stream_t stream, void * audio_samples, 102 | uint32_t count); 103 | 104 | #ifdef __cplusplus 105 | }; 106 | #endif 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/cubeb_resampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | #ifndef CUBEB_RESAMPLER_H 8 | #define CUBEB_RESAMPLER_H 9 | 10 | #include "cubeb/cubeb.h" 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | typedef struct cubeb_resampler cubeb_resampler; 17 | 18 | typedef enum { 19 | CUBEB_RESAMPLER_QUALITY_VOIP, 20 | CUBEB_RESAMPLER_QUALITY_DEFAULT, 21 | CUBEB_RESAMPLER_QUALITY_DESKTOP 22 | } cubeb_resampler_quality; 23 | 24 | typedef enum { 25 | CUBEB_RESAMPLER_RECLOCK_NONE, 26 | CUBEB_RESAMPLER_RECLOCK_INPUT 27 | } cubeb_resampler_reclock; 28 | 29 | /** 30 | * Create a resampler to adapt the requested sample rate into something that 31 | * is accepted by the audio backend. 32 | * @param stream A cubeb_stream instance supplied to the data callback. 33 | * @param input_params Used to calculate bytes per frame and buffer size for 34 | * resampling of the input side of the stream. NULL if input should not be 35 | * resampled. 36 | * @param output_params Used to calculate bytes per frame and buffer size for 37 | * resampling of the output side of the stream. NULL if output should not be 38 | * resampled. 39 | * @param target_rate The sampling rate after resampling for the input side of 40 | * the stream, and/or the sampling rate prior to resampling of the output side 41 | * of the stream. 42 | * @param callback A callback to request data for resampling. 43 | * @param user_ptr User data supplied to the data callback. 44 | * @param quality Quality of the resampler. 45 | * @retval A non-null pointer if success. 46 | */ 47 | cubeb_resampler * 48 | cubeb_resampler_create(cubeb_stream * stream, 49 | cubeb_stream_params * input_params, 50 | cubeb_stream_params * output_params, 51 | unsigned int target_rate, cubeb_data_callback callback, 52 | void * user_ptr, cubeb_resampler_quality quality, 53 | cubeb_resampler_reclock reclock); 54 | 55 | /** 56 | * Fill the buffer with frames acquired using the data callback. Resampling will 57 | * happen if necessary. 58 | * @param resampler A cubeb_resampler instance. 59 | * @param input_buffer A buffer of input samples 60 | * @param input_frame_count The size of the buffer. Returns the number of frames 61 | * consumed. 62 | * @param output_buffer The buffer to be filled. 63 | * @param output_frames_needed Number of frames that should be produced. 64 | * @retval Number of frames that are actually produced. 65 | * @retval CUBEB_ERROR on error. 66 | */ 67 | long 68 | cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer, 69 | long * input_frame_count, void * output_buffer, 70 | long output_frames_needed); 71 | 72 | /** 73 | * Destroy a cubeb_resampler. 74 | * @param resampler A cubeb_resampler instance. 75 | */ 76 | void 77 | cubeb_resampler_destroy(cubeb_resampler * resampler); 78 | 79 | /** 80 | * Returns the latency, in frames, of the resampler. 81 | * @param resampler A cubeb resampler instance. 82 | * @retval The latency, in frames, induced by the resampler. 83 | */ 84 | long 85 | cubeb_resampler_latency(cubeb_resampler * resampler); 86 | 87 | /** 88 | * Test-only introspection API to ensure that there is no buffering 89 | * buildup when resampling. 90 | */ 91 | typedef struct { 92 | size_t input_input_buffer_size; 93 | size_t input_output_buffer_size; 94 | size_t output_input_buffer_size; 95 | size_t output_output_buffer_size; 96 | } cubeb_resampler_stats; 97 | 98 | cubeb_resampler_stats 99 | cubeb_resampler_stats_get(cubeb_resampler * resampler); 100 | 101 | #if defined(__cplusplus) 102 | } 103 | #endif 104 | 105 | #endif /* CUBEB_RESAMPLER_H */ 106 | -------------------------------------------------------------------------------- /test/test_tone.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2011 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb api/function test. Plays a simple tone. */ 9 | #include "gtest/gtest.h" 10 | #if !defined(_XOPEN_SOURCE) 11 | #define _XOPEN_SOURCE 600 12 | #endif 13 | #include "cubeb/cubeb.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // #define ENABLE_NORMAL_LOG 22 | // #define ENABLE_VERBOSE_LOG 23 | #include "common.h" 24 | 25 | #define SAMPLE_FREQUENCY 48000 26 | #define STREAM_FORMAT CUBEB_SAMPLE_S16LE 27 | 28 | /* store the phase of the generated waveform */ 29 | struct cb_user_data { 30 | std::atomic position; 31 | }; 32 | 33 | long 34 | data_cb_tone(cubeb_stream * stream, void * user, const void * /*inputbuffer*/, 35 | void * outputbuffer, long nframes) 36 | { 37 | struct cb_user_data * u = (struct cb_user_data *)user; 38 | short * b = (short *)outputbuffer; 39 | float t1, t2; 40 | int i; 41 | 42 | if (stream == NULL || u == NULL) 43 | return CUBEB_ERROR; 44 | 45 | /* generate our test tone on the fly */ 46 | for (i = 0; i < nframes; i++) { 47 | /* North American dial tone */ 48 | t1 = sin(2 * M_PI * (i + u->position) * 350 / SAMPLE_FREQUENCY); 49 | t2 = sin(2 * M_PI * (i + u->position) * 440 / SAMPLE_FREQUENCY); 50 | b[i] = (SHRT_MAX / 2) * t1; 51 | b[i] += (SHRT_MAX / 2) * t2; 52 | /* European dial tone */ 53 | /* 54 | t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY); 55 | b[i] = SHRT_MAX * t1; 56 | */ 57 | } 58 | /* remember our phase to avoid clicking on buffer transitions */ 59 | /* we'll still click if position overflows */ 60 | u->position += nframes; 61 | 62 | return nframes; 63 | } 64 | 65 | void 66 | state_cb_tone(cubeb_stream * stream, void * user, cubeb_state state) 67 | { 68 | struct cb_user_data * u = (struct cb_user_data *)user; 69 | 70 | if (stream == NULL || u == NULL) 71 | return; 72 | 73 | switch (state) { 74 | case CUBEB_STATE_STARTED: 75 | fprintf(stderr, "stream started\n"); 76 | break; 77 | case CUBEB_STATE_STOPPED: 78 | fprintf(stderr, "stream stopped\n"); 79 | break; 80 | case CUBEB_STATE_DRAINED: 81 | fprintf(stderr, "stream drained\n"); 82 | break; 83 | default: 84 | fprintf(stderr, "unknown stream state %d\n", state); 85 | } 86 | 87 | return; 88 | } 89 | 90 | TEST(cubeb, tone) 91 | { 92 | cubeb * ctx; 93 | cubeb_stream * stream; 94 | cubeb_stream_params params; 95 | int r; 96 | 97 | r = common_init(&ctx, "Cubeb tone example"); 98 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 99 | 100 | std::unique_ptr cleanup_cubeb_at_exit( 101 | ctx, cubeb_destroy); 102 | 103 | params.format = STREAM_FORMAT; 104 | params.rate = SAMPLE_FREQUENCY; 105 | params.channels = 1; 106 | params.layout = CUBEB_LAYOUT_MONO; 107 | params.prefs = CUBEB_STREAM_PREF_NONE; 108 | 109 | std::unique_ptr user_data(new cb_user_data()); 110 | ASSERT_TRUE(!!user_data) << "Error allocating user data"; 111 | 112 | user_data->position = 0; 113 | 114 | r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, 115 | ¶ms, 4096, data_cb_tone, state_cb_tone, 116 | user_data.get()); 117 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 118 | 119 | std::unique_ptr 120 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 121 | 122 | cubeb_stream_start(stream); 123 | delay(5000); 124 | cubeb_stream_stop(stream); 125 | 126 | ASSERT_TRUE(user_data->position.load()); 127 | } 128 | 129 | #undef SAMPLE_FREQUENCY 130 | #undef STREAM_FORMAT 131 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | env: 9 | BUILD_TYPE: ${{ matrix.type }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-24.04, windows-2025, macos-13, macos-14] 13 | type: [Release, Debug] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: true 19 | 20 | - name: Install Dependencies (Linux) 21 | run: sudo apt-get update && sudo apt-get install libpulse-dev pulseaudio 22 | if: matrix.os == 'ubuntu-24.04' 23 | 24 | - name: Start Sound Server (Linux) 25 | run: pulseaudio -D --start --exit-idle-time=-1 26 | if: matrix.os == 'ubuntu-24.04' 27 | 28 | - name: Install virtual audio devices (Windows) 29 | run: git clone https://github.com/LABSN/sound-ci-helpers && powershell sound-ci-helpers/windows/setup_sound.ps1 30 | if: ${{ matrix.os == 'windows-2025' }} 31 | 32 | - name: Allow microphone access to all apps (Windows) 33 | shell: pwsh 34 | run: | 35 | New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy\" 36 | New-ItemProperty -Path "HKLM:\SOFTWARE\policies\microsoft\windows\appprivacy" -Name "LetAppsAccessMicrophone" -Value "0x00000001" -PropertyType "dword" 37 | if: ${{ matrix.os == 'windows-2025' }} 38 | 39 | - name: Install virtual audio devices (macOS) 40 | if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }} 41 | run: | 42 | brew install switchaudio-osx 43 | brew install blackhole-2ch 44 | sudo kill -9 `pgrep coreaudiod` 45 | sleep 10 46 | SwitchAudioSource -s "BlackHole 2ch" -t input 47 | SwitchAudioSource -s "BlackHole 2ch" -t output 48 | 49 | - name: Allow microphone access to all apps (macOS) 50 | if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }} 51 | env: 52 | tcc_extra_columns: ${{ matrix.os == 'macos-14' && ',NULL,NULL,''UNUSED'',1687786159' || '' }} 53 | run: sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159${{ env.tcc_extra_columns }});" 54 | 55 | - name: Configure CMake 56 | shell: bash 57 | run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE 58 | 59 | - name: Build 60 | shell: bash 61 | run: cmake --build build 62 | 63 | - name: Test 64 | shell: bash 65 | run: (cd build && ctest -V) 66 | 67 | - name: Test winmm 68 | shell: bash 69 | run: (cd build && CUBEB_BACKEND=winmm ctest -V) 70 | if: ${{ matrix.os == 'windows-2025' }} 71 | 72 | build-android: 73 | runs-on: ubuntu-24.04 74 | env: 75 | BUILD_TYPE: ${{ matrix.type }} 76 | BUILD_ARCH: ${{ matrix.arch }} 77 | strategy: 78 | matrix: 79 | type: [Release, Debug] 80 | arch: [arm64-v8a, armeabi-v7a, x86_64] 81 | steps: 82 | - uses: actions/checkout@v4 83 | with: 84 | submodules: true 85 | 86 | - name: Configure CMake 87 | shell: bash 88 | run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-28 -DANDROID_ABI=$BUILD_ARCH 89 | 90 | - name: Build 91 | shell: bash 92 | run: cmake --build build 93 | 94 | check_format: 95 | runs-on: ubuntu-24.04 96 | steps: 97 | - uses: actions/checkout@v4 98 | with: 99 | submodules: true 100 | 101 | - name: Install Dependencies (Linux) 102 | run: sudo apt-get update && sudo apt-get install clang-format-15 103 | 104 | - name: Configure CMake 105 | shell: bash 106 | run: cmake -S . -B build -DCLANG_FORMAT_BINARY=clang-format-15 107 | 108 | - name: Check format 109 | shell: bash 110 | run: cmake --build build --target clang-format-check 111 | 112 | -------------------------------------------------------------------------------- /test/test_device_changed_callback.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2018 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb api/function test. Check behaviors of registering device changed 9 | * callbacks for the streams. */ 10 | #include "gtest/gtest.h" 11 | #if !defined(_XOPEN_SOURCE) 12 | #define _XOPEN_SOURCE 600 13 | #endif 14 | #include "cubeb/cubeb.h" 15 | #include 16 | #include 17 | 18 | // #define ENABLE_NORMAL_LOG 19 | // #define ENABLE_VERBOSE_LOG 20 | #include "common.h" 21 | 22 | #define SAMPLE_FREQUENCY 48000 23 | #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE 24 | #define INPUT_CHANNELS 1 25 | #define INPUT_LAYOUT CUBEB_LAYOUT_MONO 26 | #define OUTPUT_CHANNELS 2 27 | #define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO 28 | 29 | long 30 | data_callback(cubeb_stream * stream, void * user, const void * inputbuffer, 31 | void * outputbuffer, long nframes) 32 | { 33 | return 0; 34 | } 35 | 36 | void 37 | state_callback(cubeb_stream * stream, void * user, cubeb_state state) 38 | { 39 | } 40 | 41 | void 42 | device_changed_callback(void * user) 43 | { 44 | fprintf(stderr, "device changed callback\n"); 45 | ASSERT_TRUE(false) << "Error: device changed callback" 46 | " called without changing devices"; 47 | } 48 | 49 | void 50 | test_registering_null_callback_twice(cubeb_stream * stream) 51 | { 52 | int r = cubeb_stream_register_device_changed_callback(stream, nullptr); 53 | if (r == CUBEB_ERROR_NOT_SUPPORTED) { 54 | return; 55 | } 56 | ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback"; 57 | 58 | r = cubeb_stream_register_device_changed_callback(stream, nullptr); 59 | ASSERT_EQ(r, CUBEB_OK) 60 | << "Error registering null device changed callback again"; 61 | } 62 | 63 | void 64 | test_registering_and_unregistering_callback(cubeb_stream * stream) 65 | { 66 | int r = cubeb_stream_register_device_changed_callback( 67 | stream, device_changed_callback); 68 | if (r == CUBEB_ERROR_NOT_SUPPORTED) { 69 | return; 70 | } 71 | ASSERT_EQ(r, CUBEB_OK) << "Error registering device changed callback"; 72 | 73 | r = cubeb_stream_register_device_changed_callback(stream, nullptr); 74 | ASSERT_EQ(r, CUBEB_OK) << "Error unregistering device changed callback"; 75 | } 76 | 77 | TEST(cubeb, device_changed_callbacks) 78 | { 79 | cubeb * ctx; 80 | cubeb_stream * stream; 81 | cubeb_stream_params input_params; 82 | cubeb_stream_params output_params; 83 | int r = CUBEB_OK; 84 | uint32_t latency_frames = 0; 85 | 86 | r = common_init(&ctx, "Cubeb duplex example with device change"); 87 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 88 | 89 | if (!can_run_audio_input_test(ctx)) { 90 | return; 91 | } 92 | 93 | std::unique_ptr cleanup_cubeb_at_exit( 94 | ctx, cubeb_destroy); 95 | 96 | /* typical user-case: mono input, stereo output, low latency. */ 97 | input_params.format = STREAM_FORMAT; 98 | input_params.rate = SAMPLE_FREQUENCY; 99 | input_params.channels = INPUT_CHANNELS; 100 | input_params.layout = INPUT_LAYOUT; 101 | input_params.prefs = CUBEB_STREAM_PREF_NONE; 102 | output_params.format = STREAM_FORMAT; 103 | output_params.rate = SAMPLE_FREQUENCY; 104 | output_params.channels = OUTPUT_CHANNELS; 105 | output_params.layout = OUTPUT_LAYOUT; 106 | output_params.prefs = CUBEB_STREAM_PREF_NONE; 107 | 108 | r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 109 | ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 110 | 111 | r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 112 | &output_params, latency_frames, data_callback, 113 | state_callback, nullptr); 114 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 115 | 116 | test_registering_null_callback_twice(stream); 117 | 118 | test_registering_and_unregistering_callback(stream); 119 | 120 | cubeb_stream_destroy(stream); 121 | } 122 | 123 | #undef SAMPLE_FREQUENCY 124 | #undef STREAM_FORMAT 125 | #undef INPUT_CHANNELS 126 | #undef INPUT_LAYOUT 127 | #undef OUTPUT_CHANNELS 128 | #undef OUTPUT_LAYOUT 129 | -------------------------------------------------------------------------------- /src/cubeb_ring_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef CUBEB_RING_ARRAY_H 9 | #define CUBEB_RING_ARRAY_H 10 | 11 | #include "cubeb_utils.h" 12 | #include 13 | 14 | /** Ring array of pointers is used to hold buffers. In case that 15 | asynchronous producer/consumer callbacks do not arrive in a 16 | repeated order the ring array stores the buffers and fetch 17 | them in the correct order. */ 18 | 19 | typedef struct { 20 | AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated 21 | space for the buffers. */ 22 | unsigned int tail; /**< Index of the last element (first to deliver). */ 23 | unsigned int count; /**< Number of elements in the array. */ 24 | unsigned int capacity; /**< Total length of the array. */ 25 | } ring_array; 26 | 27 | static int 28 | single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame, 29 | uint32_t channelsPerFrame, uint32_t frames) 30 | { 31 | assert(buffer); 32 | assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0); 33 | 34 | size_t size = bytesPerFrame * frames; 35 | buffer->mData = operator new(size); 36 | if (buffer->mData == NULL) { 37 | return CUBEB_ERROR; 38 | } 39 | PodZero(static_cast(buffer->mData), size); 40 | 41 | buffer->mNumberChannels = channelsPerFrame; 42 | buffer->mDataByteSize = size; 43 | 44 | return CUBEB_OK; 45 | } 46 | 47 | /** Initialize the ring array. 48 | @param ra The ring_array pointer of allocated structure. 49 | @retval 0 on success. */ 50 | static int 51 | ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame, 52 | uint32_t channelsPerFrame, uint32_t framesPerBuffer) 53 | { 54 | assert(ra); 55 | if (capacity == 0 || bytesPerFrame == 0 || channelsPerFrame == 0 || 56 | framesPerBuffer == 0) { 57 | return CUBEB_ERROR_INVALID_PARAMETER; 58 | } 59 | ra->capacity = capacity; 60 | ra->tail = 0; 61 | ra->count = 0; 62 | 63 | ra->buffer_array = new AudioBuffer[ra->capacity]; 64 | PodZero(ra->buffer_array, ra->capacity); 65 | if (ra->buffer_array == NULL) { 66 | return CUBEB_ERROR; 67 | } 68 | 69 | for (unsigned int i = 0; i < ra->capacity; ++i) { 70 | if (single_audiobuffer_init(&ra->buffer_array[i], bytesPerFrame, 71 | channelsPerFrame, 72 | framesPerBuffer) != CUBEB_OK) { 73 | return CUBEB_ERROR; 74 | } 75 | } 76 | 77 | return CUBEB_OK; 78 | } 79 | 80 | /** Destroy the ring array. 81 | @param ra The ring_array pointer.*/ 82 | static void 83 | ring_array_destroy(ring_array * ra) 84 | { 85 | assert(ra); 86 | if (ra->buffer_array == NULL) { 87 | return; 88 | } 89 | for (unsigned int i = 0; i < ra->capacity; ++i) { 90 | if (ra->buffer_array[i].mData) { 91 | operator delete(ra->buffer_array[i].mData); 92 | } 93 | } 94 | delete[] ra->buffer_array; 95 | } 96 | 97 | /** Get the allocated buffer to be stored with fresh data. 98 | @param ra The ring_array pointer. 99 | @retval Pointer of the allocated space to be stored with fresh data or NULL 100 | if full. */ 101 | static AudioBuffer * 102 | ring_array_get_free_buffer(ring_array * ra) 103 | { 104 | assert(ra && ra->buffer_array); 105 | assert(ra->buffer_array[0].mData != NULL); 106 | if (ra->count == ra->capacity) { 107 | return NULL; 108 | } 109 | 110 | assert(ra->count == 0 || (ra->tail + ra->count) % ra->capacity != ra->tail); 111 | AudioBuffer * ret = &ra->buffer_array[(ra->tail + ra->count) % ra->capacity]; 112 | 113 | ++ra->count; 114 | assert(ra->count <= ra->capacity); 115 | 116 | return ret; 117 | } 118 | 119 | /** Get the next available buffer with data. 120 | @param ra The ring_array pointer. 121 | @retval Pointer of the next in order data buffer or NULL if empty. */ 122 | static AudioBuffer * 123 | ring_array_get_data_buffer(ring_array * ra) 124 | { 125 | assert(ra && ra->buffer_array); 126 | assert(ra->buffer_array[0].mData != NULL); 127 | 128 | if (ra->count == 0) { 129 | return NULL; 130 | } 131 | AudioBuffer * ret = &ra->buffer_array[ra->tail]; 132 | 133 | ra->tail = (ra->tail + 1) % ra->capacity; 134 | assert(ra->tail < ra->capacity); 135 | 136 | assert(ra->count > 0); 137 | --ra->count; 138 | 139 | return ret; 140 | } 141 | 142 | #endif // CUBEB_RING_ARRAY_H 143 | -------------------------------------------------------------------------------- /subprojects/speex/resample_sse.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2007-2008 Jean-Marc Valin 2 | * Copyright (C) 2008 Thorvald Natvig 3 | */ 4 | /** 5 | @file resample_sse.h 6 | @brief Resampler functions (SSE version) 7 | */ 8 | /* 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 13 | - Redistributions of source code must retain the above copyright 14 | notice, this list of conditions and the following disclaimer. 15 | 16 | - Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 20 | - Neither the name of the Xiph.org Foundation nor the names of its 21 | contributors may be used to endorse or promote products derived from 22 | this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 28 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 29 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 31 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 33 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | 39 | #define OVERRIDE_INNER_PRODUCT_SINGLE 40 | static inline float inner_product_single(const float *a, const float *b, unsigned int len) 41 | { 42 | int i; 43 | float ret; 44 | __m128 sum = _mm_setzero_ps(); 45 | for (i=0;i 76 | #define OVERRIDE_INNER_PRODUCT_DOUBLE 77 | 78 | static inline double inner_product_double(const float *a, const float *b, unsigned int len) 79 | { 80 | int i; 81 | double ret; 82 | __m128d sum = _mm_setzero_pd(); 83 | __m128 t; 84 | for (i=0;i 15 | #include 16 | #else 17 | #include 18 | #endif 19 | 20 | #include "cubeb/cubeb.h" 21 | #include "cubeb_mixer.h" 22 | #include "gtest/gtest.h" 23 | #include 24 | #include 25 | #include 26 | 27 | template 28 | constexpr size_t 29 | ARRAY_LENGTH(T (&)[N]) 30 | { 31 | return N; 32 | } 33 | 34 | inline void 35 | delay(unsigned int ms) 36 | { 37 | #if defined(_WIN32) 38 | Sleep(ms); 39 | #else 40 | sleep(ms / 1000); 41 | usleep(ms % 1000 * 1000); 42 | #endif 43 | } 44 | 45 | #if !defined(M_PI) 46 | #define M_PI 3.14159265358979323846 47 | #endif 48 | 49 | typedef struct { 50 | char const * name; 51 | unsigned int const channels; 52 | uint32_t const layout; 53 | } layout_info; 54 | 55 | struct backend_caps { 56 | const char * id; 57 | const int input_capabilities; 58 | }; 59 | 60 | // This static table allows knowing if a backend has audio input capabilities. 61 | // We don't rely on opening a stream and checking if it works, because this 62 | // would make the test skip the tests that make use of audio input, if a 63 | // particular backend has a bug that causes a failure during audio input stream 64 | // creation 65 | static backend_caps backend_capabilities[] = { 66 | {"sun", 1}, {"wasapi", 1}, {"kai", 1}, {"audiounit", 1}, 67 | {"audiotrack", 0}, {"opensl", 1}, {"aaudio", 1}, {"jack", 1}, 68 | {"pulse", 1}, {"sndio", 1}, {"oss", 1}, {"winmm", 0}, 69 | {"alsa", 1}, 70 | }; 71 | 72 | inline int 73 | can_run_audio_input_test(cubeb * ctx) 74 | { 75 | cubeb_device_collection devices; 76 | int input_device_available = 0; 77 | int r; 78 | /* Bail out early if the host does not have input devices. */ 79 | r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices); 80 | if (r != CUBEB_OK) { 81 | fprintf(stderr, "error enumerating devices."); 82 | return 0; 83 | } 84 | 85 | if (devices.count == 0) { 86 | fprintf(stderr, "no input device available, skipping test.\n"); 87 | cubeb_device_collection_destroy(ctx, &devices); 88 | return 0; 89 | } 90 | 91 | for (uint32_t i = 0; i < devices.count; i++) { 92 | input_device_available |= 93 | (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED); 94 | } 95 | 96 | if (!input_device_available) { 97 | fprintf(stderr, "there are input devices, but they are not " 98 | "available, skipping\n"); 99 | } 100 | 101 | cubeb_device_collection_destroy(ctx, &devices); 102 | 103 | int backend_has_input_capabilities; 104 | const char * backend_id = cubeb_get_backend_id(ctx); 105 | for (uint32_t i = 0; i < sizeof(backend_capabilities) / sizeof(backend_caps); 106 | i++) { 107 | if (strcmp(backend_capabilities[i].id, backend_id) == 0) { 108 | backend_has_input_capabilities = 109 | backend_capabilities[i].input_capabilities; 110 | } 111 | } 112 | 113 | return !!input_device_available && !!backend_has_input_capabilities; 114 | } 115 | 116 | inline void 117 | print_log(const char * msg, ...) 118 | { 119 | va_list args; 120 | va_start(args, msg); 121 | vprintf(msg, args); 122 | printf("\n"); 123 | va_end(args); 124 | } 125 | 126 | /** Initialize cubeb with backend override. 127 | * Create call cubeb_init passing value for CUBEB_BACKEND env var as 128 | * override. */ 129 | inline int 130 | common_init(cubeb ** ctx, char const * ctx_name) 131 | { 132 | #ifdef ENABLE_NORMAL_LOG 133 | if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) { 134 | fprintf(stderr, "Set normal log callback failed\n"); 135 | } 136 | #endif 137 | 138 | #ifdef ENABLE_VERBOSE_LOG 139 | if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) { 140 | fprintf(stderr, "Set verbose log callback failed\n"); 141 | } 142 | #endif 143 | 144 | int r; 145 | char const * backend; 146 | char const * ctx_backend; 147 | 148 | backend = getenv("CUBEB_BACKEND"); 149 | r = cubeb_init(ctx, ctx_name, backend); 150 | if (r == CUBEB_OK && backend) { 151 | ctx_backend = cubeb_get_backend_id(*ctx); 152 | if (strcmp(backend, ctx_backend) != 0) { 153 | fprintf(stderr, "Requested backend `%s', got `%s'\n", backend, 154 | ctx_backend); 155 | } 156 | } 157 | 158 | return r; 159 | } 160 | 161 | #if defined(_WIN32) 162 | class TestEnvironment : public ::testing::Environment { 163 | public: 164 | void SetUp() override { hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); } 165 | 166 | void TearDown() override 167 | { 168 | if (SUCCEEDED(hr)) { 169 | CoUninitialize(); 170 | } 171 | } 172 | 173 | private: 174 | HRESULT hr; 175 | }; 176 | 177 | ::testing::Environment * const foo_env = 178 | ::testing::AddGlobalTestEnvironment(new TestEnvironment); 179 | #endif 180 | 181 | #endif /* TEST_COMMON */ 182 | -------------------------------------------------------------------------------- /subprojects/speex/fixed_generic.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2003 Jean-Marc Valin */ 2 | /** 3 | @file fixed_generic.h 4 | @brief Generic fixed-point operations 5 | */ 6 | /* 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | - Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | - Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | - Neither the name of the Xiph.org Foundation nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef FIXED_GENERIC_H 36 | #define FIXED_GENERIC_H 37 | 38 | #define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) 39 | #define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) 40 | 41 | #define NEG16(x) (-(x)) 42 | #define NEG32(x) (-(x)) 43 | #define EXTRACT16(x) ((spx_word16_t)(x)) 44 | #define EXTEND32(x) ((spx_word32_t)(x)) 45 | #define SHR16(a,shift) ((a) >> (shift)) 46 | #define SHL16(a,shift) ((a) << (shift)) 47 | #define SHR32(a,shift) ((a) >> (shift)) 48 | #define SHL32(a,shift) ((a) << (shift)) 49 | #define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift)) 50 | #define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift)) 51 | #define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) 52 | #define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) 53 | #define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) 54 | 55 | #define SATURATE32PSHR(x,shift,a) (((x)>=(SHL32(a,shift))) ? (a) : \ 56 | (x)<=-(SHL32(a,shift)) ? -(a) : \ 57 | (PSHR32(x, shift))) 58 | 59 | #define SHR(a,shift) ((a) >> (shift)) 60 | #define SHL(a,shift) ((spx_word32_t)(a) << (shift)) 61 | #define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift)) 62 | #define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) 63 | 64 | 65 | #define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b))) 66 | #define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b)) 67 | #define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b)) 68 | #define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b)) 69 | 70 | 71 | /* result fits in 16 bits */ 72 | #define MULT16_16_16(a,b) (((spx_word16_t)(a))*((spx_word16_t)(b))) 73 | /* result fits in 32 bits */ 74 | #define MULT16_32_32(a,b) (((spx_word16_t)(a))*((spx_word32_t)(b))) 75 | 76 | /* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */ 77 | #define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b))) 78 | 79 | #define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) 80 | 81 | #define MULT16_32_P15(a,b) ADD32(MULT16_32_32(a,SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) 82 | #define MULT16_32_Q15(a,b) ADD32(MULT16_32_32(a,SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) 83 | #define MAC16_32_Q15(c,a,b) ADD32(c,MULT16_32_Q15(a,b)) 84 | 85 | 86 | #define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) 87 | #define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13))) 88 | #define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13))) 89 | 90 | #define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) 91 | #define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) 92 | #define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) 93 | #define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) 94 | 95 | #define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) 96 | #define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) 97 | #define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) 98 | 99 | #define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15)) 100 | 101 | #define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b)))) 102 | #define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b)))) 103 | #define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b))) 104 | #define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b))) 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /src/android/sles_definitions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in 19 | * the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep 20 | * using a C compiler in cubeb. 21 | */ 22 | 23 | #ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_ 24 | #define OPENSL_ES_ANDROIDCONFIGURATION_H_ 25 | 26 | /*---------------------------------------------------------------------------*/ 27 | /* Android AudioRecorder configuration */ 28 | /*---------------------------------------------------------------------------*/ 29 | 30 | /** Audio recording preset */ 31 | /** Audio recording preset key */ 32 | #define SL_ANDROID_KEY_RECORDING_PRESET \ 33 | ((const SLchar *)"androidRecordingPreset") 34 | /** Audio recording preset values */ 35 | /** preset "none" cannot be set, it is used to indicate the current settings 36 | * do not match any of the presets. */ 37 | #define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000) 38 | /** generic recording configuration on the platform */ 39 | #define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001) 40 | /** uses the microphone audio source with the same orientation as the camera 41 | * if available, the main device microphone otherwise */ 42 | #define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002) 43 | /** uses the main microphone tuned for voice recognition */ 44 | #define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003) 45 | /** uses the main microphone tuned for audio communications */ 46 | #define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004) 47 | /** uses the main microphone unprocessed */ 48 | #define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005) 49 | 50 | /*---------------------------------------------------------------------------*/ 51 | /* Android AudioPlayer configuration */ 52 | /*---------------------------------------------------------------------------*/ 53 | 54 | /** Audio playback stream type */ 55 | /** Audio playback stream type key */ 56 | #define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType") 57 | 58 | /** Audio playback stream type values */ 59 | /* same as android.media.AudioManager.STREAM_VOICE_CALL */ 60 | #define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000) 61 | /* same as android.media.AudioManager.STREAM_SYSTEM */ 62 | #define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001) 63 | /* same as android.media.AudioManager.STREAM_RING */ 64 | #define SL_ANDROID_STREAM_RING ((SLint32)0x00000002) 65 | /* same as android.media.AudioManager.STREAM_MUSIC */ 66 | #define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003) 67 | /* same as android.media.AudioManager.STREAM_ALARM */ 68 | #define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004) 69 | /* same as android.media.AudioManager.STREAM_NOTIFICATION */ 70 | #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005) 71 | 72 | /*---------------------------------------------------------------------------*/ 73 | /* Android AudioPlayer and AudioRecorder configuration */ 74 | /*---------------------------------------------------------------------------*/ 75 | 76 | /** Audio Performance mode. 77 | * Performance mode tells the framework how to configure the audio path 78 | * for a player or recorder according to application performance and 79 | * functional requirements. 80 | * It affects the output or input latency based on acceptable tradeoffs on 81 | * battery drain and use of pre or post processing effects. 82 | * Performance mode should be set before realizing the object and should be 83 | * read after realizing the object to check if the requested mode could be 84 | * granted or not. 85 | */ 86 | /** Audio Performance mode key */ 87 | #define SL_ANDROID_KEY_PERFORMANCE_MODE \ 88 | ((const SLchar *)"androidPerformanceMode") 89 | 90 | /** Audio performance values */ 91 | /* No specific performance requirement. Allows HW and SW pre/post 92 | * processing. */ 93 | #define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000) 94 | /* Priority given to latency. No HW or software pre/post processing. 95 | * This is the default if no performance mode is specified. */ 96 | #define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001) 97 | /* Priority given to latency while still allowing HW pre and post 98 | * processing. */ 99 | #define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002) 100 | /* Priority given to power saving if latency is not a concern. 101 | * Allows HW and SW pre/post processing. */ 102 | #define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003) 103 | 104 | #endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libcubeb - Cross-platform Audio I/O Library 2 | 3 | [![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml) 4 | 5 | `libcubeb` is a cross-platform C library for high and low-latency audio input/output. It provides a simple, consistent API for audio playback and recording across multiple platforms and audio backends. It is written in C, C++ and Rust, with a C ABI and [Rust](https://github.com/mozilla/cubeb-rs) bindings. While originally written for use in the Firefox Web browser, a number of other software projects have adopted it. 6 | 7 | ## Features 8 | 9 | - **Cross-platform support**: Windows, macOS, Linux, Android, and other platforms 10 | - **Versatile**: Optimized for low-latency real-time audio applications, or power efficient higher latency playback 11 | - **A/V sync**: Latency compensated audio clock reporting for easy audio/video synchronization 12 | - **Full-duplex support**: Simultaneous audio input and output, reclocked 13 | - **Device enumeration**: Query available audio devices 14 | - **Audio processing for speech**: Can use VoiceProcessing IO on recent macOS 15 | 16 | ## Supported Backends & status 17 | 18 | | *Backend* | *Support Level* | *Platform version* | *Notes* | 19 | |-------------------|-----------------|--------------------|--------------------------------------------------| 20 | | PulseAudio (Rust) | Tier-1 | | Main Linux desktop backend | 21 | | AudioUnit (Rust) | Tier-1 | | Main macOS backend | 22 | | WASAPI | Tier-1 | Windows >= 7 | Main Windows backend | 23 | | AAudio | Tier-1 | Android >= 8 | Main Android backend for most devices | 24 | | OpenSL | Tier-1 | Android >= 2.3 | Android backend for older devices | 25 | | OSS | Tier-2 | | | 26 | | sndio | Tier-2 | | | 27 | | Sun | Tier-2 | | | 28 | | WinMM | Tier-3 | Windows XP | Was Tier-1, Firefox minimum Windows version 7. | 29 | | AudioTrack | Tier-3 | Android < 2.3 | Was Tier-1, Firefox minimum Android version 4.1. | 30 | | ALSA | Tier-3 | | | 31 | | JACK | Tier-3 | | | 32 | | KAI | Tier-3 | | | 33 | | PulseAudio (C) | Tier-4 | | Was Tier-1, superseded by Rust | 34 | | AudioUnit (C++) | Tier-4 | | Was Tier-1, superseded by Rust | 35 | 36 | Tier-1: Actively maintained. Should have CI coverage. Critical for Firefox. 37 | 38 | Tier-2: Actively maintained by contributors. CI coverage appreciated. 39 | 40 | Tier-3: Maintainers/patches accepted. Status unclear. 41 | 42 | Tier-4: Deprecated, obsolete. Scheduled to be removed. 43 | 44 | Note that the support level is not a judgement of the relative merits 45 | of a backend, only the current state of support, which is informed 46 | by Firefox's needs, the responsiveness of a backend's 47 | maintainer, and the level of contributions to that backend. 48 | 49 | ## Building 50 | 51 | ### Prerequisites 52 | 53 | - CMake 3.15 or later 54 | - Non-ancient MSVC, clang or gcc, for compiling both C and C++ 55 | - Platform-specific audio libraries (automatically detected) 56 | - Optional but recommended: Rust compiler to compile and link more recent backends for macOS and PulseAudio 57 | 58 | ### Quick build 59 | 60 | ```bash 61 | git clone https://github.com/mozilla/cubeb.git 62 | cd cubeb 63 | cmake -B build 64 | cmake --build build 65 | ``` 66 | 67 | ### Better build with Rust backends 68 | 69 | ```bash 70 | git clone --recursive https://github.com/mozilla/cubeb.git 71 | cd cubeb 72 | cmake -B build -DBUILD_RUST_LIBS=ON 73 | cmake --build build 74 | ``` 75 | 76 | ### Platform-Specific Notes 77 | 78 | **Windows**: Supports Visual Studio 2015+ and MinGW-w64. Use `-G "Visual Studio 16 2019"` or `-G "MinGW Makefiles"`. 79 | 80 | **macOS**: Requires Xcode command line tools. Audio frameworks are automatically linked. 81 | 82 | **Linux**: Development packages for desired backends: 83 | ```bash 84 | # Ubuntu/Debian 85 | sudo apt-get install libpulse-dev libasound2-dev libjack-dev 86 | 87 | # Fedora/RHEL 88 | sudo dnf install pulseaudio-libs-devel alsa-lib-devel jack-audio-connection-kit-devel 89 | ``` 90 | 91 | **Android**: Use with Android NDK. AAudio requires API level 26+. 92 | 93 | ## Testing 94 | 95 | Run the test suite: 96 | ```bash 97 | cd build 98 | ctest 99 | ``` 100 | 101 | Use the interactive test tool: 102 | ```bash 103 | ./cubeb-test 104 | ``` 105 | 106 | ## License 107 | 108 | Licensed under an ISC-style license. See [LICENSE](LICENSE) for details. 109 | 110 | ## Contributing 111 | 112 | Contributions are welcome! Please see the [contribution guidelines](CONTRIBUTING.md) and check the [issue tracker](https://github.com/mozilla/cubeb/issues). 113 | 114 | ## Links 115 | 116 | - [GitHub Repository](https://github.com/mozilla/cubeb) 117 | - [API Documentation](https://mozilla.github.io/cubeb/) 118 | -------------------------------------------------------------------------------- /test/test_logging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* cubeb_logging test */ 9 | #include "gtest/gtest.h" 10 | #if !defined(_XOPEN_SOURCE) 11 | #define _XOPEN_SOURCE 600 12 | #endif 13 | #include "cubeb/cubeb.h" 14 | #include "cubeb_log.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "common.h" 23 | 24 | #define PRINT_LOGS_TO_STDERR 0 25 | 26 | std::atomic log_statements_received = {0}; 27 | std::atomic data_callback_call_count = {0}; 28 | 29 | static void 30 | test_logging_callback(char const * fmt, ...) 31 | { 32 | log_statements_received++; 33 | #if PRINT_LOGS_TO_STDERR == 1 34 | char buf[1024]; 35 | va_list argslist; 36 | va_start(argslist, fmt); 37 | vsnprintf(buf, 1024, fmt, argslist); 38 | fprintf(stderr, "%s\n", buf); 39 | va_end(argslist); 40 | #endif // PRINT_LOGS_TO_STDERR 41 | } 42 | 43 | static long 44 | data_cb_load(cubeb_stream * stream, void * user, const void * inputbuffer, 45 | void * outputbuffer, long nframes) 46 | { 47 | data_callback_call_count++; 48 | return nframes; 49 | } 50 | 51 | static void 52 | state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state) 53 | { 54 | if (stream == NULL) 55 | return; 56 | 57 | switch (state) { 58 | case CUBEB_STATE_STARTED: 59 | fprintf(stderr, "stream started\n"); 60 | break; 61 | case CUBEB_STATE_STOPPED: 62 | fprintf(stderr, "stream stopped\n"); 63 | break; 64 | case CUBEB_STATE_DRAINED: 65 | fprintf(stderr, "stream drained\n"); 66 | break; 67 | default: 68 | fprintf(stderr, "unknown stream state %d\n", state); 69 | } 70 | 71 | return; 72 | } 73 | 74 | // Waits for at least one audio callback to have occured. 75 | void 76 | wait_for_audio_callback() 77 | { 78 | uint32_t audio_callback_index = 79 | data_callback_call_count.load(std::memory_order_acquire); 80 | while (audio_callback_index == 81 | data_callback_call_count.load(std::memory_order_acquire)) { 82 | delay(100); 83 | } 84 | } 85 | 86 | TEST(cubeb, logging) 87 | { 88 | cubeb * ctx; 89 | cubeb_stream * stream; 90 | cubeb_stream_params output_params; 91 | int r; 92 | uint32_t latency_frames = 0; 93 | 94 | cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback); 95 | 96 | r = common_init(&ctx, "Cubeb logging test"); 97 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 98 | 99 | std::unique_ptr cleanup_cubeb_at_exit( 100 | ctx, cubeb_destroy); 101 | 102 | output_params.format = CUBEB_SAMPLE_FLOAT32LE; 103 | output_params.rate = 48000; 104 | output_params.channels = 2; 105 | output_params.layout = CUBEB_LAYOUT_STEREO; 106 | output_params.prefs = CUBEB_STREAM_PREF_NONE; 107 | 108 | r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 109 | if (r != CUBEB_OK) { 110 | // not fatal 111 | latency_frames = 1024; 112 | } 113 | 114 | r = cubeb_stream_init(ctx, &stream, "Cubeb logging", NULL, NULL, NULL, 115 | &output_params, latency_frames, data_cb_load, state_cb, 116 | NULL); 117 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 118 | 119 | std::unique_ptr 120 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 121 | 122 | ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u); 123 | 124 | cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr); 125 | log_statements_received.store(0, std::memory_order_release); 126 | 127 | // This is synchronous and we'll receive log messages on all backends that we 128 | // test 129 | cubeb_stream_start(stream); 130 | 131 | ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u); 132 | 133 | cubeb_set_log_callback(CUBEB_LOG_VERBOSE, test_logging_callback); 134 | 135 | wait_for_audio_callback(); 136 | 137 | ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u); 138 | 139 | bool log_callback_set = true; 140 | uint32_t iterations = 100; 141 | while (iterations--) { 142 | wait_for_audio_callback(); 143 | 144 | if (!log_callback_set) { 145 | ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u); 146 | // Set a logging callback, start logging 147 | cubeb_set_log_callback(CUBEB_LOG_VERBOSE, test_logging_callback); 148 | log_callback_set = true; 149 | } else { 150 | // Disable the logging callback, stop logging. 151 | ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u); 152 | cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr); 153 | log_statements_received.store(0, std::memory_order_release); 154 | // Disabling logging should flush any log message -- wait a bit and check 155 | // that this is true. 156 | ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u); 157 | log_callback_set = false; 158 | } 159 | } 160 | 161 | cubeb_stream_stop(stream); 162 | } 163 | 164 | TEST(cubeb, logging_stress) 165 | { 166 | cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback); 167 | 168 | std::atomic thread_done = {false}; 169 | 170 | auto t = std::thread([&thread_done]() { 171 | uint32_t count = 0; 172 | do { 173 | while (rand() % 10) { 174 | ALOG("Log message #%d!", count++); 175 | } 176 | } while (count < 1e4); 177 | thread_done.store(true); 178 | }); 179 | 180 | bool enabled = true; 181 | while (!thread_done.load()) { 182 | if (enabled) { 183 | cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr); 184 | enabled = false; 185 | } else { 186 | cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback); 187 | enabled = true; 188 | } 189 | } 190 | 191 | cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr); 192 | 193 | t.join(); 194 | 195 | ASSERT_TRUE(true); 196 | } 197 | -------------------------------------------------------------------------------- /src/cubeb_audio_dump.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #define NOMINMAX 9 | 10 | #include "cubeb_audio_dump.h" 11 | #include "cubeb/cubeb.h" 12 | #include "cubeb_ringbuffer.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using std::thread; 19 | using std::vector; 20 | 21 | uint32_t 22 | bytes_per_sample(cubeb_stream_params params) 23 | { 24 | switch (params.format) { 25 | case CUBEB_SAMPLE_S16LE: 26 | case CUBEB_SAMPLE_S16BE: 27 | return sizeof(int16_t); 28 | case CUBEB_SAMPLE_FLOAT32LE: 29 | case CUBEB_SAMPLE_FLOAT32BE: 30 | return sizeof(float); 31 | }; 32 | } 33 | 34 | struct cubeb_audio_dump_stream { 35 | public: 36 | explicit cubeb_audio_dump_stream(cubeb_stream_params params) 37 | : sample_size(bytes_per_sample(params)), 38 | ringbuffer( 39 | static_cast(params.rate * params.channels * sample_size)) 40 | { 41 | } 42 | 43 | int open(const char * name) 44 | { 45 | file = fopen(name, "wb"); 46 | if (!file) { 47 | return CUBEB_ERROR; 48 | } 49 | return CUBEB_OK; 50 | } 51 | int close() 52 | { 53 | if (fclose(file)) { 54 | return CUBEB_ERROR; 55 | } 56 | return CUBEB_OK; 57 | } 58 | 59 | // Directly write to the file. Useful to write the header. 60 | size_t write(uint8_t * data, uint32_t count) 61 | { 62 | return fwrite(data, count, 1, file); 63 | } 64 | 65 | size_t write_all() 66 | { 67 | size_t written = 0; 68 | const int buf_sz = 16 * 1024; 69 | uint8_t buf[buf_sz]; 70 | while (int rv = ringbuffer.dequeue(buf, buf_sz)) { 71 | written += fwrite(buf, rv, 1, file); 72 | } 73 | return written; 74 | } 75 | int dump(void * samples, uint32_t count) 76 | { 77 | int bytes = static_cast(count * sample_size); 78 | int rv = ringbuffer.enqueue(static_cast(samples), bytes); 79 | return rv == bytes; 80 | } 81 | 82 | private: 83 | uint32_t sample_size; 84 | FILE * file{}; 85 | lock_free_queue ringbuffer; 86 | }; 87 | 88 | struct cubeb_audio_dump_session { 89 | public: 90 | cubeb_audio_dump_session() = default; 91 | ~cubeb_audio_dump_session() 92 | { 93 | assert(streams.empty()); 94 | session_thread.join(); 95 | } 96 | cubeb_audio_dump_session(const cubeb_audio_dump_session &) = delete; 97 | cubeb_audio_dump_session & 98 | operator=(const cubeb_audio_dump_session &) = delete; 99 | cubeb_audio_dump_session & operator=(cubeb_audio_dump_session &&) = delete; 100 | 101 | cubeb_audio_dump_stream_t create_stream(cubeb_stream_params params, 102 | const char * name) 103 | { 104 | if (running) { 105 | return nullptr; 106 | } 107 | auto * stream = new cubeb_audio_dump_stream(params); 108 | streams.push_back(stream); 109 | int rv = stream->open(name); 110 | if (rv != CUBEB_OK) { 111 | delete stream; 112 | return nullptr; 113 | } 114 | 115 | struct riff_header { 116 | char chunk_id[4] = {'R', 'I', 'F', 'F'}; 117 | int32_t chunk_size = 0; 118 | char format[4] = {'W', 'A', 'V', 'E'}; 119 | 120 | char subchunk_id_1[4] = {'f', 'm', 't', 0x20}; 121 | int32_t subchunk_1_size = 16; 122 | int16_t audio_format{}; 123 | int16_t num_channels{}; 124 | int32_t sample_rate{}; 125 | int32_t byte_rate{}; 126 | int16_t block_align{}; 127 | int16_t bits_per_sample{}; 128 | 129 | char subchunk_id_2[4] = {'d', 'a', 't', 'a'}; 130 | int32_t subchunkd_2_size = std::numeric_limits::max(); 131 | }; 132 | 133 | riff_header header; 134 | // 1 is integer PCM, 3 is float PCM 135 | header.audio_format = bytes_per_sample(params) == 2 ? 1 : 3; 136 | header.num_channels = params.channels; 137 | header.sample_rate = params.rate; 138 | header.byte_rate = bytes_per_sample(params) * params.rate * params.channels; 139 | header.block_align = params.channels * bytes_per_sample(params); 140 | header.bits_per_sample = bytes_per_sample(params) * 8; 141 | 142 | stream->write(reinterpret_cast(&header), sizeof(riff_header)); 143 | 144 | return stream; 145 | } 146 | int delete_stream(cubeb_audio_dump_stream * stream) 147 | { 148 | assert(!running); 149 | stream->close(); 150 | streams.erase(std::remove(streams.begin(), streams.end(), stream), 151 | streams.end()); 152 | delete stream; 153 | return CUBEB_OK; 154 | } 155 | int start() 156 | { 157 | assert(!running); 158 | running = true; 159 | session_thread = std::thread([this] { 160 | while (running) { 161 | for (auto * stream : streams) { 162 | stream->write_all(); 163 | } 164 | const int DUMP_INTERVAL = 10; 165 | std::this_thread::sleep_for(std::chrono::milliseconds(DUMP_INTERVAL)); 166 | } 167 | }); 168 | return CUBEB_OK; 169 | } 170 | int stop() 171 | { 172 | assert(running); 173 | running = false; 174 | return CUBEB_OK; 175 | } 176 | 177 | private: 178 | thread session_thread; 179 | vector streams{}; 180 | std::atomic running = false; 181 | }; 182 | 183 | int 184 | cubeb_audio_dump_init(cubeb_audio_dump_session_t * session) 185 | { 186 | *session = new cubeb_audio_dump_session; 187 | return CUBEB_OK; 188 | } 189 | 190 | int 191 | cubeb_audio_dump_shutdown(cubeb_audio_dump_session_t session) 192 | { 193 | delete session; 194 | return CUBEB_OK; 195 | } 196 | 197 | int 198 | cubeb_audio_dump_stream_init(cubeb_audio_dump_session_t session, 199 | cubeb_audio_dump_stream_t * stream, 200 | cubeb_stream_params stream_params, 201 | const char * name) 202 | { 203 | *stream = session->create_stream(stream_params, name); 204 | return CUBEB_OK; 205 | } 206 | 207 | int 208 | cubeb_audio_dump_stream_shutdown(cubeb_audio_dump_session_t session, 209 | cubeb_audio_dump_stream_t stream) 210 | { 211 | return session->delete_stream(stream); 212 | } 213 | 214 | int 215 | cubeb_audio_dump_start(cubeb_audio_dump_session_t session) 216 | { 217 | return session->start(); 218 | } 219 | 220 | int 221 | cubeb_audio_dump_stop(cubeb_audio_dump_session_t session) 222 | { 223 | return session->stop(); 224 | } 225 | 226 | int 227 | cubeb_audio_dump_write(cubeb_audio_dump_stream_t stream, void * audio_samples, 228 | uint32_t count) 229 | { 230 | stream->dump(audio_samples, count); 231 | return CUBEB_OK; 232 | } 233 | -------------------------------------------------------------------------------- /test/test_ring_buffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #ifndef NOMINMAX 9 | #define NOMINMAX 10 | #endif 11 | 12 | #include "cubeb_ringbuffer.h" 13 | #include "gtest/gtest.h" 14 | #include 15 | #include 16 | #include 17 | 18 | /* Generate a monotonically increasing sequence of numbers. */ 19 | template class sequence_generator { 20 | public: 21 | sequence_generator(size_t channels) : channels(channels) {} 22 | void get(T * elements, size_t frames) 23 | { 24 | for (size_t i = 0; i < frames; i++) { 25 | for (size_t c = 0; c < channels; c++) { 26 | elements[i * channels + c] = static_cast(index_); 27 | } 28 | index_++; 29 | } 30 | } 31 | void rewind(size_t frames) { index_ -= frames; } 32 | 33 | private: 34 | size_t index_ = 0; 35 | size_t channels = 0; 36 | }; 37 | 38 | /* Checks that a sequence is monotonically increasing. */ 39 | template class sequence_verifier { 40 | public: 41 | sequence_verifier(size_t channels) : channels(channels) {} 42 | void check(T * elements, size_t frames) 43 | { 44 | for (size_t i = 0; i < frames; i++) { 45 | for (size_t c = 0; c < channels; c++) { 46 | if (elements[i * channels + c] != static_cast(index_)) { 47 | std::cerr << "Element " << i << " is different. Expected " 48 | << static_cast(index_) << ", got " << elements[i] 49 | << ". (channel count: " << channels << ")." << std::endl; 50 | ASSERT_TRUE(false); 51 | } 52 | } 53 | index_++; 54 | } 55 | } 56 | 57 | private: 58 | size_t index_ = 0; 59 | size_t channels = 0; 60 | }; 61 | 62 | template 63 | void 64 | test_ring(lock_free_audio_ring_buffer & buf, int channels, 65 | int capacity_frames) 66 | { 67 | std::unique_ptr seq(new T[capacity_frames * channels]); 68 | sequence_generator gen(channels); 69 | sequence_verifier checker(channels); 70 | 71 | int iterations = 1002; 72 | 73 | const int block_size = 128; 74 | 75 | while (iterations--) { 76 | gen.get(seq.get(), block_size); 77 | int rv = buf.enqueue(seq.get(), block_size); 78 | ASSERT_EQ(rv, block_size); 79 | PodZero(seq.get(), block_size); 80 | rv = buf.dequeue(seq.get(), block_size); 81 | ASSERT_EQ(rv, block_size); 82 | checker.check(seq.get(), block_size); 83 | } 84 | } 85 | 86 | template 87 | void 88 | test_ring_multi(lock_free_audio_ring_buffer & buf, int channels, 89 | int capacity_frames) 90 | { 91 | sequence_verifier checker(channels); 92 | std::unique_ptr out_buffer(new T[capacity_frames * channels]); 93 | 94 | const int block_size = 128; 95 | 96 | std::thread t([=, &buf] { 97 | int iterations = 1002; 98 | std::unique_ptr in_buffer(new T[capacity_frames * channels]); 99 | sequence_generator gen(channels); 100 | 101 | while (iterations--) { 102 | std::this_thread::yield(); 103 | gen.get(in_buffer.get(), block_size); 104 | int rv = buf.enqueue(in_buffer.get(), block_size); 105 | ASSERT_TRUE(rv <= block_size); 106 | if (rv != block_size) { 107 | gen.rewind(block_size - rv); 108 | } 109 | } 110 | }); 111 | 112 | int remaining = 1002; 113 | 114 | while (remaining--) { 115 | std::this_thread::yield(); 116 | int rv = buf.dequeue(out_buffer.get(), block_size); 117 | ASSERT_TRUE(rv <= block_size); 118 | checker.check(out_buffer.get(), rv); 119 | } 120 | 121 | t.join(); 122 | } 123 | 124 | template 125 | void 126 | basic_api_test(T & ring) 127 | { 128 | ASSERT_EQ(ring.capacity(), 128); 129 | 130 | ASSERT_EQ(ring.available_read(), 0); 131 | ASSERT_EQ(ring.available_write(), 128); 132 | 133 | int rv = ring.enqueue_default(63); 134 | 135 | ASSERT_TRUE(rv == 63); 136 | ASSERT_EQ(ring.available_read(), 63); 137 | ASSERT_EQ(ring.available_write(), 65); 138 | 139 | rv = ring.enqueue_default(65); 140 | 141 | ASSERT_EQ(rv, 65); 142 | ASSERT_EQ(ring.available_read(), 128); 143 | ASSERT_EQ(ring.available_write(), 0); 144 | 145 | rv = ring.dequeue(nullptr, 63); 146 | 147 | ASSERT_EQ(ring.available_read(), 65); 148 | ASSERT_EQ(ring.available_write(), 63); 149 | 150 | rv = ring.dequeue(nullptr, 65); 151 | 152 | ASSERT_EQ(ring.available_read(), 0); 153 | ASSERT_EQ(ring.available_write(), 128); 154 | } 155 | 156 | void 157 | test_reset_api() 158 | { 159 | const size_t ring_buffer_size = 128; 160 | const size_t enqueue_size = ring_buffer_size / 2; 161 | 162 | lock_free_queue ring(ring_buffer_size); 163 | std::thread t([=, &ring] { 164 | std::unique_ptr in_buffer(new float[enqueue_size]); 165 | ring.enqueue(in_buffer.get(), enqueue_size); 166 | }); 167 | 168 | t.join(); 169 | 170 | ring.reset_thread_ids(); 171 | 172 | // Enqueue with a different thread. We have reset the thread ID 173 | // in the ring buffer, this should work. 174 | std::thread t2([=, &ring] { 175 | std::unique_ptr in_buffer(new float[enqueue_size]); 176 | ring.enqueue(in_buffer.get(), enqueue_size); 177 | }); 178 | 179 | t2.join(); 180 | 181 | ASSERT_TRUE(true); 182 | } 183 | 184 | TEST(cubeb, ring_buffer) 185 | { 186 | /* Basic API test. */ 187 | const int min_channels = 1; 188 | const int max_channels = 10; 189 | const int min_capacity = 199; 190 | const int max_capacity = 1277; 191 | const int capacity_increment = 27; 192 | 193 | lock_free_queue q1(128); 194 | basic_api_test(q1); 195 | lock_free_queue q2(128); 196 | basic_api_test(q2); 197 | 198 | for (size_t channels = min_channels; channels < max_channels; channels++) { 199 | lock_free_audio_ring_buffer q3(channels, 128); 200 | basic_api_test(q3); 201 | lock_free_audio_ring_buffer q4(channels, 128); 202 | basic_api_test(q4); 203 | } 204 | 205 | /* Single thread testing. */ 206 | /* Test mono to 9.1 */ 207 | for (size_t channels = min_channels; channels < max_channels; channels++) { 208 | /* Use non power-of-two numbers to catch edge-cases. */ 209 | for (size_t capacity_frames = min_capacity; capacity_frames < max_capacity; 210 | capacity_frames += capacity_increment) { 211 | lock_free_audio_ring_buffer ring(channels, capacity_frames); 212 | test_ring(ring, channels, capacity_frames); 213 | } 214 | } 215 | 216 | /* Multi thread testing */ 217 | for (size_t channels = min_channels; channels < max_channels; channels++) { 218 | /* Use non power-of-two numbers to catch edge-cases. */ 219 | for (size_t capacity_frames = min_capacity; capacity_frames < max_capacity; 220 | capacity_frames += capacity_increment) { 221 | lock_free_audio_ring_buffer ring(channels, capacity_frames); 222 | test_ring_multi(ring, channels, capacity_frames); 223 | } 224 | } 225 | 226 | test_reset_api(); 227 | } 228 | 229 | #undef NOMINMAX 230 | -------------------------------------------------------------------------------- /test/test_audio.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013 Sebastien Alaiwan 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb api/function exhaustive test. Plays a series of tones in different 9 | * conditions. */ 10 | #include "gtest/gtest.h" 11 | #if !defined(_XOPEN_SOURCE) 12 | #define _XOPEN_SOURCE 600 13 | #endif 14 | #include "cubeb/cubeb.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | // #define ENABLE_NORMAL_LOG 23 | // #define ENABLE_VERBOSE_LOG 24 | #include "common.h" 25 | 26 | using namespace std; 27 | 28 | #define MAX_NUM_CHANNELS 32 29 | #define VOLUME 0.2 30 | 31 | float 32 | get_frequency(int channel_index) 33 | { 34 | return 220.0f * (channel_index + 1); 35 | } 36 | 37 | template 38 | T 39 | ConvertSample(double input); 40 | template <> 41 | float 42 | ConvertSample(double input) 43 | { 44 | return input; 45 | } 46 | template <> 47 | short 48 | ConvertSample(double input) 49 | { 50 | return short(input * 32767.0f); 51 | } 52 | 53 | /* store the phase of the generated waveform */ 54 | struct synth_state { 55 | synth_state(int num_channels_, float sample_rate_) 56 | : num_channels(num_channels_), sample_rate(sample_rate_) 57 | { 58 | for (int i = 0; i < MAX_NUM_CHANNELS; ++i) 59 | phase[i] = 0.0f; 60 | } 61 | 62 | template void run(T * audiobuffer, long nframes) 63 | { 64 | for (int c = 0; c < num_channels; ++c) { 65 | float freq = get_frequency(c); 66 | float phase_inc = 2.0 * M_PI * freq / sample_rate; 67 | for (long n = 0; n < nframes; ++n) { 68 | audiobuffer[n * num_channels + c] = 69 | ConvertSample(sin(phase[c]) * VOLUME); 70 | phase[c] += phase_inc; 71 | } 72 | } 73 | } 74 | 75 | private: 76 | int num_channels; 77 | float phase[MAX_NUM_CHANNELS]; 78 | float sample_rate; 79 | }; 80 | 81 | template 82 | long 83 | data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, 84 | void * outputbuffer, long nframes) 85 | { 86 | synth_state * synth = (synth_state *)user; 87 | synth->run((T *)outputbuffer, nframes); 88 | return nframes; 89 | } 90 | 91 | void 92 | state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, 93 | cubeb_state /*state*/) 94 | { 95 | } 96 | 97 | /* Our android backends don't support float, only int16. */ 98 | int 99 | supports_float32(string backend_id) 100 | { 101 | return backend_id != "opensl" && backend_id != "audiotrack"; 102 | } 103 | 104 | /* Some backends don't have code to deal with more than mono or stereo. */ 105 | int 106 | supports_channel_count(string backend_id, int nchannels) 107 | { 108 | return nchannels <= 2 || 109 | (backend_id != "opensl" && backend_id != "audiotrack"); 110 | } 111 | 112 | int 113 | run_test(int num_channels, int sampling_rate, int is_float) 114 | { 115 | int r = CUBEB_OK; 116 | 117 | cubeb * ctx = NULL; 118 | 119 | r = common_init(&ctx, "Cubeb audio test: channels"); 120 | if (r != CUBEB_OK) { 121 | fprintf(stderr, "Error initializing cubeb library\n"); 122 | return r; 123 | } 124 | std::unique_ptr cleanup_cubeb_at_exit( 125 | ctx, cubeb_destroy); 126 | 127 | const char * backend_id = cubeb_get_backend_id(ctx); 128 | 129 | if ((is_float && !supports_float32(backend_id)) || 130 | !supports_channel_count(backend_id, num_channels)) { 131 | /* don't treat this as a test failure. */ 132 | return CUBEB_OK; 133 | } 134 | 135 | fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, 136 | sampling_rate, is_float ? "float" : "short", 137 | cubeb_get_backend_id(ctx)); 138 | 139 | cubeb_stream_params params; 140 | params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE; 141 | params.rate = sampling_rate; 142 | params.channels = num_channels; 143 | params.layout = CUBEB_LAYOUT_UNDEFINED; 144 | params.prefs = CUBEB_STREAM_PREF_NONE; 145 | 146 | synth_state synth(params.channels, params.rate); 147 | 148 | cubeb_stream * stream = NULL; 149 | r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms, 150 | 4096, is_float ? &data_cb : &data_cb, 151 | state_cb_audio, &synth); 152 | if (r != CUBEB_OK) { 153 | fprintf(stderr, "Error initializing cubeb stream: %d\n", r); 154 | return r; 155 | } 156 | 157 | std::unique_ptr 158 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 159 | 160 | cubeb_stream_start(stream); 161 | delay(200); 162 | cubeb_stream_stop(stream); 163 | 164 | return r; 165 | } 166 | 167 | int 168 | run_volume_test(int is_float) 169 | { 170 | int r = CUBEB_OK; 171 | 172 | cubeb * ctx = NULL; 173 | 174 | r = common_init(&ctx, "Cubeb audio test"); 175 | if (r != CUBEB_OK) { 176 | fprintf(stderr, "Error initializing cubeb library\n"); 177 | return r; 178 | } 179 | 180 | std::unique_ptr cleanup_cubeb_at_exit( 181 | ctx, cubeb_destroy); 182 | 183 | const char * backend_id = cubeb_get_backend_id(ctx); 184 | 185 | if ((is_float && !supports_float32(backend_id))) { 186 | /* don't treat this as a test failure. */ 187 | return CUBEB_OK; 188 | } 189 | 190 | cubeb_stream_params params; 191 | params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE; 192 | params.rate = 44100; 193 | params.channels = 2; 194 | params.layout = CUBEB_LAYOUT_STEREO; 195 | params.prefs = CUBEB_STREAM_PREF_NONE; 196 | 197 | synth_state synth(params.channels, params.rate); 198 | 199 | cubeb_stream * stream = NULL; 200 | r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms, 201 | 4096, is_float ? &data_cb : &data_cb, 202 | state_cb_audio, &synth); 203 | if (r != CUBEB_OK) { 204 | fprintf(stderr, "Error initializing cubeb stream: %d\n", r); 205 | return r; 206 | } 207 | 208 | std::unique_ptr 209 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 210 | 211 | fprintf(stderr, "Testing: volume\n"); 212 | for (int i = 0; i <= 4; ++i) { 213 | fprintf(stderr, "Volume: %d%%\n", i * 25); 214 | 215 | cubeb_stream_set_volume(stream, i / 4.0f); 216 | cubeb_stream_start(stream); 217 | delay(400); 218 | cubeb_stream_stop(stream); 219 | delay(100); 220 | } 221 | 222 | return r; 223 | } 224 | 225 | TEST(cubeb, run_volume_test_short) { ASSERT_EQ(run_volume_test(0), CUBEB_OK); } 226 | 227 | TEST(cubeb, run_volume_test_float) { ASSERT_EQ(run_volume_test(1), CUBEB_OK); } 228 | 229 | TEST(cubeb, run_channel_rate_test) 230 | { 231 | unsigned int channel_values[] = { 232 | 1, 2, 3, 4, 6, 233 | }; 234 | 235 | int freq_values[] = { 236 | 16000, 237 | 24000, 238 | 44100, 239 | 48000, 240 | }; 241 | 242 | for (auto channels : channel_values) { 243 | for (auto freq : freq_values) { 244 | ASSERT_TRUE(channels < MAX_NUM_CHANNELS); 245 | fprintf(stderr, "--------------------------\n"); 246 | ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK); 247 | ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK); 248 | } 249 | } 250 | } 251 | 252 | #undef MAX_NUM_CHANNELS 253 | #undef VOLUME 254 | -------------------------------------------------------------------------------- /subprojects/speex/arch.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2003 Jean-Marc Valin */ 2 | /** 3 | @file arch.h 4 | @brief Various architecture definitions Speex 5 | */ 6 | /* 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | - Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | - Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | - Neither the name of the Xiph.org Foundation nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef ARCH_H 36 | #define ARCH_H 37 | 38 | /* A couple test to catch stupid option combinations */ 39 | #ifdef FIXED_POINT 40 | 41 | #ifdef FLOATING_POINT 42 | #error You cannot compile as floating point and fixed point at the same time 43 | #endif 44 | #ifdef USE_SSE 45 | #error SSE is only for floating-point 46 | #endif 47 | #if defined(ARM4_ASM) + defined(ARM5E_ASM) + defined(BFIN_ASM) > 1 48 | #error Make up your mind. What CPU do you have? 49 | #endif 50 | #ifdef VORBIS_PSYCHO 51 | #error Vorbis-psy model currently not implemented in fixed-point 52 | #endif 53 | 54 | #else 55 | 56 | #ifndef FLOATING_POINT 57 | #error You now need to define either FIXED_POINT or FLOATING_POINT 58 | #endif 59 | #if defined(ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) 60 | #error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? 61 | #endif 62 | #ifdef FIXED_DEBUG 63 | #error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" 64 | #endif 65 | 66 | 67 | #endif 68 | 69 | #ifndef OUTSIDE_SPEEX 70 | #include "speex/speexdsp_types.h" 71 | #endif 72 | 73 | #define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ 74 | #define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ 75 | #define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */ 76 | #define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ 77 | #define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ 78 | #define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */ 79 | #define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ 80 | 81 | #ifdef FIXED_POINT 82 | 83 | typedef spx_int16_t spx_word16_t; 84 | typedef spx_int32_t spx_word32_t; 85 | typedef spx_word32_t spx_mem_t; 86 | typedef spx_word16_t spx_coef_t; 87 | typedef spx_word16_t spx_lsp_t; 88 | typedef spx_word32_t spx_sig_t; 89 | 90 | #define Q15ONE 32767 91 | 92 | #define LPC_SCALING 8192 93 | #define SIG_SCALING 16384 94 | #define LSP_SCALING 8192. 95 | #define GAMMA_SCALING 32768. 96 | #define GAIN_SCALING 64 97 | #define GAIN_SCALING_1 0.015625 98 | 99 | #define LPC_SHIFT 13 100 | #define LSP_SHIFT 13 101 | #define SIG_SHIFT 14 102 | #define GAIN_SHIFT 6 103 | 104 | #define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x))) 105 | 106 | #define VERY_SMALL 0 107 | #define VERY_LARGE32 ((spx_word32_t)2147483647) 108 | #define VERY_LARGE16 ((spx_word16_t)32767) 109 | #define Q15_ONE ((spx_word16_t)32767) 110 | 111 | 112 | #ifdef FIXED_DEBUG 113 | #include "fixed_debug.h" 114 | #else 115 | 116 | #include "fixed_generic.h" 117 | 118 | #ifdef ARM5E_ASM 119 | #include "fixed_arm5e.h" 120 | #elif defined(ARM4_ASM) 121 | #include "fixed_arm4.h" 122 | #elif defined(BFIN_ASM) 123 | #include "fixed_bfin.h" 124 | #endif 125 | 126 | #endif 127 | 128 | 129 | #else 130 | 131 | typedef float spx_mem_t; 132 | typedef float spx_coef_t; 133 | typedef float spx_lsp_t; 134 | typedef float spx_sig_t; 135 | typedef float spx_word16_t; 136 | typedef float spx_word32_t; 137 | 138 | #define Q15ONE 1.0f 139 | #define LPC_SCALING 1.f 140 | #define SIG_SCALING 1.f 141 | #define LSP_SCALING 1.f 142 | #define GAMMA_SCALING 1.f 143 | #define GAIN_SCALING 1.f 144 | #define GAIN_SCALING_1 1.f 145 | 146 | 147 | #define VERY_SMALL 1e-15f 148 | #define VERY_LARGE32 1e15f 149 | #define VERY_LARGE16 1e15f 150 | #define Q15_ONE ((spx_word16_t)1.f) 151 | 152 | #define QCONST16(x,bits) (x) 153 | #define QCONST32(x,bits) (x) 154 | 155 | #define NEG16(x) (-(x)) 156 | #define NEG32(x) (-(x)) 157 | #define EXTRACT16(x) (x) 158 | #define EXTEND32(x) (x) 159 | #define SHR16(a,shift) (a) 160 | #define SHL16(a,shift) (a) 161 | #define SHR32(a,shift) (a) 162 | #define SHL32(a,shift) (a) 163 | #define PSHR16(a,shift) (a) 164 | #define PSHR32(a,shift) (a) 165 | #define VSHR32(a,shift) (a) 166 | #define SATURATE16(x,a) (x) 167 | #define SATURATE32(x,a) (x) 168 | #define SATURATE32PSHR(x,shift,a) (x) 169 | 170 | #define PSHR(a,shift) (a) 171 | #define SHR(a,shift) (a) 172 | #define SHL(a,shift) (a) 173 | #define SATURATE(x,a) (x) 174 | 175 | #define ADD16(a,b) ((a)+(b)) 176 | #define SUB16(a,b) ((a)-(b)) 177 | #define ADD32(a,b) ((a)+(b)) 178 | #define SUB32(a,b) ((a)-(b)) 179 | #define MULT16_16_16(a,b) ((a)*(b)) 180 | #define MULT16_32_32(a,b) ((a)*(b)) 181 | #define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) 182 | #define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) 183 | 184 | #define MULT16_32_Q15(a,b) ((a)*(b)) 185 | #define MULT16_32_P15(a,b) ((a)*(b)) 186 | 187 | #define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) 188 | 189 | #define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) 190 | #define MAC16_16_Q13(c,a,b) ((c)+(a)*(b)) 191 | #define MAC16_16_P13(c,a,b) ((c)+(a)*(b)) 192 | #define MULT16_16_Q11_32(a,b) ((a)*(b)) 193 | #define MULT16_16_Q13(a,b) ((a)*(b)) 194 | #define MULT16_16_Q14(a,b) ((a)*(b)) 195 | #define MULT16_16_Q15(a,b) ((a)*(b)) 196 | #define MULT16_16_P15(a,b) ((a)*(b)) 197 | #define MULT16_16_P13(a,b) ((a)*(b)) 198 | #define MULT16_16_P14(a,b) ((a)*(b)) 199 | 200 | #define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) 201 | #define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) 202 | #define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) 203 | #define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) 204 | 205 | #define WORD2INT(x) ((x) < -32767.5f ? -32768 : \ 206 | ((x) > 32766.5f ? 32767 : (spx_int16_t)floor(.5 + (x)))) 207 | #endif 208 | 209 | 210 | #if defined(CONFIG_TI_C54X) || defined(CONFIG_TI_C55X) 211 | 212 | /* 2 on TI C5x DSP */ 213 | #define BYTES_PER_CHAR 2 214 | #define BITS_PER_CHAR 16 215 | #define LOG2_BITS_PER_CHAR 4 216 | 217 | #else 218 | 219 | #define BYTES_PER_CHAR 1 220 | #define BITS_PER_CHAR 8 221 | #define LOG2_BITS_PER_CHAR 3 222 | 223 | #endif 224 | 225 | 226 | 227 | #ifdef FIXED_DEBUG 228 | extern long long spx_mips; 229 | #endif 230 | 231 | 232 | #endif 233 | -------------------------------------------------------------------------------- /src/cubeb_log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | #define NOMINMAX 8 | 9 | #include "cubeb_log.h" 10 | #include "cubeb_ringbuffer.h" 11 | #include "cubeb_tracing.h" 12 | #include 13 | #ifdef _WIN32 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | std::atomic g_cubeb_log_level; 20 | std::atomic g_cubeb_log_callback; 21 | 22 | /** The maximum size of a log message, after having been formatted. */ 23 | const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; 24 | /** The maximum number of log messages that can be queued before dropping 25 | * messages. */ 26 | const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40; 27 | /** Number of milliseconds to wait before dequeuing log messages. */ 28 | const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10; 29 | 30 | void 31 | cubeb_noop_log_callback(char const * /* fmt */, ...) 32 | { 33 | } 34 | 35 | /** 36 | * This wraps an inline buffer, that represents a log message, that must be 37 | * null-terminated. 38 | * This class should not use system calls or other potentially blocking code. 39 | */ 40 | class cubeb_log_message { 41 | public: 42 | cubeb_log_message() { *storage = '\0'; } 43 | cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) 44 | { 45 | size_t length = strlen(str); 46 | /* paranoia against malformed message */ 47 | assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE); 48 | if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) { 49 | return; 50 | } 51 | PodCopy(storage, str, length); 52 | storage[length] = '\0'; 53 | } 54 | char const * get() { return storage; } 55 | 56 | private: 57 | char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]{}; 58 | }; 59 | 60 | /** Lock-free asynchronous logger, made so that logging from a 61 | * real-time audio callback does not block the audio thread. */ 62 | class cubeb_async_logger { 63 | public: 64 | /* This is thread-safe since C++11 */ 65 | static cubeb_async_logger & get() 66 | { 67 | static cubeb_async_logger instance; 68 | return instance; 69 | } 70 | void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) 71 | { 72 | cubeb_log_message msg(str); 73 | auto * owned_queue = msg_queue.load(); 74 | // Check if the queue is being deallocated. If not, grab ownership. If yes, 75 | // return, the message won't be logged. 76 | if (!owned_queue || 77 | !msg_queue.compare_exchange_strong(owned_queue, nullptr)) { 78 | return; 79 | } 80 | owned_queue->enqueue(msg); 81 | // Return ownership. 82 | msg_queue.store(owned_queue); 83 | } 84 | void run() 85 | { 86 | assert(logging_thread.get_id() == std::thread::id()); 87 | logging_thread = std::thread([this]() { 88 | CUBEB_REGISTER_THREAD("cubeb_log"); 89 | while (!shutdown_thread) { 90 | cubeb_log_message msg; 91 | while (msg_queue_consumer.load()->dequeue(&msg, 1)) { 92 | cubeb_log_internal_no_format(msg.get()); 93 | } 94 | std::this_thread::sleep_for( 95 | std::chrono::milliseconds(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS)); 96 | } 97 | CUBEB_UNREGISTER_THREAD(); 98 | }); 99 | } 100 | // Tell the underlying queue the producer thread has changed, so it does not 101 | // assert in debug. This should be called with the thread stopped. 102 | void reset_producer_thread() 103 | { 104 | if (msg_queue) { 105 | msg_queue.load()->reset_thread_ids(); 106 | } 107 | } 108 | void start() 109 | { 110 | auto * queue = 111 | new lock_free_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH); 112 | msg_queue.store(queue); 113 | msg_queue_consumer.store(queue); 114 | shutdown_thread = false; 115 | run(); 116 | } 117 | void stop() 118 | { 119 | assert(((g_cubeb_log_callback == cubeb_noop_log_callback) || 120 | !g_cubeb_log_callback) && 121 | "Only call stop after logging has been disabled."); 122 | shutdown_thread = true; 123 | if (logging_thread.get_id() != std::thread::id()) { 124 | logging_thread.join(); 125 | logging_thread = std::thread(); 126 | auto * owned_queue = msg_queue.load(); 127 | // Check if the queue is being used. If not, grab ownership. If yes, 128 | // try again shortly. At this point, the logging thread has been joined, 129 | // so nothing is going to dequeue. 130 | // If there is a valid pointer here, then the real-time audio thread that 131 | // logs won't attempt to write into the queue, and instead drop the 132 | // message. 133 | while (!msg_queue.compare_exchange_weak(owned_queue, nullptr)) { 134 | } 135 | delete owned_queue; 136 | msg_queue_consumer.store(nullptr); 137 | } 138 | } 139 | 140 | private: 141 | cubeb_async_logger() {} 142 | ~cubeb_async_logger() 143 | { 144 | assert(logging_thread.get_id() == std::thread::id() && 145 | (g_cubeb_log_callback == cubeb_noop_log_callback || 146 | !g_cubeb_log_callback)); 147 | if (msg_queue.load()) { 148 | delete msg_queue.load(); 149 | } 150 | } 151 | /** This is quite a big data structure, but is only instantiated if the 152 | * asynchronous logger is used. The two pointers point to the same object, but 153 | * the first one can be temporarily null when a message is being enqueued. */ 154 | std::atomic *> msg_queue = {nullptr}; 155 | 156 | std::atomic *> msg_queue_consumer = { 157 | nullptr}; 158 | std::atomic shutdown_thread = {false}; 159 | std::thread logging_thread; 160 | }; 161 | 162 | void 163 | cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...) 164 | { 165 | va_list args; 166 | va_start(args, fmt); 167 | char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; 168 | vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); 169 | va_end(args); 170 | g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg); 171 | } 172 | 173 | void 174 | cubeb_log_internal_no_format(const char * msg) 175 | { 176 | g_cubeb_log_callback.load()(msg); 177 | } 178 | 179 | void 180 | cubeb_async_log(char const * fmt, ...) 181 | { 182 | // This is going to copy a 256 bytes array around, which is fine. 183 | // We don't want to allocate memory here, because this is made to 184 | // be called from a real-time callback. 185 | va_list args; 186 | va_start(args, fmt); 187 | char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; 188 | vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); 189 | cubeb_async_logger::get().push(msg); 190 | va_end(args); 191 | } 192 | 193 | void 194 | cubeb_async_log_reset_threads(void) 195 | { 196 | if (!g_cubeb_log_callback) { 197 | return; 198 | } 199 | cubeb_async_logger::get().reset_producer_thread(); 200 | } 201 | 202 | void 203 | cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback) 204 | { 205 | g_cubeb_log_level = log_level; 206 | // Once a callback has a been set, `g_cubeb_log_callback` is never set back to 207 | // nullptr, to prevent a TOCTOU race between checking the pointer 208 | if (log_callback && log_level != CUBEB_LOG_DISABLED) { 209 | g_cubeb_log_callback = log_callback; 210 | if (log_level == CUBEB_LOG_VERBOSE) { 211 | cubeb_async_logger::get().start(); 212 | } 213 | } else if (!log_callback || CUBEB_LOG_DISABLED) { 214 | g_cubeb_log_callback = cubeb_noop_log_callback; 215 | // This returns once the thread has joined. 216 | // This is safe even if CUBEB_LOG_VERBOSE was not set; the thread will 217 | // simply not be joinable. 218 | cubeb_async_logger::get().stop(); 219 | } else { 220 | assert(false && "Incorrect parameters passed to cubeb_log_set"); 221 | } 222 | } 223 | 224 | cubeb_log_level 225 | cubeb_log_get_level() 226 | { 227 | return g_cubeb_log_level; 228 | } 229 | 230 | cubeb_log_callback 231 | cubeb_log_get_callback() 232 | { 233 | if (g_cubeb_log_callback == cubeb_noop_log_callback) { 234 | return nullptr; 235 | } 236 | return g_cubeb_log_callback; 237 | } 238 | -------------------------------------------------------------------------------- /test/test_callback_ret.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright � 2017 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb api/function test. Test that different return values from user 9 | specified callbacks are handled correctly. */ 10 | #include "gtest/gtest.h" 11 | #if !defined(_XOPEN_SOURCE) 12 | #define _XOPEN_SOURCE 600 13 | #endif 14 | #include "cubeb/cubeb.h" 15 | #include 16 | #include 17 | #include 18 | 19 | // #define ENABLE_NORMAL_LOG 20 | // #define ENABLE_VERBOSE_LOG 21 | #include "common.h" 22 | 23 | const uint32_t SAMPLE_FREQUENCY = 48000; 24 | const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE; 25 | 26 | enum test_direction { INPUT_ONLY, OUTPUT_ONLY, DUPLEX }; 27 | 28 | // Structure which is used by data callbacks to track the total callbacks 29 | // executed vs the number of callbacks expected. 30 | struct user_state_callback_ret { 31 | std::atomic cb_count{0}; 32 | std::atomic expected_cb_count{0}; 33 | std::atomic error_state{0}; 34 | }; 35 | 36 | // Data callback that always returns 0 37 | long 38 | data_cb_ret_zero(cubeb_stream * stream, void * user, const void * inputbuffer, 39 | void * outputbuffer, long nframes) 40 | { 41 | user_state_callback_ret * u = (user_state_callback_ret *)user; 42 | // If this is the first time the callback has been called set our expected 43 | // callback count 44 | if (u->cb_count == 0) { 45 | u->expected_cb_count = 1; 46 | } 47 | u->cb_count++; 48 | if (nframes < 1) { 49 | // This shouldn't happen 50 | EXPECT_TRUE(false) << "nframes should not be 0 in data callback!"; 51 | } 52 | return 0; 53 | } 54 | 55 | // Data callback that always returns nframes - 1 56 | long 57 | data_cb_ret_nframes_minus_one(cubeb_stream * stream, void * user, 58 | const void * inputbuffer, void * outputbuffer, 59 | long nframes) 60 | { 61 | user_state_callback_ret * u = (user_state_callback_ret *)user; 62 | // If this is the first time the callback has been called set our expected 63 | // callback count 64 | if (u->cb_count == 0) { 65 | u->expected_cb_count = 1; 66 | } 67 | u->cb_count++; 68 | if (nframes < 1) { 69 | // This shouldn't happen 70 | EXPECT_TRUE(false) << "nframes should not be 0 in data callback!"; 71 | } 72 | if (outputbuffer != NULL) { 73 | // If we have an output buffer insert silence 74 | short * ob = (short *)outputbuffer; 75 | for (long i = 0; i < nframes - 1; i++) { 76 | ob[i] = 0; 77 | } 78 | } 79 | return nframes - 1; 80 | } 81 | 82 | // Data callback that always returns nframes 83 | long 84 | data_cb_ret_nframes(cubeb_stream * stream, void * user, 85 | const void * inputbuffer, void * outputbuffer, long nframes) 86 | { 87 | user_state_callback_ret * u = (user_state_callback_ret *)user; 88 | u->cb_count++; 89 | // Every callback returns nframes, so every callback is expected 90 | u->expected_cb_count++; 91 | if (nframes < 1) { 92 | // This shouldn't happen 93 | EXPECT_TRUE(false) << "nframes should not be 0 in data callback!"; 94 | } 95 | if (outputbuffer != NULL) { 96 | // If we have an output buffer insert silence 97 | short * ob = (short *)outputbuffer; 98 | for (long i = 0; i < nframes; i++) { 99 | ob[i] = 0; 100 | } 101 | } 102 | return nframes; 103 | } 104 | 105 | // Data callback that always returns CUBEB_ERROR 106 | long 107 | data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer, 108 | void * outputbuffer, long nframes) 109 | { 110 | user_state_callback_ret * u = (user_state_callback_ret *)user; 111 | // If this is the first time the callback has been called set our expected 112 | // callback count 113 | if (u->cb_count == 0) { 114 | u->expected_cb_count = 1; 115 | } 116 | u->cb_count++; 117 | if (nframes < 1) { 118 | // This shouldn't happen 119 | EXPECT_TRUE(false) << "nframes should not be 0 in data callback!"; 120 | } 121 | return CUBEB_ERROR; 122 | } 123 | 124 | void 125 | state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state) 126 | { 127 | if (stream == NULL) 128 | return; 129 | user_state_callback_ret * u = (user_state_callback_ret *)user; 130 | 131 | switch (state) { 132 | case CUBEB_STATE_STARTED: 133 | fprintf(stderr, "stream started\n"); 134 | break; 135 | case CUBEB_STATE_STOPPED: 136 | fprintf(stderr, "stream stopped\n"); 137 | break; 138 | case CUBEB_STATE_DRAINED: 139 | fprintf(stderr, "stream drained\n"); 140 | break; 141 | case CUBEB_STATE_ERROR: 142 | fprintf(stderr, "stream error\n"); 143 | u->error_state.fetch_add(1); 144 | break; 145 | default: 146 | fprintf(stderr, "unknown stream state %d\n", state); 147 | } 148 | } 149 | 150 | void 151 | run_test_callback(test_direction direction, cubeb_data_callback data_cb, 152 | const std::string & test_desc) 153 | { 154 | cubeb * ctx; 155 | cubeb_stream * stream; 156 | cubeb_stream_params input_params; 157 | cubeb_stream_params output_params; 158 | int r; 159 | user_state_callback_ret user_state; 160 | uint32_t latency_frames = 0; 161 | 162 | r = common_init(&ctx, "Cubeb callback return value example"); 163 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 164 | 165 | std::unique_ptr cleanup_cubeb_at_exit( 166 | ctx, cubeb_destroy); 167 | 168 | if ((direction == INPUT_ONLY || direction == DUPLEX) && 169 | !can_run_audio_input_test(ctx)) { 170 | /* This test needs an available input device, skip it if this host does not 171 | * have one or if the backend doesn't implement input. */ 172 | return; 173 | } 174 | 175 | // Setup all params, but only pass them later as required by direction 176 | input_params.format = SAMPLE_FORMAT; 177 | input_params.rate = SAMPLE_FREQUENCY; 178 | input_params.channels = 1; 179 | input_params.layout = CUBEB_LAYOUT_MONO; 180 | input_params.prefs = CUBEB_STREAM_PREF_NONE; 181 | output_params = input_params; 182 | 183 | r = cubeb_get_min_latency(ctx, &input_params, &latency_frames); 184 | if (r != CUBEB_OK) { 185 | // not fatal 186 | latency_frames = 1024; 187 | } 188 | 189 | switch (direction) { 190 | case INPUT_ONLY: 191 | r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input", NULL, 192 | &input_params, NULL, NULL, latency_frames, data_cb, 193 | state_cb_ret, &user_state); 194 | break; 195 | case OUTPUT_ONLY: 196 | r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output", NULL, NULL, 197 | NULL, &output_params, latency_frames, data_cb, 198 | state_cb_ret, &user_state); 199 | break; 200 | case DUPLEX: 201 | r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex", NULL, 202 | &input_params, NULL, &output_params, latency_frames, 203 | data_cb, state_cb_ret, &user_state); 204 | break; 205 | default: 206 | ASSERT_TRUE(false) << "Unrecognized test direction!"; 207 | } 208 | EXPECT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 209 | 210 | std::unique_ptr 211 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 212 | 213 | cubeb_stream_start(stream); 214 | delay(100); 215 | cubeb_stream_stop(stream); 216 | 217 | ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) 218 | << "Callback called unexpected number of times for " << test_desc << "!"; 219 | // TODO: On some test configurations, the data_callback is never called. 220 | if (data_cb == data_cb_ret_error && user_state.cb_count != 0) { 221 | ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state"; 222 | } 223 | } 224 | 225 | TEST(cubeb, test_input_callback) 226 | { 227 | run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0"); 228 | run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one, 229 | "input only, return nframes - 1"); 230 | run_test_callback(INPUT_ONLY, data_cb_ret_nframes, 231 | "input only, return nframes"); 232 | run_test_callback(INPUT_ONLY, data_cb_ret_error, 233 | "input only, return CUBEB_ERROR"); 234 | } 235 | 236 | TEST(cubeb, test_output_callback) 237 | { 238 | run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0"); 239 | run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one, 240 | "output only, return nframes - 1"); 241 | run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes, 242 | "output only, return nframes"); 243 | run_test_callback(OUTPUT_ONLY, data_cb_ret_error, 244 | "output only, return CUBEB_ERROR"); 245 | } 246 | 247 | TEST(cubeb, test_duplex_callback) 248 | { 249 | run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0"); 250 | run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one, 251 | "duplex, return nframes - 1"); 252 | run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes"); 253 | run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR"); 254 | } 255 | -------------------------------------------------------------------------------- /test/test_devices.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 Haakon Sporsheim 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb enumerate device test/example. 9 | * Prints out a list of devices enumerated. */ 10 | #include "cubeb/cubeb.h" 11 | #include "gtest/gtest.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // #define ENABLE_NORMAL_LOG 18 | // #define ENABLE_VERBOSE_LOG 19 | #include "common.h" 20 | 21 | static long 22 | data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, 23 | void * outputbuffer, long nframes) 24 | { 25 | // noop, unused 26 | return 0; 27 | } 28 | 29 | static void 30 | state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state) 31 | { 32 | // noop, unused 33 | } 34 | 35 | static void 36 | print_device_info(cubeb_device_info * info, FILE * f) 37 | { 38 | char devfmts[64] = ""; 39 | const char *devtype, *devstate, *devdeffmt; 40 | 41 | switch (info->type) { 42 | case CUBEB_DEVICE_TYPE_INPUT: 43 | devtype = "input"; 44 | break; 45 | case CUBEB_DEVICE_TYPE_OUTPUT: 46 | devtype = "output"; 47 | break; 48 | case CUBEB_DEVICE_TYPE_UNKNOWN: 49 | default: 50 | devtype = "unknown?"; 51 | break; 52 | }; 53 | 54 | switch (info->state) { 55 | case CUBEB_DEVICE_STATE_DISABLED: 56 | devstate = "disabled"; 57 | break; 58 | case CUBEB_DEVICE_STATE_UNPLUGGED: 59 | devstate = "unplugged"; 60 | break; 61 | case CUBEB_DEVICE_STATE_ENABLED: 62 | devstate = "enabled"; 63 | break; 64 | default: 65 | devstate = "unknown?"; 66 | break; 67 | }; 68 | 69 | switch (info->default_format) { 70 | case CUBEB_DEVICE_FMT_S16LE: 71 | devdeffmt = "S16LE"; 72 | break; 73 | case CUBEB_DEVICE_FMT_S16BE: 74 | devdeffmt = "S16BE"; 75 | break; 76 | case CUBEB_DEVICE_FMT_F32LE: 77 | devdeffmt = "F32LE"; 78 | break; 79 | case CUBEB_DEVICE_FMT_F32BE: 80 | devdeffmt = "F32BE"; 81 | break; 82 | default: 83 | devdeffmt = "unknown?"; 84 | break; 85 | }; 86 | 87 | if (info->format & CUBEB_DEVICE_FMT_S16LE) 88 | strcat(devfmts, " S16LE"); 89 | if (info->format & CUBEB_DEVICE_FMT_S16BE) 90 | strcat(devfmts, " S16BE"); 91 | if (info->format & CUBEB_DEVICE_FMT_F32LE) 92 | strcat(devfmts, " F32LE"); 93 | if (info->format & CUBEB_DEVICE_FMT_F32BE) 94 | strcat(devfmts, " F32BE"); 95 | 96 | fprintf(f, 97 | "dev: \"%s\"%s\n" 98 | "\tName: \"%s\"\n" 99 | "\tGroup: \"%s\"\n" 100 | "\tVendor: \"%s\"\n" 101 | "\tType: %s\n" 102 | "\tState: %s\n" 103 | "\tCh: %u\n" 104 | "\tFormat: %s (0x%x) (default: %s)\n" 105 | "\tRate: %u - %u (default: %u)\n" 106 | "\tLatency: lo %u frames, hi %u frames\n", 107 | info->device_id, info->preferred ? " (PREFERRED)" : "", 108 | info->friendly_name, info->group_id, info->vendor_name, devtype, 109 | devstate, info->max_channels, 110 | (devfmts[0] == '\0') ? devfmts : devfmts + 1, 111 | (unsigned int)info->format, devdeffmt, info->min_rate, info->max_rate, 112 | info->default_rate, info->latency_lo, info->latency_hi); 113 | } 114 | 115 | static void 116 | print_device_collection(cubeb_device_collection * collection, FILE * f) 117 | { 118 | uint32_t i; 119 | 120 | for (i = 0; i < collection->count; i++) 121 | print_device_info(&collection->device[i], f); 122 | } 123 | 124 | TEST(cubeb, destroy_default_collection) 125 | { 126 | int r; 127 | cubeb * ctx = NULL; 128 | cubeb_device_collection collection{nullptr, 0}; 129 | 130 | r = common_init(&ctx, "Cubeb audio test"); 131 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 132 | 133 | std::unique_ptr cleanup_cubeb_at_exit( 134 | ctx, cubeb_destroy); 135 | 136 | ASSERT_EQ(collection.device, nullptr); 137 | ASSERT_EQ(collection.count, (size_t)0); 138 | 139 | r = cubeb_device_collection_destroy(ctx, &collection); 140 | if (r != CUBEB_ERROR_NOT_SUPPORTED) { 141 | ASSERT_EQ(r, CUBEB_OK); 142 | ASSERT_EQ(collection.device, nullptr); 143 | ASSERT_EQ(collection.count, (size_t)0); 144 | } 145 | } 146 | 147 | TEST(cubeb, enumerate_devices) 148 | { 149 | int r; 150 | cubeb * ctx = NULL; 151 | cubeb_device_collection collection; 152 | 153 | r = common_init(&ctx, "Cubeb audio test"); 154 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 155 | 156 | std::unique_ptr cleanup_cubeb_at_exit( 157 | ctx, cubeb_destroy); 158 | 159 | fprintf(stdout, "Enumerating input devices for backend %s\n", 160 | cubeb_get_backend_id(ctx)); 161 | 162 | r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection); 163 | if (r == CUBEB_ERROR_NOT_SUPPORTED) { 164 | fprintf(stderr, "Device enumeration not supported" 165 | " for this backend, skipping this test.\n"); 166 | return; 167 | } 168 | ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r; 169 | 170 | fprintf(stdout, "Found %zu input devices\n", collection.count); 171 | print_device_collection(&collection, stdout); 172 | cubeb_device_collection_destroy(ctx, &collection); 173 | 174 | fprintf(stdout, "Enumerating output devices for backend %s\n", 175 | cubeb_get_backend_id(ctx)); 176 | 177 | r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection); 178 | ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r; 179 | 180 | fprintf(stdout, "Found %zu output devices\n", collection.count); 181 | print_device_collection(&collection, stdout); 182 | cubeb_device_collection_destroy(ctx, &collection); 183 | 184 | uint32_t count_before_creating_duplex_stream; 185 | r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection); 186 | ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r; 187 | count_before_creating_duplex_stream = collection.count; 188 | cubeb_device_collection_destroy(ctx, &collection); 189 | 190 | if (!can_run_audio_input_test(ctx)) { 191 | return; 192 | } 193 | cubeb_stream * stream; 194 | cubeb_stream_params input_params; 195 | cubeb_stream_params output_params; 196 | 197 | input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE; 198 | input_params.rate = output_params.rate = 48000; 199 | input_params.channels = output_params.channels = 1; 200 | input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO; 201 | input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE; 202 | 203 | r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 204 | &output_params, 1024, data_cb_duplex, state_cb_duplex, 205 | nullptr); 206 | 207 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 208 | 209 | r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection); 210 | ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r; 211 | ASSERT_EQ(count_before_creating_duplex_stream, collection.count); 212 | cubeb_device_collection_destroy(ctx, &collection); 213 | 214 | cubeb_stream_destroy(stream); 215 | } 216 | 217 | TEST(cubeb, stream_get_current_device) 218 | { 219 | cubeb * ctx = NULL; 220 | int r = common_init(&ctx, "Cubeb audio test"); 221 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 222 | 223 | std::unique_ptr cleanup_cubeb_at_exit( 224 | ctx, cubeb_destroy); 225 | 226 | fprintf(stdout, "Getting current devices for backend %s\n", 227 | cubeb_get_backend_id(ctx)); 228 | 229 | if (!can_run_audio_input_test(ctx)) { 230 | return; 231 | } 232 | 233 | cubeb_stream * stream = NULL; 234 | cubeb_stream_params input_params; 235 | cubeb_stream_params output_params; 236 | 237 | input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE; 238 | input_params.rate = output_params.rate = 48000; 239 | input_params.channels = output_params.channels = 1; 240 | input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO; 241 | input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE; 242 | 243 | r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 244 | &output_params, 1024, data_cb_duplex, state_cb_duplex, 245 | nullptr); 246 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 247 | std::unique_ptr 248 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 249 | 250 | cubeb_device * device; 251 | r = cubeb_stream_get_current_device(stream, &device); 252 | if (r == CUBEB_ERROR_NOT_SUPPORTED) { 253 | fprintf(stderr, "Getting current device is not supported" 254 | " for this backend, skipping this test.\n"); 255 | return; 256 | } 257 | ASSERT_EQ(r, CUBEB_OK) << "Error getting current devices"; 258 | 259 | fprintf(stdout, "Current output device: %s\n", device->output_name); 260 | fprintf(stdout, "Current input device: %s\n", device->input_name); 261 | 262 | r = cubeb_stream_device_destroy(stream, device); 263 | ASSERT_EQ(r, CUBEB_OK) << "Error destroying current devices"; 264 | } 265 | -------------------------------------------------------------------------------- /src/cubeb_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | #if !defined(CUBEB_UTILS) 9 | #define CUBEB_UTILS 10 | 11 | #include "cubeb/cubeb.h" 12 | 13 | #ifdef __cplusplus 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #if defined(_WIN32) 21 | #include "cubeb_utils_win.h" 22 | #else 23 | #include "cubeb_utils_unix.h" 24 | #endif 25 | 26 | /** Similar to memcpy, but accounts for the size of an element. */ 27 | template 28 | void 29 | PodCopy(T * destination, const T * source, size_t count) 30 | { 31 | static_assert(std::is_trivial::value, "Requires trivial type"); 32 | assert(destination && source); 33 | memcpy(destination, source, count * sizeof(T)); 34 | } 35 | 36 | /** Similar to memmove, but accounts for the size of an element. */ 37 | template 38 | void 39 | PodMove(T * destination, const T * source, size_t count) 40 | { 41 | static_assert(std::is_trivial::value, "Requires trivial type"); 42 | assert(destination && source); 43 | memmove(destination, source, count * sizeof(T)); 44 | } 45 | 46 | /** Similar to a memset to zero, but accounts for the size of an element. */ 47 | template 48 | void 49 | PodZero(T * destination, size_t count) 50 | { 51 | static_assert(std::is_trivial::value, "Requires trivial type"); 52 | assert(destination); 53 | memset(destination, 0, count * sizeof(T)); 54 | } 55 | 56 | namespace { 57 | template 58 | void 59 | Copy(T * destination, const T * source, size_t count, Trait) 60 | { 61 | for (size_t i = 0; i < count; i++) { 62 | destination[i] = source[i]; 63 | } 64 | } 65 | 66 | template 67 | void 68 | Copy(T * destination, const T * source, size_t count, std::true_type) 69 | { 70 | PodCopy(destination, source, count); 71 | } 72 | } // namespace 73 | 74 | /** 75 | * This allows copying a number of elements from a `source` pointer to a 76 | * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that 77 | * calls the constructors and destructors otherwise. 78 | */ 79 | template 80 | void 81 | Copy(T * destination, const T * source, size_t count) 82 | { 83 | assert(destination && source); 84 | Copy(destination, source, count, typename std::is_trivial::type()); 85 | } 86 | 87 | namespace { 88 | template 89 | void 90 | ConstructDefault(T * destination, size_t count, Trait) 91 | { 92 | for (size_t i = 0; i < count; i++) { 93 | destination[i] = T(); 94 | } 95 | } 96 | 97 | template 98 | void 99 | ConstructDefault(T * destination, size_t count, std::true_type) 100 | { 101 | PodZero(destination, count); 102 | } 103 | } // namespace 104 | 105 | /** 106 | * This allows zeroing (using memset) or default-constructing a number of 107 | * elements calling the constructors and destructors if necessary. 108 | */ 109 | template 110 | void 111 | ConstructDefault(T * destination, size_t count) 112 | { 113 | assert(destination); 114 | ConstructDefault(destination, count, typename std::is_arithmetic::type()); 115 | } 116 | 117 | template class auto_array { 118 | public: 119 | explicit auto_array(uint32_t capacity = 0) 120 | : data_(capacity ? new T[capacity] : nullptr), capacity_(capacity), 121 | length_(0) 122 | { 123 | } 124 | 125 | ~auto_array() { delete[] data_; } 126 | 127 | /** Get a constant pointer to the underlying data. */ 128 | T * data() const { return data_; } 129 | 130 | T * end() const { return data_ + length_; } 131 | 132 | const T & at(size_t index) const 133 | { 134 | assert(index < length_ && "out of range"); 135 | return data_[index]; 136 | } 137 | 138 | T & at(size_t index) 139 | { 140 | assert(index < length_ && "out of range"); 141 | return data_[index]; 142 | } 143 | 144 | /** Get how much underlying storage this auto_array has. */ 145 | size_t capacity() const { return capacity_; } 146 | 147 | /** Get how much elements this auto_array contains. */ 148 | size_t length() const { return length_; } 149 | 150 | /** Keeps the storage, but removes all the elements from the array. */ 151 | void clear() { length_ = 0; } 152 | 153 | /** Change the storage of this auto array, copying the elements to the new 154 | * storage. 155 | * @returns true in case of success 156 | * @returns false if the new capacity is not big enough to accomodate for the 157 | * elements in the array. 158 | */ 159 | bool reserve(size_t new_capacity) 160 | { 161 | if (new_capacity < length_) { 162 | return false; 163 | } 164 | T * new_data = new T[new_capacity]; 165 | if (data_ && length_) { 166 | PodCopy(new_data, data_, length_); 167 | } 168 | capacity_ = new_capacity; 169 | delete[] data_; 170 | data_ = new_data; 171 | 172 | return true; 173 | } 174 | 175 | /** Append `length` elements to the end of the array, resizing the array if 176 | * needed. 177 | * @parameter elements the elements to append to the array. 178 | * @parameter length the number of elements to append to the array. 179 | */ 180 | void push(const T * elements, size_t length) 181 | { 182 | if (length_ + length > capacity_) { 183 | reserve(length_ + length); 184 | } 185 | if (data_) { 186 | PodCopy(data_ + length_, elements, length); 187 | } 188 | length_ += length; 189 | } 190 | 191 | /** Append `length` zero-ed elements to the end of the array, resizing the 192 | * array if needed. 193 | * @parameter length the number of elements to append to the array. 194 | */ 195 | void push_silence(size_t length) 196 | { 197 | if (length_ + length > capacity_) { 198 | reserve(length + length_); 199 | } 200 | if (data_) { 201 | PodZero(data_ + length_, length); 202 | } 203 | length_ += length; 204 | } 205 | 206 | /** Prepend `length` zero-ed elements to the front of the array, resizing and 207 | * shifting the array if needed. 208 | * @parameter length the number of elements to prepend to the array. 209 | */ 210 | void push_front_silence(size_t length) 211 | { 212 | if (length_ + length > capacity_) { 213 | reserve(length + length_); 214 | } 215 | if (data_) { 216 | PodMove(data_ + length, data_, length_); 217 | PodZero(data_, length); 218 | } 219 | length_ += length; 220 | } 221 | 222 | /** Return the number of free elements in the array. */ 223 | size_t available() const { return capacity_ - length_; } 224 | 225 | /** Copies `length` elements to `elements` if it is not null, and shift 226 | * the remaining elements of the `auto_array` to the beginning. 227 | * @parameter elements a buffer to copy the elements to, or nullptr. 228 | * @parameter length the number of elements to copy. 229 | * @returns true in case of success. 230 | * @returns false if the auto_array contains less than `length` elements. */ 231 | bool pop(T * elements, size_t length) 232 | { 233 | if (length > length_) { 234 | return false; 235 | } 236 | if (!data_) { 237 | return true; 238 | } 239 | if (elements) { 240 | PodCopy(elements, data_, length); 241 | } 242 | PodMove(data_, data_ + length, length_ - length); 243 | 244 | length_ -= length; 245 | 246 | return true; 247 | } 248 | 249 | void set_length(size_t length) 250 | { 251 | assert(length <= capacity_); 252 | length_ = length; 253 | } 254 | 255 | private: 256 | /** The underlying storage */ 257 | T * data_; 258 | /** The size, in number of elements, of the storage. */ 259 | size_t capacity_; 260 | /** The number of elements the array contains. */ 261 | size_t length_; 262 | }; 263 | 264 | struct auto_array_wrapper { 265 | virtual void push(void * elements, size_t length) = 0; 266 | virtual size_t length() = 0; 267 | virtual void push_silence(size_t length) = 0; 268 | virtual bool pop(size_t length) = 0; 269 | virtual void * data() = 0; 270 | virtual void * end() = 0; 271 | virtual void clear() = 0; 272 | virtual bool reserve(size_t capacity) = 0; 273 | virtual void set_length(size_t length) = 0; 274 | virtual ~auto_array_wrapper() {} 275 | }; 276 | 277 | template 278 | struct auto_array_wrapper_impl : public auto_array_wrapper { 279 | auto_array_wrapper_impl() {} 280 | 281 | explicit auto_array_wrapper_impl(uint32_t size) : ar(size) {} 282 | 283 | void push(void * elements, size_t length) override 284 | { 285 | ar.push(static_cast(elements), length); 286 | } 287 | 288 | size_t length() override { return ar.length(); } 289 | 290 | void push_silence(size_t length) override { ar.push_silence(length); } 291 | 292 | bool pop(size_t length) override { return ar.pop(nullptr, length); } 293 | 294 | void * data() override { return ar.data(); } 295 | 296 | void * end() override { return ar.end(); } 297 | 298 | void clear() override { ar.clear(); } 299 | 300 | bool reserve(size_t capacity) override { return ar.reserve(capacity); } 301 | 302 | void set_length(size_t length) override { ar.set_length(length); } 303 | 304 | ~auto_array_wrapper_impl() { ar.clear(); } 305 | 306 | private: 307 | auto_array ar; 308 | }; 309 | 310 | extern "C" { 311 | size_t 312 | cubeb_sample_size(cubeb_sample_format format); 313 | } 314 | 315 | using auto_lock = std::lock_guard; 316 | #endif // __cplusplus 317 | 318 | #endif /* CUBEB_UTILS */ 319 | -------------------------------------------------------------------------------- /src/cubeb_kai.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "cubeb-internal.h" 15 | #include "cubeb/cubeb.h" 16 | 17 | /* We don't support more than 2 channels in KAI */ 18 | #define MAX_CHANNELS 2 19 | 20 | #define NBUFS 2 21 | #define FRAME_SIZE 2048 22 | 23 | struct cubeb_stream_item { 24 | cubeb_stream * stream; 25 | }; 26 | 27 | static struct cubeb_ops const kai_ops; 28 | 29 | struct cubeb { 30 | struct cubeb_ops const * ops; 31 | }; 32 | 33 | struct cubeb_stream { 34 | /* Note: Must match cubeb_stream layout in cubeb.c. */ 35 | cubeb * context; 36 | void * user_ptr; 37 | /**/ 38 | cubeb_stream_params params; 39 | cubeb_data_callback data_callback; 40 | cubeb_state_callback state_callback; 41 | 42 | HKAI hkai; 43 | KAISPEC spec; 44 | uint64_t total_frames; 45 | float soft_volume; 46 | _fmutex mutex; 47 | float float_buffer[FRAME_SIZE * MAX_CHANNELS]; 48 | }; 49 | 50 | static inline long 51 | frames_to_bytes(long frames, cubeb_stream_params params) 52 | { 53 | return frames * 2 * params.channels; /* 2 bytes per frame */ 54 | } 55 | 56 | static inline long 57 | bytes_to_frames(long bytes, cubeb_stream_params params) 58 | { 59 | return bytes / 2 / params.channels; /* 2 bytes per frame */ 60 | } 61 | 62 | static void 63 | kai_destroy(cubeb * ctx); 64 | 65 | /*static*/ int 66 | kai_init(cubeb ** context, char const * context_name) 67 | { 68 | cubeb * ctx; 69 | 70 | XASSERT(context); 71 | *context = NULL; 72 | 73 | if (kaiInit(KAIM_AUTO)) 74 | return CUBEB_ERROR; 75 | 76 | ctx = calloc(1, sizeof(*ctx)); 77 | XASSERT(ctx); 78 | 79 | ctx->ops = &kai_ops; 80 | 81 | *context = ctx; 82 | 83 | return CUBEB_OK; 84 | } 85 | 86 | static char const * 87 | kai_get_backend_id(cubeb * ctx) 88 | { 89 | return "kai"; 90 | } 91 | 92 | static void 93 | kai_destroy(cubeb * ctx) 94 | { 95 | kaiDone(); 96 | 97 | free(ctx); 98 | } 99 | 100 | static void 101 | float_to_s16ne(int16_t * dst, float * src, size_t n) 102 | { 103 | long l; 104 | 105 | while (n--) { 106 | l = lrintf(*src++ * 0x8000); 107 | if (l > 32767) 108 | l = 32767; 109 | if (l < -32768) 110 | l = -32768; 111 | *dst++ = (int16_t)l; 112 | } 113 | } 114 | 115 | static ULONG APIENTRY 116 | kai_callback(PVOID cbdata, PVOID buffer, ULONG len) 117 | { 118 | cubeb_stream * stm = cbdata; 119 | void * p; 120 | long wanted_frames; 121 | long frames; 122 | float soft_volume; 123 | int elements = len / sizeof(int16_t); 124 | 125 | p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE ? stm->float_buffer : buffer; 126 | 127 | wanted_frames = bytes_to_frames(len, stm->params); 128 | frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames); 129 | 130 | _fmutex_request(&stm->mutex, 0); 131 | stm->total_frames += frames; 132 | soft_volume = stm->soft_volume; 133 | _fmutex_release(&stm->mutex); 134 | 135 | if (frames < wanted_frames) 136 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 137 | 138 | if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) 139 | float_to_s16ne(buffer, p, elements); 140 | 141 | if (soft_volume != -1.0f) { 142 | int16_t * b = buffer; 143 | int i; 144 | 145 | for (i = 0; i < elements; i++) 146 | *b++ *= soft_volume; 147 | } 148 | 149 | return frames_to_bytes(frames, stm->params); 150 | } 151 | 152 | static void 153 | kai_stream_destroy(cubeb_stream * stm); 154 | 155 | static int 156 | kai_stream_init(cubeb * context, cubeb_stream ** stream, 157 | char const * stream_name, cubeb_devid input_device, 158 | cubeb_stream_params * input_stream_params, 159 | cubeb_devid output_device, 160 | cubeb_stream_params * output_stream_params, 161 | unsigned int latency, cubeb_data_callback data_callback, 162 | cubeb_state_callback state_callback, void * user_ptr) 163 | { 164 | cubeb_stream * stm; 165 | KAISPEC wanted_spec; 166 | 167 | XASSERT(!input_stream_params && "not supported."); 168 | if (input_device || output_device) { 169 | /* Device selection not yet implemented. */ 170 | return CUBEB_ERROR_DEVICE_UNAVAILABLE; 171 | } 172 | 173 | if (!output_stream_params) 174 | return CUBEB_ERROR_INVALID_PARAMETER; 175 | 176 | // Loopback is unsupported 177 | if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { 178 | return CUBEB_ERROR_NOT_SUPPORTED; 179 | } 180 | 181 | if (output_stream_params->channels < 1 || 182 | output_stream_params->channels > MAX_CHANNELS) 183 | return CUBEB_ERROR_INVALID_FORMAT; 184 | 185 | XASSERT(context); 186 | XASSERT(stream); 187 | 188 | *stream = NULL; 189 | 190 | stm = calloc(1, sizeof(*stm)); 191 | XASSERT(stm); 192 | 193 | stm->context = context; 194 | stm->params = *output_stream_params; 195 | stm->data_callback = data_callback; 196 | stm->state_callback = state_callback; 197 | stm->user_ptr = user_ptr; 198 | stm->soft_volume = -1.0f; 199 | 200 | if (_fmutex_create(&stm->mutex, 0)) { 201 | free(stm); 202 | return CUBEB_ERROR; 203 | } 204 | 205 | wanted_spec.usDeviceIndex = 0; 206 | wanted_spec.ulType = KAIT_PLAY; 207 | wanted_spec.ulBitsPerSample = BPS_16; 208 | wanted_spec.ulSamplingRate = stm->params.rate; 209 | wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM; 210 | wanted_spec.ulChannels = stm->params.channels; 211 | wanted_spec.ulNumBuffers = NBUFS; 212 | wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params); 213 | wanted_spec.fShareable = TRUE; 214 | wanted_spec.pfnCallBack = kai_callback; 215 | wanted_spec.pCallBackData = stm; 216 | 217 | if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) { 218 | _fmutex_close(&stm->mutex); 219 | free(stm); 220 | return CUBEB_ERROR; 221 | } 222 | 223 | *stream = stm; 224 | 225 | return CUBEB_OK; 226 | } 227 | 228 | static void 229 | kai_stream_destroy(cubeb_stream * stm) 230 | { 231 | kaiClose(stm->hkai); 232 | _fmutex_close(&stm->mutex); 233 | free(stm); 234 | } 235 | 236 | static int 237 | kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 238 | { 239 | XASSERT(ctx && max_channels); 240 | 241 | *max_channels = MAX_CHANNELS; 242 | 243 | return CUBEB_OK; 244 | } 245 | 246 | static int 247 | kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency) 248 | { 249 | /* We have at least two buffers. One is being played, the other one is being 250 | filled. So there is as much latency as one buffer. */ 251 | *latency = FRAME_SIZE; 252 | 253 | return CUBEB_OK; 254 | } 255 | 256 | static int 257 | kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 258 | { 259 | cubeb_stream_params params; 260 | KAISPEC wanted_spec; 261 | KAISPEC spec; 262 | HKAI hkai; 263 | 264 | params.format = CUBEB_SAMPLE_S16NE; 265 | params.rate = 48000; 266 | params.channels = 2; 267 | 268 | wanted_spec.usDeviceIndex = 0; 269 | wanted_spec.ulType = KAIT_PLAY; 270 | wanted_spec.ulBitsPerSample = BPS_16; 271 | wanted_spec.ulSamplingRate = params.rate; 272 | wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM; 273 | wanted_spec.ulChannels = params.channels; 274 | wanted_spec.ulNumBuffers = NBUFS; 275 | wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params); 276 | wanted_spec.fShareable = TRUE; 277 | wanted_spec.pfnCallBack = kai_callback; 278 | wanted_spec.pCallBackData = NULL; 279 | 280 | /* Test 48KHz */ 281 | if (kaiOpen(&wanted_spec, &spec, &hkai)) { 282 | /* Not supported. Fall back to 44.1KHz */ 283 | params.rate = 44100; 284 | } else { 285 | /* Supported. Use 48KHz */ 286 | kaiClose(hkai); 287 | } 288 | 289 | *rate = params.rate; 290 | 291 | return CUBEB_OK; 292 | } 293 | 294 | static int 295 | kai_stream_start(cubeb_stream * stm) 296 | { 297 | if (kaiPlay(stm->hkai)) 298 | return CUBEB_ERROR; 299 | 300 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); 301 | 302 | return CUBEB_OK; 303 | } 304 | 305 | static int 306 | kai_stream_stop(cubeb_stream * stm) 307 | { 308 | if (kaiStop(stm->hkai)) 309 | return CUBEB_ERROR; 310 | 311 | stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); 312 | 313 | return CUBEB_OK; 314 | } 315 | 316 | static int 317 | kai_stream_get_position(cubeb_stream * stm, uint64_t * position) 318 | { 319 | _fmutex_request(&stm->mutex, 0); 320 | *position = stm->total_frames; 321 | _fmutex_release(&stm->mutex); 322 | 323 | return CUBEB_OK; 324 | } 325 | 326 | static int 327 | kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 328 | { 329 | /* Out of buffers, one is being played, the others are being filled. 330 | So there is as much latency as total buffers - 1. */ 331 | *latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) * 332 | (stm->spec.ulNumBuffers - 1); 333 | 334 | return CUBEB_OK; 335 | } 336 | 337 | static int 338 | kai_stream_set_volume(cubeb_stream * stm, float volume) 339 | { 340 | _fmutex_request(&stm->mutex, 0); 341 | stm->soft_volume = volume; 342 | _fmutex_release(&stm->mutex); 343 | 344 | return CUBEB_OK; 345 | } 346 | 347 | static struct cubeb_ops const kai_ops = { 348 | /*.init =*/kai_init, 349 | /*.get_backend_id =*/kai_get_backend_id, 350 | /*.get_max_channel_count=*/kai_get_max_channel_count, 351 | /*.get_min_latency=*/kai_get_min_latency, 352 | /*.get_preferred_sample_rate =*/kai_get_preferred_sample_rate, 353 | /*.get_preferred_channel_layout =*/NULL, 354 | /*.get_supported_input_processing_params =*/NULL, 355 | /*.enumerate_devices =*/NULL, 356 | /*.device_collection_destroy =*/NULL, 357 | /*.destroy =*/kai_destroy, 358 | /*.stream_init =*/kai_stream_init, 359 | /*.stream_destroy =*/kai_stream_destroy, 360 | /*.stream_start =*/kai_stream_start, 361 | /*.stream_stop =*/kai_stream_stop, 362 | /*.stream_get_position =*/kai_stream_get_position, 363 | /*.stream_get_latency = */ kai_stream_get_latency, 364 | /*.stream_get_input_latency = */ NULL, 365 | /*.stream_set_volume =*/kai_stream_set_volume, 366 | /*.stream_set_name =*/NULL, 367 | /*.stream_get_current_device =*/NULL, 368 | /*.stream_set_input_mute =*/NULL, 369 | /*.stream_set_input_processing_params =*/NULL, 370 | /*.stream_device_destroy =*/NULL, 371 | /*.stream_register_device_changed_callback=*/NULL, 372 | /*.register_device_collection_changed=*/NULL}; 373 | -------------------------------------------------------------------------------- /subprojects/speex/resample_neon.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2007-2008 Jean-Marc Valin 2 | * Copyright (C) 2008 Thorvald Natvig 3 | * Copyright (C) 2011 Texas Instruments 4 | * author Jyri Sarha 5 | */ 6 | /** 7 | @file resample_neon.h 8 | @brief Resampler functions (NEON version) 9 | */ 10 | /* 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 15 | - Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | 18 | - Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in the 20 | documentation and/or other materials provided with the distribution. 21 | 22 | - Neither the name of the Xiph.org Foundation nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 30 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 31 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 32 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 33 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 34 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 35 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 36 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #include 40 | 41 | #ifdef FIXED_POINT 42 | #if defined(__aarch64__) 43 | static inline int32_t saturate_32bit_to_16bit(int32_t a) { 44 | int32_t ret; 45 | asm ("fmov s0, %w[a]\n" 46 | "sqxtn h0, s0\n" 47 | "sxtl v0.4s, v0.4h\n" 48 | "fmov %w[ret], s0\n" 49 | : [ret] "=r" (ret) 50 | : [a] "r" (a) 51 | : "v0" ); 52 | return ret; 53 | } 54 | #elif defined(__thumb2__) 55 | static inline int32_t saturate_32bit_to_16bit(int32_t a) { 56 | int32_t ret; 57 | asm ("ssat %[ret], #16, %[a]" 58 | : [ret] "=r" (ret) 59 | : [a] "r" (a) 60 | : ); 61 | return ret; 62 | } 63 | #else 64 | static inline int32_t saturate_32bit_to_16bit(int32_t a) { 65 | int32_t ret; 66 | asm ("vmov.s32 d0[0], %[a]\n" 67 | "vqmovn.s32 d0, q0\n" 68 | "vmov.s16 %[ret], d0[0]\n" 69 | : [ret] "=r" (ret) 70 | : [a] "r" (a) 71 | : "q0"); 72 | return ret; 73 | } 74 | #endif 75 | #undef WORD2INT 76 | #define WORD2INT(x) (saturate_32bit_to_16bit(x)) 77 | 78 | #define OVERRIDE_INNER_PRODUCT_SINGLE 79 | /* Only works when len % 4 == 0 and len >= 4 */ 80 | #if defined(__aarch64__) 81 | static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len) 82 | { 83 | int32_t ret; 84 | uint32_t remainder = len % 16; 85 | len = len - remainder; 86 | 87 | asm volatile (" cmp %w[len], #0\n" 88 | " b.ne 1f\n" 89 | " ld1 {v16.4h}, [%[b]], #8\n" 90 | " ld1 {v20.4h}, [%[a]], #8\n" 91 | " subs %w[remainder], %w[remainder], #4\n" 92 | " smull v0.4s, v16.4h, v20.4h\n" 93 | " b.ne 4f\n" 94 | " b 5f\n" 95 | "1:" 96 | " ld1 {v16.4h, v17.4h, v18.4h, v19.4h}, [%[b]], #32\n" 97 | " ld1 {v20.4h, v21.4h, v22.4h, v23.4h}, [%[a]], #32\n" 98 | " subs %w[len], %w[len], #16\n" 99 | " smull v0.4s, v16.4h, v20.4h\n" 100 | " smlal v0.4s, v17.4h, v21.4h\n" 101 | " smlal v0.4s, v18.4h, v22.4h\n" 102 | " smlal v0.4s, v19.4h, v23.4h\n" 103 | " b.eq 3f\n" 104 | "2:" 105 | " ld1 {v16.4h, v17.4h, v18.4h, v19.4h}, [%[b]], #32\n" 106 | " ld1 {v20.4h, v21.4h, v22.4h, v23.4h}, [%[a]], #32\n" 107 | " subs %w[len], %w[len], #16\n" 108 | " smlal v0.4s, v16.4h, v20.4h\n" 109 | " smlal v0.4s, v17.4h, v21.4h\n" 110 | " smlal v0.4s, v18.4h, v22.4h\n" 111 | " smlal v0.4s, v19.4h, v23.4h\n" 112 | " b.ne 2b\n" 113 | "3:" 114 | " cmp %w[remainder], #0\n" 115 | " b.eq 5f\n" 116 | "4:" 117 | " ld1 {v18.4h}, [%[b]], #8\n" 118 | " ld1 {v22.4h}, [%[a]], #8\n" 119 | " subs %w[remainder], %w[remainder], #4\n" 120 | " smlal v0.4s, v18.4h, v22.4h\n" 121 | " b.ne 4b\n" 122 | "5:" 123 | " saddlv d0, v0.4s\n" 124 | " sqxtn s0, d0\n" 125 | " sqrshrn h0, s0, #15\n" 126 | " sxtl v0.4s, v0.4h\n" 127 | " fmov %w[ret], s0\n" 128 | : [ret] "=r" (ret), [a] "+r" (a), [b] "+r" (b), 129 | [len] "+r" (len), [remainder] "+r" (remainder) 130 | : 131 | : "cc", "v0", 132 | "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23"); 133 | return ret; 134 | } 135 | #else 136 | static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len) 137 | { 138 | int32_t ret; 139 | uint32_t remainder = len % 16; 140 | len = len - remainder; 141 | 142 | asm volatile (" cmp %[len], #0\n" 143 | " bne 1f\n" 144 | " vld1.16 {d16}, [%[b]]!\n" 145 | " vld1.16 {d20}, [%[a]]!\n" 146 | " subs %[remainder], %[remainder], #4\n" 147 | " vmull.s16 q0, d16, d20\n" 148 | " beq 5f\n" 149 | " b 4f\n" 150 | "1:" 151 | " vld1.16 {d16, d17, d18, d19}, [%[b]]!\n" 152 | " vld1.16 {d20, d21, d22, d23}, [%[a]]!\n" 153 | " subs %[len], %[len], #16\n" 154 | " vmull.s16 q0, d16, d20\n" 155 | " vmlal.s16 q0, d17, d21\n" 156 | " vmlal.s16 q0, d18, d22\n" 157 | " vmlal.s16 q0, d19, d23\n" 158 | " beq 3f\n" 159 | "2:" 160 | " vld1.16 {d16, d17, d18, d19}, [%[b]]!\n" 161 | " vld1.16 {d20, d21, d22, d23}, [%[a]]!\n" 162 | " subs %[len], %[len], #16\n" 163 | " vmlal.s16 q0, d16, d20\n" 164 | " vmlal.s16 q0, d17, d21\n" 165 | " vmlal.s16 q0, d18, d22\n" 166 | " vmlal.s16 q0, d19, d23\n" 167 | " bne 2b\n" 168 | "3:" 169 | " cmp %[remainder], #0\n" 170 | " beq 5f\n" 171 | "4:" 172 | " vld1.16 {d16}, [%[b]]!\n" 173 | " vld1.16 {d20}, [%[a]]!\n" 174 | " subs %[remainder], %[remainder], #4\n" 175 | " vmlal.s16 q0, d16, d20\n" 176 | " bne 4b\n" 177 | "5:" 178 | " vaddl.s32 q0, d0, d1\n" 179 | " vadd.s64 d0, d0, d1\n" 180 | " vqmovn.s64 d0, q0\n" 181 | " vqrshrn.s32 d0, q0, #15\n" 182 | " vmov.s16 %[ret], d0[0]\n" 183 | : [ret] "=r" (ret), [a] "+r" (a), [b] "+r" (b), 184 | [len] "+r" (len), [remainder] "+r" (remainder) 185 | : 186 | : "cc", "q0", 187 | "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23"); 188 | 189 | return ret; 190 | } 191 | #endif // !defined(__aarch64__) 192 | 193 | #elif defined(FLOATING_POINT) 194 | #if defined(__aarch64__) 195 | static inline int32_t saturate_float_to_16bit(float a) { 196 | int32_t ret; 197 | asm ("fcvtas s1, %s[a]\n" 198 | "sqxtn h1, s1\n" 199 | "sxtl v1.4s, v1.4h\n" 200 | "fmov %w[ret], s1\n" 201 | : [ret] "=r" (ret) 202 | : [a] "w" (a) 203 | : "v1"); 204 | return ret; 205 | } 206 | #else 207 | static inline int32_t saturate_float_to_16bit(float a) { 208 | int32_t ret; 209 | asm ("vmov.f32 d0[0], %[a]\n" 210 | "vcvt.s32.f32 d0, d0, #15\n" 211 | "vqrshrn.s32 d0, q0, #15\n" 212 | "vmov.s16 %[ret], d0[0]\n" 213 | : [ret] "=r" (ret) 214 | : [a] "r" (a) 215 | : "q0"); 216 | return ret; 217 | } 218 | #endif 219 | 220 | #undef WORD2INT 221 | #define WORD2INT(x) (saturate_float_to_16bit(x)) 222 | 223 | #define OVERRIDE_INNER_PRODUCT_SINGLE 224 | /* Only works when len % 4 == 0 and len >= 4 */ 225 | #if defined(__aarch64__) 226 | static inline float inner_product_single(const float *a, const float *b, unsigned int len) 227 | { 228 | float ret; 229 | uint32_t remainder = len % 16; 230 | len = len - remainder; 231 | 232 | asm volatile (" cmp %w[len], #0\n" 233 | " b.ne 1f\n" 234 | " ld1 {v16.4s}, [%[b]], #16\n" 235 | " ld1 {v20.4s}, [%[a]], #16\n" 236 | " subs %w[remainder], %w[remainder], #4\n" 237 | " fmul v1.4s, v16.4s, v20.4s\n" 238 | " b.ne 4f\n" 239 | " b 5f\n" 240 | "1:" 241 | " ld1 {v16.4s, v17.4s, v18.4s, v19.4s}, [%[b]], #64\n" 242 | " ld1 {v20.4s, v21.4s, v22.4s, v23.4s}, [%[a]], #64\n" 243 | " subs %w[len], %w[len], #16\n" 244 | " fmul v1.4s, v16.4s, v20.4s\n" 245 | " fmul v2.4s, v17.4s, v21.4s\n" 246 | " fmul v3.4s, v18.4s, v22.4s\n" 247 | " fmul v4.4s, v19.4s, v23.4s\n" 248 | " b.eq 3f\n" 249 | "2:" 250 | " ld1 {v16.4s, v17.4s, v18.4s, v19.4s}, [%[b]], #64\n" 251 | " ld1 {v20.4s, v21.4s, v22.4s, v23.4s}, [%[a]], #64\n" 252 | " subs %w[len], %w[len], #16\n" 253 | " fmla v1.4s, v16.4s, v20.4s\n" 254 | " fmla v2.4s, v17.4s, v21.4s\n" 255 | " fmla v3.4s, v18.4s, v22.4s\n" 256 | " fmla v4.4s, v19.4s, v23.4s\n" 257 | " b.ne 2b\n" 258 | "3:" 259 | " fadd v16.4s, v1.4s, v2.4s\n" 260 | " fadd v17.4s, v3.4s, v4.4s\n" 261 | " cmp %w[remainder], #0\n" 262 | " fadd v1.4s, v16.4s, v17.4s\n" 263 | " b.eq 5f\n" 264 | "4:" 265 | " ld1 {v18.4s}, [%[b]], #16\n" 266 | " ld1 {v22.4s}, [%[a]], #16\n" 267 | " subs %w[remainder], %w[remainder], #4\n" 268 | " fmla v1.4s, v18.4s, v22.4s\n" 269 | " b.ne 4b\n" 270 | "5:" 271 | " faddp v1.4s, v1.4s, v1.4s\n" 272 | " faddp %[ret].4s, v1.4s, v1.4s\n" 273 | : [ret] "=w" (ret), [a] "+r" (a), [b] "+r" (b), 274 | [len] "+r" (len), [remainder] "+r" (remainder) 275 | : 276 | : "cc", "v1", "v2", "v3", "v4", 277 | "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23"); 278 | return ret; 279 | } 280 | #else 281 | static inline float inner_product_single(const float *a, const float *b, unsigned int len) 282 | { 283 | float ret; 284 | uint32_t remainder = len % 16; 285 | len = len - remainder; 286 | 287 | asm volatile (" cmp %[len], #0\n" 288 | " bne 1f\n" 289 | " vld1.32 {q4}, [%[b]]!\n" 290 | " vld1.32 {q8}, [%[a]]!\n" 291 | " subs %[remainder], %[remainder], #4\n" 292 | " vmul.f32 q0, q4, q8\n" 293 | " bne 4f\n" 294 | " b 5f\n" 295 | "1:" 296 | " vld1.32 {q4, q5}, [%[b]]!\n" 297 | " vld1.32 {q8, q9}, [%[a]]!\n" 298 | " vld1.32 {q6, q7}, [%[b]]!\n" 299 | " vld1.32 {q10, q11}, [%[a]]!\n" 300 | " subs %[len], %[len], #16\n" 301 | " vmul.f32 q0, q4, q8\n" 302 | " vmul.f32 q1, q5, q9\n" 303 | " vmul.f32 q2, q6, q10\n" 304 | " vmul.f32 q3, q7, q11\n" 305 | " beq 3f\n" 306 | "2:" 307 | " vld1.32 {q4, q5}, [%[b]]!\n" 308 | " vld1.32 {q8, q9}, [%[a]]!\n" 309 | " vld1.32 {q6, q7}, [%[b]]!\n" 310 | " vld1.32 {q10, q11}, [%[a]]!\n" 311 | " subs %[len], %[len], #16\n" 312 | " vmla.f32 q0, q4, q8\n" 313 | " vmla.f32 q1, q5, q9\n" 314 | " vmla.f32 q2, q6, q10\n" 315 | " vmla.f32 q3, q7, q11\n" 316 | " bne 2b\n" 317 | "3:" 318 | " vadd.f32 q4, q0, q1\n" 319 | " vadd.f32 q5, q2, q3\n" 320 | " cmp %[remainder], #0\n" 321 | " vadd.f32 q0, q4, q5\n" 322 | " beq 5f\n" 323 | "4:" 324 | " vld1.32 {q6}, [%[b]]!\n" 325 | " vld1.32 {q10}, [%[a]]!\n" 326 | " subs %[remainder], %[remainder], #4\n" 327 | " vmla.f32 q0, q6, q10\n" 328 | " bne 4b\n" 329 | "5:" 330 | " vadd.f32 d0, d0, d1\n" 331 | " vpadd.f32 d0, d0, d0\n" 332 | " vmov.f32 %[ret], d0[0]\n" 333 | : [ret] "=r" (ret), [a] "+r" (a), [b] "+r" (b), 334 | [len] "+l" (len), [remainder] "+l" (remainder) 335 | : 336 | : "cc", "q0", "q1", "q2", "q3", 337 | "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11"); 338 | return ret; 339 | } 340 | #endif // defined(__aarch64__) 341 | #endif 342 | -------------------------------------------------------------------------------- /test/test_duplex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016 Mozilla Foundation 3 | * 4 | * This program is made available under an ISC-style license. See the 5 | * accompanying file LICENSE for details. 6 | */ 7 | 8 | /* libcubeb api/function test. Loops input back to output and check audio 9 | * is flowing. */ 10 | #include "gtest/gtest.h" 11 | #if !defined(_XOPEN_SOURCE) 12 | #define _XOPEN_SOURCE 600 13 | #endif 14 | #include "cubeb/cubeb.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // #define ENABLE_NORMAL_LOG 22 | // #define ENABLE_VERBOSE_LOG 23 | #include "common.h" 24 | 25 | #define SAMPLE_FREQUENCY 48000 26 | #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE 27 | #define INPUT_CHANNELS 1 28 | #define INPUT_LAYOUT CUBEB_LAYOUT_MONO 29 | #define OUTPUT_CHANNELS 2 30 | #define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO 31 | 32 | struct user_state_duplex { 33 | std::atomic invalid_audio_value{0}; 34 | }; 35 | 36 | long 37 | data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, 38 | void * outputbuffer, long nframes) 39 | { 40 | user_state_duplex * u = reinterpret_cast(user); 41 | float * ib = (float *)inputbuffer; 42 | float * ob = (float *)outputbuffer; 43 | 44 | if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) { 45 | return CUBEB_ERROR; 46 | } 47 | 48 | // Loop back: upmix the single input channel to the two output channels, 49 | // checking if there is noise in the process. 50 | long output_index = 0; 51 | for (long i = 0; i < nframes; i++) { 52 | if (ib[i] <= -1.0 || ib[i] >= 1.0) { 53 | u->invalid_audio_value = 1; 54 | } 55 | ob[output_index] = ob[output_index + 1] = ib[i]; 56 | output_index += 2; 57 | } 58 | 59 | return nframes; 60 | } 61 | 62 | void 63 | state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state) 64 | { 65 | if (stream == NULL) 66 | return; 67 | 68 | switch (state) { 69 | case CUBEB_STATE_STARTED: 70 | fprintf(stderr, "stream started\n"); 71 | break; 72 | case CUBEB_STATE_STOPPED: 73 | fprintf(stderr, "stream stopped\n"); 74 | break; 75 | case CUBEB_STATE_DRAINED: 76 | fprintf(stderr, "stream drained\n"); 77 | break; 78 | default: 79 | fprintf(stderr, "unknown stream state %d\n", state); 80 | } 81 | 82 | return; 83 | } 84 | 85 | TEST(cubeb, duplex) 86 | { 87 | cubeb * ctx; 88 | cubeb_stream * stream; 89 | cubeb_stream_params input_params; 90 | cubeb_stream_params output_params; 91 | int r; 92 | user_state_duplex stream_state; 93 | uint32_t latency_frames = 0; 94 | 95 | r = common_init(&ctx, "Cubeb duplex example"); 96 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 97 | 98 | std::unique_ptr cleanup_cubeb_at_exit( 99 | ctx, cubeb_destroy); 100 | 101 | /* This test needs an available input device, skip it if this host does not 102 | * have one. */ 103 | if (!can_run_audio_input_test(ctx)) { 104 | return; 105 | } 106 | 107 | /* typical user-case: mono input, stereo output, low latency. */ 108 | input_params.format = STREAM_FORMAT; 109 | input_params.rate = SAMPLE_FREQUENCY; 110 | input_params.channels = INPUT_CHANNELS; 111 | input_params.layout = INPUT_LAYOUT; 112 | input_params.prefs = CUBEB_STREAM_PREF_NONE; 113 | output_params.format = STREAM_FORMAT; 114 | output_params.rate = SAMPLE_FREQUENCY; 115 | output_params.channels = OUTPUT_CHANNELS; 116 | output_params.layout = OUTPUT_LAYOUT; 117 | output_params.prefs = CUBEB_STREAM_PREF_NONE; 118 | 119 | r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 120 | ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 121 | 122 | r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 123 | &output_params, latency_frames, data_cb_duplex, 124 | state_cb_duplex, &stream_state); 125 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 126 | 127 | std::unique_ptr 128 | cleanup_stream_at_exit(stream, cubeb_stream_destroy); 129 | 130 | cubeb_stream_start(stream); 131 | delay(500); 132 | cubeb_stream_stop(stream); 133 | 134 | ASSERT_FALSE(stream_state.invalid_audio_value.load()); 135 | } 136 | 137 | void 138 | device_collection_changed_callback(cubeb * context, void * user) 139 | { 140 | fprintf(stderr, "collection changed callback\n"); 141 | ASSERT_TRUE(false) << "Error: device collection changed callback" 142 | " called when opening a stream"; 143 | } 144 | 145 | void 146 | duplex_collection_change_impl(cubeb * ctx) 147 | { 148 | cubeb_stream * stream; 149 | cubeb_stream_params input_params; 150 | cubeb_stream_params output_params; 151 | int r; 152 | uint32_t latency_frames = 0; 153 | 154 | r = cubeb_register_device_collection_changed( 155 | ctx, static_cast(CUBEB_DEVICE_TYPE_INPUT), 156 | device_collection_changed_callback, nullptr); 157 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 158 | 159 | /* typical user-case: mono input, stereo output, low latency. */ 160 | input_params.format = STREAM_FORMAT; 161 | input_params.rate = SAMPLE_FREQUENCY; 162 | input_params.channels = INPUT_CHANNELS; 163 | input_params.layout = INPUT_LAYOUT; 164 | input_params.prefs = CUBEB_STREAM_PREF_NONE; 165 | output_params.format = STREAM_FORMAT; 166 | output_params.rate = SAMPLE_FREQUENCY; 167 | output_params.channels = OUTPUT_CHANNELS; 168 | output_params.layout = OUTPUT_LAYOUT; 169 | output_params.prefs = CUBEB_STREAM_PREF_NONE; 170 | 171 | r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 172 | ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 173 | 174 | r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 175 | &output_params, latency_frames, data_cb_duplex, 176 | state_cb_duplex, nullptr); 177 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 178 | cubeb_stream_destroy(stream); 179 | } 180 | 181 | TEST(cubeb, duplex_collection_change) 182 | { 183 | cubeb * ctx; 184 | int r; 185 | 186 | r = common_init(&ctx, "Cubeb duplex example with collection change"); 187 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 188 | std::unique_ptr cleanup_cubeb_at_exit( 189 | ctx, cubeb_destroy); 190 | 191 | /* This test needs an available input device, skip it if this host does not 192 | * have one. */ 193 | if (!can_run_audio_input_test(ctx)) { 194 | return; 195 | } 196 | 197 | duplex_collection_change_impl(ctx); 198 | r = cubeb_register_device_collection_changed( 199 | ctx, static_cast(CUBEB_DEVICE_TYPE_INPUT), nullptr, 200 | nullptr); 201 | ASSERT_EQ(r, CUBEB_OK); 202 | } 203 | 204 | #ifdef GTEST_HAS_DEATH_TEST 205 | TEST(cubeb, duplex_collection_change_no_unregister) 206 | { 207 | cubeb * ctx; 208 | int r; 209 | 210 | r = common_init(&ctx, "Cubeb duplex example with collection change"); 211 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 212 | 213 | /* This test needs an available input device, skip it if this host does not 214 | * have one. */ 215 | if (!can_run_audio_input_test(ctx)) { 216 | cubeb_destroy(ctx); 217 | return; 218 | } 219 | 220 | std::unique_ptr cleanup_cubeb_at_exit( 221 | ctx, [](cubeb * p) noexcept { EXPECT_DEATH(cubeb_destroy(p), ""); }); 222 | 223 | duplex_collection_change_impl(ctx); 224 | } 225 | #endif 226 | 227 | long 228 | data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, 229 | void * outputbuffer, long nframes) 230 | { 231 | if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) { 232 | return CUBEB_ERROR; 233 | } 234 | 235 | return nframes; 236 | } 237 | 238 | void 239 | state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state) 240 | { 241 | if (stream == NULL) 242 | return; 243 | 244 | switch (state) { 245 | case CUBEB_STATE_STARTED: 246 | fprintf(stderr, "stream started\n"); 247 | break; 248 | case CUBEB_STATE_STOPPED: 249 | fprintf(stderr, "stream stopped\n"); 250 | break; 251 | case CUBEB_STATE_DRAINED: 252 | fprintf(stderr, "stream drained\n"); 253 | break; 254 | case CUBEB_STATE_ERROR: 255 | fprintf(stderr, "stream runs into error state\n"); 256 | break; 257 | default: 258 | fprintf(stderr, "unknown stream state %d\n", state); 259 | } 260 | 261 | return; 262 | } 263 | 264 | std::vector 265 | get_devices(cubeb * ctx, cubeb_device_type type) 266 | { 267 | std::vector devices; 268 | 269 | cubeb_device_collection collection; 270 | int r = cubeb_enumerate_devices(ctx, type, &collection); 271 | 272 | if (r != CUBEB_OK) { 273 | fprintf(stderr, "Failed to enumerate devices\n"); 274 | return devices; 275 | } 276 | 277 | for (uint32_t i = 0; i < collection.count; i++) { 278 | if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { 279 | devices.emplace_back(collection.device[i].devid); 280 | } 281 | } 282 | 283 | cubeb_device_collection_destroy(ctx, &collection); 284 | 285 | return devices; 286 | } 287 | 288 | TEST(cubeb, one_duplex_one_input) 289 | { 290 | cubeb * ctx; 291 | cubeb_stream * duplex_stream; 292 | cubeb_stream_params input_params; 293 | cubeb_stream_params output_params; 294 | int r; 295 | user_state_duplex duplex_stream_state; 296 | uint32_t latency_frames = 0; 297 | 298 | r = common_init(&ctx, "Cubeb duplex example"); 299 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 300 | 301 | std::unique_ptr cleanup_cubeb_at_exit( 302 | ctx, cubeb_destroy); 303 | 304 | /* This test needs at least two available input devices. */ 305 | std::vector input_devices = 306 | get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT); 307 | if (input_devices.size() < 2) { 308 | return; 309 | } 310 | 311 | /* This test needs at least one available output device. */ 312 | std::vector output_devices = 313 | get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT); 314 | if (output_devices.size() < 1) { 315 | return; 316 | } 317 | 318 | cubeb_devid duplex_input = input_devices.front(); 319 | cubeb_devid duplex_output = nullptr; // default device 320 | cubeb_devid input_only = input_devices.back(); 321 | 322 | /* typical use-case: mono voice input, stereo output, low latency. */ 323 | input_params.format = STREAM_FORMAT; 324 | input_params.rate = SAMPLE_FREQUENCY; 325 | input_params.channels = INPUT_CHANNELS; 326 | input_params.layout = CUBEB_LAYOUT_UNDEFINED; 327 | input_params.prefs = CUBEB_STREAM_PREF_VOICE; 328 | 329 | output_params.format = STREAM_FORMAT; 330 | output_params.rate = SAMPLE_FREQUENCY; 331 | output_params.channels = OUTPUT_CHANNELS; 332 | output_params.layout = OUTPUT_LAYOUT; 333 | output_params.prefs = CUBEB_STREAM_PREF_NONE; 334 | 335 | r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 336 | ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 337 | 338 | r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex", duplex_input, 339 | &input_params, duplex_output, &output_params, 340 | latency_frames, data_cb_duplex, state_cb_duplex, 341 | &duplex_stream_state); 342 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream"; 343 | 344 | std::unique_ptr 345 | cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy); 346 | 347 | r = cubeb_stream_start(duplex_stream); 348 | ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream"; 349 | delay(500); 350 | 351 | cubeb_stream * input_stream; 352 | r = cubeb_stream_init(ctx, &input_stream, "Cubeb input", input_only, 353 | &input_params, NULL, NULL, latency_frames, 354 | data_cb_input, state_cb_input, nullptr); 355 | ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream"; 356 | 357 | std::unique_ptr 358 | cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy); 359 | 360 | r = cubeb_stream_start(input_stream); 361 | ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream"; 362 | delay(500); 363 | 364 | r = cubeb_stream_stop(duplex_stream); 365 | ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream"; 366 | 367 | r = cubeb_stream_stop(input_stream); 368 | ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream"; 369 | 370 | ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load()); 371 | } 372 | 373 | #undef SAMPLE_FREQUENCY 374 | #undef STREAM_FORMAT 375 | #undef INPUT_CHANNELS 376 | #undef INPUT_LAYOUT 377 | #undef OUTPUT_CHANNELS 378 | #undef OUTPUT_LAYOUT 379 | --------------------------------------------------------------------------------