From bc11ff05719b74ae95997f3e5b38ea9d258b1ad8 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Wed, 1 Oct 2025 16:19:27 +0100 Subject: [PATCH] 3rdparty: Update cubeb to e495bee --- 3rdparty/cubeb/README.md | 116 ++++- 3rdparty/cubeb/include/cubeb/cubeb.h | 97 ++-- 3rdparty/cubeb/src/cubeb.c | 125 ++++- 3rdparty/cubeb/src/cubeb_audiounit.cpp | 17 +- 3rdparty/cubeb/src/cubeb_log.cpp | 161 +++++- 3rdparty/cubeb/src/cubeb_log.h | 18 +- 3rdparty/cubeb/src/cubeb_resampler.cpp | 6 + 3rdparty/cubeb/src/cubeb_resampler.h | 14 + 3rdparty/cubeb/src/cubeb_resampler_internal.h | 92 ++-- 3rdparty/cubeb/src/cubeb_wasapi.cpp | 490 +++++++----------- 3rdparty/cubeb/subprojects/speex/arch.h | 19 +- .../cubeb/subprojects/speex/fixed_generic.h | 16 +- 3rdparty/cubeb/subprojects/speex/resample.c | 90 ++-- .../cubeb/subprojects/speex/resample_neon.h | 168 +++++- .../cubeb/subprojects/speex/resample_sse.h | 2 +- pcsx2/Host/CubebAudioStream.cpp | 6 +- 16 files changed, 960 insertions(+), 477 deletions(-) diff --git a/3rdparty/cubeb/README.md b/3rdparty/cubeb/README.md index e4e1658824..028c5b63b0 100644 --- a/3rdparty/cubeb/README.md +++ b/3rdparty/cubeb/README.md @@ -1,7 +1,117 @@ +# libcubeb - Cross-platform Audio I/O Library + [![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml) -See INSTALL.md for build instructions. +`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. -See [Backend Support](https://github.com/mozilla/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend. +## Features -Licensed under an ISC-style license. See LICENSE for details. +- **Cross-platform support**: Windows, macOS, Linux, Android, and other platforms +- **Versatile**: Optimized for low-latency real-time audio applications, or power efficient higher latency playback +- **A/V sync**: Latency compensated audio clock reporting for easy audio/video synchronization +- **Full-duplex support**: Simultaneous audio input and output, reclocked +- **Device enumeration**: Query available audio devices +- **Audio processing for speech**: Can use VoiceProcessing IO on recent macOS + +## Supported Backends & status + +| *Backend* | *Support Level* | *Platform version* | *Notes* | +|-------------------|-----------------|--------------------|--------------------------------------------------| +| PulseAudio (Rust) | Tier-1 | | Main Linux desktop backend | +| AudioUnit (Rust) | Tier-1 | | Main macOS backend | +| WASAPI | Tier-1 | Windows >= 7 | Main Windows backend | +| AAudio | Tier-1 | Android >= 8 | Main Android backend for most devices | +| OpenSL | Tier-1 | Android >= 2.3 | Android backend for older devices | +| OSS | Tier-2 | | | +| sndio | Tier-2 | | | +| Sun | Tier-2 | | | +| WinMM | Tier-3 | Windows XP | Was Tier-1, Firefox minimum Windows version 7. | +| AudioTrack | Tier-3 | Android < 2.3 | Was Tier-1, Firefox minimum Android version 4.1. | +| ALSA | Tier-3 | | | +| JACK | Tier-3 | | | +| KAI | Tier-3 | | | +| PulseAudio (C) | Tier-4 | | Was Tier-1, superseded by Rust | +| AudioUnit (C++) | Tier-4 | | Was Tier-1, superseded by Rust | + +Tier-1: Actively maintained. Should have CI coverage. Critical for Firefox. + +Tier-2: Actively maintained by contributors. CI coverage appreciated. + +Tier-3: Maintainers/patches accepted. Status unclear. + +Tier-4: Deprecated, obsolete. Scheduled to be removed. + +Note that the support level is not a judgement of the relative merits +of a backend, only the current state of support, which is informed +by Firefox's needs, the responsiveness of a backend's +maintainer, and the level of contributions to that backend. + +## Building + +### Prerequisites + +- CMake 3.15 or later +- Non-ancient MSVC, clang or gcc, for compiling both C and C++ +- Platform-specific audio libraries (automatically detected) +- Optional but recommended: Rust compiler to compile and link more recent backends for macOS and PulseAudio + +### Quick build + +```bash +git clone https://github.com/mozilla/cubeb.git +cd cubeb +cmake -B build +cmake --build build +``` + +### Better build with Rust backends + +```bash +git clone --recursive https://github.com/mozilla/cubeb.git +cd cubeb +cmake -B build -DBUILD_RUST_LIBS=ON +cmake --build build +``` + +### Platform-Specific Notes + +**Windows**: Supports Visual Studio 2015+ and MinGW-w64. Use `-G "Visual Studio 16 2019"` or `-G "MinGW Makefiles"`. + +**macOS**: Requires Xcode command line tools. Audio frameworks are automatically linked. + +**Linux**: Development packages for desired backends: +```bash +# Ubuntu/Debian +sudo apt-get install libpulse-dev libasound2-dev libjack-dev + +# Fedora/RHEL +sudo dnf install pulseaudio-libs-devel alsa-lib-devel jack-audio-connection-kit-devel +``` + +**Android**: Use with Android NDK. AAudio requires API level 26+. + +## Testing + +Run the test suite: +```bash +cd build +ctest +``` + +Use the interactive test tool: +```bash +./cubeb-test +``` + +## License + +Licensed under an ISC-style license. See [LICENSE](LICENSE) for details. + +## Contributing + +Contributions are welcome! Please see the [contribution guidelines](CONTRIBUTING.md) and check the [issue tracker](https://github.com/mozilla/cubeb/issues). + +## Links + +- [GitHub Repository](https://github.com/mozilla/cubeb) +- [API Documentation](https://mozilla.github.io/cubeb/) diff --git a/3rdparty/cubeb/include/cubeb/cubeb.h b/3rdparty/cubeb/include/cubeb/cubeb.h index 5d30d52f91..2941b4baa8 100644 --- a/3rdparty/cubeb/include/cubeb/cubeb.h +++ b/3rdparty/cubeb/include/cubeb/cubeb.h @@ -49,6 +49,7 @@ extern "C" { output_params.channels = 2; output_params.layout = CUBEB_LAYOUT_UNDEFINED; output_params.prefs = CUBEB_STREAM_PREF_NONE; + output_params.input_params = CUBEB_INPUT_PROCESSING_PARAM_NONE; rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames); if (rv != CUBEB_OK) { @@ -62,6 +63,7 @@ extern "C" { input_params.channels = 1; input_params.layout = CUBEB_LAYOUT_UNDEFINED; input_params.prefs = CUBEB_STREAM_PREF_NONE; + input_params.input_params = CUBEB_INPUT_PROCESSING_PARAM_NONE; cubeb_stream * stm; rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1", @@ -193,39 +195,39 @@ typedef uint32_t cubeb_channel_layout; // Some common layout definitions. enum { CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined. - CUBEB_LAYOUT_MONO = (uint32_t)CHANNEL_FRONT_CENTER, - CUBEB_LAYOUT_MONO_LFE = (uint32_t)CUBEB_LAYOUT_MONO | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_STEREO = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT, - CUBEB_LAYOUT_STEREO_LFE = (uint32_t)CUBEB_LAYOUT_STEREO | (uint32_t)CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER, + CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT, + CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_3F = - (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | (uint32_t)CHANNEL_FRONT_CENTER, - CUBEB_LAYOUT_3F_LFE = (uint32_t)CUBEB_LAYOUT_3F | (uint32_t)CHANNEL_LOW_FREQUENCY, + CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER, + CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_2F1 = - (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | (uint32_t)CHANNEL_BACK_CENTER, - CUBEB_LAYOUT_2F1_LFE = (uint32_t)CUBEB_LAYOUT_2F1 | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F1 = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | - (uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_BACK_CENTER, - CUBEB_LAYOUT_3F1_LFE = (uint32_t)CUBEB_LAYOUT_3F1 | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_2F2 = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | - (uint32_t)CHANNEL_SIDE_LEFT | (uint32_t)CHANNEL_SIDE_RIGHT, - CUBEB_LAYOUT_2F2_LFE = (uint32_t)CUBEB_LAYOUT_2F2 | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_QUAD = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | - (uint32_t)CHANNEL_BACK_LEFT | (uint32_t)CHANNEL_BACK_RIGHT, - CUBEB_LAYOUT_QUAD_LFE = (uint32_t)CUBEB_LAYOUT_QUAD | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F2 = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | - (uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_SIDE_LEFT | - (uint32_t)CHANNEL_SIDE_RIGHT, - CUBEB_LAYOUT_3F2_LFE = (uint32_t)CUBEB_LAYOUT_3F2 | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F2_BACK = (uint32_t)CUBEB_LAYOUT_QUAD | (uint32_t)CHANNEL_FRONT_CENTER, - CUBEB_LAYOUT_3F2_LFE_BACK = (uint32_t)CUBEB_LAYOUT_3F2_BACK | (uint32_t)CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F3R_LFE = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | - (uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_LOW_FREQUENCY | - (uint32_t)CHANNEL_BACK_CENTER | (uint32_t)CHANNEL_SIDE_LEFT | - (uint32_t)CHANNEL_SIDE_RIGHT, - CUBEB_LAYOUT_3F4_LFE = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | - (uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_LOW_FREQUENCY | - (uint32_t)CHANNEL_BACK_LEFT | (uint32_t)CHANNEL_BACK_RIGHT | - (uint32_t)CHANNEL_SIDE_LEFT | (uint32_t)CHANNEL_SIDE_RIGHT, + CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER, + CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER, + CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, + CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT, + CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT, + CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER, + CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | + CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT, + CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | + CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT | + CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, }; /** Miscellaneous stream preferences. */ @@ -279,7 +281,10 @@ typedef struct { cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */ - cubeb_stream_prefs prefs; /**< Requested preferences. */ + cubeb_stream_prefs prefs; /**< Requested preferences. */ + cubeb_input_processing_params input_params; /**< Requested input processing + params. Ignored for output streams. At present, only supported on the + WASAPI backend; others should use cubeb_set_input_processing_params. */ } cubeb_stream_params; /** Audio device description */ @@ -414,6 +419,13 @@ typedef struct { size_t count; /**< Device count in collection. */ } cubeb_device_collection; +/** Array of compiled backends returned by `cubeb_get_backend_names`. */ +typedef struct { + const char * const * + names; /**< Array of strings representing backend names. */ + size_t count; /**< Length of the array. */ +} cubeb_backend_names; + /** User supplied data callback. - Calling other cubeb functions from this callback is unsafe. - The code in the callback should be non-blocking. @@ -454,6 +466,8 @@ typedef void (*cubeb_device_changed_callback)(void * user_ptr); /** * User supplied callback called when the underlying device collection changed. + * This callback will be called when devices are added or removed from the + * system, or when the default device changes for the specified device type. * @param context A pointer to the cubeb context. * @param user_ptr The pointer passed to * cubeb_register_device_collection_changed. */ @@ -485,17 +499,18 @@ CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name, char const * backend_name); -/** Returns a list of backend names which can be supplid to cubeb_init(). - Array is null-terminated. */ -CUBEB_EXPORT const char** -cubeb_get_backend_names(); - /** Get a read-only string identifying this context's current backend. @param context A pointer to the cubeb context. @retval Read-only string identifying current backend. */ CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context); +/** Get a read-only array of strings identifying available backends. + These can be passed as `backend_name` parameter to `cubeb_init`. + @retval Struct containing the array with backend names. */ +CUBEB_EXPORT cubeb_backend_names +cubeb_get_backend_names(); + /** Get the maximum possible number of channels. @param context A pointer to the cubeb context. @param max_channels The maximum number of channels. @@ -674,7 +689,7 @@ cubeb_stream_get_current_device(cubeb_stream * stm, application is accessing audio input. When all inputs are muted they can prove to the user that the application is not actively capturing any input. @param stream the stream for which to set input mute state - @param muted whether the input should mute or not + @param mute whether the input should mute or not @retval CUBEB_OK @retval CUBEB_ERROR_INVALID_PARAMETER if this stream does not have an input device @@ -745,14 +760,16 @@ cubeb_device_collection_destroy(cubeb * context, cubeb_device_collection * collection); /** Registers a callback which is called when the system detects - a new device or a device is removed. + a new device or a device is removed, or when the default device + changes for the specified device type. @param context @param devtype device type to include. Different callbacks and user pointers can be registered for each devtype. The hybrid devtype `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid and will register the provided callback and user pointer in both sides. - @param callback a function called whenever the system device list changes. + @param callback a function called whenever the system device list changes, + including when default devices change. Passing NULL allow to unregister a function. You have to unregister first before you register a new callback. @param user_ptr pointer to user specified data which will be present in diff --git a/3rdparty/cubeb/src/cubeb.c b/3rdparty/cubeb/src/cubeb.c index da52563c3c..0511ad98d3 100644 --- a/3rdparty/cubeb/src/cubeb.c +++ b/3rdparty/cubeb/src/cubeb.c @@ -31,6 +31,10 @@ struct cubeb_stream { int pulse_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_PULSE_RUST) +int +pulse_rust_init(cubeb ** contet, char const * context_name); +#endif #if defined(USE_JACK) int jack_init(cubeb ** context, char const * context_name); @@ -43,6 +47,10 @@ alsa_init(cubeb ** context, char const * context_name); int audiounit_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_AUDIOUNIT_RUST) +int +audiounit_rust_init(cubeb ** contet, char const * context_name); +#endif #if defined(USE_WINMM) int winmm_init(cubeb ** context, char const * context_name); @@ -55,10 +63,30 @@ wasapi_init(cubeb ** context, char const * context_name); int sndio_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_SUN) +int +sun_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_OPENSL) +int +opensl_init(cubeb ** context, char const * context_name); +#endif #if defined(USE_OSS) int oss_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_AAUDIO) +int +aaudio_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_AUDIOTRACK) +int +audiotrack_init(cubeb ** context, char const * context_name); +#endif +#if defined(USE_KAI) +int +kai_init(cubeb ** context, char const * context_name); +#endif static int validate_stream_params(cubeb_stream_params * input_stream_params, @@ -123,6 +151,10 @@ cubeb_init(cubeb ** context, char const * context_name, if (!strcmp(backend_name, "pulse")) { #if defined(USE_PULSE) init_oneshot = pulse_init; +#endif + } else if (!strcmp(backend_name, "pulse-rust")) { +#if defined(USE_PULSE_RUST) + init_oneshot = pulse_rust_init; #endif } else if (!strcmp(backend_name, "jack")) { #if defined(USE_JACK) @@ -135,6 +167,10 @@ cubeb_init(cubeb ** context, char const * context_name, } else if (!strcmp(backend_name, "audiounit")) { #if defined(USE_AUDIOUNIT) init_oneshot = audiounit_init; +#endif + } else if (!strcmp(backend_name, "audiounit-rust")) { +#if defined(USE_AUDIOUNIT_RUST) + init_oneshot = audiounit_rust_init; #endif } else if (!strcmp(backend_name, "wasapi")) { #if defined(USE_WASAPI) @@ -147,10 +183,30 @@ cubeb_init(cubeb ** context, char const * context_name, } else if (!strcmp(backend_name, "sndio")) { #if defined(USE_SNDIO) init_oneshot = sndio_init; +#endif + } else if (!strcmp(backend_name, "sun")) { +#if defined(USE_SUN) + init_oneshot = sun_init; +#endif + } else if (!strcmp(backend_name, "opensl")) { +#if defined(USE_OPENSL) + init_oneshot = opensl_init; #endif } else if (!strcmp(backend_name, "oss")) { #if defined(USE_OSS) init_oneshot = oss_init; +#endif + } else if (!strcmp(backend_name, "aaudio")) { +#if defined(USE_AAUDIO) + init_oneshot = aaudio_init; +#endif + } else if (!strcmp(backend_name, "audiotrack")) { +#if defined(USE_AUDIOTRACK) + init_oneshot = audiotrack_init; +#endif + } else if (!strcmp(backend_name, "kai")) { +#if defined(USE_KAI) + init_oneshot = kai_init; #endif } else { /* Already set */ @@ -163,6 +219,9 @@ cubeb_init(cubeb ** context, char const * context_name, * to override all other choices */ init_oneshot, +#if defined(USE_PULSE_RUST) + pulse_rust_init, +#endif #if defined(USE_PULSE) pulse_init, #endif @@ -178,6 +237,9 @@ cubeb_init(cubeb ** context, char const * context_name, #if defined(USE_OSS) oss_init, #endif +#if defined(USE_AUDIOUNIT_RUST) + audiounit_rust_init, +#endif #if defined(USE_AUDIOUNIT) audiounit_init, #endif @@ -189,6 +251,18 @@ cubeb_init(cubeb ** context, char const * context_name, #endif #if defined(USE_SUN) sun_init, +#endif +#if defined(USE_AAUDIO) + aaudio_init, +#endif +#if defined(USE_OPENSL) + opensl_init, +#endif +#if defined(USE_AUDIOTRACK) + audiotrack_init, +#endif +#if defined(USE_KAI) + kai_init, #endif }; int i; @@ -214,13 +288,26 @@ cubeb_init(cubeb ** context, char const * context_name, return CUBEB_ERROR; } -const char** +char const * +cubeb_get_backend_id(cubeb * context) +{ + if (!context) { + return NULL; + } + + return context->ops->get_backend_id(context); +} + +cubeb_backend_names cubeb_get_backend_names() { - static const char* backend_names[] = { + static const char * const backend_names[] = { #if defined(USE_PULSE) "pulse", #endif +#if defined(USE_PULSE_RUST) + "pulse-rust", +#endif #if defined(USE_JACK) "jack", #endif @@ -230,6 +317,9 @@ cubeb_get_backend_names() #if defined(USE_AUDIOUNIT) "audiounit", #endif +#if defined(USE_AUDIOUNIT_RUST) + "audiounit-rust", +#endif #if defined(USE_WASAPI) "wasapi", #endif @@ -239,23 +329,30 @@ cubeb_get_backend_names() #if defined(USE_SNDIO) "sndio", #endif +#if defined(USE_SUN) + "sun", +#endif +#if defined(USE_OPENSL) + "opensl", +#endif #if defined(USE_OSS) "oss", #endif - NULL, +#if defined(USE_AAUDIO) + "aaudio", +#endif +#if defined(USE_AUDIOTRACK) + "audiotrack", +#endif +#if defined(USE_KAI) + "kai", +#endif }; - return backend_names; -} - -char const * -cubeb_get_backend_id(cubeb * context) -{ - if (!context) { - return NULL; - } - - return context->ops->get_backend_id(context); + return (cubeb_backend_names){ + .names = backend_names, + .count = NELEMS(backend_names), + }; } int diff --git a/3rdparty/cubeb/src/cubeb_audiounit.cpp b/3rdparty/cubeb/src/cubeb_audiounit.cpp index d823e80ff8..e69aac3e66 100644 --- a/3rdparty/cubeb/src/cubeb_audiounit.cpp +++ b/3rdparty/cubeb/src/cubeb_audiounit.cpp @@ -213,12 +213,19 @@ struct cubeb_stream { cubeb_device_changed_callback device_changed_callback = nullptr; owned_critical_section device_changed_callback_lock; /* Stream creation parameters */ - cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, + cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, + 0, + 0, CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; - cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, - CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; + CUBEB_STREAM_PREF_NONE, + CUBEB_INPUT_PROCESSING_PARAM_NONE}; + cubeb_stream_params output_stream_params = { + CUBEB_SAMPLE_FLOAT32NE, + 0, + 0, + CUBEB_LAYOUT_UNDEFINED, + CUBEB_STREAM_PREF_NONE, + CUBEB_INPUT_PROCESSING_PARAM_NONE}; device_info input_device; device_info output_device; /* Format descriptions */ diff --git a/3rdparty/cubeb/src/cubeb_log.cpp b/3rdparty/cubeb/src/cubeb_log.cpp index 9a1c54edfb..f0ee362fd5 100644 --- a/3rdparty/cubeb/src/cubeb_log.cpp +++ b/3rdparty/cubeb/src/cubeb_log.cpp @@ -16,8 +16,8 @@ #include #endif -static std::atomic g_cubeb_log_level; -static std::atomic g_cubeb_log_callback; +std::atomic g_cubeb_log_level; +std::atomic g_cubeb_log_callback; /** The maximum size of a log message, after having been formatted. */ const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; @@ -32,6 +32,133 @@ cubeb_noop_log_callback(char const * /* fmt */, ...) { } +/** + * This wraps an inline buffer, that represents a log message, that must be + * null-terminated. + * This class should not use system calls or other potentially blocking code. + */ +class cubeb_log_message { +public: + cubeb_log_message() { *storage = '\0'; } + cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) + { + size_t length = strlen(str); + /* paranoia against malformed message */ + assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE); + if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) { + return; + } + PodCopy(storage, str, length); + storage[length] = '\0'; + } + char const * get() { return storage; } + +private: + char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]{}; +}; + +/** Lock-free asynchronous logger, made so that logging from a + * real-time audio callback does not block the audio thread. */ +class cubeb_async_logger { +public: + /* This is thread-safe since C++11 */ + static cubeb_async_logger & get() + { + static cubeb_async_logger instance; + return instance; + } + void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) + { + cubeb_log_message msg(str); + auto * owned_queue = msg_queue.load(); + // Check if the queue is being deallocated. If not, grab ownership. If yes, + // return, the message won't be logged. + if (!owned_queue || + !msg_queue.compare_exchange_strong(owned_queue, nullptr)) { + return; + } + owned_queue->enqueue(msg); + // Return ownership. + msg_queue.store(owned_queue); + } + void run() + { + assert(logging_thread.get_id() == std::thread::id()); + logging_thread = std::thread([this]() { + CUBEB_REGISTER_THREAD("cubeb_log"); + while (!shutdown_thread) { + cubeb_log_message msg; + while (msg_queue_consumer.load()->dequeue(&msg, 1)) { + cubeb_log_internal_no_format(msg.get()); + } + std::this_thread::sleep_for( + std::chrono::milliseconds(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS)); + } + CUBEB_UNREGISTER_THREAD(); + }); + } + // Tell the underlying queue the producer thread has changed, so it does not + // assert in debug. This should be called with the thread stopped. + void reset_producer_thread() + { + if (msg_queue) { + msg_queue.load()->reset_thread_ids(); + } + } + void start() + { + auto * queue = + new lock_free_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH); + msg_queue.store(queue); + msg_queue_consumer.store(queue); + shutdown_thread = false; + run(); + } + void stop() + { + assert(((g_cubeb_log_callback == cubeb_noop_log_callback) || + !g_cubeb_log_callback) && + "Only call stop after logging has been disabled."); + shutdown_thread = true; + if (logging_thread.get_id() != std::thread::id()) { + logging_thread.join(); + logging_thread = std::thread(); + auto * owned_queue = msg_queue.load(); + // Check if the queue is being used. If not, grab ownership. If yes, + // try again shortly. At this point, the logging thread has been joined, + // so nothing is going to dequeue. + // If there is a valid pointer here, then the real-time audio thread that + // logs won't attempt to write into the queue, and instead drop the + // message. + while (!msg_queue.compare_exchange_weak(owned_queue, nullptr)) { + } + delete owned_queue; + msg_queue_consumer.store(nullptr); + } + } + +private: + cubeb_async_logger() {} + ~cubeb_async_logger() + { + assert(logging_thread.get_id() == std::thread::id() && + (g_cubeb_log_callback == cubeb_noop_log_callback || + !g_cubeb_log_callback)); + if (msg_queue.load()) { + delete msg_queue.load(); + } + } + /** This is quite a big data structure, but is only instantiated if the + * asynchronous logger is used. The two pointers point to the same object, but + * the first one can be temporarily null when a message is being enqueued. */ + std::atomic *> msg_queue = {nullptr}; + + std::atomic *> msg_queue_consumer = { + nullptr}; + std::atomic shutdown_thread = {false}; + std::thread logging_thread; +}; + void cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...) { @@ -49,6 +176,29 @@ cubeb_log_internal_no_format(const char * msg) g_cubeb_log_callback.load()(msg); } +void +cubeb_async_log(char const * fmt, ...) +{ + // This is going to copy a 256 bytes array around, which is fine. + // We don't want to allocate memory here, because this is made to + // be called from a real-time callback. + va_list args; + va_start(args, fmt); + char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; + vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); + cubeb_async_logger::get().push(msg); + va_end(args); +} + +void +cubeb_async_log_reset_threads(void) +{ + if (!g_cubeb_log_callback) { + return; + } + cubeb_async_logger::get().reset_producer_thread(); +} + void cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback) { @@ -57,8 +207,15 @@ cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback) // nullptr, to prevent a TOCTOU race between checking the pointer if (log_callback && log_level != CUBEB_LOG_DISABLED) { g_cubeb_log_callback = log_callback; + if (log_level == CUBEB_LOG_VERBOSE) { + cubeb_async_logger::get().start(); + } } else if (!log_callback || CUBEB_LOG_DISABLED) { g_cubeb_log_callback = cubeb_noop_log_callback; + // This returns once the thread has joined. + // This is safe even if CUBEB_LOG_VERBOSE was not set; the thread will + // simply not be joinable. + cubeb_async_logger::get().stop(); } else { assert(false && "Incorrect parameters passed to cubeb_log_set"); } diff --git a/3rdparty/cubeb/src/cubeb_log.h b/3rdparty/cubeb/src/cubeb_log.h index d42a0b77d1..ff028fc9ff 100644 --- a/3rdparty/cubeb/src/cubeb_log.h +++ b/3rdparty/cubeb/src/cubeb_log.h @@ -39,7 +39,12 @@ cubeb_log_get_callback(void); void cubeb_log_internal_no_format(const char * msg); void -cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...); +cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...) + PRINTF_FORMAT(3, 4); +void +cubeb_async_log(const char * fmt, ...) PRINTF_FORMAT(1, 2); +void +cubeb_async_log_reset_threads(void); #ifdef __cplusplus } @@ -55,9 +60,16 @@ cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...); } \ } while (0) +#define ALOG_INTERNAL(level, fmt, ...) \ + do { \ + if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \ + cubeb_async_log(fmt, ##__VA_ARGS__); \ + } \ + } while (0) + /* Asynchronous logging macros to log in real-time callbacks. */ /* Should not be used on android due to the use of global/static variables. */ -#define ALOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) -#define ALOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) +#define ALOGV(msg, ...) ALOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) +#define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #endif // CUBEB_LOG diff --git a/3rdparty/cubeb/src/cubeb_resampler.cpp b/3rdparty/cubeb/src/cubeb_resampler.cpp index c31944b826..c09ea4b9cb 100644 --- a/3rdparty/cubeb/src/cubeb_resampler.cpp +++ b/3rdparty/cubeb/src/cubeb_resampler.cpp @@ -371,3 +371,9 @@ cubeb_resampler_latency(cubeb_resampler * resampler) { return resampler->latency(); } + +cubeb_resampler_stats +cubeb_resampler_stats_get(cubeb_resampler * resampler) +{ + return resampler->stats(); +} diff --git a/3rdparty/cubeb/src/cubeb_resampler.h b/3rdparty/cubeb/src/cubeb_resampler.h index 711a3771d4..25781ea9e6 100644 --- a/3rdparty/cubeb/src/cubeb_resampler.h +++ b/3rdparty/cubeb/src/cubeb_resampler.h @@ -84,6 +84,20 @@ cubeb_resampler_destroy(cubeb_resampler * resampler); long cubeb_resampler_latency(cubeb_resampler * resampler); +/** + * Test-only introspection API to ensure that there is no buffering + * buildup when resampling. + */ +typedef struct { + size_t input_input_buffer_size; + size_t input_output_buffer_size; + size_t output_input_buffer_size; + size_t output_output_buffer_size; +} cubeb_resampler_stats; + +cubeb_resampler_stats +cubeb_resampler_stats_get(cubeb_resampler * resampler); + #if defined(__cplusplus) } #endif diff --git a/3rdparty/cubeb/src/cubeb_resampler_internal.h b/3rdparty/cubeb/src/cubeb_resampler_internal.h index 285f24dd0b..08e019c610 100644 --- a/3rdparty/cubeb/src/cubeb_resampler_internal.h +++ b/3rdparty/cubeb/src/cubeb_resampler_internal.h @@ -56,6 +56,7 @@ struct cubeb_resampler { virtual long fill(void * input_buffer, long * input_frames_count, void * output_buffer, long frames_needed) = 0; virtual long latency() = 0; + virtual cubeb_resampler_stats stats() = 0; virtual ~cubeb_resampler() {} }; @@ -86,6 +87,16 @@ public: virtual long latency() { return 0; } + virtual cubeb_resampler_stats stats() + { + cubeb_resampler_stats stats; + stats.input_input_buffer_size = internal_input_buffer.length(); + stats.input_output_buffer_size = 0; + stats.output_input_buffer_size = 0; + stats.output_output_buffer_size = 0; + return stats; + } + void drop_audio_if_needed() { uint32_t to_keep = min_buffered_audio_frame(sample_rate); @@ -122,6 +133,20 @@ public: virtual long fill(void * input_buffer, long * input_frames_count, void * output_buffer, long output_frames_needed); + virtual cubeb_resampler_stats stats() + { + cubeb_resampler_stats stats = {}; + if (input_processor) { + stats.input_input_buffer_size = input_processor->input_buffer_size(); + stats.input_output_buffer_size = input_processor->output_buffer_size(); + } + if (output_processor) { + stats.output_input_buffer_size = output_processor->input_buffer_size(); + stats.output_output_buffer_size = output_processor->output_buffer_size(); + } + return stats; + } + virtual long latency() { if (input_processor && output_processor) { @@ -280,29 +305,28 @@ public: } /** Returns the number of frames to pass in the input of the resampler to have - * exactly `output_frame_count` resampled frames. This can return a number - * slightly bigger than what is strictly necessary, but it guaranteed that the - * number of output frames will be exactly equal. */ + * at least `output_frame_count` resampled frames. */ uint32_t input_needed_for_output(int32_t output_frame_count) const { assert(output_frame_count >= 0); // Check overflow int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); - int32_t resampled_frames_left = - samples_to_frames(resampling_out_buffer.length()); - float input_frames_needed = - (output_frame_count - unresampled_frames_left) * resampling_ratio - - resampled_frames_left; - if (input_frames_needed < 0) { - return 0; - } - return (uint32_t)ceilf(input_frames_needed); + float input_frames_needed_frac = + static_cast(output_frame_count) * resampling_ratio; + // speex_resample()` can be irregular in its consumption of input samples. + // Provide one more frame than the number that would be required with + // regular consumption, to make the speex resampler behave more regularly, + // and so predictably. + auto input_frame_needed = + 1 + static_cast(ceilf(input_frames_needed_frac)); + input_frame_needed -= std::min(unresampled_frames_left, input_frame_needed); + return input_frame_needed; } /** Returns a pointer to the input buffer, that contains empty space for at - * least `frame_count` elements. This is useful so that consumer can directly - * write into the input buffer of the resampler. The pointer returned is - * adjusted so that leftover data are not overwritten. + * least `frame_count` elements. This is useful so that consumer can + * directly write into the input buffer of the resampler. The pointer + * returned is adjusted so that leftover data are not overwritten. */ T * input_buffer(size_t frame_count) { @@ -312,8 +336,8 @@ public: return resampling_in_buffer.data() + leftover_samples; } - /** This method works with `input_buffer`, and allows to inform the processor - how much frames have been written in the provided buffer. */ + /** This method works with `input_buffer`, and allows to inform the + processor how much frames have been written in the provided buffer. */ void written(size_t written_frames) { resampling_in_buffer.set_length(leftover_samples + @@ -331,6 +355,9 @@ public: } } + size_t input_buffer_size() const { return resampling_in_buffer.length(); } + size_t output_buffer_size() const { return resampling_out_buffer.length(); } + private: /** Wrapper for the speex resampling functions to have a typed * interface. */ @@ -359,6 +386,7 @@ private: output_frame_count); assert(rv == RESAMPLER_ERR_SUCCESS); } + /** The state for the speex resampler used internaly. */ SpeexResamplerState * speex_resampler; /** Source rate / target rate. */ @@ -371,8 +399,8 @@ private: auto_array resampling_out_buffer; /** Additional latency inserted into the pipeline for synchronisation. */ uint32_t additional_latency; - /** When `input_buffer` is called, this allows tracking the number of samples - that were in the buffer. */ + /** When `input_buffer` is called, this allows tracking the number of + samples that were in the buffer. */ uint32_t leftover_samples; }; @@ -417,8 +445,8 @@ public: return delay_output_buffer.data(); } /** Get a pointer to the first writable location in the input buffer> - * @parameter frames_needed the number of frames the user needs to write into - * the buffer. + * @parameter frames_needed the number of frames the user needs to write + * into the buffer. * @returns a pointer to a location in the input buffer where #frames_needed * can be writen. */ T * input_buffer(uint32_t frames_needed) @@ -428,8 +456,8 @@ public: frames_to_samples(frames_needed)); return delay_input_buffer.data() + leftover_samples; } - /** This method works with `input_buffer`, and allows to inform the processor - how much frames have been written in the provided buffer. */ + /** This method works with `input_buffer`, and allows to inform the + processor how much frames have been written in the provided buffer. */ void written(size_t frames_written) { delay_input_buffer.set_length(leftover_samples + @@ -450,8 +478,8 @@ public: return to_pop; } - /** Returns the number of frames one needs to input into the delay line to get - * #frames_needed frames back. + /** Returns the number of frames one needs to input into the delay line to + * get #frames_needed frames back. * @parameter frames_needed the number of frames one want to write into the * delay_line * @returns the number of frames one will get. */ @@ -469,19 +497,23 @@ public: void drop_audio_if_needed() { - size_t available = samples_to_frames(delay_input_buffer.length()); + uint32_t available = samples_to_frames(delay_input_buffer.length()); uint32_t to_keep = min_buffered_audio_frame(sample_rate); if (available > to_keep) { ALOGV("Dropping %u frames", available - to_keep); + delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); } } + size_t input_buffer_size() const { return delay_input_buffer.length(); } + size_t output_buffer_size() const { return delay_output_buffer.length(); } + private: /** The length, in frames, of this delay line */ uint32_t length; - /** When `input_buffer` is called, this allows tracking the number of samples - that where in the buffer. */ + /** When `input_buffer` is called, this allows tracking the number of + samples that where in the buffer. */ uint32_t leftover_samples; /** The input buffer, where the delay is applied. */ auto_array delay_input_buffer; @@ -511,8 +543,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream, "need at least one valid parameter pointer."); /* All the streams we have have a sample rate that matches the target - sample rate, use a no-op resampler, that simply forwards the buffers to the - callback. */ + sample rate, use a no-op resampler, that simply forwards the buffers to + the callback. */ if (((input_params && input_params->rate == target_rate) && (output_params && output_params->rate == target_rate)) || (input_params && !output_params && (input_params->rate == target_rate)) || diff --git a/3rdparty/cubeb/src/cubeb_wasapi.cpp b/3rdparty/cubeb/src/cubeb_wasapi.cpp index b192cc48ab..36bad4bfac 100644 --- a/3rdparty/cubeb/src/cubeb_wasapi.cpp +++ b/3rdparty/cubeb/src/cubeb_wasapi.cpp @@ -4,8 +4,12 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0603 +#endif // !_WIN32_WINNT +#ifndef NOMINMAX #define NOMINMAX +#endif // !NOMINMAX #include #include @@ -37,31 +41,6 @@ #include "cubeb_tracing.h" #include "cubeb_utils.h" -// Some people have reported glitches with IAudioClient3 capture streams: -// http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html -// https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 -#define ALLOW_AUDIO_CLIENT_3_FOR_INPUT 0 -// IAudioClient3::GetSharedModeEnginePeriod() seem to return min latencies -// bigger than IAudioClient::GetDevicePeriod(), which is confusing (10ms vs -// 3ms), though the default latency is usually the same and we should use the -// IAudioClient3 function anyway, as it's more correct -#define USE_AUDIO_CLIENT_3_MIN_PERIOD 1 -// If this is true, we allow IAudioClient3 the creation of sessions with a -// latency above the default one (usually 10ms). -// Whether we should default this to true or false depend on many things: -// -Does creating a shared IAudioClient3 session (not locked to a format) -// actually forces all the IAudioClient(1) sessions to have the same latency? -// I could find no proof of that. -// -Does creating a shared IAudioClient3 session with a latency >= the default -// one actually improve the latency (as in how late the audio is) at all? -// -Maybe we could expose this as cubeb stream pref -// (e.g. take priority over other apps)? -#define ALLOW_AUDIO_CLIENT_3_LATENCY_OVER_DEFAULT 1 -// If this is true and the user specified a target latency >= the IAudioClient3 -// max one, then we reject it and fall back to IAudioClient(1). There wouldn't -// be much point in having a low latency if that's not what the user wants. -#define REJECT_AUDIO_CLIENT_3_LATENCY_OVER_MAX 0 - // Windows 10 exposes the IAudioClient3 interface to create low-latency streams. // Copy the interface definition from audioclient.h here to make the code // simpler and so that we can still access IAudioClient3 via COM if cubeb was @@ -229,11 +208,6 @@ struct auto_stream_ref { cubeb_stream * stm; }; -using set_mm_thread_characteristics_function = - decltype(&AvSetMmThreadCharacteristicsW); -using revert_mm_thread_characteristics_function = - decltype(&AvRevertMmThreadCharacteristics); - extern cubeb_ops const wasapi_ops; static com_heap_ptr @@ -304,8 +278,8 @@ wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type, static int wasapi_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection); -static char const * -wstr_to_utf8(wchar_t const * str); +static std::unique_ptr +wstr_to_utf8(LPCWSTR str); static std::unique_ptr utf8_to_wstr(char const * str); @@ -314,6 +288,15 @@ utf8_to_wstr(char const * str); class wasapi_collection_notification_client; class monitor_device_notifications; +typedef enum { + /* Clear options */ + CUBEB_AUDIO_CLIENT2_NONE, + /* Use AUDCLNT_STREAMOPTIONS_RAW */ + CUBEB_AUDIO_CLIENT2_RAW, + /* Use CUBEB_STREAM_PREF_COMMUNICATIONS */ + CUBEB_AUDIO_CLIENT2_VOICE +} AudioClient2Option; + struct cubeb { cubeb_ops const * ops = &wasapi_ops; owned_critical_section lock; @@ -331,13 +314,6 @@ struct cubeb { nullptr; void * output_collection_changed_user_ptr = nullptr; UINT64 performance_counter_frequency; - /* Library dynamically opened to increase the render thread priority, and - the two function pointers we need. */ - HMODULE mmcss_module = nullptr; - set_mm_thread_characteristics_function set_mm_thread_characteristics = - nullptr; - revert_mm_thread_characteristics_function revert_mm_thread_characteristics = - nullptr; }; class wasapi_endpoint_notification_client; @@ -360,20 +336,33 @@ struct cubeb_stream { /* Mixer pameters. We need to convert the input stream to this samplerate/channel layout, as WASAPI does not resample nor upmix itself. */ - cubeb_stream_params input_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, + cubeb_stream_params input_mix_params = {CUBEB_SAMPLE_FLOAT32NE, + 0, + 0, CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; - cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, + CUBEB_STREAM_PREF_NONE, + CUBEB_INPUT_PROCESSING_PARAM_NONE}; + cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE, + 0, + 0, CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; + CUBEB_STREAM_PREF_NONE, + CUBEB_INPUT_PROCESSING_PARAM_NONE}; /* Stream parameters. This is what the client requested, * and what will be presented in the callback. */ - cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, + cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, + 0, + 0, CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; - cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, - CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; + CUBEB_STREAM_PREF_NONE, + CUBEB_INPUT_PROCESSING_PARAM_NONE}; + cubeb_stream_params output_stream_params = { + CUBEB_SAMPLE_FLOAT32NE, + 0, + 0, + CUBEB_LAYOUT_UNDEFINED, + CUBEB_STREAM_PREF_NONE, + CUBEB_INPUT_PROCESSING_PARAM_NONE}; /* A MMDevice role for this stream: either communication or console here. */ ERole role; /* True if this stream will transport voice-data. */ @@ -662,6 +651,10 @@ public: LPCWSTR device_id) { LOG("collection: Audio device default changed, id = %S.", device_id); + + /* Default device changes count as device collection changes */ + monitor_notifications.notify(flow); + return S_OK; } @@ -772,7 +765,7 @@ public: LPCWSTR device_id) { LOG("endpoint: Audio device default changed flow=%d role=%d " - "new_device_id=%ws.", + "new_device_id=%S.", flow, role, device_id); /* we only support a single stream type for now. */ @@ -783,11 +776,13 @@ public: DWORD last_change_ms = timeGetTime() - last_device_change; bool same_device = default_device_id && device_id && wcscmp(default_device_id.get(), device_id) == 0; - LOG("endpoint: Audio device default changed last_change=%u same_device=%d", + LOG("endpoint: Audio device default changed last_change=%lu same_device=%d", last_change_ms, same_device); if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) { if (device_id) { - default_device_id.reset(_wcsdup(device_id)); + wchar_t * new_device_id = new wchar_t[wcslen(device_id) + 1]; + wcscpy(new_device_id, device_id); + default_device_id.reset(new_device_id); } else { default_device_id.reset(); } @@ -863,16 +858,12 @@ intern_device_id(cubeb * ctx, wchar_t const * id) auto_lock lock(ctx->lock); - char const * tmp = wstr_to_utf8(id); + std::unique_ptr tmp = wstr_to_utf8(id); if (!tmp) { return nullptr; } - char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); - - free((void *)tmp); - - return interned; + return cubeb_strings_intern(ctx->device_ids, tmp.get()); } bool @@ -977,7 +968,7 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, cubeb_resampler_fill(stm->resampler.get(), input_buffer, &input_frames_count, dest, output_frames_needed); if (out_frames < 0) { - ALOGV("Callback refill error: %d", out_frames); + ALOGV("Callback refill error: %ld", out_frames); wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return out_frames; } @@ -1263,8 +1254,8 @@ refill_callback_duplex(cubeb_stream * stm) XASSERT(has_input(stm) && has_output(stm)); if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) { - HRESULT rv = get_input_buffer(stm); - if (FAILED(rv)) { + rv = get_input_buffer(stm); + if (!rv) { return rv; } } @@ -1274,7 +1265,6 @@ refill_callback_duplex(cubeb_stream * stm) rv = get_output_buffer(stm, output_buffer, output_frames); if (!rv) { - hr = stm->render_client->ReleaseBuffer(output_frames, 0); return rv; } @@ -1291,9 +1281,11 @@ refill_callback_duplex(cubeb_stream * stm) stm->total_output_frames += output_frames; - ALOGV("in: %zu, out: %zu, missing: %ld, ratio: %f", stm->total_input_frames, - stm->total_output_frames, - static_cast(stm->total_output_frames) - stm->total_input_frames, + ALOGV("in: %llu, out: %llu, missing: %ld, ratio: %f", + (unsigned long long)stm->total_input_frames, + (unsigned long long)stm->total_output_frames, + static_cast(stm->total_output_frames) - + static_cast(stm->total_input_frames), static_cast(stm->total_output_frames) / stm->total_input_frames); long got; @@ -1438,8 +1430,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) /* We could consider using "Pro Audio" here for WebAudio and maybe WebRTC. */ - mmcss_handle = - stm->context->set_mm_thread_characteristics(L"Audio", &mmcss_task_index); + mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); if (!mmcss_handle) { /* This is not fatal, but we might glitch under heavy load. */ LOG("Unable to use mmcss to bump the render thread priority: %lx", @@ -1519,8 +1510,8 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) is_playing = stm->refill_callback(stm); break; case WAIT_OBJECT_0 + 3: { /* input available */ - HRESULT rv = get_input_buffer(stm); - if (FAILED(rv)) { + bool rv = get_input_buffer(stm); + if (!rv) { is_playing = false; continue; } @@ -1532,8 +1523,11 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) break; } default: - LOG("case %lu not handled in render loop.", waitResult); - XASSERT(false); + LOG("render_loop: waitResult=%lu (lastError=%lu) unhandled, exiting", + waitResult, GetLastError()); + is_playing = false; + hr = E_FAIL; + continue; } } @@ -1547,7 +1541,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) } if (mmcss_handle) { - stm->context->revert_mm_thread_characteristics(mmcss_handle); + AvRevertMmThreadCharacteristics(mmcss_handle); } if (FAILED(hr)) { @@ -1560,18 +1554,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) void wasapi_destroy(cubeb * context); -HANDLE WINAPI -set_mm_thread_characteristics_noop(LPCWSTR, LPDWORD mmcss_task_index) -{ - return (HANDLE)1; -} - -BOOL WINAPI -revert_mm_thread_characteristics_noop(HANDLE mmcss_handle) -{ - return true; -} - HRESULT register_notification_client(cubeb_stream * stm) { @@ -1807,31 +1789,6 @@ wasapi_init(cubeb ** context, char const * context_name) ctx->performance_counter_frequency = 0; } - ctx->mmcss_module = LoadLibraryW(L"Avrt.dll"); - - bool success = false; - if (ctx->mmcss_module) { - ctx->set_mm_thread_characteristics = - reinterpret_cast( - GetProcAddress(ctx->mmcss_module, "AvSetMmThreadCharacteristicsW")); - ctx->revert_mm_thread_characteristics = - reinterpret_cast( - GetProcAddress(ctx->mmcss_module, - "AvRevertMmThreadCharacteristics")); - success = ctx->set_mm_thread_characteristics && - ctx->revert_mm_thread_characteristics; - } - if (!success) { - // This is not a fatal error, but we might end up glitching when - // the system is under high load. - LOG("Could not load avrt.dll or fetch AvSetMmThreadCharacteristicsW " - "AvRevertMmThreadCharacteristics: %lx", - GetLastError()); - ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop; - ctx->revert_mm_thread_characteristics = - &revert_mm_thread_characteristics_noop; - } - *context = ctx; return CUBEB_OK; @@ -1839,7 +1796,6 @@ wasapi_init(cubeb ** context, char const * context_name) } namespace { -enum ShutdownPhase { OnStop, OnDestroy }; bool stop_and_join_render_thread(cubeb_stream * stm) @@ -1855,16 +1811,7 @@ stop_and_join_render_thread(cubeb_stream * stm) return false; } - /* Wait five seconds for the rendering thread to return. It's supposed to - * check its event loop very often, five seconds is rather conservative. - * Note: 5*1s loop to work around timer sleep issues on pre-Windows 8. */ - DWORD r; - for (int i = 0; i < 5; ++i) { - r = WaitForSingleObject(stm->thread, 1000); - if (r == WAIT_OBJECT_0) { - break; - } - } + DWORD r = WaitForSingleObject(stm->thread, INFINITE); if (r != WAIT_OBJECT_0) { LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: " "%lx, %lx", @@ -1888,10 +1835,6 @@ wasapi_destroy(cubeb * context) } } - if (context->mmcss_module) { - FreeLibrary(context->mmcss_module); - } - delete context; } @@ -1949,44 +1892,6 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, return CUBEB_ERROR; } -#if USE_AUDIO_CLIENT_3_MIN_PERIOD - // This is unreliable as we can't know the actual mixer format cubeb will - // ask for later on (nor we can branch on ALLOW_AUDIO_CLIENT_3_FOR_INPUT), - // and the min latency can change based on that. - com_ptr client3; - hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, NULL, - client3.receive_vpp()); - if (SUCCEEDED(hr)) { - WAVEFORMATEX * mix_format = nullptr; - hr = client3->GetMixFormat(&mix_format); - - if (SUCCEEDED(hr)) { - uint32_t default_period = 0, fundamental_period = 0, min_period = 0, - max_period = 0; - hr = client3->GetSharedModeEnginePeriod(mix_format, &default_period, - &fundamental_period, &min_period, - &max_period); - - auto sample_rate = mix_format->nSamplesPerSec; - CoTaskMemFree(mix_format); - if (SUCCEEDED(hr)) { - // Print values in the same format as IAudioDevice::GetDevicePeriod() - REFERENCE_TIME min_period_rt(frames_to_hns(sample_rate, min_period)); - REFERENCE_TIME default_period_rt( - frames_to_hns(sample_rate, default_period)); - LOG("default device period: %I64d, minimum device period: %I64d", - default_period_rt, min_period_rt); - - *latency_frames = hns_to_frames(params.rate, min_period_rt); - - LOG("Minimum latency in frames: %u", *latency_frames); - - return CUBEB_OK; - } - } - } -#endif - com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, client.receive_vpp()); @@ -2006,8 +1911,18 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, LOG("default device period: %I64d, minimum device period: %I64d", default_period, minimum_period); - // The minimum_period is only relevant in exclusive streams. + /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency. + Otherwise, according to the docs, the best latency we can achieve is by + synchronizing the stream and the engine. + http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx + */ + + // #ifdef _WIN32_WINNT_WIN10 +#if 0 + *latency_frames = hns_to_frames(params.rate, minimum_period); +#else *latency_frames = hns_to_frames(params.rate, default_period); +#endif LOG("Minimum latency in frames: %u", *latency_frames); @@ -2044,6 +1959,21 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) return CUBEB_OK; } +int +wasapi_get_supported_input_processing_params( + cubeb * ctx, cubeb_input_processing_params * params) +{ + // This is not entirely accurate -- windows doesn't document precisely what + // AudioCategory_Communications does -- but assume that we can set all or none + // of them. + *params = static_cast( + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION | + CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION | + CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL | + CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION); + return CUBEB_OK; +} + static void waveformatex_update_derived_properties(WAVEFORMATEX * format) { @@ -2097,10 +2027,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, if (hr == S_FALSE) { /* Channel layout not supported, but WASAPI gives us a suggestion. Use it, and handle the eventual upmix/downmix ourselves. Ignore the subformat of - the suggestion, since it seems to always be IEEE_FLOAT. - This fallback doesn't update the bit depth, so if a device - only supported bit depths cubeb doesn't support, so IAudioClient3 - streams might fail */ + the suggestion, since it seems to always be IEEE_FLOAT. */ LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); WAVEFORMATEXTENSIBLE * closest_pcm = @@ -2122,7 +2049,8 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, } static int -initialize_iaudioclient2(com_ptr & audio_client) +initialize_iaudioclient2(com_ptr & audio_client, + AudioClient2Option option) { com_ptr audio_client2; audio_client->QueryInterface(audio_client2.receive()); @@ -2131,10 +2059,14 @@ initialize_iaudioclient2(com_ptr & audio_client) "AUDCLNT_STREAMOPTIONS_RAW."); return CUBEB_OK; } - AudioClientProperties properties = {0}; + AudioClientProperties properties = {}; properties.cbSize = sizeof(AudioClientProperties); #ifndef __MINGW32__ - properties.Options |= AUDCLNT_STREAMOPTIONS_RAW; + if (option == CUBEB_AUDIO_CLIENT2_RAW) { + properties.Options |= AUDCLNT_STREAMOPTIONS_RAW; + } else if (option == CUBEB_AUDIO_CLIENT2_VOICE) { + properties.eCategory = AudioCategory_Communications; + } #endif HRESULT hr = audio_client2->SetClientProperties(&properties); if (FAILED(hr)) { @@ -2144,12 +2076,12 @@ initialize_iaudioclient2(com_ptr & audio_client) return CUBEB_OK; } +#if 0 bool initialize_iaudioclient3(com_ptr & audio_client, cubeb_stream * stm, const com_heap_ptr & mix_format, - DWORD flags, EDataFlow direction, - REFERENCE_TIME latency_hns) + DWORD flags, EDataFlow direction) { com_ptr audio_client3; audio_client->QueryInterface(audio_client3.receive()); @@ -2165,22 +2097,24 @@ initialize_iaudioclient3(com_ptr & audio_client, return false; } + // Some people have reported glitches with capture streams: + // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html + if (direction == eCapture) { + LOG("Audio stream is capture, not using IAudioClient3"); + return false; + } + // Possibly initialize a shared-mode stream using IAudioClient3. Initializing // a stream this way lets you request lower latencies, but also locks the // global WASAPI engine at that latency. // - If we request a shared-mode stream, streams created with IAudioClient - // might have their latency adjusted to match. When the shared-mode stream - // is closed, they'll go back to normal. - // - If there's already a shared-mode stream running, if it created with the - // AUDCLNT_STREAMOPTIONS_MATCH_FORMAT option, the audio engine would be - // locked to that format, so we have to match it (a custom one would fail). - // - We don't lock the WASAPI engine to a format, as it's antisocial towards - // other apps, especially if we locked to a latency >= than its default. - // - If the user requested latency is >= the default one, we might still - // accept it (without locking the format) depending on - // ALLOW_AUDIO_CLIENT_3_LATENCY_OVER_DEFAULT, as we might want to prioritize - // to lower our latency over other apps - // (there might still be latency advantages compared to IAudioDevice(1)). + // will + // have their latency adjusted to match. When the shared-mode stream is + // closed, they'll go back to normal. + // - If there's already a shared-mode stream running, then we cannot request + // the engine change to a different latency - we have to match it. + // - It's antisocial to lock the WASAPI engine at its default latency. If we + // would do this, then stop and use IAudioClient instead. HRESULT hr; uint32_t default_period = 0, fundamental_period = 0, min_period = 0, @@ -2192,59 +2126,28 @@ initialize_iaudioclient3(com_ptr & audio_client, LOG("Could not get shared mode engine period: error: %lx", hr); return false; } - uint32_t requested_latency = - hns_to_frames(mix_format->nSamplesPerSec, latency_hns); -#if !ALLOW_AUDIO_CLIENT_3_LATENCY_OVER_DEFAULT + uint32_t requested_latency = stm->latency; if (requested_latency >= default_period) { - LOG("Requested latency %i equal or greater than default latency %i," - " not using IAudioClient3", + LOG("Requested latency %i greater than default latency %i, not using " + "IAudioClient3", requested_latency, default_period); return false; } -#elif REJECT_AUDIO_CLIENT_3_LATENCY_OVER_MAX - if (requested_latency > max_period) { - // Fallback to IAudioClient(1) as it's more accepting of large latencies - LOG("Requested latency %i greater than max latency %i," - " not using IAudioClient3", - requested_latency, max_period); - return false; - } -#endif LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i", default_period, fundamental_period, min_period, max_period); // Snap requested latency to a valid value uint32_t old_requested_latency = requested_latency; - // The period is required to be a multiple of the fundamental period - // (and >= min and <= max, which should still be true) - requested_latency -= requested_latency % fundamental_period; if (requested_latency < min_period) { requested_latency = min_period; } - // Likely unnecessary, but won't hurt - if (requested_latency > max_period) { - requested_latency = max_period; - } + requested_latency -= (requested_latency - min_period) % fundamental_period; if (requested_latency != old_requested_latency) { LOG("Requested latency %i was adjusted to %i", old_requested_latency, requested_latency); } - DWORD new_flags = flags; - // Always add these flags to IAudioClient3, they might help - // if the stream doesn't have the same format as the audio engine. - new_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM; - new_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; - - hr = audio_client3->InitializeSharedAudioStream(new_flags, requested_latency, + hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency, mix_format.get(), NULL); - // This error should be returned first even if - // the period was locked (AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) - if (hr == AUDCLNT_E_INVALID_STREAM_FLAG) { - LOG("Got AUDCLNT_E_INVALID_STREAM_FLAG, removing some flags"); - hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency, - mix_format.get(), NULL); - } - if (SUCCEEDED(hr)) { return true; } else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) { @@ -2256,37 +2159,22 @@ initialize_iaudioclient3(com_ptr & audio_client, } uint32_t current_period = 0; - WAVEFORMATEX * current_format_ptr = nullptr; + WAVEFORMATEX * current_format = nullptr; // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise // GetCurrentSharedModeEnginePeriod will return E_POINTER - hr = audio_client3->GetCurrentSharedModeEnginePeriod(¤t_format_ptr, + hr = audio_client3->GetCurrentSharedModeEnginePeriod(¤t_format, ¤t_period); + CoTaskMemFree(current_format); if (FAILED(hr)) { LOG("Could not get current shared mode engine period: error: %lx", hr); return false; } - com_heap_ptr current_format(current_format_ptr); - if (current_format->nSamplesPerSec != mix_format->nSamplesPerSec) { - // Unless some other external app locked the shared mode engine period - // within our audio initialization, this is unlikely to happen, though we - // can't respect the user selected latency, so we fallback on IAudioClient - LOG("IAudioClient3::GetCurrentSharedModeEnginePeriod() returned a " - "different mixer format (nSamplesPerSec) from " - "IAudioClient::GetMixFormat(); not using IAudioClient3"); - return false; - } -#if REJECT_AUDIO_CLIENT_3_LATENCY_OVER_MAX - // Reject IAudioClient3 if we can't respect the user target latency. - // We don't need to check against default_latency anymore, - // as the current_period is already the best one we could get. - if (old_requested_latency > current_period) { - LOG("Requested latency %i greater than currently locked shared mode " - "latency %i, not using IAudioClient3", - old_requested_latency, current_period); + if (current_period >= default_period) { + LOG("Current shared mode engine period %i too high, not using IAudioClient", + current_period); return false; } -#endif hr = audio_client3->InitializeSharedAudioStream(flags, current_period, mix_format.get(), NULL); @@ -2299,6 +2187,7 @@ initialize_iaudioclient3(com_ptr & audio_client, LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); return false; } +#endif #define DIRECTION_NAME (direction == eCapture ? "capture" : "render") @@ -2322,12 +2211,6 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, return CUBEB_ERROR; } -#if ALLOW_AUDIO_CLIENT_3_FOR_INPUT - constexpr bool allow_audio_client_3 = true; -#else - const bool allow_audio_client_3 = direction == eRender; -#endif - stm->stream_reset_lock.assert_current_thread_owns(); // If user doesn't specify a particular device, we can choose another one when // the given devid is unavailable. @@ -2364,14 +2247,17 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, /* Get a client. We will get all other interfaces we need from * this pointer. */ - if (allow_audio_client_3) { - hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, NULL, - audio_client.receive_vpp()); - } - if (!allow_audio_client_3 || hr == E_NOINTERFACE) { - hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, - audio_client.receive_vpp()); +#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 + hr = device->Activate(__uuidof(IAudioClient3), + CLSCTX_INPROC_SERVER, + NULL, audio_client.receive_vpp()); + if (hr == E_NOINTERFACE) { +#endif + hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, + audio_client.receive_vpp()); +#if 0 } +#endif if (FAILED(hr)) { LOG("Could not activate the device to get an audio" @@ -2494,21 +2380,41 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, } if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) { - if (initialize_iaudioclient2(audio_client) != CUBEB_OK) { + if (initialize_iaudioclient2(audio_client, CUBEB_AUDIO_CLIENT2_RAW) != + CUBEB_OK) { LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError()); // This is not fatal. } + } else if (direction == eCapture && + (stream_params->prefs & CUBEB_STREAM_PREF_VOICE) && + stream_params->input_params != CUBEB_INPUT_PROCESSING_PARAM_NONE) { + if (stream_params->input_params == + (CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION | + CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION | + CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL | + CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION)) { + if (initialize_iaudioclient2(audio_client, CUBEB_AUDIO_CLIENT2_VOICE) != + CUBEB_OK) { + LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError()); + // This is not fatal. + } + } else { + LOG("Invalid combination of input processing params %#x", + stream_params->input_params); + return CUBEB_ERROR; + } } - if (allow_audio_client_3 && - initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction, - latency_hns)) { +#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 + if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { LOG("Initialized with IAudioClient3"); } else { - hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, - 0, mix_format.get(), NULL); +#endif + hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0, + mix_format.get(), NULL); +#if 0 } - +#endif if (FAILED(hr)) { LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); return CUBEB_ERROR; @@ -2970,6 +2876,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, } } + cubeb_async_log_reset_threads(); stm->thread = (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); @@ -3031,7 +2938,7 @@ wasapi_stream_add_ref(cubeb_stream * stm) { XASSERT(stm); LONG result = InterlockedIncrement(&stm->ref_count); - LOGV("Stream ref count incremented = %i (%p)", result, stm); + LOGV("Stream ref count incremented = %ld (%p)", result, stm); return result; } @@ -3041,7 +2948,7 @@ wasapi_stream_release(cubeb_stream * stm) XASSERT(stm); LONG result = InterlockedDecrement(&stm->ref_count); - LOGV("Stream ref count decremented = %i (%p)", result, stm); + LOGV("Stream ref count decremented = %ld (%p)", result, stm); if (result == 0) { LOG("Stream ref count hit zero, destroying (%p)", stm); @@ -3303,7 +3210,7 @@ wasapi_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } -static char const * +static std::unique_ptr wstr_to_utf8(LPCWSTR str) { int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL); @@ -3311,8 +3218,8 @@ wstr_to_utf8(LPCWSTR str) return nullptr; } - char * ret = static_cast(malloc(size)); - ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); + std::unique_ptr ret(new char[size]); + ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret.get(), size, NULL, NULL); return ret; } @@ -3440,7 +3347,7 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, prop_variant namevar; hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) { - ret.friendly_name = wstr_to_utf8(namevar.pwszVal); + ret.friendly_name = wstr_to_utf8(namevar.pwszVal).release(); } if (!ret.friendly_name) { // This is not fatal, but a valid string is expected in all cases. @@ -3461,7 +3368,7 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, prop_variant instancevar; hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) { - ret.group_id = wstr_to_utf8(instancevar.pwszVal); + ret.group_id = wstr_to_utf8(instancevar.pwszVal).release(); } } @@ -3477,7 +3384,8 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION); - } else if (defaults->is_default(flow, eCommunications, device_id.get())) { + } + if (defaults->is_default(flow, eCommunications, device_id.get())) { ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); } @@ -3504,7 +3412,6 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, CUBEB_DEVICE_FMT_S16NE); ret.default_format = CUBEB_DEVICE_FMT_F32NE; prop_variant fmtvar; - WAVEFORMATEX * wfx = NULL; hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { @@ -3514,7 +3421,8 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec; ret.max_channels = pcm->wf.nChannels; } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { - wfx = reinterpret_cast(fmtvar.blob.pBlobData); + WAVEFORMATEX * wfx = + reinterpret_cast(fmtvar.blob.pBlobData); if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || wfx->wFormatTag == WAVE_FORMAT_PCM) { @@ -3524,30 +3432,9 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, } } -#if USE_AUDIO_CLIENT_3_MIN_PERIOD - // Here we assume an IAudioClient3 stream will successfully - // be initialized later (it might fail) -#if ALLOW_AUDIO_CLIENT_3_FOR_INPUT - constexpr bool allow_audio_client_3 = true; -#else - const bool allow_audio_client_3 = flow == eRender; -#endif - com_ptr client3; - uint32_t def, fun, min, max; - if (allow_audio_client_3 && wfx && - SUCCEEDED(dev->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, - NULL, client3.receive_vpp())) && - SUCCEEDED( - client3->GetSharedModeEnginePeriod(wfx, &def, &fun, &min, &max))) { - ret.latency_lo = min; - // This latency might actually be used as "default" and not "max" later on, - // so we return the default (we never really want to use the max anyway) - ret.latency_hi = def; - } else -#endif - if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, client.receive_vpp())) && - SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { + if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, + NULL, client.receive_vpp())) && + SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { ret.latency_lo = hns_to_frames(ret.default_rate, min_period); ret.latency_hi = hns_to_frames(ret.default_rate, def_period); } else { @@ -3638,7 +3525,7 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, { return wasapi_enumerate_devices_internal( context, type, out, - DEVICE_STATE_ACTIVE /*| DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED*/); + DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED); } static int @@ -3656,6 +3543,14 @@ wasapi_device_collection_destroy(cubeb * /*ctx*/, return CUBEB_OK; } +int +wasapi_set_input_processing_params(cubeb_stream * stream, + cubeb_input_processing_params params) +{ + LOG("Cannot set voice processing params after init. Use cubeb_stream_init."); + return CUBEB_ERROR_NOT_SUPPORTED; +} + static int wasapi_register_device_collection_changed( cubeb * context, cubeb_device_type devtype, @@ -3736,7 +3631,8 @@ cubeb_ops const wasapi_ops = { /*.get_max_channel_count =*/wasapi_get_max_channel_count, /*.get_min_latency =*/wasapi_get_min_latency, /*.get_preferred_sample_rate =*/wasapi_get_preferred_sample_rate, - /*.get_supported_input_processing_params =*/NULL, + /*.get_supported_input_processing_params =*/ + wasapi_get_supported_input_processing_params, /*.enumerate_devices =*/wasapi_enumerate_devices, /*.device_collection_destroy =*/wasapi_device_collection_destroy, /*.destroy =*/wasapi_destroy, @@ -3751,7 +3647,7 @@ cubeb_ops const wasapi_ops = { /*.stream_set_name =*/NULL, /*.stream_get_current_device =*/NULL, /*.stream_set_input_mute =*/NULL, - /*.stream_set_input_processing_params =*/NULL, + /*.stream_set_input_processing_params =*/wasapi_set_input_processing_params, /*.stream_device_destroy =*/NULL, /*.stream_register_device_changed_callback =*/NULL, /*.register_device_collection_changed =*/ diff --git a/3rdparty/cubeb/subprojects/speex/arch.h b/3rdparty/cubeb/subprojects/speex/arch.h index 73a45a069f..1cac3d9c89 100644 --- a/3rdparty/cubeb/subprojects/speex/arch.h +++ b/3rdparty/cubeb/subprojects/speex/arch.h @@ -41,10 +41,10 @@ #ifdef FLOATING_POINT #error You cannot compile as floating point and fixed point at the same time #endif -#ifdef _USE_SSE +#ifdef USE_SSE #error SSE is only for floating-point #endif -#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM)) +#if defined(ARM4_ASM) + defined(ARM5E_ASM) + defined(BFIN_ASM) > 1 #error Make up your mind. What CPU do you have? #endif #ifdef VORBIS_PSYCHO @@ -56,10 +56,10 @@ #ifndef FLOATING_POINT #error You now need to define either FIXED_POINT or FLOATING_POINT #endif -#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) +#if defined(ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) #error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? #endif -#ifdef FIXED_POINT_DEBUG +#ifdef FIXED_DEBUG #error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" #endif @@ -117,9 +117,9 @@ typedef spx_word32_t spx_sig_t; #ifdef ARM5E_ASM #include "fixed_arm5e.h" -#elif defined (ARM4_ASM) +#elif defined(ARM4_ASM) #include "fixed_arm4.h" -#elif defined (BFIN_ASM) +#elif defined(BFIN_ASM) #include "fixed_bfin.h" #endif @@ -177,16 +177,13 @@ typedef float spx_word32_t; #define ADD32(a,b) ((a)+(b)) #define SUB32(a,b) ((a)-(b)) #define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_32_32(a,b) ((a)*(b)) #define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) #define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) -#define MULT16_32_Q11(a,b) ((a)*(b)) -#define MULT16_32_Q13(a,b) ((a)*(b)) -#define MULT16_32_Q14(a,b) ((a)*(b)) #define MULT16_32_Q15(a,b) ((a)*(b)) #define MULT16_32_P15(a,b) ((a)*(b)) -#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b)) #define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) #define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) @@ -210,7 +207,7 @@ typedef float spx_word32_t; #endif -#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) +#if defined(CONFIG_TI_C54X) || defined(CONFIG_TI_C55X) /* 2 on TI C5x DSP */ #define BYTES_PER_CHAR 2 diff --git a/3rdparty/cubeb/subprojects/speex/fixed_generic.h b/3rdparty/cubeb/subprojects/speex/fixed_generic.h index 12d27aac55..09366c36ce 100644 --- a/3rdparty/cubeb/subprojects/speex/fixed_generic.h +++ b/3rdparty/cubeb/subprojects/speex/fixed_generic.h @@ -69,22 +69,18 @@ /* result fits in 16 bits */ -#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b)))) +#define MULT16_16_16(a,b) (((spx_word16_t)(a))*((spx_word16_t)(b))) +/* result fits in 32 bits */ +#define MULT16_32_32(a,b) (((spx_word16_t)(a))*((spx_word32_t)(b))) /* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */ #define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b))) #define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) -#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12)) -#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13)) -#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14)) -#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)) -#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))) - -#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) -#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) -#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) +#define MULT16_32_P15(a,b) ADD32(MULT16_32_32(a,SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MULT16_32_Q15(a,b) ADD32(MULT16_32_32(a,SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MAC16_32_Q15(c,a,b) ADD32(c,MULT16_32_Q15(a,b)) #define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) diff --git a/3rdparty/cubeb/subprojects/speex/resample.c b/3rdparty/cubeb/subprojects/speex/resample.c index 10cb06593d..2783f55f0c 100644 --- a/3rdparty/cubeb/subprojects/speex/resample.c +++ b/3rdparty/cubeb/subprojects/speex/resample.c @@ -46,7 +46,7 @@ Smith, Julius O. Digital Audio Resampling Home Page Center for Computer Research in Music and Acoustics (CCRMA), Stanford University, 2007. - Web published at http://ccrma.stanford.edu/~jos/resample/. + Web published at https://ccrma.stanford.edu/~jos/resample/. There is one main difference, though. This resampler uses cubic interpolation instead of linear interpolation in the above paper. This @@ -63,9 +63,12 @@ #ifdef OUTSIDE_SPEEX #include -static void *speex_alloc (int size) {return calloc(size,1);} -static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);} -static void speex_free (void *ptr) {free(ptr);} +static void *speex_alloc(int size) {return calloc(size,1);} +static void *speex_realloc(void *ptr, int size) {return realloc(ptr, size);} +static void speex_free(void *ptr) {free(ptr);} +#ifndef EXPORT +#define EXPORT +#endif #include "speex_resampler.h" #include "arch.h" #else /* OUTSIDE_SPEEX */ @@ -75,7 +78,6 @@ static void speex_free (void *ptr) {free(ptr);} #include "os_support.h" #endif /* OUTSIDE_SPEEX */ -#include "stack_alloc.h" #include #include @@ -91,18 +93,18 @@ static void speex_free (void *ptr) {free(ptr);} #endif #ifndef UINT32_MAX -#define UINT32_MAX 4294967296U +#define UINT32_MAX 4294967295U #endif -#ifdef _USE_SSE +#ifdef USE_SSE #include "resample_sse.h" #endif -#ifdef _USE_NEON +#ifdef USE_NEON #include "resample_neon.h" #endif -/* Numer of elements to allocate on the stack */ +/* Number of elements to allocate on the stack */ #ifdef VAR_ARRAYS #define FIXED_STACK_ALLOC 8192 #else @@ -194,16 +196,14 @@ struct FuncDef { int oversample; }; -static const struct FuncDef _KAISER12 = {kaiser12_table, 64}; -#define KAISER12 (&_KAISER12) -/*static struct FuncDef _KAISER12 = {kaiser12_table, 32}; -#define KAISER12 (&_KAISER12)*/ -static const struct FuncDef _KAISER10 = {kaiser10_table, 32}; -#define KAISER10 (&_KAISER10) -static const struct FuncDef _KAISER8 = {kaiser8_table, 32}; -#define KAISER8 (&_KAISER8) -static const struct FuncDef _KAISER6 = {kaiser6_table, 32}; -#define KAISER6 (&_KAISER6) +static const struct FuncDef kaiser12_funcdef = {kaiser12_table, 64}; +#define KAISER12 (&kaiser12_funcdef) +static const struct FuncDef kaiser10_funcdef = {kaiser10_table, 32}; +#define KAISER10 (&kaiser10_funcdef) +static const struct FuncDef kaiser8_funcdef = {kaiser8_table, 32}; +#define KAISER8 (&kaiser8_funcdef) +static const struct FuncDef kaiser6_funcdef = {kaiser6_table, 32}; +#define KAISER6 (&kaiser6_funcdef) struct QualityMapping { int base_length; @@ -473,7 +473,7 @@ static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint3 } cubic_coef(frac, interp); - sum = MULT16_32_Q15(interp[0],SHR32(accum[0], 1)) + MULT16_32_Q15(interp[1],SHR32(accum[1], 1)) + MULT16_32_Q15(interp[2],SHR32(accum[2], 1)) + MULT16_32_Q15(interp[3],SHR32(accum[3], 1)); + sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]); sum = SATURATE32PSHR(sum, 15, 32767); #else cubic_coef(frac, interp); @@ -572,6 +572,7 @@ static int resampler_basic_zero(SpeexResamplerState *st, spx_uint32_t channel_in const int frac_advance = st->frac_advance; const spx_uint32_t den_rate = st->den_rate; + (void)in; while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) { out[out_stride * out_sample++] = 0; @@ -589,16 +590,15 @@ static int resampler_basic_zero(SpeexResamplerState *st, spx_uint32_t channel_in return out_sample; } -static int _muldiv(spx_uint32_t *result, spx_uint32_t value, spx_uint32_t mul, spx_uint32_t div) +static int multiply_frac(spx_uint32_t *result, spx_uint32_t value, spx_uint32_t num, spx_uint32_t den) { - speex_assert(result); - spx_uint32_t major = value / div; - spx_uint32_t remainder = value % div; + spx_uint32_t major = value / den; + spx_uint32_t remain = value % den; /* TODO: Could use 64 bits operation to check for overflow. But only guaranteed in C99+ */ - if (remainder > UINT32_MAX / mul || major > UINT32_MAX / mul - || major * mul > UINT32_MAX - remainder * mul / div) + if (remain > UINT32_MAX / num || major > UINT32_MAX / num + || major * num > UINT32_MAX - remain * num / den) return RESAMPLER_ERR_OVERFLOW; - *result = remainder * mul / div + major * mul; + *result = remain * num / den + major * num; return RESAMPLER_ERR_SUCCESS; } @@ -619,7 +619,7 @@ static int update_filter(SpeexResamplerState *st) { /* down-sampling */ st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; - if (_muldiv(&st->filt_len,st->filt_len,st->num_rate,st->den_rate) != RESAMPLER_ERR_SUCCESS) + if (multiply_frac(&st->filt_len,st->filt_len,st->num_rate,st->den_rate) != RESAMPLER_ERR_SUCCESS) goto fail; /* Round up to make sure we have a multiple of 8 for SSE */ st->filt_len = ((st->filt_len-1)&(~0x7))+8; @@ -638,12 +638,12 @@ static int update_filter(SpeexResamplerState *st) st->cutoff = quality_map[st->quality].upsample_bandwidth; } - /* Choose the resampling type that requires the least amount of memory */ #ifdef RESAMPLE_FULL_SINC_TABLE use_direct = 1; if (INT_MAX/sizeof(spx_word16_t)/st->den_rate < st->filt_len) goto fail; #else + /* Choose the resampling type that requires the least amount of memory */ use_direct = st->filt_len*st->den_rate <= st->filt_len*st->oversample+8 && INT_MAX/sizeof(spx_word16_t)/st->den_rate >= st->filt_len; #endif @@ -733,16 +733,18 @@ static int update_filter(SpeexResamplerState *st) { spx_uint32_t j; spx_uint32_t olen = old_length; + spx_uint32_t start = i*st->mem_alloc_size; + spx_uint32_t magic_samples = st->magic_samples[i]; /*if (st->magic_samples[i])*/ { /* Try and remove the magic samples as if nothing had happened */ /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */ - olen = old_length + 2*st->magic_samples[i]; - for (j=old_length-1+st->magic_samples[i];j--;) - st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; - for (j=0;jmagic_samples[i];j++) - st->mem[i*st->mem_alloc_size+j] = 0; + olen = old_length + 2*magic_samples; + for (j=old_length-1+magic_samples;j--;) + st->mem[start+j+magic_samples] = st->mem[i*old_alloc_size+j]; + for (j=0;jmem[start+j] = 0; st->magic_samples[i] = 0; } if (st->filt_len > olen) @@ -750,17 +752,18 @@ static int update_filter(SpeexResamplerState *st) /* If the new filter length is still bigger than the "augmented" length */ /* Copy data going backward */ for (j=0;jmem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)]; + st->mem[start+(st->filt_len-2-j)] = st->mem[start+(olen-2-j)]; /* Then put zeros for lack of anything better */ for (;jfilt_len-1;j++) - st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0; + st->mem[start+(st->filt_len-2-j)] = 0; /* Adjust last_sample */ st->last_sample[i] += (st->filt_len - olen)/2; } else { /* Put back some of the magic! */ - st->magic_samples[i] = (olen - st->filt_len)/2; - for (j=0;jfilt_len-1+st->magic_samples[i];j++) - st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + magic_samples = (olen - st->filt_len)/2; + for (j=0;jfilt_len-1+magic_samples;j++) + st->mem[start+j] = st->mem[start+j+magic_samples]; + st->magic_samples[i] = magic_samples; } } } else if (st->filt_len < old_length) @@ -977,8 +980,7 @@ EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t cha const spx_uint32_t xlen = st->mem_alloc_size - (st->filt_len - 1); #ifdef VAR_ARRAYS const unsigned int ylen = (olen < FIXED_STACK_ALLOC) ? olen : FIXED_STACK_ALLOC; - VARDECL(spx_word16_t *ystack); - ALLOC(ystack, ylen, spx_word16_t); + spx_word16_t ystack[ylen]; #else const unsigned int ylen = FIXED_STACK_ALLOC; spx_word16_t ystack[FIXED_STACK_ALLOC]; @@ -1093,7 +1095,7 @@ EXPORT void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_r *out_rate = st->out_rate; } -static inline spx_uint32_t _gcd(spx_uint32_t a, spx_uint32_t b) +static inline spx_uint32_t compute_gcd(spx_uint32_t a, spx_uint32_t b) { while (b != 0) { @@ -1123,7 +1125,7 @@ EXPORT int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t r st->num_rate = ratio_num; st->den_rate = ratio_den; - fact = _gcd (st->num_rate, st->den_rate); + fact = compute_gcd(st->num_rate, st->den_rate); st->num_rate /= fact; st->den_rate /= fact; @@ -1132,7 +1134,7 @@ EXPORT int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t r { for (i=0;inb_channels;i++) { - if (_muldiv(&st->samp_frac_num[i],st->samp_frac_num[i],st->den_rate,old_den) != RESAMPLER_ERR_SUCCESS) + if (multiply_frac(&st->samp_frac_num[i],st->samp_frac_num[i],st->den_rate,old_den) != RESAMPLER_ERR_SUCCESS) return RESAMPLER_ERR_OVERFLOW; /* Safety net */ if (st->samp_frac_num[i] >= st->den_rate) diff --git a/3rdparty/cubeb/subprojects/speex/resample_neon.h b/3rdparty/cubeb/subprojects/speex/resample_neon.h index 0acbd27b9a..85a51fe1c3 100644 --- a/3rdparty/cubeb/subprojects/speex/resample_neon.h +++ b/3rdparty/cubeb/subprojects/speex/resample_neon.h @@ -36,14 +36,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include +#include #ifdef FIXED_POINT -#ifdef __thumb2__ +#if defined(__aarch64__) +static inline int32_t saturate_32bit_to_16bit(int32_t a) { + int32_t ret; + asm ("fmov s0, %w[a]\n" + "sqxtn h0, s0\n" + "sxtl v0.4s, v0.4h\n" + "fmov %w[ret], s0\n" + : [ret] "=r" (ret) + : [a] "r" (a) + : "v0" ); + return ret; +} +#elif defined(__thumb2__) static inline int32_t saturate_32bit_to_16bit(int32_t a) { int32_t ret; asm ("ssat %[ret], #16, %[a]" - : [ret] "=&r" (ret) + : [ret] "=r" (ret) : [a] "r" (a) : ); return ret; @@ -54,7 +66,7 @@ static inline int32_t saturate_32bit_to_16bit(int32_t a) { asm ("vmov.s32 d0[0], %[a]\n" "vqmovn.s32 d0, q0\n" "vmov.s16 %[ret], d0[0]\n" - : [ret] "=&r" (ret) + : [ret] "=r" (ret) : [a] "r" (a) : "q0"); return ret; @@ -64,7 +76,63 @@ static inline int32_t saturate_32bit_to_16bit(int32_t a) { #define WORD2INT(x) (saturate_32bit_to_16bit(x)) #define OVERRIDE_INNER_PRODUCT_SINGLE -/* Only works when len % 4 == 0 */ +/* Only works when len % 4 == 0 and len >= 4 */ +#if defined(__aarch64__) +static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len) +{ + int32_t ret; + uint32_t remainder = len % 16; + len = len - remainder; + + asm volatile (" cmp %w[len], #0\n" + " b.ne 1f\n" + " ld1 {v16.4h}, [%[b]], #8\n" + " ld1 {v20.4h}, [%[a]], #8\n" + " subs %w[remainder], %w[remainder], #4\n" + " smull v0.4s, v16.4h, v20.4h\n" + " b.ne 4f\n" + " b 5f\n" + "1:" + " ld1 {v16.4h, v17.4h, v18.4h, v19.4h}, [%[b]], #32\n" + " ld1 {v20.4h, v21.4h, v22.4h, v23.4h}, [%[a]], #32\n" + " subs %w[len], %w[len], #16\n" + " smull v0.4s, v16.4h, v20.4h\n" + " smlal v0.4s, v17.4h, v21.4h\n" + " smlal v0.4s, v18.4h, v22.4h\n" + " smlal v0.4s, v19.4h, v23.4h\n" + " b.eq 3f\n" + "2:" + " ld1 {v16.4h, v17.4h, v18.4h, v19.4h}, [%[b]], #32\n" + " ld1 {v20.4h, v21.4h, v22.4h, v23.4h}, [%[a]], #32\n" + " subs %w[len], %w[len], #16\n" + " smlal v0.4s, v16.4h, v20.4h\n" + " smlal v0.4s, v17.4h, v21.4h\n" + " smlal v0.4s, v18.4h, v22.4h\n" + " smlal v0.4s, v19.4h, v23.4h\n" + " b.ne 2b\n" + "3:" + " cmp %w[remainder], #0\n" + " b.eq 5f\n" + "4:" + " ld1 {v18.4h}, [%[b]], #8\n" + " ld1 {v22.4h}, [%[a]], #8\n" + " subs %w[remainder], %w[remainder], #4\n" + " smlal v0.4s, v18.4h, v22.4h\n" + " b.ne 4b\n" + "5:" + " saddlv d0, v0.4s\n" + " sqxtn s0, d0\n" + " sqrshrn h0, s0, #15\n" + " sxtl v0.4s, v0.4h\n" + " fmov %w[ret], s0\n" + : [ret] "=r" (ret), [a] "+r" (a), [b] "+r" (b), + [len] "+r" (len), [remainder] "+r" (remainder) + : + : "cc", "v0", + "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23"); + return ret; +} +#else static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len) { int32_t ret; @@ -112,33 +180,104 @@ static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, u " vqmovn.s64 d0, q0\n" " vqrshrn.s32 d0, q0, #15\n" " vmov.s16 %[ret], d0[0]\n" - : [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b), + : [ret] "=r" (ret), [a] "+r" (a), [b] "+r" (b), [len] "+r" (len), [remainder] "+r" (remainder) : : "cc", "q0", - "d16", "d17", "d18", "d19", - "d20", "d21", "d22", "d23"); + "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23"); return ret; } -#elif defined(FLOATING_POINT) +#endif // !defined(__aarch64__) +#elif defined(FLOATING_POINT) +#if defined(__aarch64__) +static inline int32_t saturate_float_to_16bit(float a) { + int32_t ret; + asm ("fcvtas s1, %s[a]\n" + "sqxtn h1, s1\n" + "sxtl v1.4s, v1.4h\n" + "fmov %w[ret], s1\n" + : [ret] "=r" (ret) + : [a] "w" (a) + : "v1"); + return ret; +} +#else static inline int32_t saturate_float_to_16bit(float a) { int32_t ret; asm ("vmov.f32 d0[0], %[a]\n" "vcvt.s32.f32 d0, d0, #15\n" "vqrshrn.s32 d0, q0, #15\n" "vmov.s16 %[ret], d0[0]\n" - : [ret] "=&r" (ret) + : [ret] "=r" (ret) : [a] "r" (a) : "q0"); return ret; } +#endif + #undef WORD2INT #define WORD2INT(x) (saturate_float_to_16bit(x)) #define OVERRIDE_INNER_PRODUCT_SINGLE -/* Only works when len % 4 == 0 */ +/* Only works when len % 4 == 0 and len >= 4 */ +#if defined(__aarch64__) +static inline float inner_product_single(const float *a, const float *b, unsigned int len) +{ + float ret; + uint32_t remainder = len % 16; + len = len - remainder; + + asm volatile (" cmp %w[len], #0\n" + " b.ne 1f\n" + " ld1 {v16.4s}, [%[b]], #16\n" + " ld1 {v20.4s}, [%[a]], #16\n" + " subs %w[remainder], %w[remainder], #4\n" + " fmul v1.4s, v16.4s, v20.4s\n" + " b.ne 4f\n" + " b 5f\n" + "1:" + " ld1 {v16.4s, v17.4s, v18.4s, v19.4s}, [%[b]], #64\n" + " ld1 {v20.4s, v21.4s, v22.4s, v23.4s}, [%[a]], #64\n" + " subs %w[len], %w[len], #16\n" + " fmul v1.4s, v16.4s, v20.4s\n" + " fmul v2.4s, v17.4s, v21.4s\n" + " fmul v3.4s, v18.4s, v22.4s\n" + " fmul v4.4s, v19.4s, v23.4s\n" + " b.eq 3f\n" + "2:" + " ld1 {v16.4s, v17.4s, v18.4s, v19.4s}, [%[b]], #64\n" + " ld1 {v20.4s, v21.4s, v22.4s, v23.4s}, [%[a]], #64\n" + " subs %w[len], %w[len], #16\n" + " fmla v1.4s, v16.4s, v20.4s\n" + " fmla v2.4s, v17.4s, v21.4s\n" + " fmla v3.4s, v18.4s, v22.4s\n" + " fmla v4.4s, v19.4s, v23.4s\n" + " b.ne 2b\n" + "3:" + " fadd v16.4s, v1.4s, v2.4s\n" + " fadd v17.4s, v3.4s, v4.4s\n" + " cmp %w[remainder], #0\n" + " fadd v1.4s, v16.4s, v17.4s\n" + " b.eq 5f\n" + "4:" + " ld1 {v18.4s}, [%[b]], #16\n" + " ld1 {v22.4s}, [%[a]], #16\n" + " subs %w[remainder], %w[remainder], #4\n" + " fmla v1.4s, v18.4s, v22.4s\n" + " b.ne 4b\n" + "5:" + " faddp v1.4s, v1.4s, v1.4s\n" + " faddp %[ret].4s, v1.4s, v1.4s\n" + : [ret] "=w" (ret), [a] "+r" (a), [b] "+r" (b), + [len] "+r" (len), [remainder] "+r" (remainder) + : + : "cc", "v1", "v2", "v3", "v4", + "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23"); + return ret; +} +#else static inline float inner_product_single(const float *a, const float *b, unsigned int len) { float ret; @@ -191,11 +330,12 @@ static inline float inner_product_single(const float *a, const float *b, unsigne " vadd.f32 d0, d0, d1\n" " vpadd.f32 d0, d0, d0\n" " vmov.f32 %[ret], d0[0]\n" - : [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b), + : [ret] "=r" (ret), [a] "+r" (a), [b] "+r" (b), [len] "+l" (len), [remainder] "+l" (remainder) : - : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", - "q9", "q10", "q11"); + : "cc", "q0", "q1", "q2", "q3", + "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11"); return ret; } +#endif // defined(__aarch64__) #endif diff --git a/3rdparty/cubeb/subprojects/speex/resample_sse.h b/3rdparty/cubeb/subprojects/speex/resample_sse.h index fed5b8276f..00dc29465b 100644 --- a/3rdparty/cubeb/subprojects/speex/resample_sse.h +++ b/3rdparty/cubeb/subprojects/speex/resample_sse.h @@ -71,7 +71,7 @@ static inline float interpolate_product_single(const float *a, const float *b, u return ret; } -#ifdef _USE_SSE2 +#ifdef USE_SSE2 #include #define OVERRIDE_INNER_PRODUCT_DOUBLE diff --git a/pcsx2/Host/CubebAudioStream.cpp b/pcsx2/Host/CubebAudioStream.cpp index 020e0d17d6..b1d85c2daf 100644 --- a/pcsx2/Host/CubebAudioStream.cpp +++ b/pcsx2/Host/CubebAudioStream.cpp @@ -288,9 +288,9 @@ std::vector> AudioStream::GetCubebDriverName std::vector> names; names.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default")); - const char** cubeb_names = cubeb_get_backend_names(); - for (u32 i = 0; cubeb_names[i] != nullptr; i++) - names.emplace_back(cubeb_names[i], cubeb_names[i]); + auto cubeb_names = cubeb_get_backend_names(); + for (int i = 0; i < cubeb_names.count; i++) + names.emplace_back(cubeb_names.names[i], cubeb_names.names[i]); return names; }