diff --git a/dep/cubeb/CMakeLists.txt b/dep/cubeb/CMakeLists.txt index e13a8ceb4..a694157aa 100644 --- a/dep/cubeb/CMakeLists.txt +++ b/dep/cubeb/CMakeLists.txt @@ -1,7 +1,7 @@ # TODO # - backend selection via command line, rather than simply detecting headers. -cmake_minimum_required(VERSION 3.14 FATAL_ERROR) +cmake_minimum_required(VERSION 3.19 FATAL_ERROR) project(cubeb C CXX) option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON) @@ -33,21 +33,19 @@ add_library(cubeb src/cubeb_strings.c src/cubeb_utils.cpp ) -target_include_directories(cubeb - PUBLIC $ $ -) +target_include_directories(cubeb PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) - add_library(speex OBJECT subprojects/speex/resample.c) - set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) - target_include_directories(speex INTERFACE subprojects) - target_compile_definitions(speex PUBLIC - OUTSIDE_SPEEX - FLOATING_POINT - EXPORT= - RANDOM_PREFIX=speex - ) - add_library(speex_resampler_headers INTERFACE) - target_include_directories(speex_resampler_headers INTERFACE subprojects) +add_library(speex OBJECT subprojects/speex/resample.c) +set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) +target_include_directories(speex INTERFACE subprojects) +target_compile_definitions(speex PUBLIC + OUTSIDE_SPEEX + FLOATING_POINT + EXPORT= + RANDOM_PREFIX=speex +) +add_library(speex_resampler_headers INTERFACE) +target_include_directories(speex_resampler_headers INTERFACE subprojects) # $ required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415 target_link_libraries(cubeb PRIVATE $) @@ -144,7 +142,7 @@ if(USE_WASAPI) target_sources(cubeb PRIVATE src/cubeb_wasapi.cpp) target_compile_definitions(cubeb PRIVATE USE_WASAPI) - target_link_libraries(cubeb PRIVATE ole32 ksuser) + target_link_libraries(cubeb PRIVATE avrt ole32 ksuser) endif() check_include_files("windows.h;mmsystem.h" USE_WINMM) diff --git a/dep/cubeb/README.md b/dep/cubeb/README.md index e4e165882..028c5b63b 100644 --- a/dep/cubeb/README.md +++ b/dep/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/dep/cubeb/cubeb.vcxproj b/dep/cubeb/cubeb.vcxproj index 0751bdd73..8c018a8b0 100644 --- a/dep/cubeb/cubeb.vcxproj +++ b/dep/cubeb/cubeb.vcxproj @@ -22,7 +22,6 @@ - diff --git a/dep/cubeb/cubeb.vcxproj.filters b/dep/cubeb/cubeb.vcxproj.filters index 223265d04..de60d463c 100644 --- a/dep/cubeb/cubeb.vcxproj.filters +++ b/dep/cubeb/cubeb.vcxproj.filters @@ -27,9 +27,6 @@ speex - - speex - speex diff --git a/dep/cubeb/include/cubeb/cubeb.h b/dep/cubeb/include/cubeb/cubeb.h index 5d30d52f9..488ac8a94 100644 --- a/dep/cubeb/include/cubeb/cubeb.h +++ b/dep/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", @@ -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. @@ -570,8 +585,9 @@ cubeb_destroy(cubeb * context); NULL if this stream is input only. When input and output stream parameters are supplied, their rate has to be the same. - @param latency_frames Stream latency in frames. Valid range - is [1, 96000]. + @param latency_frames Requested stream latency in frames. Valid range is + [1, 96000]. The actual latency may differ depending + on the backend, platform, and hardware. @param data_callback Will be called to preroll data before playback is started by cubeb_stream_start. @param state_callback A pointer to a state callback. @@ -674,7 +690,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 +761,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/dep/cubeb/src/cubeb.c b/dep/cubeb/src/cubeb.c index da52563c3..f8163c93e 100644 --- a/dep/cubeb/src/cubeb.c +++ b/dep/cubeb/src/cubeb.c @@ -214,13 +214,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 +243,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 +255,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/dep/cubeb/src/cubeb_alsa.c b/dep/cubeb/src/cubeb_alsa.c index f114f27d7..be9faa490 100644 --- a/dep/cubeb/src/cubeb_alsa.c +++ b/dep/cubeb/src/cubeb_alsa.c @@ -294,6 +294,9 @@ set_timeout(struct timeval * timeout, unsigned int ms) static void stream_buffer_decrement(cubeb_stream * stm, long count) { + if (count < 0 || (snd_pcm_uframes_t)count > stm->bufframes) { + count = stm->bufframes; + } char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count); memmove(stm->buffer, bufremains, diff --git a/dep/cubeb/src/cubeb_audiounit.cpp b/dep/cubeb/src/cubeb_audiounit.cpp index d823e80ff..e69aac3e6 100644 --- a/dep/cubeb/src/cubeb_audiounit.cpp +++ b/dep/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/dep/cubeb/src/cubeb_jack.cpp b/dep/cubeb/src/cubeb_jack.cpp index b417078fc..9d64b52c7 100644 --- a/dep/cubeb/src/cubeb_jack.cpp +++ b/dep/cubeb/src/cubeb_jack.cpp @@ -405,12 +405,15 @@ cbjack_process(jack_nframes_t nframes, void * arg) for (int j = 0; j < MAX_STREAMS; j++) { cubeb_stream * stm = &ctx->streams[j]; - float * bufs_out[stm->out_params.channels]; - float * bufs_in[stm->in_params.channels]; if (!stm->in_use) continue; + float * bufs_out[MAX_CHANNELS] = {}; + float * bufs_in[MAX_CHANNELS] = {}; + XASSERT(stm->out_params.channels <= MAX_CHANNELS); + XASSERT(stm->in_params.channels <= MAX_CHANNELS); + // handle xruns by skipping audio that should have been played stm->position += t_jack_xruns * ctx->fragment_size * stm->ratio; @@ -851,6 +854,14 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, return CUBEB_ERROR_INVALID_FORMAT; } + if ((output_stream_params && + (output_stream_params->channels < 1 || + output_stream_params->channels > MAX_CHANNELS)) || + (input_stream_params && (input_stream_params->channels < 1 || + input_stream_params->channels > MAX_CHANNELS))) { + return CUBEB_ERROR_INVALID_FORMAT; + } + if ((input_device && input_device != JACK_DEFAULT_IN) || (output_device && output_device != JACK_DEFAULT_OUT)) { return CUBEB_ERROR_NOT_SUPPORTED; diff --git a/dep/cubeb/src/cubeb_log.h b/dep/cubeb/src/cubeb_log.h index d42a0b77d..c0c89951c 100644 --- a/dep/cubeb/src/cubeb_log.h +++ b/dep/cubeb/src/cubeb_log.h @@ -39,7 +39,8 @@ 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); #ifdef __cplusplus } diff --git a/dep/cubeb/src/cubeb_resampler.cpp b/dep/cubeb/src/cubeb_resampler.cpp index c31944b82..c09ea4b9c 100644 --- a/dep/cubeb/src/cubeb_resampler.cpp +++ b/dep/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/dep/cubeb/src/cubeb_resampler.h b/dep/cubeb/src/cubeb_resampler.h index 711a3771d..25781ea9e 100644 --- a/dep/cubeb/src/cubeb_resampler.h +++ b/dep/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/dep/cubeb/src/cubeb_resampler_internal.h b/dep/cubeb/src/cubeb_resampler_internal.h index 285f24dd0..08e019c61 100644 --- a/dep/cubeb/src/cubeb_resampler_internal.h +++ b/dep/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/dep/cubeb/src/cubeb_wasapi.cpp b/dep/cubeb/src/cubeb_wasapi.cpp index b192cc48a..6ac7d5217 100644 --- a/dep/cubeb/src/cubeb_wasapi.cpp +++ b/dep/cubeb/src/cubeb_wasapi.cpp @@ -4,12 +4,17 @@ * 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 #include +#include #include #include #include @@ -229,11 +234,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 +304,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 +314,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,16 +340,10 @@ 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; +class wasapi_session_notification_client; /* We have three possible callbacks we can use with a stream: * - input only @@ -360,20 +363,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. */ @@ -426,6 +442,10 @@ struct cubeb_stream { audio device changes and route the audio to the new default audio output device */ com_ptr notification_client; + /* Session notification client, to be notified when the audio session is + disconnected (e.g. when an audio device is removed from the system). */ + com_ptr session_control; + com_ptr session_notification_client; /* Main andle to the WASAPI capture stream. */ com_ptr input_client; /* Interface to use the event driven capture interface */ @@ -662,6 +682,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 +796,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 +807,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(); } @@ -839,6 +865,89 @@ private: DWORD last_device_change; }; +class wasapi_session_notification_client : public IAudioSessionEvents { +public: + ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); } + + ULONG STDMETHODCALLTYPE Release() + { + ULONG ulRef = InterlockedDecrement(&ref_count); + if (0 == ulRef) { + delete this; + } + return ulRef; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface) + { + if (__uuidof(IUnknown) == riid) { + AddRef(); + *ppvInterface = (IUnknown *)this; + } else if (__uuidof(IAudioSessionEvents) == riid) { + AddRef(); + *ppvInterface = (IAudioSessionEvents *)this; + } else { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + wasapi_session_notification_client(HANDLE event) + : ref_count(1), reconfigure_event(event) + { + } + + virtual ~wasapi_session_notification_client() {} + + HRESULT STDMETHODCALLTYPE + OnSessionDisconnected(AudioSessionDisconnectReason reason) + { + LOG("session: Audio session disconnected, reason: %d", reason); + BOOL ok = SetEvent(reconfigure_event); + if (!ok) { + LOG("session: SetEvent on reconfigure_event failed: %lx", GetLastError()); + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR value, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR value, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float volume, BOOL mute, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD channel_count, + float volumes[], + DWORD changed_channel, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID grouping_param, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState state) + { + return S_OK; + } + +private: + LONG ref_count; + HANDLE reconfigure_event; +}; + namespace { long @@ -863,16 +972,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 +1082,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 +1368,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 +1379,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 +1395,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 +1544,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", @@ -1469,13 +1574,12 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) XASSERT(stm->output_client || stm->input_client); LOG("Reconfiguring the stream"); /* Close the stream */ - bool was_running = false; if (stm->output_client) { - was_running = stm->output_client->Stop() == S_OK; + stm->output_client->Stop(); LOG("Output stopped."); } if (stm->input_client) { - was_running = stm->input_client->Stop() == S_OK; + stm->input_client->Stop(); LOG("Input stopped."); } close_wasapi_stream(stm); @@ -1493,7 +1597,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) } LOG("Stream setup successfuly."); XASSERT(stm->output_client || stm->input_client); - if (was_running && stm->output_client) { + if (stm->output_client) { hr = stm->output_client->Start(); if (FAILED(hr)) { LOG("Error starting output after reconfigure, error: %lx", hr); @@ -1502,7 +1606,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) } LOG("Output started after reconfigure."); } - if (was_running && stm->input_client) { + if (stm->input_client) { hr = stm->input_client->Start(); if (FAILED(hr)) { LOG("Error starting input after reconfiguring, error: %lx", hr); @@ -1519,8 +1623,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 +1636,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 +1654,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 +1667,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 +1902,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 +1909,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 +1924,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 +1948,6 @@ wasapi_destroy(cubeb * context) } } - if (context->mmcss_module) { - FreeLibrary(context->mmcss_module); - } - delete context; } @@ -2044,6 +2100,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) { @@ -2122,7 +2193,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 +2203,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)) { @@ -2494,10 +2570,29 @@ 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 && @@ -2737,6 +2832,22 @@ setup_wasapi_stream(cubeb_stream * stm) return CUBEB_ERROR; } + hr = stm->output_client->GetService(__uuidof(IAudioSessionControl), + stm->session_control.receive_vpp()); + if (SUCCEEDED(hr)) { + stm->session_notification_client.reset( + new wasapi_session_notification_client(stm->reconfigure_event)); + hr = stm->session_control->RegisterAudioSessionNotification( + stm->session_notification_client.get()); + if (FAILED(hr)) { + LOG("Could not register session notification client: %lx", hr); + stm->session_notification_client = nullptr; + stm->session_control = nullptr; + } + } else { + LOG("Could not get the IAudioSessionControl: %lx", hr); + } + #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME /* Restore the stream volume over a device change. */ if (stream_set_volume(stm, stm->volume) != CUBEB_OK) { @@ -2999,6 +3110,13 @@ close_wasapi_stream(cubeb_stream * stm) stm->stream_reset_lock.assert_current_thread_owns(); + if (stm->session_control && stm->session_notification_client) { + stm->session_control->UnregisterAudioSessionNotification( + stm->session_notification_client.get()); + stm->session_notification_client = nullptr; + stm->session_control = nullptr; + } + #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME stm->audio_stream_volume = nullptr; #endif @@ -3031,7 +3149,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 +3159,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 +3421,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 +3429,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 +3558,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 +3579,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 +3595,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); } @@ -3656,6 +3775,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, @@ -3668,11 +3795,13 @@ wasapi_register_device_collection_changed( } if (collection_changed_callback) { - // Make sure it has been unregistered first. - XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) && - !context->input_collection_changed_callback) || - ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) && - !context->output_collection_changed_callback)); + if (((devtype & CUBEB_DEVICE_TYPE_INPUT) && + context->input_collection_changed_callback) || + ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) && + context->output_collection_changed_callback)) { + LOG("register_device_collection_changed: callback already registered"); + return CUBEB_ERROR_INVALID_PARAMETER; + } // Stop the notification client. Notifications arrive on // a separate thread. We stop them here to avoid @@ -3736,7 +3865,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 +3881,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/dep/cubeb/subprojects/speex/arch.h b/dep/cubeb/subprojects/speex/arch.h index 73a45a069..1cac3d9c8 100644 --- a/dep/cubeb/subprojects/speex/arch.h +++ b/dep/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/dep/cubeb/subprojects/speex/fixed_generic.h b/dep/cubeb/subprojects/speex/fixed_generic.h index 12d27aac5..09366c36c 100644 --- a/dep/cubeb/subprojects/speex/fixed_generic.h +++ b/dep/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/dep/cubeb/subprojects/speex/resample.c b/dep/cubeb/subprojects/speex/resample.c index 10cb06593..2783f55f0 100644 --- a/dep/cubeb/subprojects/speex/resample.c +++ b/dep/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/dep/cubeb/subprojects/speex/resample_neon.h b/dep/cubeb/subprojects/speex/resample_neon.h index 0acbd27b9..85a51fe1c 100644 --- a/dep/cubeb/subprojects/speex/resample_neon.h +++ b/dep/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/dep/cubeb/subprojects/speex/resample_sse.h b/dep/cubeb/subprojects/speex/resample_sse.h index fed5b8276..00dc29465 100644 --- a/dep/cubeb/subprojects/speex/resample_sse.h +++ b/dep/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/dep/cubeb/subprojects/speex/stack_alloc.h b/dep/cubeb/subprojects/speex/stack_alloc.h deleted file mode 100644 index 6c56334f8..000000000 --- a/dep/cubeb/subprojects/speex/stack_alloc.h +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright (C) 2002 Jean-Marc Valin */ -/** - @file stack_alloc.h - @brief Temporary memory allocation on stack -*/ -/* - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - - Neither the name of the Xiph.org Foundation nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef STACK_ALLOC_H -#define STACK_ALLOC_H - -#ifdef USE_ALLOCA -# ifdef WIN32 -# include -# else -# ifdef HAVE_ALLOCA_H -# include -# else -# include -# endif -# endif -#endif - -/** - * @def ALIGN(stack, size) - * - * Aligns the stack to a 'size' boundary - * - * @param stack Stack - * @param size New size boundary - */ - -/** - * @def PUSH(stack, size, type) - * - * Allocates 'size' elements of type 'type' on the stack - * - * @param stack Stack - * @param size Number of elements - * @param type Type of element - */ - -/** - * @def VARDECL(var) - * - * Declare variable on stack - * - * @param var Variable to declare - */ - -/** - * @def ALLOC(var, size, type) - * - * Allocate 'size' elements of 'type' on stack - * - * @param var Name of variable to allocate - * @param size Number of elements - * @param type Type of element - */ - -#ifdef ENABLE_VALGRIND - -#include - -#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) - -#define PUSH(stack, size, type) (VALGRIND_MAKE_NOACCESS(stack, 1000),ALIGN((stack),sizeof(type)),VALGRIND_MAKE_WRITABLE(stack, ((size)*sizeof(type))),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) - -#else - -#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) - -#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) - -#endif - -#if defined(VAR_ARRAYS) -#define VARDECL(var) -#define ALLOC(var, size, type) type var[size] -#elif defined(USE_ALLOCA) -#define VARDECL(var) var -#define ALLOC(var, size, type) var = alloca(sizeof(type)*(size)) -#else -#define VARDECL(var) var -#define ALLOC(var, size, type) var = PUSH(stack, size, type) -#endif - - -#endif diff --git a/src/util/cubeb_audio_stream.cpp b/src/util/cubeb_audio_stream.cpp index 0264995c6..c844f509d 100644 --- a/src/util/cubeb_audio_stream.cpp +++ b/src/util/cubeb_audio_stream.cpp @@ -337,9 +337,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]); + const cubeb_backend_names backend_names = cubeb_get_backend_names(); + for (size_t i = 0; i < backend_names.count; i++) + names.emplace_back(backend_names.names[i], backend_names.names[i]); return names; }