diff --git a/media/libcubeb/gtest/test_sanity.cpp b/media/libcubeb/gtest/test_sanity.cpp index 91ecf9df05f7..e1a60d0a4b9d 100644 --- a/media/libcubeb/gtest/test_sanity.cpp +++ b/media/libcubeb/gtest/test_sanity.cpp @@ -227,6 +227,9 @@ TEST(cubeb, configure_stream) r = cubeb_stream_set_volume(stream, 1.0f); ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED); + r = cubeb_stream_set_name(stream, "test 2"); + ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED); + cubeb_stream_destroy(stream); cubeb_destroy(ctx); } diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index f8abfcece779..9b92baecc3ef 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -230,12 +230,18 @@ 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_prefs; /** Stream format initialization parameters. */ @@ -584,6 +590,14 @@ CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t @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/media/libcubeb/moz.yaml b/media/libcubeb/moz.yaml index a5e76e7ee309..cb94f820cb5e 100644 --- a/media/libcubeb/moz.yaml +++ b/media/libcubeb/moz.yaml @@ -19,5 +19,5 @@ origin: license: "ISC" # update.sh will update this value - release: "1358724f731b9813410f1ef4015ea8c26a201ab2 (2020-09-22 11:09:51 +0200)" + release: "a7e83aa2b1571b842a555158e8f25aeb1419ebd1 (2020-10-13 12:05:17 +0100)" diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index 4aee68f9a872..9d3d1dd2fdbd 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -65,6 +65,7 @@ struct cubeb_ops { 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/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index 751e0444911f..84bf330a3208 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/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 @@ -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,6 +207,9 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam #if defined(USE_ALSA) alsa_init, #endif +#if defined (USE_OSS) + oss_init, +#endif #if defined(USE_AUDIOUNIT_RUST) audiounit_rust_init, #endif @@ -448,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) { @@ -676,4 +700,3 @@ int cubeb_set_log_callback(cubeb_log_level log_level, return CUBEB_OK; } - diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 4b479dcf73ce..deac2711253e 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -1446,6 +1446,7 @@ static struct cubeb_ops const alsa_ops = { .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/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp index 4b506d0d2836..c68903f1ea0f 100644 --- a/media/libcubeb/src/cubeb_audiounit.cpp +++ b/media/libcubeb/src/cubeb_audiounit.cpp @@ -1513,7 +1513,6 @@ audiounit_set_channel_layout(AudioUnit unit, return CUBEB_OK; } - OSStatus r; uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout); @@ -2070,7 +2069,6 @@ audiounit_create_unit(AudioUnit * unit, device_info * device) return CUBEB_OK; } - if (device->flags & DEV_INPUT) { r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE); if (r != CUBEB_OK) { @@ -2217,7 +2215,6 @@ buffer_size_changed_callback(void * inClientData, } switch (inPropertyID) { - case kAudioDevicePropertyBufferFrameSize: { if (inScope != au_scope) { break; @@ -2731,7 +2728,6 @@ audiounit_setup_stream(cubeb_stream * stm) LOG("(%p) Could not install all device change callback.", stm); } - return CUBEB_OK; } @@ -3212,7 +3208,6 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope } else { *min = *max = 0; } - } static UInt32 @@ -3623,6 +3618,7 @@ cubeb_ops const audiounit_ops = { /*.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/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp index d913b158c5c4..f80bdeadefab 100644 --- a/media/libcubeb/src/cubeb_jack.cpp +++ b/media/libcubeb/src/cubeb_jack.cpp @@ -134,6 +134,7 @@ static struct cubeb_ops const cbjack_ops = { .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, @@ -238,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) { @@ -257,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: @@ -269,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: @@ -381,7 +400,6 @@ cbjack_process(jack_nframes_t nframes, void * arg) } } } else { - // try to lock stream mutex if (pthread_mutex_trylock(&stm->mutex) == 0) { diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index 65520cf3fe8e..4d6e59cd7417 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -943,7 +943,6 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) } } - if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { SLAndroidConfigurationItf recorderConfig; res = (*stm->recorderObj) @@ -1756,6 +1755,7 @@ static struct cubeb_ops const opensl_ops = { .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/media/libcubeb/src/cubeb_oss.c b/media/libcubeb/src/cubeb_oss.c new file mode 100644 index 000000000000..3348cdc3dbea --- /dev/null +++ b/media/libcubeb/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/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 6f4c1e54c569..03a79ffbb80d 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -220,7 +220,6 @@ sndio_mainloop(void *arg) /* was this last call-back invocation (aka end-of-stream) ? */ if (nfr < s->nfr) { - if (!(s->mode & SIO_PLAY) || nfr == 0) { state = CUBEB_STATE_DRAINED; break; @@ -662,6 +661,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/media/libcubeb/src/cubeb_sun.c b/media/libcubeb/src/cubeb_sun.c index a0d77ee653ed..206de447f53b 100644 --- a/media/libcubeb/src/cubeb_sun.c +++ b/media/libcubeb/src/cubeb_sun.c @@ -723,6 +723,7 @@ static struct cubeb_ops const sun_ops = { .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/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index 653dd9986b55..1194eb942e82 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/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 @@ -1812,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, @@ -1835,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 @@ -2032,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. @@ -2082,6 +2110,13 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, 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"); @@ -3211,6 +3246,7 @@ cubeb_ops const wasapi_ops = { /*.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/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index 363fcd4ab6f2..a94c2a1c10e3 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -1061,6 +1061,7 @@ static struct cubeb_ops const winmm_ops = { /*.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,