diff --git a/dep/cubeb/CMakeLists.txt b/dep/cubeb/CMakeLists.txt index 378cc5866..5e48b7f89 100644 --- a/dep/cubeb/CMakeLists.txt +++ b/dep/cubeb/CMakeLists.txt @@ -110,6 +110,18 @@ if(USE_OPENSL) target_link_libraries(cubeb PRIVATE OpenSLES) endif() +check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H) +if(HAVE_SYS_SOUNDCARD_H) + try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests" + ${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c) + if(USE_OSS) + target_sources(cubeb PRIVATE + src/cubeb_oss.c) + target_compile_definitions(cubeb PRIVATE USE_OSS) + target_link_libraries(cubeb PRIVATE pthread) + endif() +endif() + check_include_files(android/log.h USE_AUDIOTRACK) if(USE_AUDIOTRACK) target_sources(cubeb PRIVATE diff --git a/dep/cubeb/include/cubeb/cubeb.h b/dep/cubeb/include/cubeb/cubeb.h index b3fc56d2c..e88536f0a 100644 --- a/dep/cubeb/include/cubeb/cubeb.h +++ b/dep/cubeb/include/cubeb/cubeb.h @@ -230,12 +230,22 @@ typedef enum { CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching default device on OS changes. */ - CUBEB_STREAM_PREF_VOICE = 0x04 /**< This stream is going to transport voice data. + CUBEB_STREAM_PREF_VOICE = 0x04, /**< This stream is going to transport voice data. Depending on the backend and platform, this can change the audio input or output devices selected, as well as the quality of the stream, for example to accomodate bluetooth SCO modes on bluetooth devices. */ + CUBEB_STREAM_PREF_RAW = 0x08, /**< Windows only. Bypass all signal processing + except for always on APO, driver and hardware. */ + CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute settings + should persist across restarts of the stream + and/or application. May not be honored for + all backends and platforms. */ + + CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to connect + ports. Only affects the jack + backend. */ } cubeb_stream_prefs; /** Stream format initialization parameters. */ @@ -487,11 +497,17 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context); cubeb stream. @param stream_name A name for this stream. @param input_device Device for the input side of the stream. If NULL the - default input device is used. + default input device is used. Passing a valid cubeb_devid + means the stream only ever uses that device. Passing a NULL + cubeb_devid allows the stream to follow that device type's + OS default. @param input_stream_params Parameters for the input side of the stream, or NULL if this stream is output only. @param output_device Device for the output side of the stream. If NULL the - default output device is used. + default output device is used. Passing a valid cubeb_devid + means the stream only ever uses that device. Passing a NULL + cubeb_devid allows the stream to follow that device type's + OS default. @param output_stream_params Parameters for the output side of the stream, or NULL if this stream is input only. @param latency_frames Stream latency in frames. Valid range @@ -559,6 +575,16 @@ CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * pos @retval CUBEB_ERROR */ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency); +/** Get the input latency for this stream, in frames. This is the number of + frames between the time the audio input devices records the data, and they + are available in the data callback. + This returns CUBEB_ERROR when the stream is output-only. + @param stream + @param latency Current approximate stream latency in frames. + @retval CUBEB_OK + @retval CUBEB_ERROR_NOT_SUPPORTED + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency); /** Set the volume for a stream. @param stream the stream for which to adjust the volume. @param volume a float between 0.0 (muted) and 1.0 (maximum volume) @@ -568,6 +594,14 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume); +/** Change a stream's name. + @param stream the stream for which to set the name. + @param stream_name the new name for the stream + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid + @retval CUBEB_ERROR_NOT_SUPPORTED */ +CUBEB_EXPORT int cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name); + /** Get the current output device for this stream. @param stm the stream for which to query the current output device @param device a pointer in which the current output device will be stored. diff --git a/dep/cubeb/src/cubeb-internal.h b/dep/cubeb/src/cubeb-internal.h index 312a9ea3a..9d3d1dd2f 100644 --- a/dep/cubeb/src/cubeb-internal.h +++ b/dep/cubeb/src/cubeb-internal.h @@ -63,7 +63,9 @@ struct cubeb_ops { int (* stream_reset_default_device)(cubeb_stream * stream); int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); + int (* stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency); int (* stream_set_volume)(cubeb_stream * stream, float volumes); + int (* stream_set_name)(cubeb_stream * stream, char const * stream_name); int (* stream_get_current_device)(cubeb_stream * stream, cubeb_device ** const device); int (* stream_device_destroy)(cubeb_stream * stream, diff --git a/dep/cubeb/src/cubeb.c b/dep/cubeb/src/cubeb.c index a43d012a4..74c17dbe6 100644 --- a/dep/cubeb/src/cubeb.c +++ b/dep/cubeb/src/cubeb.c @@ -60,6 +60,9 @@ int sun_init(cubeb ** context, char const * context_name); #if defined(USE_OPENSL) int opensl_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_OSS) +int oss_init(cubeb ** context, char const * context_name); +#endif #if defined(USE_AUDIOTRACK) int audiotrack_init(cubeb ** context, char const * context_name); #endif @@ -80,7 +83,7 @@ validate_stream_params(cubeb_stream_params * input_stream_params, } if (input_stream_params) { if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 || - input_stream_params->channels < 1 || input_stream_params->channels > 8) { + input_stream_params->channels < 1 || input_stream_params->channels > UINT8_MAX) { return CUBEB_ERROR_INVALID_FORMAT; } } @@ -165,6 +168,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam } else if (!strcmp(backend_name, "opensl")) { #if defined(USE_OPENSL) init_oneshot = opensl_init; +#endif + } else if (!strcmp(backend_name, "oss")) { +#if defined(USE_OSS) + init_oneshot = oss_init; #endif } else if (!strcmp(backend_name, "audiotrack")) { #if defined(USE_AUDIOTRACK) @@ -200,12 +207,15 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam #if defined(USE_ALSA) alsa_init, #endif -#if defined(USE_AUDIOUNIT) - audiounit_init, +#if defined (USE_OSS) + oss_init, #endif #if defined(USE_AUDIOUNIT_RUST) audiounit_rust_init, #endif +#if defined(USE_AUDIOUNIT) + audiounit_init, +#endif #if defined(USE_WASAPI) wasapi_init, #endif @@ -420,6 +430,20 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency) return stream->context->ops->stream_get_latency(stream, latency); } +int +cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency) +{ + if (!stream || !latency) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_get_input_latency) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_get_input_latency(stream, latency); +} + int cubeb_stream_set_volume(cubeb_stream * stream, float volume) { @@ -434,6 +458,20 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume) return stream->context->ops->stream_set_volume(stream, volume); } +int +cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name) +{ + if (!stream || !stream_name) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_set_name) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_set_name(stream, stream_name); +} + int cubeb_stream_get_current_device(cubeb_stream * stream, cubeb_device ** const device) { diff --git a/dep/cubeb/src/cubeb_alsa.c b/dep/cubeb/src/cubeb_alsa.c index a564fbfc6..deac27112 100644 --- a/dep/cubeb/src/cubeb_alsa.c +++ b/dep/cubeb/src/cubeb_alsa.c @@ -1444,7 +1444,9 @@ static struct cubeb_ops const alsa_ops = { .stream_reset_default_device = NULL, .stream_get_position = alsa_stream_get_position, .stream_get_latency = alsa_stream_get_latency, + .stream_get_input_latency = NULL, .stream_set_volume = alsa_stream_set_volume, + .stream_set_name = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/dep/cubeb/src/cubeb_audiotrack.c b/dep/cubeb/src/cubeb_audiotrack.c index 22f1fe0bc..6496df042 100644 --- a/dep/cubeb/src/cubeb_audiotrack.c +++ b/dep/cubeb/src/cubeb_audiotrack.c @@ -433,7 +433,9 @@ static struct cubeb_ops const audiotrack_ops = { .stream_reset_default_device = NULL, .stream_get_position = audiotrack_stream_get_position, .stream_get_latency = audiotrack_stream_get_latency, + .stream_get_input_latency = NULL, .stream_set_volume = audiotrack_stream_set_volume, + .stream_set_name = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/dep/cubeb/src/cubeb_audiounit.cpp b/dep/cubeb/src/cubeb_audiounit.cpp index 4e89a7d5a..f0586a15b 100644 --- a/dep/cubeb/src/cubeb_audiounit.cpp +++ b/dep/cubeb/src/cubeb_audiounit.cpp @@ -2871,6 +2871,15 @@ audiounit_stream_destroy_internal(cubeb_stream *stm) static void audiounit_stream_destroy(cubeb_stream * stm) { + int r = audiounit_uninstall_system_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not uninstall the device changed callback", stm); + } + r = audiounit_uninstall_device_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not uninstall all device change listeners", stm); + } + if (!stm->shutdown.load()){ auto_lock context_lock(stm->context->mutex); audiounit_stream_stop_internal(stm); @@ -3612,7 +3621,9 @@ cubeb_ops const audiounit_ops = { /*.stream_reset_default_device =*/ nullptr, /*.stream_get_position =*/ audiounit_stream_get_position, /*.stream_get_latency =*/ audiounit_stream_get_latency, + /*.stream_get_input_latency =*/ NULL, /*.stream_set_volume =*/ audiounit_stream_set_volume, + /*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/ audiounit_stream_get_current_device, /*.stream_device_destroy =*/ audiounit_stream_device_destroy, /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback, diff --git a/dep/cubeb/src/cubeb_jack.cpp b/dep/cubeb/src/cubeb_jack.cpp index 9fc97a4b8..5c1567268 100644 --- a/dep/cubeb/src/cubeb_jack.cpp +++ b/dep/cubeb/src/cubeb_jack.cpp @@ -132,7 +132,9 @@ static struct cubeb_ops const cbjack_ops = { .stream_reset_default_device = NULL, .stream_get_position = cbjack_stream_get_position, .stream_get_latency = cbjack_get_latency, + .stream_get_input_latency = NULL, .stream_set_volume = cbjack_stream_set_volume, + .stream_set_name = NULL, .stream_get_current_device = cbjack_stream_get_current_device, .stream_device_destroy = cbjack_stream_device_destroy, .stream_register_device_changed_callback = NULL, @@ -237,6 +239,22 @@ load_jack_lib(cubeb * context) return CUBEB_OK; } +static void +cbjack_connect_port_out (cubeb_stream * stream, const size_t out_port, const char * const phys_in_port) +{ + const char *src_port = api_jack_port_name (stream->output_ports[out_port]); + + api_jack_connect (stream->context->jack_client, src_port, phys_in_port); +} + +static void +cbjack_connect_port_in (cubeb_stream * stream, const char * const phys_out_port, size_t in_port) +{ + const char *src_port = api_jack_port_name (stream->input_ports[in_port]); + + api_jack_connect (stream->context->jack_client, phys_out_port, src_port); +} + static int cbjack_connect_ports (cubeb_stream * stream) { @@ -256,10 +274,14 @@ cbjack_connect_ports (cubeb_stream * stream) // Connect outputs to playback for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) { - const char *src_port = api_jack_port_name (stream->output_ports[c]); - - api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]); + cbjack_connect_port_out(stream, c, phys_in_ports[c]); } + + // Special case playing mono source in stereo + if (stream->out_params.channels == 1 && phys_in_ports[1] != NULL) { + cbjack_connect_port_out(stream, 0, phys_in_ports[1]); + } + r = CUBEB_OK; skipplayback: @@ -268,9 +290,7 @@ skipplayback: } // Connect inputs to capture for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) { - const char *src_port = api_jack_port_name (stream->input_ports[c]); - - api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port); + cbjack_connect_port_in(stream, phys_out_ports[c], c); } r = CUBEB_OK; end: @@ -289,9 +309,9 @@ cbjack_xrun_callback(void * arg) cubeb * ctx = (cubeb *)arg; float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client); - int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate ) - / (float)(ctx->jack_buffer_size) ); - ctx->jack_xruns += fragments; + float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) / ctx->jack_buffer_size); + + ctx->jack_xruns += (unsigned int)fragments; return 0; } @@ -331,9 +351,11 @@ static int cbjack_process(jack_nframes_t nframes, void * arg) { cubeb * ctx = (cubeb *)arg; - int t_jack_xruns = ctx->jack_xruns; + unsigned int t_jack_xruns = ctx->jack_xruns; int i; + ctx->jack_xruns = 0; + for (int j = 0; j < MAX_STREAMS; j++) { cubeb_stream *stm = &ctx->streams[j]; float *bufs_out[stm->out_params.channels]; @@ -343,10 +365,7 @@ cbjack_process(jack_nframes_t nframes, void * arg) continue; // handle xruns by skipping audio that should have been played - for (i = 0; i < t_jack_xruns; i++) { - stm->position += ctx->fragment_size * stm->ratio; - } - ctx->jack_xruns -= t_jack_xruns; + stm->position += t_jack_xruns * ctx->fragment_size * stm->ratio; if (!stm->ports_ready) continue; @@ -888,10 +907,12 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_ } } - if (cbjack_connect_ports(stm) != CUBEB_OK) { - pthread_mutex_unlock(&stm->mutex); - cbjack_stream_destroy(stm); - return CUBEB_ERROR; + if (!input_stream_params->prefs & CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT) { + if (cbjack_connect_ports(stm) != CUBEB_OK) { + pthread_mutex_unlock(&stm->mutex); + cbjack_stream_destroy(stm); + return CUBEB_ERROR; + } } *stream = stm; diff --git a/dep/cubeb/src/cubeb_kai.c b/dep/cubeb/src/cubeb_kai.c index 5333968c8..36eaf126a 100644 --- a/dep/cubeb/src/cubeb_kai.c +++ b/dep/cubeb/src/cubeb_kai.c @@ -361,7 +361,9 @@ static struct cubeb_ops const kai_ops = { /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ kai_stream_get_position, /*.stream_get_latency = */ kai_stream_get_latency, + /*.stream_get_input_latency = */ NULL, /*.stream_set_volume =*/ kai_stream_set_volume, + /*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/ NULL, diff --git a/dep/cubeb/src/cubeb_log.h b/dep/cubeb/src/cubeb_log.h index a79976bb3..446e29a5c 100644 --- a/dep/cubeb/src/cubeb_log.h +++ b/dep/cubeb/src/cubeb_log.h @@ -16,8 +16,15 @@ extern "C" { #if defined(__GNUC__) || defined(__clang__) #define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args))) +#if defined(__FILE_NAME__) +#define __FILENAME__ __FILE_NAME__ +#else +#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__) +#endif #else #define PRINTF_FORMAT(fmt, args) +#include +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #endif extern cubeb_log_level g_cubeb_log_level; @@ -32,10 +39,10 @@ void cubeb_async_log_reset_threads(); #define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) #define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) -#define LOG_INTERNAL(level, fmt, ...) do { \ - if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ - g_cubeb_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ - } \ +#define LOG_INTERNAL(level, fmt, ...) do { \ + if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ + g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, ##__VA_ARGS__); \ + } \ } while(0) /* Asynchronous verbose logging, to log in real-time callbacks. */ diff --git a/dep/cubeb/src/cubeb_opensl.c b/dep/cubeb/src/cubeb_opensl.c index 021390e66..f34ab7ac5 100644 --- a/dep/cubeb/src/cubeb_opensl.c +++ b/dep/cubeb/src/cubeb_opensl.c @@ -168,7 +168,8 @@ struct cubeb_stream { int64_t lastPosition; int64_t lastPositionTimeStamp; int64_t lastCompensativePosition; - int voice; + int voice_input; + int voice_output; }; /* Forward declaration. */ @@ -959,8 +960,8 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) // Voice recognition is the lowest latency, according to the docs. Camcorder // uses a microphone that is in the same direction as the camera. - SLint32 streamType = stm->voice ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION - : SL_ANDROID_RECORDING_PRESET_CAMCORDER; + SLint32 streamType = stm->voice_input ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION + : SL_ANDROID_RECORDING_PRESET_CAMCORDER; res = (*recorderConfig) ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, @@ -1185,7 +1186,7 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { } SLint32 streamType = SL_ANDROID_STREAM_MEDIA; - if (stm->voice) { + if (stm->voice_output) { streamType = SL_ANDROID_STREAM_VOICE; } res = (*playerConfig)->SetConfiguration(playerConfig, @@ -1385,10 +1386,11 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name stm->input_enabled = (input_stream_params) ? 1 : 0; stm->output_enabled = (output_stream_params) ? 1 : 0; stm->shutdown = 1; - stm->voice = has_pref_set(input_stream_params, output_stream_params, CUBEB_STREAM_PREF_VOICE); - - LOG("cubeb stream prefs: voice: %s", stm->voice ? "true" : "false"); + stm->voice_input = has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE); + stm->voice_output = has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE); + LOG("cubeb stream prefs: voice_input: %s voice_output: %s", stm->voice_input ? "true" : "false", + stm->voice_output ? "true" : "false"); #ifdef DEBUG pthread_mutexattr_t attr; @@ -1752,7 +1754,9 @@ static struct cubeb_ops const opensl_ops = { .stream_reset_default_device = NULL, .stream_get_position = opensl_stream_get_position, .stream_get_latency = opensl_stream_get_latency, + .stream_get_input_latency = NULL, .stream_set_volume = opensl_stream_set_volume, + .stream_set_name = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/dep/cubeb/src/cubeb_oss.c b/dep/cubeb/src/cubeb_oss.c new file mode 100644 index 000000000..3348cdc3d --- /dev/null +++ b/dep/cubeb/src/cubeb_oss.c @@ -0,0 +1,1261 @@ +/* + * Copyright © 2019-2020 Nia Alarie + * Copyright © 2020 Ka Ho Ng + * Copyright © 2020 The FreeBSD Foundation + * + * Portions of this software were developed by Ka Ho Ng + * under sponsorship from the FreeBSD Foundation. + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cubeb/cubeb.h" +#include "cubeb_mixer.h" +#include "cubeb_strings.h" +#include "cubeb-internal.h" + +/* Supported well by most hardware. */ +#ifndef OSS_PREFER_RATE +#define OSS_PREFER_RATE (48000) +#endif + +/* Standard acceptable minimum. */ +#ifndef OSS_LATENCY_MS +#define OSS_LATENCY_MS (8) +#endif + +#ifndef OSS_NFRAGS +#define OSS_NFRAGS (4) +#endif + +#ifndef OSS_DEFAULT_DEVICE +#define OSS_DEFAULT_DEVICE "/dev/dsp" +#endif + +#ifndef OSS_DEFAULT_MIXER +#define OSS_DEFAULT_MIXER "/dev/mixer" +#endif + +#define ENV_AUDIO_DEVICE "AUDIO_DEVICE" + +#ifndef OSS_MAX_CHANNELS +# if defined(__FreeBSD__) || defined(__DragonFly__) +/* + * The current maximum number of channels supported + * on FreeBSD is 8. + * + * Reference: FreeBSD 12.1-RELEASE + */ +# define OSS_MAX_CHANNELS (8) +# elif defined(__sun__) +/* + * The current maximum number of channels supported + * on Illumos is 16. + * + * Reference: PSARC 2008/318 + */ +# define OSS_MAX_CHANNELS (16) +# else +# define OSS_MAX_CHANNELS (2) +# endif +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) +#define SNDSTAT_BEGIN_STR "Installed devices:" +#define SNDSTAT_USER_BEGIN_STR "Installed devices from userspace:" +#define SNDSTAT_FV_BEGIN_STR "File Versions:" +#endif + +static struct cubeb_ops const oss_ops; + +struct cubeb { + struct cubeb_ops const * ops; + + /* Our intern string store */ + pthread_mutex_t mutex; /* protects devid_strs */ + cubeb_strings *devid_strs; +}; + +struct oss_stream { + oss_devnode_t name; + int fd; + void * buf; + + struct stream_info { + int channels; + int sample_rate; + int fmt; + int precision; + } info; + + unsigned int frame_size; /* precision in bytes * channels */ + bool floating; +}; + +struct cubeb_stream { + struct cubeb * context; + void * user_ptr; + pthread_t thread; + bool doorbell; /* (m) */ + pthread_cond_t doorbell_cv; /* (m) */ + pthread_cond_t stopped_cv; /* (m) */ + pthread_mutex_t mtx; /* Members protected by this should be marked (m) */ + bool thread_created; /* (m) */ + bool running; /* (m) */ + bool destroying; /* (m) */ + cubeb_state state; /* (m) */ + float volume /* (m) */; + struct oss_stream play; + struct oss_stream record; + cubeb_data_callback data_cb; + cubeb_state_callback state_cb; + uint64_t frames_written /* (m) */; + unsigned int nfr; /* Number of frames allocated */ + unsigned int nfrags; + unsigned int bufframes; +}; + +static char const * +oss_cubeb_devid_intern(cubeb *context, char const * devid) +{ + char const *is; + pthread_mutex_lock(&context->mutex); + is = cubeb_strings_intern(context->devid_strs, devid); + pthread_mutex_unlock(&context->mutex); + return is; +} + +int +oss_init(cubeb **context, char const *context_name) { + cubeb * c; + + (void)context_name; + if ((c = calloc(1, sizeof(cubeb))) == NULL) { + return CUBEB_ERROR; + } + + if (cubeb_strings_init(&c->devid_strs) == CUBEB_ERROR) { + goto fail; + } + + if (pthread_mutex_init(&c->mutex, NULL) != 0) { + goto fail; + } + + c->ops = &oss_ops; + *context = c; + return CUBEB_OK; + +fail: + cubeb_strings_destroy(c->devid_strs); + free(c); + return CUBEB_ERROR; +} + +static void +oss_destroy(cubeb * context) +{ + pthread_mutex_destroy(&context->mutex); + cubeb_strings_destroy(context->devid_strs); + free(context); +} + +static char const * +oss_get_backend_id(cubeb * context) +{ + return "oss"; +} + +static int +oss_get_preferred_sample_rate(cubeb * context, uint32_t * rate) +{ + (void)context; + + *rate = OSS_PREFER_RATE; + return CUBEB_OK; +} + +static int +oss_get_max_channel_count(cubeb * context, uint32_t * max_channels) +{ + (void)context; + + *max_channels = OSS_MAX_CHANNELS; + return CUBEB_OK; +} + +static int +oss_get_min_latency(cubeb * context, cubeb_stream_params params, + uint32_t * latency_frames) +{ + (void)context; + + *latency_frames = (OSS_LATENCY_MS * params.rate) / 1000; + return CUBEB_OK; +} + +static void +oss_free_cubeb_device_info_strings(cubeb_device_info *cdi) +{ + free((char *)cdi->device_id); + free((char *)cdi->friendly_name); + free((char *)cdi->group_id); + cdi->device_id = NULL; + cdi->friendly_name = NULL; + cdi->group_id = NULL; +} + +#if defined(__FreeBSD__) || defined(__DragonFly__) +/* + * Check if the specified DSP is okay for the purpose specified + * in type. Here type can only specify one operation each time + * this helper is called. + * + * Return 0 if OK, otherwise 1. + */ +static int +oss_probe_open(const char *dsppath, cubeb_device_type type, + int *fdp, oss_audioinfo *resai) +{ + oss_audioinfo ai; + int error; + int oflags = (type == CUBEB_DEVICE_TYPE_INPUT) ? O_RDONLY : O_WRONLY; + int dspfd = open(dsppath, oflags); + if (dspfd == -1) + return 1; + + ai.dev = -1; + error = ioctl(dspfd, SNDCTL_AUDIOINFO, &ai); + if (error < 0) { + close(dspfd); + return 1; + } + + if (resai) + *resai = ai; + if (fdp) + *fdp = dspfd; + else + close(dspfd); + return 0; +} + +struct sndstat_info { + oss_devnode_t devname; + const char *desc; + cubeb_device_type type; + int preferred; +}; + +static int +oss_sndstat_line_parse(char *line, int is_ud, struct sndstat_info *sinfo) +{ + char *matchptr = line, *n = NULL; + struct sndstat_info res; + + memset(&res, 0, sizeof(res)); + + n = strchr(matchptr, ':'); + if (n == NULL) + goto fail; + if (is_ud == 0) { + unsigned int devunit; + + if (sscanf(matchptr, "pcm%u: ", &devunit) < 1) + goto fail; + + if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1) + goto fail; + } else { + if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/"))) + goto fail; + + strlcpy(res.devname, "/dev/", sizeof(res.devname)); + strncat(res.devname, matchptr, n - matchptr); + } + matchptr = n + 1; + + n = strchr(matchptr, '<'); + if (n == NULL) + goto fail; + matchptr = n + 1; + n = strrchr(matchptr, '>'); + if (n == NULL) + goto fail; + *n = 0; + res.desc = matchptr; + matchptr = n + 1; + + n = strchr(matchptr, '('); + if (n == NULL) + goto fail; + matchptr = n + 1; + n = strrchr(matchptr, ')'); + if (n == NULL) + goto fail; + *n = 0; + if (!isdigit(matchptr[0])) { + if (strstr(matchptr, "play") != NULL) + res.type |= CUBEB_DEVICE_TYPE_OUTPUT; + if (strstr(matchptr, "rec") != NULL) + res.type |= CUBEB_DEVICE_TYPE_INPUT; + } else { + int p, r; + if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2) + goto fail; + if (p > 0) + res.type |= CUBEB_DEVICE_TYPE_OUTPUT; + if (r > 0) + res.type |= CUBEB_DEVICE_TYPE_INPUT; + } + matchptr = n + 1; + if (strstr(matchptr, "default") != NULL) + res.preferred = 1; + + *sinfo = res; + return 0; + +fail: + return 1; +} + +/* + * XXX: On FreeBSD we have to rely on SNDCTL_CARDINFO to get all + * the usable audio devices currently, as SNDCTL_AUDIOINFO will + * never return directly usable audio device nodes. + */ +static int +oss_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + cubeb_device_info *devinfop = NULL; + char *line = NULL; + size_t linecap = 0; + FILE *sndstatfp = NULL; + int collection_cnt = 0; + int is_ud = 0; + int skipall = 0; + + devinfop = calloc(1, sizeof(cubeb_device_info)); + if (devinfop == NULL) + goto fail; + + sndstatfp = fopen("/dev/sndstat", "r"); + if (sndstatfp == NULL) + goto fail; + while (getline(&line, &linecap, sndstatfp) > 0) { + const char *devid = NULL; + struct sndstat_info sinfo; + oss_audioinfo ai; + + if (!strncmp(line, SNDSTAT_FV_BEGIN_STR, strlen(SNDSTAT_FV_BEGIN_STR))) { + skipall = 1; + continue; + } + if (!strncmp(line, SNDSTAT_BEGIN_STR, strlen(SNDSTAT_BEGIN_STR))) { + is_ud = 0; + skipall = 0; + continue; + } + if (!strncmp(line, SNDSTAT_USER_BEGIN_STR, strlen(SNDSTAT_USER_BEGIN_STR))) { + is_ud = 1; + skipall = 0; + continue; + } + if (skipall || isblank(line[0])) + continue; + + if (oss_sndstat_line_parse(line, is_ud, &sinfo)) + continue; + + devinfop[collection_cnt].type = 0; + switch (sinfo.type) { + case CUBEB_DEVICE_TYPE_INPUT: + if (type & CUBEB_DEVICE_TYPE_OUTPUT) + continue; + break; + case CUBEB_DEVICE_TYPE_OUTPUT: + if (type & CUBEB_DEVICE_TYPE_INPUT) + continue; + break; + case 0: + continue; + } + + if (oss_probe_open(sinfo.devname, type, NULL, &ai)) + continue; + + devid = oss_cubeb_devid_intern(context, sinfo.devname); + if (devid == NULL) + continue; + + devinfop[collection_cnt].device_id = strdup(sinfo.devname); + asprintf((char **)&devinfop[collection_cnt].friendly_name, "%s: %s", + sinfo.devname, sinfo.desc); + devinfop[collection_cnt].group_id = strdup(sinfo.devname); + devinfop[collection_cnt].vendor_name = NULL; + if (devinfop[collection_cnt].device_id == NULL || + devinfop[collection_cnt].friendly_name == NULL || + devinfop[collection_cnt].group_id == NULL) { + oss_free_cubeb_device_info_strings(&devinfop[collection_cnt]); + continue; + } + + devinfop[collection_cnt].type = type; + devinfop[collection_cnt].devid = devid; + devinfop[collection_cnt].state = CUBEB_DEVICE_STATE_ENABLED; + devinfop[collection_cnt].preferred = + (sinfo.preferred) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; + devinfop[collection_cnt].format = CUBEB_DEVICE_FMT_S16NE; + devinfop[collection_cnt].default_format = CUBEB_DEVICE_FMT_S16NE; + devinfop[collection_cnt].max_channels = ai.max_channels; + devinfop[collection_cnt].default_rate = OSS_PREFER_RATE; + devinfop[collection_cnt].max_rate = ai.max_rate; + devinfop[collection_cnt].min_rate = ai.min_rate; + devinfop[collection_cnt].latency_lo = 0; + devinfop[collection_cnt].latency_hi = 0; + + collection_cnt++; + + void *newp = reallocarray(devinfop, collection_cnt + 1, + sizeof(cubeb_device_info)); + if (newp == NULL) + goto fail; + devinfop = newp; + } + + free(line); + fclose(sndstatfp); + + collection->count = collection_cnt; + collection->device = devinfop; + + return CUBEB_OK; + +fail: + free(line); + if (sndstatfp) + fclose(sndstatfp); + free(devinfop); + return CUBEB_ERROR; +} + +#else + +static int +oss_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + oss_sysinfo si; + int error, i; + cubeb_device_info *devinfop = NULL; + int collection_cnt = 0; + int mixer_fd = -1; + + mixer_fd = open(OSS_DEFAULT_MIXER, O_RDWR); + if (mixer_fd == -1) { + LOG("Failed to open mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno); + return CUBEB_ERROR; + } + + error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si); + if (error) { + LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno); + goto fail; + } + + devinfop = calloc(si.numaudios, sizeof(cubeb_device_info)); + if (devinfop == NULL) + goto fail; + + collection->count = 0; + for (i = 0; i < si.numaudios; i++) { + oss_audioinfo ai; + cubeb_device_info cdi = { 0 }; + const char *devid = NULL; + + ai.dev = i; + error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai); + if (error) + goto fail; + + assert(ai.dev < si.numaudios); + if (!ai.enabled) + continue; + + cdi.type = 0; + switch (ai.caps & DSP_CAP_DUPLEX) { + case DSP_CAP_INPUT: + if (type & CUBEB_DEVICE_TYPE_OUTPUT) + continue; + break; + case DSP_CAP_OUTPUT: + if (type & CUBEB_DEVICE_TYPE_INPUT) + continue; + break; + case 0: + continue; + } + cdi.type = type; + + devid = oss_cubeb_devid_intern(context, ai.devnode); + cdi.device_id = strdup(ai.name); + cdi.friendly_name = strdup(ai.name); + cdi.group_id = strdup(ai.name); + if (devid == NULL || cdi.device_id == NULL || cdi.friendly_name == NULL || + cdi.group_id == NULL) { + oss_free_cubeb_device_info_strings(&cdi); + continue; + } + + cdi.devid = devid; + cdi.vendor_name = NULL; + cdi.state = CUBEB_DEVICE_STATE_ENABLED; + cdi.preferred = CUBEB_DEVICE_PREF_NONE; + cdi.format = CUBEB_DEVICE_FMT_S16NE; + cdi.default_format = CUBEB_DEVICE_FMT_S16NE; + cdi.max_channels = ai.max_channels; + cdi.default_rate = OSS_PREFER_RATE; + cdi.max_rate = ai.max_rate; + cdi.min_rate = ai.min_rate; + cdi.latency_lo = 0; + cdi.latency_hi = 0; + + devinfop[collection_cnt++] = cdi; + } + + collection->count = collection_cnt; + collection->device = devinfop; + + if (mixer_fd != -1) + close(mixer_fd); + return CUBEB_OK; + +fail: + if (mixer_fd != -1) + close(mixer_fd); + free(devinfop); + return CUBEB_ERROR; +} + +#endif + +static int +oss_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) +{ + size_t i; + for (i = 0; i < collection->count; i++) { + oss_free_cubeb_device_info_strings(&collection->device[i]); + } + free(collection->device); + collection->device = NULL; + collection->count = 0; + return 0; +} + +static unsigned int +oss_chn_from_cubeb(cubeb_channel chn) +{ + switch (chn) { + case CHANNEL_FRONT_LEFT: + return CHID_L; + case CHANNEL_FRONT_RIGHT: + return CHID_R; + case CHANNEL_FRONT_CENTER: + return CHID_C; + case CHANNEL_LOW_FREQUENCY: + return CHID_LFE; + case CHANNEL_BACK_LEFT: + return CHID_LR; + case CHANNEL_BACK_RIGHT: + return CHID_RR; + case CHANNEL_SIDE_LEFT: + return CHID_LS; + case CHANNEL_SIDE_RIGHT: + return CHID_RS; + default: + return CHID_UNDEF; + } +} + +static unsigned long long +oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout) +{ + unsigned int i, nchns = 0; + unsigned long long chnorder = 0; + + for (i = 0; layout; i++, layout >>= 1) { + unsigned long long chid = oss_chn_from_cubeb((layout & 1) << i); + if (chid == CHID_UNDEF) + continue; + + chnorder |= (chid & 0xf) << nchns * 4; + nchns++; + } + + return chnorder; +} + +static int +oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params, + struct stream_info * sinfo) +{ + unsigned long long chnorder; + + sinfo->channels = params->channels; + sinfo->sample_rate = params->rate; + switch (params->format) { + case CUBEB_SAMPLE_S16LE: + sinfo->fmt = AFMT_S16_LE; + sinfo->precision = 16; + break; + case CUBEB_SAMPLE_S16BE: + sinfo->fmt = AFMT_S16_BE; + sinfo->precision = 16; + break; + case CUBEB_SAMPLE_FLOAT32NE: + sinfo->fmt = AFMT_S32_NE; + sinfo->precision = 32; + break; + default: + LOG("Unsupported format"); + return CUBEB_ERROR_INVALID_FORMAT; + } + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &sinfo->channels) == -1) { + return CUBEB_ERROR; + } + if (ioctl(fd, SNDCTL_DSP_SETFMT, &sinfo->fmt) == -1) { + return CUBEB_ERROR; + } + if (ioctl(fd, SNDCTL_DSP_SPEED, &sinfo->sample_rate) == -1) { + return CUBEB_ERROR; + } + /* Mono layout is an exception */ + if (params->layout != CUBEB_LAYOUT_UNDEFINED && params->layout != CUBEB_LAYOUT_MONO) { + chnorder = oss_cubeb_layout_to_chnorder(params->layout); + if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1) + LOG("Non-fatal error %d occured when setting channel order.", errno); + } + return CUBEB_OK; +} + +static int +oss_stream_stop(cubeb_stream * s) +{ + pthread_mutex_lock(&s->mtx); + if (s->thread_created && s->running) { + s->running = false; + s->doorbell = false; + pthread_cond_wait(&s->stopped_cv, &s->mtx); + } + if (s->state != CUBEB_STATE_STOPPED) { + s->state = CUBEB_STATE_STOPPED; + pthread_mutex_unlock(&s->mtx); + s->state_cb(s, s->user_ptr, CUBEB_STATE_STOPPED); + } else { + pthread_mutex_unlock(&s->mtx); + } + return CUBEB_OK; +} + +static void +oss_stream_destroy(cubeb_stream * s) +{ + pthread_mutex_lock(&s->mtx); + if (s->thread_created) { + s->destroying = true; + s->doorbell = true; + pthread_cond_signal(&s->doorbell_cv); + } + pthread_mutex_unlock(&s->mtx); + pthread_join(s->thread, NULL); + + pthread_cond_destroy(&s->doorbell_cv); + pthread_cond_destroy(&s->stopped_cv); + pthread_mutex_destroy(&s->mtx); + if (s->play.fd != -1) { + close(s->play.fd); + } + if (s->record.fd != -1) { + close(s->record.fd); + } + free(s->play.buf); + free(s->record.buf); + free(s); +} + +static void +oss_float_to_linear32(void * buf, unsigned sample_count, float vol) +{ + float * in = buf; + int32_t * out = buf; + int32_t * tail = out + sample_count; + + while (out < tail) { + int64_t f = *(in++) * vol * 0x80000000LL; + if (f < -INT32_MAX) + f = -INT32_MAX; + else if (f > INT32_MAX) + f = INT32_MAX; + *(out++) = f; + } +} + +static void +oss_linear32_to_float(void * buf, unsigned sample_count) +{ + int32_t * in = buf; + float * out = buf; + float * tail = out + sample_count; + + while (out < tail) { + *(out++) = (1.0 / 0x80000000LL) * *(in++); + } +} + +static void +oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol) +{ + unsigned i; + int32_t multiplier = vol * 0x8000; + + for (i = 0; i < sample_count; ++i) { + buf[i] = (buf[i] * multiplier) >> 15; + } +} + +/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */ +static int +oss_audio_loop(cubeb_stream * s, cubeb_state *new_state) +{ + cubeb_state state = CUBEB_STATE_STOPPED; + int trig = 0; + int drain = 0; + struct pollfd pfds[2]; + unsigned int ppending, rpending; + + pfds[0].fd = s->play.fd; + pfds[0].events = POLLOUT; + pfds[1].fd = s->record.fd; + pfds[1].events = POLLIN; + + ppending = 0; + rpending = s->bufframes; + + if (s->record.fd != -1) { + if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) { + LOG("Error %d occured when setting trigger on record fd", errno); + state = CUBEB_STATE_ERROR; + goto breakdown; + } + trig |= PCM_ENABLE_INPUT; + if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) { + LOG("Error %d occured when setting trigger on record fd", errno); + state = CUBEB_STATE_ERROR; + goto breakdown; + } + memset(s->record.buf, 0, s->bufframes * s->record.frame_size); + } + + while (1) { + long nfr = 0; + + pthread_mutex_lock(&s->mtx); + if (!s->running || s->destroying) { + pthread_mutex_unlock(&s->mtx); + break; + } + pthread_mutex_unlock(&s->mtx); + if (s->play.fd == -1 && s->record.fd == -1) { + /* + * Stop here if the stream is not play & record stream, + * play-only stream or record-only stream + */ + + goto breakdown; + } + + while ((s->bufframes - ppending) >= s->nfr && rpending >= s->nfr) { + long n = ((s->bufframes - ppending) < rpending) ? s->bufframes - ppending : rpending; + char *rptr = NULL, *pptr = NULL; + if (s->record.fd != -1) + rptr = (char *)s->record.buf; + if (s->play.fd != -1) + pptr = (char *)s->play.buf + ppending * s->play.frame_size; + if (s->record.fd != -1 && s->record.floating) { + oss_linear32_to_float(s->record.buf, s->record.info.channels * n); + } + nfr = s->data_cb(s, s->user_ptr, rptr, pptr, n); + if (nfr == CUBEB_ERROR) { + state = CUBEB_STATE_ERROR; + goto breakdown; + } + if (pptr) { + float vol; + + pthread_mutex_lock(&s->mtx); + vol = s->volume; + pthread_mutex_unlock(&s->mtx); + + if (s->play.floating) { + oss_float_to_linear32(pptr, s->play.info.channels * nfr, vol); + } else { + oss_linear16_set_vol((int16_t *)pptr, s->play.info.channels * nfr, vol); + } + } + if (pptr) { + ppending += nfr; + assert(ppending <= s->bufframes); + } + if (rptr) { + assert(rpending >= nfr); + rpending -= nfr; + memmove(rptr, rptr + nfr * s->record.frame_size, + (s->bufframes - nfr) * s->record.frame_size); + } + if (nfr < n) { + if (s->play.fd != -1) { + drain = 1; + break; + } else { + /* + * This is a record-only stream and number of frames + * returned from data_cb() is smaller than number + * of frames required to read. Stop here. + */ + + state = CUBEB_STATE_STOPPED; + goto breakdown; + } + } + } + + ssize_t n, frames; + int nfds; + + pfds[0].revents = 0; + pfds[1].revents = 0; + + nfds = poll(pfds, 2, 1000); + if (nfds == -1) { + if (errno == EINTR) + continue; + LOG("Error %d occured when polling playback and record fd", errno); + state = CUBEB_STATE_ERROR; + goto breakdown; + } else if (nfds == 0) + continue; + + if ((pfds[0].revents & (POLLERR | POLLHUP)) || + (pfds[1].revents & (POLLERR | POLLHUP))) { + LOG("Error occured on playback, record fds"); + state = CUBEB_STATE_ERROR; + goto breakdown; + } + + if (pfds[0].revents) { + while (ppending > 0) { + size_t bytes = ppending * s->play.frame_size; + if ((n = write(s->play.fd, (uint8_t *)s->play.buf, bytes)) < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) { + if (drain) + continue; + break; + } + state = CUBEB_STATE_ERROR; + goto breakdown; + } + frames = n / s->play.frame_size; + pthread_mutex_lock(&s->mtx); + s->frames_written += frames; + pthread_mutex_unlock(&s->mtx); + ppending -= frames; + memmove(s->play.buf, (uint8_t *)s->play.buf + n, + (s->bufframes - frames) * s->play.frame_size); + } + } + if (pfds[1].revents) { + while (s->bufframes - rpending > 0) { + size_t bytes = (s->bufframes - rpending) * s->record.frame_size; + size_t read_ofs = rpending * s->record.frame_size; + if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + break; + state = CUBEB_STATE_ERROR; + goto breakdown; + } + frames = n / s->record.frame_size; + rpending += frames; + } + } + if (drain) { + state = CUBEB_STATE_DRAINED; + goto breakdown; + } + } + + return 1; + +breakdown: + pthread_mutex_lock(&s->mtx); + *new_state = s->state = state; + s->running = false; + pthread_mutex_unlock(&s->mtx); + return 0; +} + +static void * +oss_io_routine(void *arg) +{ + cubeb_stream *s = arg; + cubeb_state new_state; + int stopped; + + do { + pthread_mutex_lock(&s->mtx); + if (s->destroying) { + pthread_mutex_unlock(&s->mtx); + break; + } + pthread_mutex_unlock(&s->mtx); + + stopped = oss_audio_loop(s, &new_state); + if (s->record.fd != -1) + ioctl(s->record.fd, SNDCTL_DSP_HALT_INPUT, NULL); + if (!stopped) + s->state_cb(s, s->user_ptr, new_state); + + pthread_mutex_lock(&s->mtx); + pthread_cond_signal(&s->stopped_cv); + if (s->destroying) { + pthread_mutex_unlock(&s->mtx); + break; + } + while (!s->doorbell) { + pthread_cond_wait(&s->doorbell_cv, &s->mtx); + } + s->doorbell = false; + pthread_mutex_unlock(&s->mtx); + } while (1); + + pthread_mutex_lock(&s->mtx); + s->thread_created = false; + pthread_mutex_unlock(&s->mtx); + return NULL; +} + +static inline int +oss_calc_frag_shift(unsigned int frames, unsigned int frame_size) +{ + int n = 4; + int blksize = (frames * frame_size + OSS_NFRAGS - 1) / OSS_NFRAGS; + while ((1 << n) < blksize) + n++; + return n; +} + +static inline int +oss_get_frag_params(unsigned int shift) +{ + return (OSS_NFRAGS << 16) | shift; +} + +static int +oss_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) +{ + int ret = CUBEB_OK; + unsigned int playnfr = 0, recnfr = 0; + cubeb_stream *s = NULL; + const char *defdsp; + + if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0') + defdsp = OSS_DEFAULT_DEVICE; + + (void)stream_name; + if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + s->state = CUBEB_STATE_STOPPED; + s->record.fd = s->play.fd = -1; + s->nfr = latency_frames; + if (input_device != NULL) { + strlcpy(s->record.name, input_device, sizeof(s->record.name)); + } else { + strlcpy(s->record.name, defdsp, sizeof(s->record.name)); + } + if (output_device != NULL) { + strlcpy(s->play.name, output_device, sizeof(s->play.name)); + } else { + strlcpy(s->play.name, defdsp, sizeof(s->play.name)); + } + if (input_stream_params != NULL) { + unsigned int nb_channels; + if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + LOG("Loopback not supported"); + ret = CUBEB_ERROR_NOT_SUPPORTED; + goto error; + } + nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout); + if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED && + nb_channels != input_stream_params->channels) { + LOG("input_stream_params->layout does not match input_stream_params->channels"); + ret = CUBEB_ERROR_INVALID_PARAMETER; + goto error; + } + if (s->record.fd == -1) { + if ((s->record.fd = open(s->record.name, O_RDONLY | O_NONBLOCK)) == -1) { + LOG("Audio device \"%s\" could not be opened as read-only", + s->record.name); + ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; + goto error; + } + } + if ((ret = oss_copy_params(s->record.fd, s, input_stream_params, + &s->record.info)) != CUBEB_OK) { + LOG("Setting record params failed"); + goto error; + } + s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); + s->record.frame_size = s->record.info.channels * (s->record.info.precision / 8); + recnfr = (1 << oss_calc_frag_shift(s->nfr, s->record.frame_size)) / s->record.frame_size; + } + if (output_stream_params != NULL) { + unsigned int nb_channels; + if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + LOG("Loopback not supported"); + ret = CUBEB_ERROR_NOT_SUPPORTED; + goto error; + } + nb_channels = cubeb_channel_layout_nb_channels(output_stream_params->layout); + if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED && + nb_channels != output_stream_params->channels) { + LOG("output_stream_params->layout does not match output_stream_params->channels"); + ret = CUBEB_ERROR_INVALID_PARAMETER; + goto error; + } + if (s->play.fd == -1) { + if ((s->play.fd = open(s->play.name, O_WRONLY | O_NONBLOCK)) == -1) { + LOG("Audio device \"%s\" could not be opened as write-only", + s->play.name); + ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; + goto error; + } + } + if ((ret = oss_copy_params(s->play.fd, s, output_stream_params, + &s->play.info)) != CUBEB_OK) { + LOG("Setting play params failed"); + goto error; + } + s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); + s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8); + playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) / s->play.frame_size; + } + /* Use the largest nframes among playing and recording streams */ + s->nfr = (playnfr > recnfr) ? playnfr : recnfr; + s->nfrags = OSS_NFRAGS; + s->bufframes = s->nfr * s->nfrags; + if (s->play.fd != -1) { + int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size)); + if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) + LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", + frag); + } + if (s->record.fd != -1) { + int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size)); + if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) + LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", + frag); + } + s->context = context; + s->volume = 1.0; + s->state_cb = state_callback; + s->data_cb = data_callback; + s->user_ptr = user_ptr; + + if (pthread_mutex_init(&s->mtx, NULL) != 0) { + LOG("Failed to create mutex"); + goto error; + } + if (pthread_cond_init(&s->doorbell_cv, NULL) != 0) { + LOG("Failed to create cv"); + goto error; + } + if (pthread_cond_init(&s->stopped_cv, NULL) != 0) { + LOG("Failed to create cv"); + goto error; + } + s->doorbell = false; + + if (s->play.fd != -1) { + if ((s->play.buf = calloc(s->bufframes, s->play.frame_size)) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + } + if (s->record.fd != -1) { + if ((s->record.buf = calloc(s->bufframes, s->record.frame_size)) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + } + + *stream = s; + return CUBEB_OK; +error: + if (s != NULL) { + oss_stream_destroy(s); + } + return ret; +} + +static int +oss_stream_thr_create(cubeb_stream * s) +{ + if (s->thread_created) { + s->doorbell = true; + pthread_cond_signal(&s->doorbell_cv); + return CUBEB_OK; + } + + if (pthread_create(&s->thread, NULL, oss_io_routine, s) != 0) { + LOG("Couldn't create thread"); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +oss_stream_start(cubeb_stream * s) +{ + s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED); + pthread_mutex_lock(&s->mtx); + /* Disallow starting an already started stream */ + assert(!s->running && s->state != CUBEB_STATE_STARTED); + if (oss_stream_thr_create(s) != CUBEB_OK) { + pthread_mutex_unlock(&s->mtx); + s->state_cb(s, s->user_ptr, CUBEB_STATE_ERROR); + return CUBEB_ERROR; + } + s->state = CUBEB_STATE_STARTED; + s->thread_created = true; + s->running = true; + pthread_mutex_unlock(&s->mtx); + return CUBEB_OK; +} + +static int +oss_stream_get_position(cubeb_stream * s, uint64_t * position) +{ + pthread_mutex_lock(&s->mtx); + *position = s->frames_written; + pthread_mutex_unlock(&s->mtx); + return CUBEB_OK; +} + +static int +oss_stream_get_latency(cubeb_stream * s, uint32_t * latency) +{ + int delay; + + if (ioctl(s->play.fd, SNDCTL_DSP_GETODELAY, &delay) == -1) { + return CUBEB_ERROR; + } + + /* Return number of frames there */ + *latency = delay / s->play.frame_size; + return CUBEB_OK; +} + +static int +oss_stream_set_volume(cubeb_stream * stream, float volume) +{ + if (volume < 0.0) + volume = 0.0; + else if (volume > 1.0) + volume = 1.0; + pthread_mutex_lock(&stream->mtx); + stream->volume = volume; + pthread_mutex_unlock(&stream->mtx); + return CUBEB_OK; +} + +static int +oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device) +{ + *device = calloc(1, sizeof(cubeb_device)); + if (*device == NULL) { + return CUBEB_ERROR; + } + (*device)->input_name = stream->record.fd != -1 ? + strdup(stream->record.name) : NULL; + (*device)->output_name = stream->play.fd != -1 ? + strdup(stream->play.name) : NULL; + return CUBEB_OK; +} + +static int +oss_stream_device_destroy(cubeb_stream * stream, cubeb_device * device) +{ + (void)stream; + free(device->input_name); + free(device->output_name); + free(device); + return CUBEB_OK; +} + +static struct cubeb_ops const oss_ops = { + .init = oss_init, + .get_backend_id = oss_get_backend_id, + .get_max_channel_count = oss_get_max_channel_count, + .get_min_latency = oss_get_min_latency, + .get_preferred_sample_rate = oss_get_preferred_sample_rate, + .enumerate_devices = oss_enumerate_devices, + .device_collection_destroy = oss_device_collection_destroy, + .destroy = oss_destroy, + .stream_init = oss_stream_init, + .stream_destroy = oss_stream_destroy, + .stream_start = oss_stream_start, + .stream_stop = oss_stream_stop, + .stream_reset_default_device = NULL, + .stream_get_position = oss_stream_get_position, + .stream_get_latency = oss_stream_get_latency, + .stream_get_input_latency = NULL, + .stream_set_volume = oss_stream_set_volume, + .stream_set_name = NULL, + .stream_get_current_device = oss_get_current_device, + .stream_device_destroy = oss_stream_device_destroy, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL}; diff --git a/dep/cubeb/src/cubeb_pulse.c b/dep/cubeb/src/cubeb_pulse.c index 94ead3a9c..c7e9d1ca1 100644 --- a/dep/cubeb/src/cubeb_pulse.c +++ b/dep/cubeb/src/cubeb_pulse.c @@ -86,6 +86,7 @@ X(pa_mainloop_api_once) \ X(pa_get_library_version) \ X(pa_channel_map_init_auto) \ + X(pa_stream_set_name) \ #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; LIBPULSE_API_VISIT(MAKE_TYPEDEF); @@ -1139,6 +1140,14 @@ volume_success(pa_context *c, int success, void *userdata) WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0); } +static void +rename_success(pa_stream *s, int success, void *userdata) +{ + cubeb_stream * stream = userdata; + assert(success); + WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0); +} + static int pulse_stream_set_volume(cubeb_stream * stm, float volume) { @@ -1183,6 +1192,28 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } +static int +pulse_stream_set_name(cubeb_stream * stm, char const * stream_name) +{ + if (!stm || !stm->output_stream) { + return CUBEB_ERROR; + } + + WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + + pa_operation * op = + WRAP(pa_stream_set_name)(stm->output_stream, stream_name, rename_success, stm); + + if (op) { + operation_wait(stm->context, stm->output_stream, op); + WRAP(pa_operation_unref)(op); + } + + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + + return CUBEB_OK; +} + typedef struct { char * default_sink_name; char * default_source_name; @@ -1597,7 +1628,9 @@ static struct cubeb_ops const pulse_ops = { .stream_reset_default_device = NULL, .stream_get_position = pulse_stream_get_position, .stream_get_latency = pulse_stream_get_latency, + .stream_get_input_latency = NULL, .stream_set_volume = pulse_stream_set_volume, + .stream_set_name = pulse_stream_set_name, .stream_get_current_device = pulse_stream_get_current_device, .stream_device_destroy = pulse_stream_device_destroy, .stream_register_device_changed_callback = NULL, diff --git a/dep/cubeb/src/cubeb_resampler.cpp b/dep/cubeb/src/cubeb_resampler.cpp index 3fc09d294..29ad7365f 100644 --- a/dep/cubeb/src/cubeb_resampler.cpp +++ b/dep/cubeb/src/cubeb_resampler.cpp @@ -61,8 +61,7 @@ long passthrough_resampler::fill(void * input_buffer, long * input_frames_cou if (input_buffer) { assert(input_frames_count); } - assert((input_buffer && output_buffer && - *input_frames_count + static_cast(samples_to_frames(internal_input_buffer.length())) >= output_frames) || + assert((input_buffer && output_buffer) || (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || (input_buffer && !output_buffer && output_frames == 0)); @@ -74,14 +73,26 @@ long passthrough_resampler::fill(void * input_buffer, long * input_frames_cou if (input_buffer && !output_buffer) { output_frames = *input_frames_count; } else if(input_buffer) { - if (internal_input_buffer.length() != 0) { - // In this case we have pending input data left and have - // to first append the input so we can pass it as one pointer - // to the callback + if (internal_input_buffer.length() != 0 || + *input_frames_count < output_frames) { + // If we have pending input data left and have to first append the input + // so we can pass it as one pointer to the callback. Or this is a glitch. + // It can happen when system's performance is poor. Audible silence is + // being pushed at the end of the short input buffer. An improvement for + // the future is to resample to the output number of frames, when that happens. internal_input_buffer.push(static_cast(input_buffer), frames_to_samples(*input_frames_count)); + if (internal_input_buffer.length() < frames_to_samples(output_frames)) { + // This is unxpected but it can happen when a glitch occurs. Fill the + // buffer with silence. First keep the actual number of input samples + // used without the silence. + pop_input_count = internal_input_buffer.length(); + internal_input_buffer.push_silence( + frames_to_samples(output_frames) - internal_input_buffer.length()); + } else { + pop_input_count = frames_to_samples(output_frames); + } in_buf = internal_input_buffer.data(); - pop_input_count = frames_to_samples(output_frames); } else if(*input_frames_count > output_frames) { // In this case we have more input that we need output and // fill the overflowing input into internal_input_buffer @@ -99,15 +110,20 @@ long passthrough_resampler::fill(void * input_buffer, long * input_frames_cou if (input_buffer) { if (pop_input_count) { internal_input_buffer.pop(nullptr, pop_input_count); + *input_frames_count = samples_to_frames(pop_input_count); + } else { + *input_frames_count = output_frames; } - - *input_frames_count = output_frames; drop_audio_if_needed(); } return rv; } +// Explicit instantiation of template class. +template class passthrough_resampler; +template class passthrough_resampler; + template cubeb_resampler_speex ::cubeb_resampler_speex(InputProcessor * input_processor, @@ -122,17 +138,6 @@ cubeb_resampler_speex , user_ptr(ptr) { if (input_processor && output_processor) { - // Add some delay on the processor that has the lowest delay so that the - // streams are synchronized. - uint32_t in_latency = input_processor->latency(); - uint32_t out_latency = output_processor->latency(); - if (in_latency > out_latency) { - uint32_t latency_diff = in_latency - out_latency; - output_processor->add_latency(latency_diff); - } else if (in_latency < out_latency) { - uint32_t latency_diff = out_latency - in_latency; - input_processor->add_latency(latency_diff); - } fill_internal = &cubeb_resampler_speex::fill_internal_duplex; } else if (input_processor) { fill_internal = &cubeb_resampler_speex::fill_internal_input; diff --git a/dep/cubeb/src/cubeb_resampler_internal.h b/dep/cubeb/src/cubeb_resampler_internal.h index fb69992ff..ed5685796 100644 --- a/dep/cubeb/src/cubeb_resampler_internal.h +++ b/dep/cubeb/src/cubeb_resampler_internal.h @@ -35,6 +35,7 @@ MOZ_END_STD_NAMESPACE #include "cubeb_utils.h" #include "cubeb-speex-resampler.h" #include "cubeb_resampler.h" +#include "cubeb_log.h" #include /* This header file contains the internal C++ API of the resamplers, for testing. */ @@ -191,6 +192,19 @@ public: speex_resampler = speex_resampler_init(channels, source_rate, target_rate, quality, &r); assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure"); + + uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler); + const size_t LATENCY_SAMPLES = 8192; + T input_buffer[LATENCY_SAMPLES] = {}; + T output_buffer[LATENCY_SAMPLES] = {}; + uint32_t input_frame_count = input_latency; + uint32_t output_frame_count = LATENCY_SAMPLES; + assert(input_latency * channels <= LATENCY_SAMPLES); + speex_resample( + input_buffer, + &input_frame_count, + output_buffer, + &output_frame_count); } /** Destructor, deallocate the resampler */ @@ -199,17 +213,6 @@ public: speex_resampler_destroy(speex_resampler); } - /** Sometimes, it is necessary to add latency on one way of a two-way - * resampler so that the stream are synchronized. This must be called only on - * a fresh resampler, otherwise, silent samples will be inserted in the - * stream. - * @param frames the number of frames of latency to add. */ - void add_latency(size_t frames) - { - additional_latency += frames; - resampling_in_buffer.push_silence(frames_to_samples(frames)); - } - /* Fill the resampler with `input_frame_count` frames. */ void input(T * input_buffer, size_t input_frame_count) { @@ -254,7 +257,14 @@ public: speex_resample(resampling_in_buffer.data(), &in_len, resampling_out_buffer.data(), &out_len); - assert(out_len == output_frame_count); + if (out_len < output_frame_count) { + LOGV("underrun during resampling: got %u frames, expected %zu", (unsigned)out_len, output_frame_count); + // silence the rightmost part + T* data = resampling_out_buffer.data(); + for (uint32_t i = frames_to_samples(out_len); i < frames_to_samples(output_frame_count); i++) { + data[i] = 0; + } + } /* This shifts back any unresampled samples to the beginning of the input buffer. */ @@ -283,8 +293,9 @@ public: * exactly `output_frame_count` resampled frames. This can return a number * slightly bigger than what is strictly necessary, but it guaranteed that the * number of output frames will be exactly equal. */ - uint32_t input_needed_for_output(uint32_t output_frame_count) const + uint32_t input_needed_for_output(int32_t output_frame_count) const { + assert(output_frame_count >= 0); // Check overflow int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); float input_frames_needed = @@ -392,13 +403,6 @@ public: /* Fill the delay line with some silent frames to add latency. */ delay_input_buffer.push_silence(frames * channels); } - /* Add some latency to the delay line. - * @param frames the number of frames of latency to add. */ - void add_latency(size_t frames) - { - length += frames; - delay_input_buffer.push_silence(frames_to_samples(frames)); - } /** Push some frames into the delay line. * @parameter buffer the frames to push. * @parameter frame_count the number of frames in #buffer. */ @@ -462,8 +466,9 @@ public: * @parameter frames_needed the number of frames one want to write into the * delay_line * @returns the number of frames one will get. */ - size_t input_needed_for_output(uint32_t frames_needed) const + uint32_t input_needed_for_output(int32_t frames_needed) const { + assert(frames_needed >= 0); // Check overflow return frames_needed; } /** Returns the number of frames produces for `input_frames` frames in input */ @@ -526,6 +531,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream, (output_params && output_params->rate == target_rate)) || (input_params && !output_params && (input_params->rate == target_rate)) || (output_params && !input_params && (output_params->rate == target_rate))) { + LOG("Input and output sample-rate match, target rate of %dHz", target_rate); return new passthrough_resampler(stream, callback, user_ptr, input_params ? input_params->channels : 0, @@ -576,6 +582,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream, } if (input_resampler && output_resampler) { + LOG("Resampling input (%d) and output (%d) to target rate of %dHz", input_params->rate, output_params->rate, target_rate); return new cubeb_resampler_speex, cubeb_resampler_speex_one_way> @@ -583,6 +590,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream, output_resampler.release(), stream, callback, user_ptr); } else if (input_resampler) { + LOG("Resampling input (%d) to target and output rate of %dHz", input_params->rate, target_rate); return new cubeb_resampler_speex, delay_line> @@ -590,6 +598,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream, output_delay.release(), stream, callback, user_ptr); } else { + LOG("Resampling output (%dHz) to target and input rate of %dHz", output_params->rate, target_rate); return new cubeb_resampler_speex, cubeb_resampler_speex_one_way> diff --git a/dep/cubeb/src/cubeb_sndio.c b/dep/cubeb/src/cubeb_sndio.c index 34b3513d5..41fa59afa 100644 --- a/dep/cubeb/src/cubeb_sndio.c +++ b/dep/cubeb/src/cubeb_sndio.c @@ -128,7 +128,7 @@ s16_to_float(void *ptr, long nsamp) static const char * sndio_get_device() { -#ifndef __OpenBSD__ +#ifdef __linux__ /* * On other platforms default to sndio devices, * so cubebs other backends can be used instead. @@ -662,6 +662,7 @@ static struct cubeb_ops const sndio_ops = { .stream_get_position = sndio_stream_get_position, .stream_get_latency = sndio_stream_get_latency, .stream_set_volume = sndio_stream_set_volume, + .stream_set_name = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/dep/cubeb/src/cubeb_sun.c b/dep/cubeb/src/cubeb_sun.c index 64ab0b5b1..206de447f 100644 --- a/dep/cubeb/src/cubeb_sun.c +++ b/dep/cubeb/src/cubeb_sun.c @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Nia Alarie + * Copyright © 2019-2020 Nia Alarie * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. @@ -9,19 +9,14 @@ #include #include #include +#include #include #include #include -#include +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#define BYTES_TO_FRAMES(bytes, channels) \ - (bytes / (channels * sizeof(int16_t))) - -#define FRAMES_TO_BYTES(frames, channels) \ - (frames * (channels * sizeof(int16_t))) - /* Default to 4 + 1 for the default device. */ #ifndef SUN_DEVICE_COUNT #define SUN_DEVICE_COUNT (5) @@ -41,10 +36,6 @@ #define SUN_DEFAULT_DEVICE "/dev/audio" #endif -#ifndef SUN_POLL_TIMEOUT -#define SUN_POLL_TIMEOUT (1000) -#endif - #ifndef SUN_BUFFER_FRAMES #define SUN_BUFFER_FRAMES (32) #endif @@ -75,26 +66,26 @@ struct cubeb { struct cubeb_ops const * ops; }; +struct sun_stream { + char name[32]; + int fd; + void * buf; + struct audio_info info; + unsigned frame_size; /* precision in bytes * channels */ + bool floating; +}; + struct cubeb_stream { struct cubeb * context; void * user_ptr; pthread_t thread; pthread_mutex_t mutex; /* protects running, volume, frames_written */ - int floating; - int running; - int play_fd; - int record_fd; + bool running; float volume; - struct audio_info p_info; /* info for the play fd */ - struct audio_info r_info; /* info for the record fd */ + struct sun_stream play; + struct sun_stream record; cubeb_data_callback data_cb; cubeb_state_callback state_cb; - int16_t * play_buf; - int16_t * record_buf; - float * f_play_buf; - float * f_record_buf; - char input_name[32]; - char output_name[32]; uint64_t frames_written; uint64_t blocks_written; }; @@ -312,18 +303,19 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params, { prinfo->channels = params->channels; prinfo->sample_rate = params->rate; - prinfo->precision = 16; #ifdef AUDIO_ENCODING_SLINEAR_LE switch (params->format) { case CUBEB_SAMPLE_S16LE: prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE; + prinfo->precision = 16; break; case CUBEB_SAMPLE_S16BE: prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE; + prinfo->precision = 16; break; case CUBEB_SAMPLE_FLOAT32NE: - stream->floating = 1; prinfo->encoding = AUDIO_ENCODING_SLINEAR; + prinfo->precision = 32; break; default: LOG("Unsupported format"); @@ -333,10 +325,11 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params, switch (params->format) { case CUBEB_SAMPLE_S16NE: prinfo->encoding = AUDIO_ENCODING_LINEAR; + prinfo->precision = 16; break; case CUBEB_SAMPLE_FLOAT32NE: - stream->floating = 1; prinfo->encoding = AUDIO_ENCODING_LINEAR; + prinfo->precision = 32; break; default: LOG("Unsupported format"); @@ -357,7 +350,7 @@ sun_stream_stop(cubeb_stream * s) { pthread_mutex_lock(&s->mutex); if (s->running) { - s->running = 0; + s->running = false; pthread_mutex_unlock(&s->mutex); pthread_join(s->thread, NULL); } else { @@ -369,55 +362,52 @@ sun_stream_stop(cubeb_stream * s) static void sun_stream_destroy(cubeb_stream * s) { - pthread_mutex_destroy(&s->mutex); sun_stream_stop(s); - if (s->play_fd != -1) { - close(s->play_fd); + pthread_mutex_destroy(&s->mutex); + if (s->play.fd != -1) { + close(s->play.fd); } - if (s->record_fd != -1) { - close(s->record_fd); + if (s->record.fd != -1) { + close(s->record.fd); } - free(s->f_play_buf); - free(s->f_record_buf); - free(s->play_buf); - free(s->record_buf); + free(s->play.buf); + free(s->record.buf); free(s); } static void -sun_float_to_linear(float * in, int16_t * out, - unsigned channels, long frames, float vol) +sun_float_to_linear32(void * buf, unsigned sample_count, float vol) { - unsigned i, sample_count = frames * channels; - float multiplier = vol * 0x8000; + float * in = buf; + int32_t * out = buf; + int32_t * tail = out + sample_count; - for (i = 0; i < sample_count; ++i) { - int32_t sample = lrintf(in[i] * multiplier); - if (sample < -0x8000) { - out[i] = -0x8000; - } else if (sample > 0x7fff) { - out[i] = 0x7fff; - } else { - out[i] = sample; - } + while (out < tail) { + float f = *(in++) * vol; + if (f < -1.0) + f = -1.0; + else if (f > 1.0) + f = 1.0; + *(out++) = f * (float)INT32_MAX; } } static void -sun_linear_to_float(int16_t * in, float * out, - unsigned channels, long frames) +sun_linear32_to_float(void * buf, unsigned sample_count) { - unsigned i, sample_count = frames * channels; + int32_t * in = buf; + float * out = buf; + float * tail = out + sample_count; - for (i = 0; i < sample_count; ++i) { - out[i] = (1.0 / 0x8000) * in[i]; + while (out < tail) { + *(out++) = (1.0 / 0x80000000) * *(in++); } } static void -sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol) +sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol) { - unsigned i, sample_count = frames * channels; + unsigned i; int32_t multiplier = vol * 0x8000; for (i = 0; i < sample_count; ++i) { @@ -445,41 +435,36 @@ sun_io_routine(void * arg) break; } pthread_mutex_unlock(&s->mutex); - if (s->floating) { - if (s->record_fd != -1) { - sun_linear_to_float(s->record_buf, s->f_record_buf, - s->r_info.record.channels, SUN_BUFFER_FRAMES); - } - to_write = s->data_cb(s, s->user_ptr, - s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES); - if (to_write == CUBEB_ERROR) { - state = CUBEB_STATE_ERROR; - break; - } - if (s->play_fd != -1) { - pthread_mutex_lock(&s->mutex); - sun_float_to_linear(s->f_play_buf, s->play_buf, - s->p_info.play.channels, to_write, s->volume); - pthread_mutex_unlock(&s->mutex); - } - } else { - to_write = s->data_cb(s, s->user_ptr, - s->record_buf, s->play_buf, SUN_BUFFER_FRAMES); - if (to_write == CUBEB_ERROR) { - state = CUBEB_STATE_ERROR; - break; - } - if (s->play_fd != -1) { - pthread_mutex_lock(&s->mutex); - sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume); - pthread_mutex_unlock(&s->mutex); + if (s->record.fd != -1 && s->record.floating) { + sun_linear32_to_float(s->record.buf, + s->record.info.record.channels * SUN_BUFFER_FRAMES); + } + to_write = s->data_cb(s, s->user_ptr, + s->record.buf, s->play.buf, SUN_BUFFER_FRAMES); + if (to_write == CUBEB_ERROR) { + state = CUBEB_STATE_ERROR; + break; + } + if (s->play.fd != -1) { + float vol; + + pthread_mutex_lock(&s->mutex); + vol = s->volume; + pthread_mutex_unlock(&s->mutex); + + if (s->play.floating) { + sun_float_to_linear32(s->play.buf, + s->play.info.play.channels * to_write, vol); + } else { + sun_linear16_set_vol(s->play.buf, + s->play.info.play.channels * to_write, vol); } } if (to_write < SUN_BUFFER_FRAMES) { drain = 1; } - to_write = s->play_fd != -1 ? to_write : 0; - to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0; + to_write = s->play.fd != -1 ? to_write : 0; + to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0; write_ofs = 0; read_ofs = 0; while (to_write > 0 || to_read > 0) { @@ -487,27 +472,27 @@ sun_io_routine(void * arg) ssize_t n, frames; if (to_write > 0) { - bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels); - if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) { + bytes = to_write * s->play.frame_size; + if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) < 0) { state = CUBEB_STATE_ERROR; break; } - frames = BYTES_TO_FRAMES(n, s->p_info.play.channels); + frames = n / s->play.frame_size; pthread_mutex_lock(&s->mutex); s->frames_written += frames; pthread_mutex_unlock(&s->mutex); to_write -= frames; - write_ofs += frames; + write_ofs += n; } if (to_read > 0) { - bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels); - if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) { + bytes = to_read * s->record.frame_size; + if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) { state = CUBEB_STATE_ERROR; break; } - frames = BYTES_TO_FRAMES(n, s->r_info.record.channels); + frames = n / s->record.frame_size; to_read -= frames; - read_ofs += frames; + read_ofs += n; } } if (drain && state != CUBEB_STATE_ERROR) { @@ -541,19 +526,19 @@ sun_stream_init(cubeb * context, ret = CUBEB_ERROR; goto error; } - s->record_fd = -1; - s->play_fd = -1; + s->record.fd = -1; + s->play.fd = -1; if (input_device != 0) { - snprintf(s->input_name, sizeof(s->input_name), + snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu", (uintptr_t)input_device - 1); } else { - snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE); + snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE); } if (output_device != 0) { - snprintf(s->output_name, sizeof(s->output_name), + snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu", (uintptr_t)output_device - 1); } else { - snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE); + snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE); } if (input_stream_params != NULL) { if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { @@ -561,22 +546,23 @@ sun_stream_init(cubeb * context, ret = CUBEB_ERROR_NOT_SUPPORTED; goto error; } - if (s->record_fd == -1) { - if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) { - LOG("Audio device cannot be opened as read-only"); + if (s->record.fd == -1) { + if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) { + LOG("Audio device could not be opened as read-only"); ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; goto error; } } - AUDIO_INITINFO(&s->r_info); + AUDIO_INITINFO(&s->record.info); #ifdef AUMODE_RECORD - s->r_info.mode = AUMODE_RECORD; + s->record.info.mode = AUMODE_RECORD; #endif - if ((ret = sun_copy_params(s->record_fd, s, input_stream_params, - &s->r_info, &s->r_info.record)) != CUBEB_OK) { + if ((ret = sun_copy_params(s->record.fd, s, input_stream_params, + &s->record.info, &s->record.info.record)) != CUBEB_OK) { LOG("Setting record params failed"); goto error; } + s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); } if (output_stream_params != NULL) { if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { @@ -584,22 +570,23 @@ sun_stream_init(cubeb * context, ret = CUBEB_ERROR_NOT_SUPPORTED; goto error; } - if (s->play_fd == -1) { - if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) { - LOG("Audio device cannot be opened as write-only"); + if (s->play.fd == -1) { + if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) { + LOG("Audio device could not be opened as write-only"); ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; goto error; } } - AUDIO_INITINFO(&s->p_info); + AUDIO_INITINFO(&s->play.info); #ifdef AUMODE_PLAY - s->p_info.mode = AUMODE_PLAY; + s->play.info.mode = AUMODE_PLAY; #endif - if ((ret = sun_copy_params(s->play_fd, s, output_stream_params, - &s->p_info, &s->p_info.play)) != CUBEB_OK) { + if ((ret = sun_copy_params(s->play.fd, s, output_stream_params, + &s->play.info, &s->play.info.play)) != CUBEB_OK) { LOG("Setting play params failed"); goto error; } + s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); } s->context = context; s->volume = 1.0; @@ -610,28 +597,20 @@ sun_stream_init(cubeb * context, LOG("Failed to create mutex"); goto error; } - if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES, - s->p_info.play.channels * sizeof(int16_t))) == NULL) { + s->play.frame_size = s->play.info.play.channels * + (s->play.info.play.precision / 8); + if (s->play.fd != -1 && + (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) { ret = CUBEB_ERROR; goto error; } - if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES, - s->r_info.record.channels * sizeof(int16_t))) == NULL) { + s->record.frame_size = s->record.info.record.channels * + (s->record.info.record.precision / 8); + if (s->record.fd != -1 && + (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == NULL) { ret = CUBEB_ERROR; goto error; } - if (s->floating) { - if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES, - s->p_info.play.channels * sizeof(float))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES, - s->r_info.record.channels * sizeof(float))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - } *stream = s; return CUBEB_OK; error: @@ -644,7 +623,7 @@ error: static int sun_stream_start(cubeb_stream * s) { - s->running = 1; + s->running = true; if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) { LOG("Couldn't create thread"); return CUBEB_ERROR; @@ -658,12 +637,11 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position) #ifdef AUDIO_GETOOFFS struct audio_offset offset; - if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) { + if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) { return CUBEB_ERROR; } s->blocks_written += offset.deltablks; - *position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize, - s->p_info.play.channels); + *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size; return CUBEB_OK; #else pthread_mutex_lock(&s->mutex); @@ -674,22 +652,21 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position) } static int -sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +sun_stream_get_latency(cubeb_stream * s, uint32_t * latency) { #ifdef AUDIO_GETBUFINFO struct audio_info info; - if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) { + if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) { return CUBEB_ERROR; } - *latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize, - info.play.channels); + *latency = (info.play.seek + info.blocksize) / s->play.frame_size; return CUBEB_OK; #else cubeb_stream_params params; - params.rate = stream->p_info.play.sample_rate; + params.rate = s->play.info.play.sample_rate; return sun_get_min_latency(NULL, params, latency); #endif @@ -711,10 +688,10 @@ sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device) if (*device == NULL) { return CUBEB_ERROR; } - (*device)->input_name = stream->record_fd != -1 ? - strdup(stream->input_name) : NULL; - (*device)->output_name = stream->play_fd != -1 ? - strdup(stream->output_name) : NULL; + (*device)->input_name = stream->record.fd != -1 ? + strdup(stream->record.name) : NULL; + (*device)->output_name = stream->play.fd != -1 ? + strdup(stream->play.name) : NULL; return CUBEB_OK; } @@ -744,7 +721,9 @@ static struct cubeb_ops const sun_ops = { .stream_reset_default_device = NULL, .stream_get_position = sun_stream_get_position, .stream_get_latency = sun_stream_get_latency, + .stream_get_input_latency = NULL, .stream_set_volume = sun_stream_set_volume, + .stream_set_name = NULL, .stream_get_current_device = sun_get_current_device, .stream_device_destroy = sun_stream_device_destroy, .stream_register_device_changed_callback = NULL, diff --git a/dep/cubeb/src/cubeb_utils.cpp b/dep/cubeb/src/cubeb_utils.cpp index 85572a9fe..d42e951e4 100644 --- a/dep/cubeb/src/cubeb_utils.cpp +++ b/dep/cubeb/src/cubeb_utils.cpp @@ -19,5 +19,6 @@ size_t cubeb_sample_size(cubeb_sample_format format) default: // should never happen as all cases are handled above. assert(false); + return 0; } } diff --git a/dep/cubeb/src/cubeb_wasapi.cpp b/dep/cubeb/src/cubeb_wasapi.cpp index e85462f12..1194eb942 100644 --- a/dep/cubeb/src/cubeb_wasapi.cpp +++ b/dep/cubeb/src/cubeb_wasapi.cpp @@ -4,7 +4,7 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#define _WIN32_WINNT 0x0600 +#define _WIN32_WINNT 0x0603 #define NOMINMAX #include @@ -89,6 +89,9 @@ DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e #endif namespace { + +const int64_t LATENCY_NOT_AVAILABLE_YET = -1; + struct com_heap_ptr_deleter { void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); @@ -190,6 +193,10 @@ int wasapi_stream_start(cubeb_stream * stm); void close_wasapi_stream(cubeb_stream * stm); int setup_wasapi_stream(cubeb_stream * stm); ERole pref_to_role(cubeb_stream_prefs param); +int wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev); +void wasapi_destroy_device(cubeb_device_info * device_info); +static int wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection * out); +static int wasapi_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection); static char const * wstr_to_utf8(wchar_t const * str); static std::unique_ptr utf8_to_wstr(char const * str); @@ -211,6 +218,7 @@ struct cubeb { /* Collection changed for output (render) devices. */ cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr; void * output_collection_changed_user_ptr = nullptr; + UINT64 performance_counter_frequency; }; class wasapi_endpoint_notification_client; @@ -241,9 +249,15 @@ struct cubeb_stream { cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; /* A MMDevice role for this stream: either communication or console here. */ ERole role; + /* True if this stream will transport voice-data. */ + bool voice; + /* True if the input device of this stream is using bluetooth handsfree. */ + bool input_bluetooth_handsfree; /* The input and output device, or NULL for default. */ - std::unique_ptr input_device; - std::unique_ptr output_device; + std::unique_ptr input_device_id; + std::unique_ptr output_device_id; + com_ptr input_device; + com_ptr output_device; /* The latency initially requested for this stream, in frames. */ unsigned latency = 0; cubeb_state_callback state_callback = nullptr; @@ -334,6 +348,9 @@ struct cubeb_stream { std::atomic*> emergency_bailout { nullptr }; /* Synchronizes render thread start to ensure safe access to emergency_bailout. */ HANDLE thread_ready_event = 0; + /* This needs an active audio input stream to be known, and is updated in the + * first audio input callback. */ + std::atomic input_latency_hns { LATENCY_NOT_AVAILABLE_YET }; }; class monitor_device_notifications { @@ -346,13 +363,14 @@ public: ~monitor_device_notifications() { - SetEvent(shutdown); - WaitForSingleObject(thread, INFINITE); + SetEvent(begin_shutdown); + WaitForSingleObject(shutdown_complete, INFINITE); CloseHandle(thread); CloseHandle(input_changed); CloseHandle(output_changed); - CloseHandle(shutdown); + CloseHandle(begin_shutdown); + CloseHandle(shutdown_complete); } void notify(EDataFlow flow) @@ -377,8 +395,9 @@ private: thread_proc(LPVOID args) { XASSERT(args); - static_cast(args) - ->notification_thread_loop(); + auto mdn = static_cast(args); + mdn->notification_thread_loop(); + SetEvent(mdn->shutdown_complete); return 0; } @@ -397,7 +416,7 @@ private: HANDLE wait_array[3] = { input_changed, output_changed, - shutdown, + begin_shutdown, }; while (true) { @@ -435,9 +454,15 @@ private: return; } - shutdown = CreateEvent(nullptr, 0, 0, nullptr); - if (!shutdown) { - LOG("Failed to create shutdown event."); + begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr); + if (!begin_shutdown) { + LOG("Failed to create begin_shutdown event."); + return; + } + + shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr); + if (!shutdown_complete) { + LOG("Failed to create shutdown_complete event."); return; } @@ -456,7 +481,8 @@ private: HANDLE thread = INVALID_HANDLE_VALUE; HANDLE output_changed = INVALID_HANDLE_VALUE; HANDLE input_changed = INVALID_HANDLE_VALUE; - HANDLE shutdown = INVALID_HANDLE_VALUE; + HANDLE begin_shutdown = INVALID_HANDLE_VALUE; + HANDLE shutdown_complete = INVALID_HANDLE_VALUE; cubeb * cubeb_context = nullptr; }; @@ -685,8 +711,9 @@ intern_device_id(cubeb * ctx, wchar_t const * id) XASSERT(id); char const * tmp = wstr_to_utf8(id); - if (!tmp) + if (!tmp) { return nullptr; + } char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); @@ -757,6 +784,12 @@ frames_to_hns(cubeb_stream * stm, uint32_t frames) return std::ceil(frames * 10000000.0 / get_rate(stm)); } +REFERENCE_TIME +frames_to_hns(uint32_t rate, uint32_t frames) +{ + return std::ceil(frames * 10000000.0 / rate); +} + /* This returns the size of a frame in the stream, before the eventual upmix occurs. */ static size_t @@ -847,6 +880,7 @@ bool get_input_buffer(cubeb_stream * stm) BYTE * input_packet = NULL; DWORD flags; UINT64 dev_pos; + UINT64 pc_position; UINT32 next; /* Get input packets until we have captured enough frames, and put them in a * contiguous buffer. */ @@ -876,13 +910,25 @@ bool get_input_buffer(cubeb_stream * stm) &frames, &flags, &dev_pos, - NULL); + &pc_position); + if (FAILED(hr)) { LOG("GetBuffer failed for capture: %lx", hr); return false; } XASSERT(frames == next); + if (stm->context->performance_counter_frequency) { + LARGE_INTEGER now; + UINT64 now_hns; + // See https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer, section "Remarks". + QueryPerformanceCounter(&now); + now_hns = 10000000 * now.QuadPart / stm->context->performance_counter_frequency; + if (now_hns >= pc_position) { + stm->input_latency_hns = now_hns - pc_position; + } + } + UINT32 input_stream_samples = frames * stm->input_stream_params.channels; // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY // flag. There a two primary (non exhaustive) scenarios we anticipate this @@ -1008,9 +1054,6 @@ refill_callback_duplex(cubeb_stream * stm) } input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; - if (!input_frames) { - return true; - } rv = get_output_buffer(stm, output_buffer, output_frames); if (!rv) { @@ -1303,17 +1346,11 @@ void wasapi_destroy(cubeb * context); HRESULT register_notification_client(cubeb_stream * stm) { - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), - NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(stm->device_enumerator.receive())); - if (FAILED(hr)) { - LOG("Could not get device enumerator: %lx", hr); - return hr; - } + assert(stm->device_enumerator); stm->notification_client.reset(new wasapi_endpoint_notification_client(stm->reconfigure_event, stm->role)); - hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get()); + HRESULT hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get()); if (FAILED(hr)) { LOG("Could not register endpoint notification callback: %lx", hr); stm->notification_client = nullptr; @@ -1341,7 +1378,6 @@ HRESULT unregister_notification_client(cubeb_stream * stm) } stm->notification_client = nullptr; - stm->device_enumerator = nullptr; return S_OK; } @@ -1450,8 +1486,7 @@ current_stream_delay(cubeb_stream * stm) double cur_pos = static_cast(pos) / freq; double max_pos = static_cast(stm->frames_written) / stm->output_mix_params.rate; - double delay = max_pos - cur_pos; - XASSERT(delay >= 0); + double delay = std::max(max_pos - cur_pos, 0.0); return delay; } @@ -1518,6 +1553,14 @@ int wasapi_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } + LARGE_INTEGER frequency; + if (QueryPerformanceFrequency(&frequency)) { + ctx->performance_counter_frequency = frequency.QuadPart; + } else { + LOG("Failed getting performance counter frequency, latency reporting will be inacurate"); + ctx->performance_counter_frequency = 0; + } + *context = ctx; return CUBEB_OK; @@ -1741,17 +1784,18 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptrIsFormatSupported(AUDCLNT_SHAREMODE_SHARED, mix_format.get(), - &closest); + &tmp); + com_heap_ptr closest(tmp); if (hr == S_FALSE) { /* Channel layout not supported, but WASAPI gives us a suggestion. Use it, and handle the eventual upmix/downmix ourselves. Ignore the subformat of the suggestion, since it seems to always be IEEE_FLOAT. */ LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); - WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest); + WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest.get()); format_pcm->dwChannelMask = closest_pcm->dwChannelMask; mix_format->nChannels = closest->nChannels; waveformatex_update_derived_properties(mix_format.get()); @@ -1768,6 +1812,28 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr & audio_client) +{ + com_ptr audio_client2; + audio_client->QueryInterface(audio_client2.receive()); + if (!audio_client2) { + LOG("Could not get IAudioClient2 interface, not setting AUDCLNT_STREAMOPTIONS_RAW."); + return CUBEB_OK; + } + AudioClientProperties properties = { 0 }; + properties.cbSize = sizeof(AudioClientProperties); +#ifndef __MINGW32__ + properties.Options |= AUDCLNT_STREAMOPTIONS_RAW; +#endif + HRESULT hr = audio_client2->SetClientProperties(&properties); + if (FAILED(hr)) { + LOG("IAudioClient2::SetClientProperties error: %lx", GetLastError()); + return CUBEB_ERROR; + } + return CUBEB_OK; +} + static bool initialize_iaudioclient3(com_ptr & audio_client, cubeb_stream * stm, @@ -1791,7 +1857,7 @@ initialize_iaudioclient3(com_ptr & audio_client, // IAudioClient3 doesn't support AUDCLNT_STREAMFLAGS_NOPERSIST, and will return // AUDCLNT_E_INVALID_STREAM_FLAG. This is undocumented. - flags = flags ^ AUDCLNT_STREAMFLAGS_NOPERSIST; + flags = flags & ~AUDCLNT_STREAMFLAGS_NOPERSIST; // Some people have reported glitches with capture streams: // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html @@ -1883,9 +1949,9 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, uint32_t * buffer_frame_count, HANDLE & event, T & render_or_capture_client, - cubeb_stream_params * mix_params) + cubeb_stream_params * mix_params, + com_ptr& device) { - com_ptr device; HRESULT hr; bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK; if (is_loopback && direction != eCapture) { @@ -1921,14 +1987,18 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, /* Get a client. We will get all other interfaces we need from * this pointer. */ +#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, NULL, audio_client.receive_vpp()); if (hr == E_NOINTERFACE) { +#endif hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, audio_client.receive_vpp()); +#if 0 } +#endif if (FAILED(hr)) { LOG("Could not activate the device to get an audio" @@ -1984,7 +2054,13 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, mix_params->format, mix_params->rate, mix_params->channels, mix_params->layout); - DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST; + + DWORD flags = 0; + + bool is_persist = stream_params->prefs & CUBEB_STREAM_PREF_PERSIST; + if (!is_persist) { + flags |= AUDCLNT_STREAMFLAGS_NOPERSIST; + } // Check if a loopback device should be requested. Note that event callbacks // do not work with loopback devices, so only request these if not looping. @@ -1994,16 +2070,67 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; } + // Sanity check the latency, it may be that the device doesn't support it. + REFERENCE_TIME minimum_period; + REFERENCE_TIME default_period; + hr = audio_client->GetDevicePeriod(&default_period, &minimum_period); + if (FAILED(hr)) { + LOG("Could not get device period: %lx", hr); + return CUBEB_ERROR; + } + + REFERENCE_TIME latency_hns; + + uint32_t latency_frames = stm->latency; + cubeb_device_info device_info; + int rv = wasapi_create_device(stm->context, device_info, stm->device_enumerator.get(), device.get()); + if (rv == CUBEB_OK) { + const char* HANDSFREE_TAG = "BTHHFEENUM"; + size_t len = sizeof(HANDSFREE_TAG); + if (direction == eCapture && strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) { + // Rather high-latency to prevent constant under-runs in this particular + // case of an input device using bluetooth handsfree. + uint32_t default_period_frames = hns_to_frames(device_info.default_rate, default_period); + latency_frames = default_period_frames * 4; + stm->input_bluetooth_handsfree = true; + LOG("Input is a bluetooth device in handsfree, latency increased to %u frames from a default of %u", latency_frames, default_period_frames); + } else { + uint32_t minimum_period_frames = hns_to_frames(device_info.default_rate, minimum_period); + latency_frames = std::max(latency_frames, minimum_period_frames); + stm->input_bluetooth_handsfree = false; + LOG("Input is a not bluetooth handsfree, latency %s to %u frames (minimum %u)", latency_frames < minimum_period_frames ? "increased" : "set", latency_frames, minimum_period_frames); + } + + latency_hns = frames_to_hns(device_info.default_rate, latency_frames); + + wasapi_destroy_device(&device_info); + } else { + stm->input_bluetooth_handsfree = false; + latency_hns = frames_to_hns(mix_params->rate, latency_frames); + LOG("Could not get cubeb_device_info."); + } + + if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) { + if (initialize_iaudioclient2(audio_client) != CUBEB_OK) { + LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError()); + // This is not fatal. + } + } + +#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { LOG("Initialized with IAudioClient3"); } else { +#endif hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, - frames_to_hns(stm, stm->latency), + latency_hns, 0, mix_format.get(), NULL); +#if 0 } +#endif if (FAILED(hr)) { LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); return CUBEB_ERROR; @@ -2037,6 +2164,54 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, #undef DIRECTION_NAME +void wasapi_find_matching_output_device(cubeb_stream * stm) { + HRESULT hr; + cubeb_device_info * input_device; + cubeb_device_collection collection; + + // Only try to match to an output device if the input device is a bluetooth + // device that is using the handsfree protocol + if (!stm->input_bluetooth_handsfree) { + return; + } + + wchar_t * tmp = nullptr; + hr = stm->input_device->GetId(&tmp); + if (FAILED(hr)) { + LOG("Couldn't get input device id in wasapi_find_matching_output_device"); + return; + } + com_heap_ptr device_id(tmp); + cubeb_devid input_device_id = intern_device_id(stm->context, device_id.get()); + if (!input_device_id) { + return; + } + + int rv = wasapi_enumerate_devices(stm->context, (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT|CUBEB_DEVICE_TYPE_OUTPUT), &collection); + + // Find the input device, and then find the output device with the same group + // id and the same rate. + for (uint32_t i = 0; i < collection.count; i++) { + cubeb_device_info dev = collection.device[i]; + if (dev.devid == input_device_id) { + input_device = &dev; + break; + } + } + + for (uint32_t i = 0; i < collection.count; i++) { + cubeb_device_info dev = collection.device[i]; + if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && + dev.group_id && !strcmp(dev.group_id, input_device->group_id) && + dev.default_rate == input_device->default_rate) { + LOG("Found matching device for %s: %s", input_device->friendly_name, dev.friendly_name); + stm->output_device_id = utf8_to_wstr(reinterpret_cast(dev.devid)); + } + } + + wasapi_device_collection_destroy(stm->context, &collection); +} + int setup_wasapi_stream(cubeb_stream * stm) { int rv; @@ -2046,17 +2221,18 @@ int setup_wasapi_stream(cubeb_stream * stm) XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); if (has_input(stm)) { - LOG("(%p) Setup capture: device=%p", stm, stm->input_device.get()); + LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get()); rv = setup_wasapi_stream_one_side(stm, &stm->input_stream_params, - stm->input_device.get(), + stm->input_device_id.get(), eCapture, __uuidof(IAudioCaptureClient), stm->input_client, &stm->input_buffer_frame_count, stm->input_available_event, stm->capture_client, - &stm->input_mix_params); + &stm->input_mix_params, + stm->input_device); if (rv != CUBEB_OK) { LOG("Failure to open the input side."); return rv; @@ -2075,6 +2251,14 @@ int setup_wasapi_stream(cubeb_stream * stm) stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count * stm->input_stream_params.channels * silent_buffer_count); + + // If this is a bluetooth device, and the output device is the default + // device, and the default device is the same bluetooth device, pick the + // right output device, running at the same rate and with the same protocol + // as the input. + if (!stm->output_device_id) { + wasapi_find_matching_output_device(stm); + } } // If we don't have an output device but are requesting a loopback device, @@ -2085,31 +2269,32 @@ int setup_wasapi_stream(cubeb_stream * stm) stm->output_stream_params.rate = stm->input_stream_params.rate; stm->output_stream_params.channels = stm->input_stream_params.channels; stm->output_stream_params.layout = stm->input_stream_params.layout; - if (stm->input_device) { - size_t len = wcslen(stm->input_device.get()); + if (stm->input_device_id) { + size_t len = wcslen(stm->input_device_id.get()); std::unique_ptr tmp(new wchar_t[len + 1]); - if (wcsncpy_s(tmp.get(), len + 1, stm->input_device.get(), len) != 0) { + if (wcsncpy_s(tmp.get(), len + 1, stm->input_device_id.get(), len) != 0) { LOG("Failed to copy device identifier while copying input stream" " configuration to output stream configuration to drive loopback."); return CUBEB_ERROR; } - stm->output_device = move(tmp); + stm->output_device_id = move(tmp); } stm->has_dummy_output = true; } if (has_output(stm)) { - LOG("(%p) Setup render: device=%p", stm, stm->output_device.get()); + LOG("(%p) Setup render: device=%p", stm, stm->output_device_id.get()); rv = setup_wasapi_stream_one_side(stm, &stm->output_stream_params, - stm->output_device.get(), + stm->output_device_id.get(), eRender, __uuidof(IAudioRenderClient), stm->output_client, &stm->output_buffer_frame_count, stm->refill_event, stm->render_client, - &stm->output_mix_params); + &stm->output_mix_params, + stm->output_device); if (rv != CUBEB_OK) { LOG("Failure to open the output side."); return rv; @@ -2168,7 +2353,7 @@ int setup_wasapi_stream(cubeb_stream * stm) target_sample_rate, stm->data_callback, stm->user_ptr, - CUBEB_RESAMPLER_QUALITY_DESKTOP)); + stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP : CUBEB_RESAMPLER_QUALITY_DESKTOP)); if (!stm->resampler) { LOG("Could not get a resampler"); return CUBEB_ERROR; @@ -2255,21 +2440,31 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; + stm->role = eConsole; + stm->input_bluetooth_handsfree = false; - if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE || - stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) { - stm->role = eCommunications; - } else { - stm->role = eConsole; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(stm->device_enumerator.receive())); + if (FAILED(hr)) { + LOG("Could not get device enumerator: %lx", hr); + return hr; } if (input_stream_params) { stm->input_stream_params = *input_stream_params; - stm->input_device = utf8_to_wstr(reinterpret_cast(input_device)); + stm->input_device_id = utf8_to_wstr(reinterpret_cast(input_device)); } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - stm->output_device = utf8_to_wstr(reinterpret_cast(output_device)); + stm->output_device_id = utf8_to_wstr(reinterpret_cast(output_device)); + } + + if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE || + stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) { + stm->voice = true; + } else { + stm->voice = false; } switch (output_stream_params ? output_stream_params->format : input_stream_params->format) { @@ -2349,6 +2544,9 @@ void close_wasapi_stream(cubeb_stream * stm) stm->input_client = nullptr; stm->capture_client = nullptr; + stm->output_device = nullptr; + stm->input_device = nullptr; + stm->audio_stream_volume = nullptr; stm->audio_clock = nullptr; @@ -2386,6 +2584,8 @@ void wasapi_stream_destroy(cubeb_stream * stm) // must be destroyed in wasapi_stream_destroy. stm->linear_input_buffer.reset(); + stm->device_enumerator = nullptr; + { auto_lock lock(stm->stream_reset_lock); close_wasapi_stream(stm); @@ -2584,15 +2784,49 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency) /* The GetStreamLatency method only works if the AudioClient has been initialized. */ if (!stm->output_client) { + LOG("get_latency: No output_client."); return CUBEB_ERROR; } REFERENCE_TIME latency_hns; HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns); if (FAILED(hr)) { + LOG("GetStreamLatency failed %lx.", hr); return CUBEB_ERROR; } - *latency = hns_to_frames(stm, latency_hns); + // This happens on windows 10: no error, but always 0 for latency. + if (latency_hns == 0) { + LOG("GetStreamLatency returned 0, using workaround."); + double delay_s = current_stream_delay(stm); + // convert to sample-frames + *latency = delay_s * stm->output_stream_params.rate; + } else { + *latency = hns_to_frames(stm, latency_hns); + } + + LOG("Output latency %u frames.", *latency); + + return CUBEB_OK; +} + + +int wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency) +{ + XASSERT(stm && latency); + + if (!has_input(stm)) { + LOG("Input latency queried on an output-only stream."); + return CUBEB_ERROR; + } + + auto_lock lock(stm->stream_reset_lock); + + if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) { + LOG("Input latency not available yet."); + return CUBEB_ERROR; + } + + *latency = hns_to_frames(stm, stm->input_latency_hns); return CUBEB_OK; } @@ -2680,6 +2914,8 @@ wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, return ret; } +/* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value + * of this function is `CUBEB_OK`. */ int wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) { @@ -2692,6 +2928,10 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * REFERENCE_TIME def_period, min_period; HRESULT hr; + // zero-out to be able to safely delete the pointers to friendly_name and + // group_id at all time in this function. + PodZero(&ret, 1); + struct prop_variant : public PROPVARIANT { prop_variant() { PropVariantInit(this); } ~prop_variant() { PropVariantClear(this); } @@ -2700,57 +2940,97 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * }; hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive())); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) { + wasapi_destroy_device(&ret); + return CUBEB_ERROR; + } hr = endpoint->GetDataFlow(&flow); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) { + wasapi_destroy_device(&ret); + return CUBEB_ERROR; + } wchar_t * tmp = nullptr; hr = dev->GetId(&tmp); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) { + wasapi_destroy_device(&ret); + return CUBEB_ERROR; + } com_heap_ptr device_id(tmp); char const * device_id_intern = intern_device_id(ctx, device_id.get()); if (!device_id_intern) { + wasapi_destroy_device(&ret); return CUBEB_ERROR; } hr = dev->OpenPropertyStore(STGM_READ, propstore.receive()); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) { + wasapi_destroy_device(&ret); + return CUBEB_ERROR; + } hr = dev->GetState(&state); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) { + wasapi_destroy_device(&ret); + return CUBEB_ERROR; + } ret.device_id = device_id_intern; ret.devid = reinterpret_cast(ret.device_id); prop_variant namevar; hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) { ret.friendly_name = wstr_to_utf8(namevar.pwszVal); + } + if (!ret.friendly_name) { + // This is not fatal, but a valid string is expected in all cases. + char* empty = new char[1]; + empty[0] = '\0'; + ret.friendly_name = empty; + } devnode = wasapi_get_device_node(enumerator, dev); if (devnode) { com_ptr ps; hr = devnode->OpenPropertyStore(STGM_READ, ps.receive()); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) { + wasapi_destroy_device(&ret); + return CUBEB_ERROR; + } prop_variant instancevar; hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); - if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) { ret.group_id = wstr_to_utf8(instancevar.pwszVal); } } - ret.preferred = CUBEB_DEVICE_PREF_NONE; - if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) - ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); - if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) - ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); - if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) - ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + if (!ret.group_id) { + // This is not fatal, but a valid string is expected in all cases. + char* empty = new char[1]; + empty[0] = '\0'; + ret.group_id = empty; + } + + ret.preferred = CUBEB_DEVICE_PREF_NONE; + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) { + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); + } + if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) { + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); + } + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) { + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + } + + if (flow == eRender) { + ret.type = CUBEB_DEVICE_TYPE_OUTPUT; + } else if (flow == eCapture) { + ret.type = CUBEB_DEVICE_TYPE_INPUT; + } - if (flow == eRender) ret.type = CUBEB_DEVICE_TYPE_OUTPUT; - else if (flow == eCapture) ret.type = CUBEB_DEVICE_TYPE_INPUT; switch (state) { case DEVICE_STATE_ACTIVE: ret.state = CUBEB_DEVICE_STATE_ENABLED; @@ -2793,9 +3073,18 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * ret.latency_hi = 0; } + XASSERT(ret.friendly_name && ret.group_id); + return CUBEB_OK; } +void +wasapi_destroy_device(cubeb_device_info * device) +{ + delete [] device->friendly_name; + delete [] device->group_id; +} + static int wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection * out) @@ -2859,8 +3148,7 @@ wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * coll for (size_t n = 0; n < collection->count; n++) { cubeb_device_info& dev = collection->device[n]; - delete [] dev.friendly_name; - delete [] dev.group_id; + wasapi_destroy_device(&dev); } delete [] collection->device; @@ -2956,7 +3244,9 @@ cubeb_ops const wasapi_ops = { /*.stream_reset_default_device =*/ wasapi_stream_reset_default_device, /*.stream_get_position =*/ wasapi_stream_get_position, /*.stream_get_latency =*/ wasapi_stream_get_latency, + /*.stream_get_input_latency =*/ wasapi_stream_get_input_latency, /*.stream_set_volume =*/ wasapi_stream_set_volume, + /*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback =*/ NULL, diff --git a/dep/cubeb/src/cubeb_winmm.c b/dep/cubeb/src/cubeb_winmm.c index e064ca079..a94c2a1c1 100644 --- a/dep/cubeb/src/cubeb_winmm.c +++ b/dep/cubeb/src/cubeb_winmm.c @@ -1059,7 +1059,9 @@ static struct cubeb_ops const winmm_ops = { /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ winmm_stream_get_position, /*.stream_get_latency = */ winmm_stream_get_latency, + /*.stream_get_input_latency = */ NULL, /*.stream_set_volume =*/ winmm_stream_set_volume, + /*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/ NULL,