3rdparty: Update cubeb to e495bee
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run

This commit is contained in:
JordanTheToaster 2025-10-01 16:19:27 +01:00 committed by Ty
parent e550cf9b63
commit bc11ff0571
16 changed files with 960 additions and 477 deletions

View File

@ -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) [![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/)

View File

@ -49,6 +49,7 @@ extern "C" {
output_params.channels = 2; output_params.channels = 2;
output_params.layout = CUBEB_LAYOUT_UNDEFINED; output_params.layout = CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = CUBEB_STREAM_PREF_NONE; 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); rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
if (rv != CUBEB_OK) { if (rv != CUBEB_OK) {
@ -62,6 +63,7 @@ extern "C" {
input_params.channels = 1; input_params.channels = 1;
input_params.layout = CUBEB_LAYOUT_UNDEFINED; input_params.layout = CUBEB_LAYOUT_UNDEFINED;
input_params.prefs = CUBEB_STREAM_PREF_NONE; input_params.prefs = CUBEB_STREAM_PREF_NONE;
input_params.input_params = CUBEB_INPUT_PROCESSING_PARAM_NONE;
cubeb_stream * stm; cubeb_stream * stm;
rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1", rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
@ -193,39 +195,39 @@ typedef uint32_t cubeb_channel_layout;
// Some common layout definitions. // Some common layout definitions.
enum { enum {
CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined. CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
CUBEB_LAYOUT_MONO = (uint32_t)CHANNEL_FRONT_CENTER, CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
CUBEB_LAYOUT_MONO_LFE = (uint32_t)CUBEB_LAYOUT_MONO | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_STEREO = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT, CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
CUBEB_LAYOUT_STEREO_LFE = (uint32_t)CUBEB_LAYOUT_STEREO | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F = CUBEB_LAYOUT_3F =
(uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | (uint32_t)CHANNEL_FRONT_CENTER, CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
CUBEB_LAYOUT_3F_LFE = (uint32_t)CUBEB_LAYOUT_3F | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_2F1 = CUBEB_LAYOUT_2F1 =
(uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | (uint32_t)CHANNEL_BACK_CENTER, CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
CUBEB_LAYOUT_2F1_LFE = (uint32_t)CUBEB_LAYOUT_2F1 | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F1 = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
(uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_BACK_CENTER, CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
CUBEB_LAYOUT_3F1_LFE = (uint32_t)CUBEB_LAYOUT_3F1 | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_2F2 = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
(uint32_t)CHANNEL_SIDE_LEFT | (uint32_t)CHANNEL_SIDE_RIGHT, CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
CUBEB_LAYOUT_2F2_LFE = (uint32_t)CUBEB_LAYOUT_2F2 | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_QUAD = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
(uint32_t)CHANNEL_BACK_LEFT | (uint32_t)CHANNEL_BACK_RIGHT, CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
CUBEB_LAYOUT_QUAD_LFE = (uint32_t)CUBEB_LAYOUT_QUAD | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F2 = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
(uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_SIDE_LEFT | CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
(uint32_t)CHANNEL_SIDE_RIGHT, CHANNEL_SIDE_RIGHT,
CUBEB_LAYOUT_3F2_LFE = (uint32_t)CUBEB_LAYOUT_3F2 | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F2_BACK = (uint32_t)CUBEB_LAYOUT_QUAD | (uint32_t)CHANNEL_FRONT_CENTER, CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
CUBEB_LAYOUT_3F2_LFE_BACK = (uint32_t)CUBEB_LAYOUT_3F2_BACK | (uint32_t)CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F3R_LFE = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
(uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_LOW_FREQUENCY | CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
(uint32_t)CHANNEL_BACK_CENTER | (uint32_t)CHANNEL_SIDE_LEFT | CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
(uint32_t)CHANNEL_SIDE_RIGHT, CHANNEL_SIDE_RIGHT,
CUBEB_LAYOUT_3F4_LFE = (uint32_t)CHANNEL_FRONT_LEFT | (uint32_t)CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
(uint32_t)CHANNEL_FRONT_CENTER | (uint32_t)CHANNEL_LOW_FREQUENCY | CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
(uint32_t)CHANNEL_BACK_LEFT | (uint32_t)CHANNEL_BACK_RIGHT | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
(uint32_t)CHANNEL_SIDE_LEFT | (uint32_t)CHANNEL_SIDE_RIGHT, CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
}; };
/** Miscellaneous stream preferences. */ /** Miscellaneous stream preferences. */
@ -279,7 +281,10 @@ typedef struct {
cubeb_channel_layout cubeb_channel_layout
layout; /**< Requested channel layout. This must be consistent with the layout; /**< Requested channel layout. This must be consistent with the
provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */ 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; } cubeb_stream_params;
/** Audio device description */ /** Audio device description */
@ -414,6 +419,13 @@ typedef struct {
size_t count; /**< Device count in collection. */ size_t count; /**< Device count in collection. */
} cubeb_device_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. /** User supplied data callback.
- Calling other cubeb functions from this callback is unsafe. - Calling other cubeb functions from this callback is unsafe.
- The code in the callback should be non-blocking. - 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. * 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 context A pointer to the cubeb context.
* @param user_ptr The pointer passed to * @param user_ptr The pointer passed to
* cubeb_register_device_collection_changed. */ * cubeb_register_device_collection_changed. */
@ -485,17 +499,18 @@ CUBEB_EXPORT int
cubeb_init(cubeb ** context, char const * context_name, cubeb_init(cubeb ** context, char const * context_name,
char const * backend_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. /** Get a read-only string identifying this context's current backend.
@param context A pointer to the cubeb context. @param context A pointer to the cubeb context.
@retval Read-only string identifying current backend. */ @retval Read-only string identifying current backend. */
CUBEB_EXPORT char const * CUBEB_EXPORT char const *
cubeb_get_backend_id(cubeb * context); 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. /** Get the maximum possible number of channels.
@param context A pointer to the cubeb context. @param context A pointer to the cubeb context.
@param max_channels The maximum number of channels. @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 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. 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 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_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if this stream does not have an input @retval CUBEB_ERROR_INVALID_PARAMETER if this stream does not have an input
device device
@ -745,14 +760,16 @@ cubeb_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection); cubeb_device_collection * collection);
/** Registers a callback which is called when the system detects /** 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 context
@param devtype device type to include. Different callbacks and user pointers @param devtype device type to include. Different callbacks and user pointers
can be registered for each devtype. The hybrid devtype can be registered for each devtype. The hybrid devtype
`CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
and will register the provided callback and user pointer in both and will register the provided callback and user pointer in both
sides. 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 Passing NULL allow to unregister a function. You have to unregister
first before you register a new callback. first before you register a new callback.
@param user_ptr pointer to user specified data which will be present in @param user_ptr pointer to user specified data which will be present in

View File

@ -31,6 +31,10 @@ struct cubeb_stream {
int int
pulse_init(cubeb ** context, char const * context_name); pulse_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_PULSE_RUST)
int
pulse_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_JACK) #if defined(USE_JACK)
int int
jack_init(cubeb ** context, char const * context_name); jack_init(cubeb ** context, char const * context_name);
@ -43,6 +47,10 @@ alsa_init(cubeb ** context, char const * context_name);
int int
audiounit_init(cubeb ** context, char const * context_name); audiounit_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_AUDIOUNIT_RUST)
int
audiounit_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_WINMM) #if defined(USE_WINMM)
int int
winmm_init(cubeb ** context, char const * context_name); winmm_init(cubeb ** context, char const * context_name);
@ -55,10 +63,30 @@ wasapi_init(cubeb ** context, char const * context_name);
int int
sndio_init(cubeb ** context, char const * context_name); sndio_init(cubeb ** context, char const * context_name);
#endif #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) #if defined(USE_OSS)
int int
oss_init(cubeb ** context, char const * context_name); oss_init(cubeb ** context, char const * context_name);
#endif #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 static int
validate_stream_params(cubeb_stream_params * input_stream_params, 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 (!strcmp(backend_name, "pulse")) {
#if defined(USE_PULSE) #if defined(USE_PULSE)
init_oneshot = pulse_init; init_oneshot = pulse_init;
#endif
} else if (!strcmp(backend_name, "pulse-rust")) {
#if defined(USE_PULSE_RUST)
init_oneshot = pulse_rust_init;
#endif #endif
} else if (!strcmp(backend_name, "jack")) { } else if (!strcmp(backend_name, "jack")) {
#if defined(USE_JACK) #if defined(USE_JACK)
@ -135,6 +167,10 @@ cubeb_init(cubeb ** context, char const * context_name,
} else if (!strcmp(backend_name, "audiounit")) { } else if (!strcmp(backend_name, "audiounit")) {
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
init_oneshot = audiounit_init; init_oneshot = audiounit_init;
#endif
} else if (!strcmp(backend_name, "audiounit-rust")) {
#if defined(USE_AUDIOUNIT_RUST)
init_oneshot = audiounit_rust_init;
#endif #endif
} else if (!strcmp(backend_name, "wasapi")) { } else if (!strcmp(backend_name, "wasapi")) {
#if defined(USE_WASAPI) #if defined(USE_WASAPI)
@ -147,10 +183,30 @@ cubeb_init(cubeb ** context, char const * context_name,
} else if (!strcmp(backend_name, "sndio")) { } else if (!strcmp(backend_name, "sndio")) {
#if defined(USE_SNDIO) #if defined(USE_SNDIO)
init_oneshot = sndio_init; 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 #endif
} else if (!strcmp(backend_name, "oss")) { } else if (!strcmp(backend_name, "oss")) {
#if defined(USE_OSS) #if defined(USE_OSS)
init_oneshot = oss_init; 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 #endif
} else { } else {
/* Already set */ /* Already set */
@ -163,6 +219,9 @@ cubeb_init(cubeb ** context, char const * context_name,
* to override all other choices * to override all other choices
*/ */
init_oneshot, init_oneshot,
#if defined(USE_PULSE_RUST)
pulse_rust_init,
#endif
#if defined(USE_PULSE) #if defined(USE_PULSE)
pulse_init, pulse_init,
#endif #endif
@ -178,6 +237,9 @@ cubeb_init(cubeb ** context, char const * context_name,
#if defined(USE_OSS) #if defined(USE_OSS)
oss_init, oss_init,
#endif #endif
#if defined(USE_AUDIOUNIT_RUST)
audiounit_rust_init,
#endif
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
audiounit_init, audiounit_init,
#endif #endif
@ -189,6 +251,18 @@ cubeb_init(cubeb ** context, char const * context_name,
#endif #endif
#if defined(USE_SUN) #if defined(USE_SUN)
sun_init, 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 #endif
}; };
int i; int i;
@ -214,13 +288,26 @@ cubeb_init(cubeb ** context, char const * context_name,
return CUBEB_ERROR; 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() cubeb_get_backend_names()
{ {
static const char* backend_names[] = { static const char * const backend_names[] = {
#if defined(USE_PULSE) #if defined(USE_PULSE)
"pulse", "pulse",
#endif #endif
#if defined(USE_PULSE_RUST)
"pulse-rust",
#endif
#if defined(USE_JACK) #if defined(USE_JACK)
"jack", "jack",
#endif #endif
@ -230,6 +317,9 @@ cubeb_get_backend_names()
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
"audiounit", "audiounit",
#endif #endif
#if defined(USE_AUDIOUNIT_RUST)
"audiounit-rust",
#endif
#if defined(USE_WASAPI) #if defined(USE_WASAPI)
"wasapi", "wasapi",
#endif #endif
@ -239,23 +329,30 @@ cubeb_get_backend_names()
#if defined(USE_SNDIO) #if defined(USE_SNDIO)
"sndio", "sndio",
#endif #endif
#if defined(USE_SUN)
"sun",
#endif
#if defined(USE_OPENSL)
"opensl",
#endif
#if defined(USE_OSS) #if defined(USE_OSS)
"oss", "oss",
#endif #endif
NULL, #if defined(USE_AAUDIO)
"aaudio",
#endif
#if defined(USE_AUDIOTRACK)
"audiotrack",
#endif
#if defined(USE_KAI)
"kai",
#endif
}; };
return backend_names; return (cubeb_backend_names){
} .names = backend_names,
.count = NELEMS(backend_names),
char const * };
cubeb_get_backend_id(cubeb * context)
{
if (!context) {
return NULL;
}
return context->ops->get_backend_id(context);
} }
int int

View File

@ -213,12 +213,19 @@ struct cubeb_stream {
cubeb_device_changed_callback device_changed_callback = nullptr; cubeb_device_changed_callback device_changed_callback = nullptr;
owned_critical_section device_changed_callback_lock; owned_critical_section device_changed_callback_lock;
/* Stream creation parameters */ /* 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_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE}; CUBEB_STREAM_PREF_NONE,
cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_INPUT_PROCESSING_PARAM_NONE};
CUBEB_LAYOUT_UNDEFINED, cubeb_stream_params output_stream_params = {
CUBEB_STREAM_PREF_NONE}; CUBEB_SAMPLE_FLOAT32NE,
0,
0,
CUBEB_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE,
CUBEB_INPUT_PROCESSING_PARAM_NONE};
device_info input_device; device_info input_device;
device_info output_device; device_info output_device;
/* Format descriptions */ /* Format descriptions */

View File

@ -16,8 +16,8 @@
#include <time.h> #include <time.h>
#endif #endif
static std::atomic<cubeb_log_level> g_cubeb_log_level; std::atomic<cubeb_log_level> g_cubeb_log_level;
static std::atomic<cubeb_log_callback> g_cubeb_log_callback; std::atomic<cubeb_log_callback> g_cubeb_log_callback;
/** The maximum size of a log message, after having been formatted. */ /** The maximum size of a log message, after having been formatted. */
const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; 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>(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<lock_free_queue<cubeb_log_message> *> msg_queue = {nullptr};
std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue_consumer = {
nullptr};
std::atomic<bool> shutdown_thread = {false};
std::thread logging_thread;
};
void void
cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...) 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); 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 void
cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback) 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 // nullptr, to prevent a TOCTOU race between checking the pointer
if (log_callback && log_level != CUBEB_LOG_DISABLED) { if (log_callback && log_level != CUBEB_LOG_DISABLED) {
g_cubeb_log_callback = log_callback; g_cubeb_log_callback = log_callback;
if (log_level == CUBEB_LOG_VERBOSE) {
cubeb_async_logger::get().start();
}
} else if (!log_callback || CUBEB_LOG_DISABLED) { } else if (!log_callback || CUBEB_LOG_DISABLED) {
g_cubeb_log_callback = cubeb_noop_log_callback; 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 { } else {
assert(false && "Incorrect parameters passed to cubeb_log_set"); assert(false && "Incorrect parameters passed to cubeb_log_set");
} }

View File

@ -39,7 +39,12 @@ cubeb_log_get_callback(void);
void void
cubeb_log_internal_no_format(const char * msg); cubeb_log_internal_no_format(const char * msg);
void 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 #ifdef __cplusplus
} }
@ -55,9 +60,16 @@ cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...);
} \ } \
} while (0) } 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. */ /* Asynchronous logging macros to log in real-time callbacks. */
/* Should not be used on android due to the use of global/static variables. */ /* 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 ALOGV(msg, ...) ALOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define ALOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
#endif // CUBEB_LOG #endif // CUBEB_LOG

View File

@ -371,3 +371,9 @@ cubeb_resampler_latency(cubeb_resampler * resampler)
{ {
return resampler->latency(); return resampler->latency();
} }
cubeb_resampler_stats
cubeb_resampler_stats_get(cubeb_resampler * resampler)
{
return resampler->stats();
}

View File

@ -84,6 +84,20 @@ cubeb_resampler_destroy(cubeb_resampler * resampler);
long long
cubeb_resampler_latency(cubeb_resampler * resampler); 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) #if defined(__cplusplus)
} }
#endif #endif

View File

@ -56,6 +56,7 @@ struct cubeb_resampler {
virtual long fill(void * input_buffer, long * input_frames_count, virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long frames_needed) = 0; void * output_buffer, long frames_needed) = 0;
virtual long latency() = 0; virtual long latency() = 0;
virtual cubeb_resampler_stats stats() = 0;
virtual ~cubeb_resampler() {} virtual ~cubeb_resampler() {}
}; };
@ -86,6 +87,16 @@ public:
virtual long latency() { return 0; } 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() void drop_audio_if_needed()
{ {
uint32_t to_keep = min_buffered_audio_frame(sample_rate); 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, virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames_needed); 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() virtual long latency()
{ {
if (input_processor && output_processor) { 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 /** 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 * at least `output_frame_count` resampled frames. */
* slightly bigger than what is strictly necessary, but it guaranteed that the
* number of output frames will be exactly equal. */
uint32_t input_needed_for_output(int32_t output_frame_count) const uint32_t input_needed_for_output(int32_t output_frame_count) const
{ {
assert(output_frame_count >= 0); // Check overflow assert(output_frame_count >= 0); // Check overflow
int32_t unresampled_frames_left = int32_t unresampled_frames_left =
samples_to_frames(resampling_in_buffer.length()); samples_to_frames(resampling_in_buffer.length());
int32_t resampled_frames_left = float input_frames_needed_frac =
samples_to_frames(resampling_out_buffer.length()); static_cast<float>(output_frame_count) * resampling_ratio;
float input_frames_needed = // speex_resample()` can be irregular in its consumption of input samples.
(output_frame_count - unresampled_frames_left) * resampling_ratio - // Provide one more frame than the number that would be required with
resampled_frames_left; // regular consumption, to make the speex resampler behave more regularly,
if (input_frames_needed < 0) { // and so predictably.
return 0; auto input_frame_needed =
} 1 + static_cast<int32_t>(ceilf(input_frames_needed_frac));
return (uint32_t)ceilf(input_frames_needed); 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 /** 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 * least `frame_count` elements. This is useful so that consumer can
* write into the input buffer of the resampler. The pointer returned is * directly write into the input buffer of the resampler. The pointer
* adjusted so that leftover data are not overwritten. * returned is adjusted so that leftover data are not overwritten.
*/ */
T * input_buffer(size_t frame_count) T * input_buffer(size_t frame_count)
{ {
@ -312,8 +336,8 @@ public:
return resampling_in_buffer.data() + leftover_samples; return resampling_in_buffer.data() + leftover_samples;
} }
/** This method works with `input_buffer`, and allows to inform the processor /** This method works with `input_buffer`, and allows to inform the
how much frames have been written in the provided buffer. */ processor how much frames have been written in the provided buffer. */
void written(size_t written_frames) void written(size_t written_frames)
{ {
resampling_in_buffer.set_length(leftover_samples + 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: private:
/** Wrapper for the speex resampling functions to have a typed /** Wrapper for the speex resampling functions to have a typed
* interface. */ * interface. */
@ -359,6 +386,7 @@ private:
output_frame_count); output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS); assert(rv == RESAMPLER_ERR_SUCCESS);
} }
/** The state for the speex resampler used internaly. */ /** The state for the speex resampler used internaly. */
SpeexResamplerState * speex_resampler; SpeexResamplerState * speex_resampler;
/** Source rate / target rate. */ /** Source rate / target rate. */
@ -371,8 +399,8 @@ private:
auto_array<T> resampling_out_buffer; auto_array<T> resampling_out_buffer;
/** Additional latency inserted into the pipeline for synchronisation. */ /** Additional latency inserted into the pipeline for synchronisation. */
uint32_t additional_latency; uint32_t additional_latency;
/** When `input_buffer` is called, this allows tracking the number of samples /** When `input_buffer` is called, this allows tracking the number of
that were in the buffer. */ samples that were in the buffer. */
uint32_t leftover_samples; uint32_t leftover_samples;
}; };
@ -417,8 +445,8 @@ public:
return delay_output_buffer.data(); return delay_output_buffer.data();
} }
/** Get a pointer to the first writable location in the input buffer> /** 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 * @parameter frames_needed the number of frames the user needs to write
* the buffer. * into the buffer.
* @returns a pointer to a location in the input buffer where #frames_needed * @returns a pointer to a location in the input buffer where #frames_needed
* can be writen. */ * can be writen. */
T * input_buffer(uint32_t frames_needed) T * input_buffer(uint32_t frames_needed)
@ -428,8 +456,8 @@ public:
frames_to_samples(frames_needed)); frames_to_samples(frames_needed));
return delay_input_buffer.data() + leftover_samples; return delay_input_buffer.data() + leftover_samples;
} }
/** This method works with `input_buffer`, and allows to inform the processor /** This method works with `input_buffer`, and allows to inform the
how much frames have been written in the provided buffer. */ processor how much frames have been written in the provided buffer. */
void written(size_t frames_written) void written(size_t frames_written)
{ {
delay_input_buffer.set_length(leftover_samples + delay_input_buffer.set_length(leftover_samples +
@ -450,8 +478,8 @@ public:
return to_pop; return to_pop;
} }
/** Returns the number of frames one needs to input into the delay line to get /** Returns the number of frames one needs to input into the delay line to
* #frames_needed frames back. * get #frames_needed frames back.
* @parameter frames_needed the number of frames one want to write into the * @parameter frames_needed the number of frames one want to write into the
* delay_line * delay_line
* @returns the number of frames one will get. */ * @returns the number of frames one will get. */
@ -469,19 +497,23 @@ public:
void drop_audio_if_needed() 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); uint32_t to_keep = min_buffered_audio_frame(sample_rate);
if (available > to_keep) { if (available > to_keep) {
ALOGV("Dropping %u frames", available - to_keep); ALOGV("Dropping %u frames", available - to_keep);
delay_input_buffer.pop(nullptr, frames_to_samples(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: private:
/** The length, in frames, of this delay line */ /** The length, in frames, of this delay line */
uint32_t length; uint32_t length;
/** When `input_buffer` is called, this allows tracking the number of samples /** When `input_buffer` is called, this allows tracking the number of
that where in the buffer. */ samples that where in the buffer. */
uint32_t leftover_samples; uint32_t leftover_samples;
/** The input buffer, where the delay is applied. */ /** The input buffer, where the delay is applied. */
auto_array<T> delay_input_buffer; auto_array<T> delay_input_buffer;
@ -511,8 +543,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
"need at least one valid parameter pointer."); "need at least one valid parameter pointer.");
/* All the streams we have have a sample rate that matches the target /* 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 sample rate, use a no-op resampler, that simply forwards the buffers to
callback. */ the callback. */
if (((input_params && input_params->rate == target_rate) && if (((input_params && input_params->rate == target_rate) &&
(output_params && output_params->rate == target_rate)) || (output_params && output_params->rate == target_rate)) ||
(input_params && !output_params && (input_params->rate == target_rate)) || (input_params && !output_params && (input_params->rate == target_rate)) ||

View File

@ -4,8 +4,12 @@
* This program is made available under an ISC-style license. See the * This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0603 #define _WIN32_WINNT 0x0603
#endif // !_WIN32_WINNT
#ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
#endif // !NOMINMAX
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
@ -37,31 +41,6 @@
#include "cubeb_tracing.h" #include "cubeb_tracing.h"
#include "cubeb_utils.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. // Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
// Copy the interface definition from audioclient.h here to make the code // 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 // 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; 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; extern cubeb_ops const wasapi_ops;
static com_heap_ptr<wchar_t> static com_heap_ptr<wchar_t>
@ -304,8 +278,8 @@ wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
static int static int
wasapi_device_collection_destroy(cubeb * ctx, wasapi_device_collection_destroy(cubeb * ctx,
cubeb_device_collection * collection); cubeb_device_collection * collection);
static char const * static std::unique_ptr<char const[]>
wstr_to_utf8(wchar_t const * str); wstr_to_utf8(LPCWSTR str);
static std::unique_ptr<wchar_t const[]> static std::unique_ptr<wchar_t const[]>
utf8_to_wstr(char const * str); utf8_to_wstr(char const * str);
@ -314,6 +288,15 @@ utf8_to_wstr(char const * str);
class wasapi_collection_notification_client; class wasapi_collection_notification_client;
class monitor_device_notifications; 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 { struct cubeb {
cubeb_ops const * ops = &wasapi_ops; cubeb_ops const * ops = &wasapi_ops;
owned_critical_section lock; owned_critical_section lock;
@ -331,13 +314,6 @@ struct cubeb {
nullptr; nullptr;
void * output_collection_changed_user_ptr = nullptr; void * output_collection_changed_user_ptr = nullptr;
UINT64 performance_counter_frequency; 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_endpoint_notification_client;
@ -360,20 +336,33 @@ struct cubeb_stream {
/* Mixer pameters. We need to convert the input stream to this /* Mixer pameters. We need to convert the input stream to this
samplerate/channel layout, as WASAPI does not resample nor upmix samplerate/channel layout, as WASAPI does not resample nor upmix
itself. */ 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_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE}; CUBEB_STREAM_PREF_NONE,
cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_INPUT_PROCESSING_PARAM_NONE};
cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE,
0,
0,
CUBEB_LAYOUT_UNDEFINED, CUBEB_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE}; CUBEB_STREAM_PREF_NONE,
CUBEB_INPUT_PROCESSING_PARAM_NONE};
/* Stream parameters. This is what the client requested, /* Stream parameters. This is what the client requested,
* and what will be presented in the callback. */ * 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_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE}; CUBEB_STREAM_PREF_NONE,
cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_INPUT_PROCESSING_PARAM_NONE};
CUBEB_LAYOUT_UNDEFINED, cubeb_stream_params output_stream_params = {
CUBEB_STREAM_PREF_NONE}; 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. */ /* A MMDevice role for this stream: either communication or console here. */
ERole role; ERole role;
/* True if this stream will transport voice-data. */ /* True if this stream will transport voice-data. */
@ -662,6 +651,10 @@ public:
LPCWSTR device_id) LPCWSTR device_id)
{ {
LOG("collection: Audio device default changed, id = %S.", 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; return S_OK;
} }
@ -772,7 +765,7 @@ public:
LPCWSTR device_id) LPCWSTR device_id)
{ {
LOG("endpoint: Audio device default changed flow=%d role=%d " LOG("endpoint: Audio device default changed flow=%d role=%d "
"new_device_id=%ws.", "new_device_id=%S.",
flow, role, device_id); flow, role, device_id);
/* we only support a single stream type for now. */ /* we only support a single stream type for now. */
@ -783,11 +776,13 @@ public:
DWORD last_change_ms = timeGetTime() - last_device_change; DWORD last_change_ms = timeGetTime() - last_device_change;
bool same_device = default_device_id && device_id && bool same_device = default_device_id && device_id &&
wcscmp(default_device_id.get(), device_id) == 0; 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); last_change_ms, same_device);
if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) { if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) {
if (device_id) { 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 { } else {
default_device_id.reset(); default_device_id.reset();
} }
@ -863,16 +858,12 @@ intern_device_id(cubeb * ctx, wchar_t const * id)
auto_lock lock(ctx->lock); auto_lock lock(ctx->lock);
char const * tmp = wstr_to_utf8(id); std::unique_ptr<char const[]> tmp = wstr_to_utf8(id);
if (!tmp) { if (!tmp) {
return nullptr; return nullptr;
} }
char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); return cubeb_strings_intern(ctx->device_ids, tmp.get());
free((void *)tmp);
return interned;
} }
bool bool
@ -977,7 +968,7 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
cubeb_resampler_fill(stm->resampler.get(), input_buffer, cubeb_resampler_fill(stm->resampler.get(), input_buffer,
&input_frames_count, dest, output_frames_needed); &input_frames_count, dest, output_frames_needed);
if (out_frames < 0) { 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); wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return out_frames; return out_frames;
} }
@ -1263,8 +1254,8 @@ refill_callback_duplex(cubeb_stream * stm)
XASSERT(has_input(stm) && has_output(stm)); XASSERT(has_input(stm) && has_output(stm));
if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) { if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
HRESULT rv = get_input_buffer(stm); rv = get_input_buffer(stm);
if (FAILED(rv)) { if (!rv) {
return rv; return rv;
} }
} }
@ -1274,7 +1265,6 @@ refill_callback_duplex(cubeb_stream * stm)
rv = get_output_buffer(stm, output_buffer, output_frames); rv = get_output_buffer(stm, output_buffer, output_frames);
if (!rv) { if (!rv) {
hr = stm->render_client->ReleaseBuffer(output_frames, 0);
return rv; return rv;
} }
@ -1291,9 +1281,11 @@ refill_callback_duplex(cubeb_stream * stm)
stm->total_output_frames += output_frames; stm->total_output_frames += output_frames;
ALOGV("in: %zu, out: %zu, missing: %ld, ratio: %f", stm->total_input_frames, ALOGV("in: %llu, out: %llu, missing: %ld, ratio: %f",
stm->total_output_frames, (unsigned long long)stm->total_input_frames,
static_cast<long>(stm->total_output_frames) - stm->total_input_frames, (unsigned long long)stm->total_output_frames,
static_cast<long long>(stm->total_output_frames) -
static_cast<long long>(stm->total_input_frames),
static_cast<float>(stm->total_output_frames) / stm->total_input_frames); static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
long got; 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 /* We could consider using "Pro Audio" here for WebAudio and
maybe WebRTC. */ maybe WebRTC. */
mmcss_handle = mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
stm->context->set_mm_thread_characteristics(L"Audio", &mmcss_task_index);
if (!mmcss_handle) { if (!mmcss_handle) {
/* This is not fatal, but we might glitch under heavy load. */ /* This is not fatal, but we might glitch under heavy load. */
LOG("Unable to use mmcss to bump the render thread priority: %lx", 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); is_playing = stm->refill_callback(stm);
break; break;
case WAIT_OBJECT_0 + 3: { /* input available */ case WAIT_OBJECT_0 + 3: { /* input available */
HRESULT rv = get_input_buffer(stm); bool rv = get_input_buffer(stm);
if (FAILED(rv)) { if (!rv) {
is_playing = false; is_playing = false;
continue; continue;
} }
@ -1532,8 +1523,11 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
break; break;
} }
default: default:
LOG("case %lu not handled in render loop.", waitResult); LOG("render_loop: waitResult=%lu (lastError=%lu) unhandled, exiting",
XASSERT(false); 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) { if (mmcss_handle) {
stm->context->revert_mm_thread_characteristics(mmcss_handle); AvRevertMmThreadCharacteristics(mmcss_handle);
} }
if (FAILED(hr)) { if (FAILED(hr)) {
@ -1560,18 +1554,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
void void
wasapi_destroy(cubeb * context); 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 HRESULT
register_notification_client(cubeb_stream * stm) register_notification_client(cubeb_stream * stm)
{ {
@ -1807,31 +1789,6 @@ wasapi_init(cubeb ** context, char const * context_name)
ctx->performance_counter_frequency = 0; 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<set_mm_thread_characteristics_function>(
GetProcAddress(ctx->mmcss_module, "AvSetMmThreadCharacteristicsW"));
ctx->revert_mm_thread_characteristics =
reinterpret_cast<revert_mm_thread_characteristics_function>(
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; *context = ctx;
return CUBEB_OK; return CUBEB_OK;
@ -1839,7 +1796,6 @@ wasapi_init(cubeb ** context, char const * context_name)
} }
namespace { namespace {
enum ShutdownPhase { OnStop, OnDestroy };
bool bool
stop_and_join_render_thread(cubeb_stream * stm) stop_and_join_render_thread(cubeb_stream * stm)
@ -1855,16 +1811,7 @@ stop_and_join_render_thread(cubeb_stream * stm)
return false; return false;
} }
/* Wait five seconds for the rendering thread to return. It's supposed to DWORD r = WaitForSingleObject(stm->thread, INFINITE);
* 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;
}
}
if (r != WAIT_OBJECT_0) { if (r != WAIT_OBJECT_0) {
LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: " LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
"%lx, %lx", "%lx, %lx",
@ -1888,10 +1835,6 @@ wasapi_destroy(cubeb * context)
} }
} }
if (context->mmcss_module) {
FreeLibrary(context->mmcss_module);
}
delete context; delete context;
} }
@ -1949,44 +1892,6 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
return CUBEB_ERROR; 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<IAudioClient3> 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<IAudioClient> client; com_ptr<IAudioClient> client;
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
client.receive_vpp()); 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", LOG("default device period: %I64d, minimum device period: %I64d",
default_period, minimum_period); 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); *latency_frames = hns_to_frames(params.rate, default_period);
#endif
LOG("Minimum latency in frames: %u", *latency_frames); 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; 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_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);
return CUBEB_OK;
}
static void static void
waveformatex_update_derived_properties(WAVEFORMATEX * format) waveformatex_update_derived_properties(WAVEFORMATEX * format)
{ {
@ -2097,10 +2027,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
if (hr == S_FALSE) { if (hr == S_FALSE) {
/* Channel layout not supported, but WASAPI gives us a suggestion. Use it, /* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
and handle the eventual upmix/downmix ourselves. Ignore the subformat of and handle the eventual upmix/downmix ourselves. Ignore the subformat of
the suggestion, since it seems to always be IEEE_FLOAT. 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 */
LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
WAVEFORMATEXTENSIBLE * closest_pcm = WAVEFORMATEXTENSIBLE * closest_pcm =
@ -2122,7 +2049,8 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
} }
static int static int
initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client) initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client,
AudioClient2Option option)
{ {
com_ptr<IAudioClient2> audio_client2; com_ptr<IAudioClient2> audio_client2;
audio_client->QueryInterface<IAudioClient2>(audio_client2.receive()); audio_client->QueryInterface<IAudioClient2>(audio_client2.receive());
@ -2131,10 +2059,14 @@ initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
"AUDCLNT_STREAMOPTIONS_RAW."); "AUDCLNT_STREAMOPTIONS_RAW.");
return CUBEB_OK; return CUBEB_OK;
} }
AudioClientProperties properties = {0}; AudioClientProperties properties = {};
properties.cbSize = sizeof(AudioClientProperties); properties.cbSize = sizeof(AudioClientProperties);
#ifndef __MINGW32__ #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 #endif
HRESULT hr = audio_client2->SetClientProperties(&properties); HRESULT hr = audio_client2->SetClientProperties(&properties);
if (FAILED(hr)) { if (FAILED(hr)) {
@ -2144,12 +2076,12 @@ initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
return CUBEB_OK; return CUBEB_OK;
} }
#if 0
bool bool
initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client, initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
cubeb_stream * stm, cubeb_stream * stm,
const com_heap_ptr<WAVEFORMATEX> & mix_format, const com_heap_ptr<WAVEFORMATEX> & mix_format,
DWORD flags, EDataFlow direction, DWORD flags, EDataFlow direction)
REFERENCE_TIME latency_hns)
{ {
com_ptr<IAudioClient3> audio_client3; com_ptr<IAudioClient3> audio_client3;
audio_client->QueryInterface<IAudioClient3>(audio_client3.receive()); audio_client->QueryInterface<IAudioClient3>(audio_client3.receive());
@ -2165,22 +2097,24 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
return false; 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 // Possibly initialize a shared-mode stream using IAudioClient3. Initializing
// a stream this way lets you request lower latencies, but also locks the // a stream this way lets you request lower latencies, but also locks the
// global WASAPI engine at that latency. // global WASAPI engine at that latency.
// - If we request a shared-mode stream, streams created with IAudioClient // - If we request a shared-mode stream, streams created with IAudioClient
// might have their latency adjusted to match. When the shared-mode stream // will
// is closed, they'll go back to normal. // have their latency adjusted to match. When the shared-mode stream is
// - If there's already a shared-mode stream running, if it created with the // closed, they'll go back to normal.
// AUDCLNT_STREAMOPTIONS_MATCH_FORMAT option, the audio engine would be // - If there's already a shared-mode stream running, then we cannot request
// locked to that format, so we have to match it (a custom one would fail). // the engine change to a different latency - we have to match it.
// - We don't lock the WASAPI engine to a format, as it's antisocial towards // - It's antisocial to lock the WASAPI engine at its default latency. If we
// other apps, especially if we locked to a latency >= than its default. // would do this, then stop and use IAudioClient instead.
// - 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)).
HRESULT hr; HRESULT hr;
uint32_t default_period = 0, fundamental_period = 0, min_period = 0, uint32_t default_period = 0, fundamental_period = 0, min_period = 0,
@ -2192,59 +2126,28 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
LOG("Could not get shared mode engine period: error: %lx", hr); LOG("Could not get shared mode engine period: error: %lx", hr);
return false; return false;
} }
uint32_t requested_latency = uint32_t requested_latency = stm->latency;
hns_to_frames(mix_format->nSamplesPerSec, latency_hns);
#if !ALLOW_AUDIO_CLIENT_3_LATENCY_OVER_DEFAULT
if (requested_latency >= default_period) { if (requested_latency >= default_period) {
LOG("Requested latency %i equal or greater than default latency %i," LOG("Requested latency %i greater than default latency %i, not using "
" not using IAudioClient3", "IAudioClient3",
requested_latency, default_period); requested_latency, default_period);
return false; 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", LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i",
default_period, fundamental_period, min_period, max_period); default_period, fundamental_period, min_period, max_period);
// Snap requested latency to a valid value // Snap requested latency to a valid value
uint32_t old_requested_latency = requested_latency; 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) { if (requested_latency < min_period) {
requested_latency = min_period; requested_latency = min_period;
} }
// Likely unnecessary, but won't hurt requested_latency -= (requested_latency - min_period) % fundamental_period;
if (requested_latency > max_period) {
requested_latency = max_period;
}
if (requested_latency != old_requested_latency) { if (requested_latency != old_requested_latency) {
LOG("Requested latency %i was adjusted to %i", old_requested_latency, LOG("Requested latency %i was adjusted to %i", old_requested_latency,
requested_latency); requested_latency);
} }
DWORD new_flags = flags; hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency,
// 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,
mix_format.get(), NULL); 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)) { if (SUCCEEDED(hr)) {
return true; return true;
} else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) { } else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) {
@ -2256,37 +2159,22 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
} }
uint32_t current_period = 0; 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 // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise
// GetCurrentSharedModeEnginePeriod will return E_POINTER // GetCurrentSharedModeEnginePeriod will return E_POINTER
hr = audio_client3->GetCurrentSharedModeEnginePeriod(&current_format_ptr, hr = audio_client3->GetCurrentSharedModeEnginePeriod(&current_format,
&current_period); &current_period);
CoTaskMemFree(current_format);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get current shared mode engine period: error: %lx", hr); LOG("Could not get current shared mode engine period: error: %lx", hr);
return false; return false;
} }
com_heap_ptr<WAVEFORMATEX> 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 if (current_period >= default_period) {
// Reject IAudioClient3 if we can't respect the user target latency. LOG("Current shared mode engine period %i too high, not using IAudioClient",
// We don't need to check against default_latency anymore, current_period);
// 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);
return false; return false;
} }
#endif
hr = audio_client3->InitializeSharedAudioStream(flags, current_period, hr = audio_client3->InitializeSharedAudioStream(flags, current_period,
mix_format.get(), NULL); mix_format.get(), NULL);
@ -2299,6 +2187,7 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr);
return false; return false;
} }
#endif
#define DIRECTION_NAME (direction == eCapture ? "capture" : "render") #define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
@ -2322,12 +2211,6 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
return CUBEB_ERROR; 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(); stm->stream_reset_lock.assert_current_thread_owns();
// If user doesn't specify a particular device, we can choose another one when // If user doesn't specify a particular device, we can choose another one when
// the given devid is unavailable. // 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 /* Get a client. We will get all other interfaces we need from
* this pointer. */ * this pointer. */
if (allow_audio_client_3) { #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, NULL, hr = device->Activate(__uuidof(IAudioClient3),
audio_client.receive_vpp()); CLSCTX_INPROC_SERVER,
} NULL, audio_client.receive_vpp());
if (!allow_audio_client_3 || hr == E_NOINTERFACE) { if (hr == E_NOINTERFACE) {
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, #endif
audio_client.receive_vpp()); hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
audio_client.receive_vpp());
#if 0
} }
#endif
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not activate the device to get an audio" 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 (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()); LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
// This is not fatal. // 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 && #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction, if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
latency_hns)) {
LOG("Initialized with IAudioClient3"); LOG("Initialized with IAudioClient3");
} else { } else {
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, #endif
0, mix_format.get(), NULL); hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0,
mix_format.get(), NULL);
#if 0
} }
#endif
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -2970,6 +2876,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
} }
} }
cubeb_async_log_reset_threads();
stm->thread = stm->thread =
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
@ -3031,7 +2938,7 @@ wasapi_stream_add_ref(cubeb_stream * stm)
{ {
XASSERT(stm); XASSERT(stm);
LONG result = InterlockedIncrement(&stm->ref_count); 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; return result;
} }
@ -3041,7 +2948,7 @@ wasapi_stream_release(cubeb_stream * stm)
XASSERT(stm); XASSERT(stm);
LONG result = InterlockedDecrement(&stm->ref_count); 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) { if (result == 0) {
LOG("Stream ref count hit zero, destroying (%p)", stm); 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; return CUBEB_OK;
} }
static char const * static std::unique_ptr<char const[]>
wstr_to_utf8(LPCWSTR str) wstr_to_utf8(LPCWSTR str)
{ {
int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL); int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL);
@ -3311,8 +3218,8 @@ wstr_to_utf8(LPCWSTR str)
return nullptr; return nullptr;
} }
char * ret = static_cast<char *>(malloc(size)); std::unique_ptr<char[]> ret(new char[size]);
::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret.get(), size, NULL, NULL);
return ret; return ret;
} }
@ -3440,7 +3347,7 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
prop_variant namevar; prop_variant namevar;
hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar);
if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) { 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) { if (!ret.friendly_name) {
// This is not fatal, but a valid string is expected in all cases. // 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; prop_variant instancevar;
hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar);
if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) { 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 = ret.preferred =
(cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA | (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA |
CUBEB_DEVICE_PREF_NOTIFICATION); 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 = ret.preferred =
(cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); (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); CUBEB_DEVICE_FMT_S16NE);
ret.default_format = CUBEB_DEVICE_FMT_F32NE; ret.default_format = CUBEB_DEVICE_FMT_F32NE;
prop_variant fmtvar; prop_variant fmtvar;
WAVEFORMATEX * wfx = NULL;
hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar);
if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) {
if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { 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_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec;
ret.max_channels = pcm->wf.nChannels; ret.max_channels = pcm->wf.nChannels;
} else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
wfx = reinterpret_cast<WAVEFORMATEX *>(fmtvar.blob.pBlobData); WAVEFORMATEX * wfx =
reinterpret_cast<WAVEFORMATEX *>(fmtvar.blob.pBlobData);
if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
wfx->wFormatTag == WAVE_FORMAT_PCM) { 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 if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
// Here we assume an IAudioClient3 stream will successfully NULL, client.receive_vpp())) &&
// be initialized later (it might fail) SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
#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<IAudioClient3> 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))) {
ret.latency_lo = hns_to_frames(ret.default_rate, min_period); ret.latency_lo = hns_to_frames(ret.default_rate, min_period);
ret.latency_hi = hns_to_frames(ret.default_rate, def_period); ret.latency_hi = hns_to_frames(ret.default_rate, def_period);
} else { } else {
@ -3638,7 +3525,7 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
{ {
return wasapi_enumerate_devices_internal( return wasapi_enumerate_devices_internal(
context, type, out, context, type, out,
DEVICE_STATE_ACTIVE /*| DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED*/); DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED);
} }
static int static int
@ -3656,6 +3543,14 @@ wasapi_device_collection_destroy(cubeb * /*ctx*/,
return CUBEB_OK; 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 static int
wasapi_register_device_collection_changed( wasapi_register_device_collection_changed(
cubeb * context, cubeb_device_type devtype, 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_max_channel_count =*/wasapi_get_max_channel_count,
/*.get_min_latency =*/wasapi_get_min_latency, /*.get_min_latency =*/wasapi_get_min_latency,
/*.get_preferred_sample_rate =*/wasapi_get_preferred_sample_rate, /*.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, /*.enumerate_devices =*/wasapi_enumerate_devices,
/*.device_collection_destroy =*/wasapi_device_collection_destroy, /*.device_collection_destroy =*/wasapi_device_collection_destroy,
/*.destroy =*/wasapi_destroy, /*.destroy =*/wasapi_destroy,
@ -3751,7 +3647,7 @@ cubeb_ops const wasapi_ops = {
/*.stream_set_name =*/NULL, /*.stream_set_name =*/NULL,
/*.stream_get_current_device =*/NULL, /*.stream_get_current_device =*/NULL,
/*.stream_set_input_mute =*/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_device_destroy =*/NULL,
/*.stream_register_device_changed_callback =*/NULL, /*.stream_register_device_changed_callback =*/NULL,
/*.register_device_collection_changed =*/ /*.register_device_collection_changed =*/

View File

@ -41,10 +41,10 @@
#ifdef FLOATING_POINT #ifdef FLOATING_POINT
#error You cannot compile as floating point and fixed point at the same time #error You cannot compile as floating point and fixed point at the same time
#endif #endif
#ifdef _USE_SSE #ifdef USE_SSE
#error SSE is only for floating-point #error SSE is only for floating-point
#endif #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? #error Make up your mind. What CPU do you have?
#endif #endif
#ifdef VORBIS_PSYCHO #ifdef VORBIS_PSYCHO
@ -56,10 +56,10 @@
#ifndef FLOATING_POINT #ifndef FLOATING_POINT
#error You now need to define either FIXED_POINT or FLOATING_POINT #error You now need to define either FIXED_POINT or FLOATING_POINT
#endif #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? #error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions?
#endif #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?" #error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?"
#endif #endif
@ -117,9 +117,9 @@ typedef spx_word32_t spx_sig_t;
#ifdef ARM5E_ASM #ifdef ARM5E_ASM
#include "fixed_arm5e.h" #include "fixed_arm5e.h"
#elif defined (ARM4_ASM) #elif defined(ARM4_ASM)
#include "fixed_arm4.h" #include "fixed_arm4.h"
#elif defined (BFIN_ASM) #elif defined(BFIN_ASM)
#include "fixed_bfin.h" #include "fixed_bfin.h"
#endif #endif
@ -177,16 +177,13 @@ typedef float spx_word32_t;
#define ADD32(a,b) ((a)+(b)) #define ADD32(a,b) ((a)+(b))
#define SUB32(a,b) ((a)-(b)) #define SUB32(a,b) ((a)-(b))
#define MULT16_16_16(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 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 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_Q15(a,b) ((a)*(b))
#define MULT16_32_P15(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_32_Q15(c,a,b) ((c)+(a)*(b))
#define MAC16_16_Q11(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 #endif
#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) #if defined(CONFIG_TI_C54X) || defined(CONFIG_TI_C55X)
/* 2 on TI C5x DSP */ /* 2 on TI C5x DSP */
#define BYTES_PER_CHAR 2 #define BYTES_PER_CHAR 2

View File

@ -69,22 +69,18 @@
/* result fits in 16 bits */ /* 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 */ /* (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 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 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 MULT16_32_P15(a,b) ADD32(MULT16_32_32(a,SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15))
#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_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 MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15))
#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))
#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)))
#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) #define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11)))

View File

@ -46,7 +46,7 @@
Smith, Julius O. Digital Audio Resampling Home Page Smith, Julius O. Digital Audio Resampling Home Page
Center for Computer Research in Music and Acoustics (CCRMA), Center for Computer Research in Music and Acoustics (CCRMA),
Stanford University, 2007. 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 There is one main difference, though. This resampler uses cubic
interpolation instead of linear interpolation in the above paper. This interpolation instead of linear interpolation in the above paper. This
@ -63,9 +63,12 @@
#ifdef OUTSIDE_SPEEX #ifdef OUTSIDE_SPEEX
#include <stdlib.h> #include <stdlib.h>
static void *speex_alloc (int size) {return calloc(size,1);} 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_realloc(void *ptr, int size) {return realloc(ptr, size);}
static void speex_free (void *ptr) {free(ptr);} static void speex_free(void *ptr) {free(ptr);}
#ifndef EXPORT
#define EXPORT
#endif
#include "speex_resampler.h" #include "speex_resampler.h"
#include "arch.h" #include "arch.h"
#else /* OUTSIDE_SPEEX */ #else /* OUTSIDE_SPEEX */
@ -75,7 +78,6 @@ static void speex_free (void *ptr) {free(ptr);}
#include "os_support.h" #include "os_support.h"
#endif /* OUTSIDE_SPEEX */ #endif /* OUTSIDE_SPEEX */
#include "stack_alloc.h"
#include <math.h> #include <math.h>
#include <limits.h> #include <limits.h>
@ -91,18 +93,18 @@ static void speex_free (void *ptr) {free(ptr);}
#endif #endif
#ifndef UINT32_MAX #ifndef UINT32_MAX
#define UINT32_MAX 4294967296U #define UINT32_MAX 4294967295U
#endif #endif
#ifdef _USE_SSE #ifdef USE_SSE
#include "resample_sse.h" #include "resample_sse.h"
#endif #endif
#ifdef _USE_NEON #ifdef USE_NEON
#include "resample_neon.h" #include "resample_neon.h"
#endif #endif
/* Numer of elements to allocate on the stack */ /* Number of elements to allocate on the stack */
#ifdef VAR_ARRAYS #ifdef VAR_ARRAYS
#define FIXED_STACK_ALLOC 8192 #define FIXED_STACK_ALLOC 8192
#else #else
@ -194,16 +196,14 @@ struct FuncDef {
int oversample; int oversample;
}; };
static const struct FuncDef _KAISER12 = {kaiser12_table, 64}; static const struct FuncDef kaiser12_funcdef = {kaiser12_table, 64};
#define KAISER12 (&_KAISER12) #define KAISER12 (&kaiser12_funcdef)
/*static struct FuncDef _KAISER12 = {kaiser12_table, 32}; static const struct FuncDef kaiser10_funcdef = {kaiser10_table, 32};
#define KAISER12 (&_KAISER12)*/ #define KAISER10 (&kaiser10_funcdef)
static const struct FuncDef _KAISER10 = {kaiser10_table, 32}; static const struct FuncDef kaiser8_funcdef = {kaiser8_table, 32};
#define KAISER10 (&_KAISER10) #define KAISER8 (&kaiser8_funcdef)
static const struct FuncDef _KAISER8 = {kaiser8_table, 32}; static const struct FuncDef kaiser6_funcdef = {kaiser6_table, 32};
#define KAISER8 (&_KAISER8) #define KAISER6 (&kaiser6_funcdef)
static const struct FuncDef _KAISER6 = {kaiser6_table, 32};
#define KAISER6 (&_KAISER6)
struct QualityMapping { struct QualityMapping {
int base_length; int base_length;
@ -473,7 +473,7 @@ static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint3
} }
cubic_coef(frac, interp); 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); sum = SATURATE32PSHR(sum, 15, 32767);
#else #else
cubic_coef(frac, interp); 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 int frac_advance = st->frac_advance;
const spx_uint32_t den_rate = st->den_rate; 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)) while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
{ {
out[out_stride * out_sample++] = 0; 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; 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 / den;
spx_uint32_t major = value / div; spx_uint32_t remain = value % den;
spx_uint32_t remainder = value % div;
/* TODO: Could use 64 bits operation to check for overflow. But only guaranteed in C99+ */ /* TODO: Could use 64 bits operation to check for overflow. But only guaranteed in C99+ */
if (remainder > UINT32_MAX / mul || major > UINT32_MAX / mul if (remain > UINT32_MAX / num || major > UINT32_MAX / num
|| major * mul > UINT32_MAX - remainder * mul / div) || major * num > UINT32_MAX - remain * num / den)
return RESAMPLER_ERR_OVERFLOW; return RESAMPLER_ERR_OVERFLOW;
*result = remainder * mul / div + major * mul; *result = remain * num / den + major * num;
return RESAMPLER_ERR_SUCCESS; return RESAMPLER_ERR_SUCCESS;
} }
@ -619,7 +619,7 @@ static int update_filter(SpeexResamplerState *st)
{ {
/* down-sampling */ /* down-sampling */
st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; 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; goto fail;
/* Round up to make sure we have a multiple of 8 for SSE */ /* Round up to make sure we have a multiple of 8 for SSE */
st->filt_len = ((st->filt_len-1)&(~0x7))+8; 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; st->cutoff = quality_map[st->quality].upsample_bandwidth;
} }
/* Choose the resampling type that requires the least amount of memory */
#ifdef RESAMPLE_FULL_SINC_TABLE #ifdef RESAMPLE_FULL_SINC_TABLE
use_direct = 1; use_direct = 1;
if (INT_MAX/sizeof(spx_word16_t)/st->den_rate < st->filt_len) if (INT_MAX/sizeof(spx_word16_t)/st->den_rate < st->filt_len)
goto fail; goto fail;
#else #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 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; && INT_MAX/sizeof(spx_word16_t)/st->den_rate >= st->filt_len;
#endif #endif
@ -733,16 +733,18 @@ static int update_filter(SpeexResamplerState *st)
{ {
spx_uint32_t j; spx_uint32_t j;
spx_uint32_t olen = old_length; 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])*/ /*if (st->magic_samples[i])*/
{ {
/* Try and remove the magic samples as if nothing had happened */ /* 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 */ /* 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]; olen = old_length + 2*magic_samples;
for (j=old_length-1+st->magic_samples[i];j--;) for (j=old_length-1+magic_samples;j--;)
st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; st->mem[start+j+magic_samples] = st->mem[i*old_alloc_size+j];
for (j=0;j<st->magic_samples[i];j++) for (j=0;j<magic_samples;j++)
st->mem[i*st->mem_alloc_size+j] = 0; st->mem[start+j] = 0;
st->magic_samples[i] = 0; st->magic_samples[i] = 0;
} }
if (st->filt_len > olen) 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 */ /* If the new filter length is still bigger than the "augmented" length */
/* Copy data going backward */ /* Copy data going backward */
for (j=0;j<olen-1;j++) for (j=0;j<olen-1;j++)
st->mem[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 */ /* Then put zeros for lack of anything better */
for (;j<st->filt_len-1;j++) for (;j<st->filt_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 */ /* Adjust last_sample */
st->last_sample[i] += (st->filt_len - olen)/2; st->last_sample[i] += (st->filt_len - olen)/2;
} else { } else {
/* Put back some of the magic! */ /* Put back some of the magic! */
st->magic_samples[i] = (olen - st->filt_len)/2; magic_samples = (olen - st->filt_len)/2;
for (j=0;j<st->filt_len-1+st->magic_samples[i];j++) for (j=0;j<st->filt_len-1+magic_samples;j++)
st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; st->mem[start+j] = st->mem[start+j+magic_samples];
st->magic_samples[i] = magic_samples;
} }
} }
} else if (st->filt_len < old_length) } 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); const spx_uint32_t xlen = st->mem_alloc_size - (st->filt_len - 1);
#ifdef VAR_ARRAYS #ifdef VAR_ARRAYS
const unsigned int ylen = (olen < FIXED_STACK_ALLOC) ? olen : FIXED_STACK_ALLOC; const unsigned int ylen = (olen < FIXED_STACK_ALLOC) ? olen : FIXED_STACK_ALLOC;
VARDECL(spx_word16_t *ystack); spx_word16_t ystack[ylen];
ALLOC(ystack, ylen, spx_word16_t);
#else #else
const unsigned int ylen = FIXED_STACK_ALLOC; const unsigned int ylen = FIXED_STACK_ALLOC;
spx_word16_t ystack[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; *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) 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->num_rate = ratio_num;
st->den_rate = ratio_den; 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->num_rate /= fact;
st->den_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;i<st->nb_channels;i++) for (i=0;i<st->nb_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; return RESAMPLER_ERR_OVERFLOW;
/* Safety net */ /* Safety net */
if (st->samp_frac_num[i] >= st->den_rate) if (st->samp_frac_num[i] >= st->den_rate)

View File

@ -36,14 +36,26 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <arm_neon.h> #include <stdint.h>
#ifdef FIXED_POINT #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) { static inline int32_t saturate_32bit_to_16bit(int32_t a) {
int32_t ret; int32_t ret;
asm ("ssat %[ret], #16, %[a]" asm ("ssat %[ret], #16, %[a]"
: [ret] "=&r" (ret) : [ret] "=r" (ret)
: [a] "r" (a) : [a] "r" (a)
: ); : );
return ret; return ret;
@ -54,7 +66,7 @@ static inline int32_t saturate_32bit_to_16bit(int32_t a) {
asm ("vmov.s32 d0[0], %[a]\n" asm ("vmov.s32 d0[0], %[a]\n"
"vqmovn.s32 d0, q0\n" "vqmovn.s32 d0, q0\n"
"vmov.s16 %[ret], d0[0]\n" "vmov.s16 %[ret], d0[0]\n"
: [ret] "=&r" (ret) : [ret] "=r" (ret)
: [a] "r" (a) : [a] "r" (a)
: "q0"); : "q0");
return ret; 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 WORD2INT(x) (saturate_32bit_to_16bit(x))
#define OVERRIDE_INNER_PRODUCT_SINGLE #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) static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len)
{ {
int32_t ret; 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" " vqmovn.s64 d0, q0\n"
" vqrshrn.s32 d0, q0, #15\n" " vqrshrn.s32 d0, q0, #15\n"
" vmov.s16 %[ret], d0[0]\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) [len] "+r" (len), [remainder] "+r" (remainder)
: :
: "cc", "q0", : "cc", "q0",
"d16", "d17", "d18", "d19", "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23");
"d20", "d21", "d22", "d23");
return ret; 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) { static inline int32_t saturate_float_to_16bit(float a) {
int32_t ret; int32_t ret;
asm ("vmov.f32 d0[0], %[a]\n" asm ("vmov.f32 d0[0], %[a]\n"
"vcvt.s32.f32 d0, d0, #15\n" "vcvt.s32.f32 d0, d0, #15\n"
"vqrshrn.s32 d0, q0, #15\n" "vqrshrn.s32 d0, q0, #15\n"
"vmov.s16 %[ret], d0[0]\n" "vmov.s16 %[ret], d0[0]\n"
: [ret] "=&r" (ret) : [ret] "=r" (ret)
: [a] "r" (a) : [a] "r" (a)
: "q0"); : "q0");
return ret; return ret;
} }
#endif
#undef WORD2INT #undef WORD2INT
#define WORD2INT(x) (saturate_float_to_16bit(x)) #define WORD2INT(x) (saturate_float_to_16bit(x))
#define OVERRIDE_INNER_PRODUCT_SINGLE #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) static inline float inner_product_single(const float *a, const float *b, unsigned int len)
{ {
float ret; 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" " vadd.f32 d0, d0, d1\n"
" vpadd.f32 d0, d0, d0\n" " vpadd.f32 d0, d0, d0\n"
" vmov.f32 %[ret], d0[0]\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) [len] "+l" (len), [remainder] "+l" (remainder)
: :
: "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", : "cc", "q0", "q1", "q2", "q3",
"q9", "q10", "q11"); "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11");
return ret; return ret;
} }
#endif // defined(__aarch64__)
#endif #endif

View File

@ -71,7 +71,7 @@ static inline float interpolate_product_single(const float *a, const float *b, u
return ret; return ret;
} }
#ifdef _USE_SSE2 #ifdef USE_SSE2
#include <emmintrin.h> #include <emmintrin.h>
#define OVERRIDE_INNER_PRODUCT_DOUBLE #define OVERRIDE_INNER_PRODUCT_DOUBLE

View File

@ -288,9 +288,9 @@ std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebDriverName
std::vector<std::pair<std::string, std::string>> names; std::vector<std::pair<std::string, std::string>> names;
names.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default")); names.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"));
const char** cubeb_names = cubeb_get_backend_names(); auto cubeb_names = cubeb_get_backend_names();
for (u32 i = 0; cubeb_names[i] != nullptr; i++) for (int i = 0; i < cubeb_names.count; i++)
names.emplace_back(cubeb_names[i], cubeb_names[i]); names.emplace_back(cubeb_names.names[i], cubeb_names.names[i]);
return names; return names;
} }