aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorkhng300 <[email protected]>2020-10-10 01:09:39 +0800
committerGitHub <[email protected]>2020-10-09 19:09:39 +0200
commit226d383c136b8586b69b74f420a5afc8b243cf49 (patch)
tree25f6a752bed0e3e796216461d730112d624f0169
parentdce06b123d69db3acafeeea811d845d491630216 (diff)
downloadcubeb-226d383c136b8586b69b74f420a5afc8b243cf49.tar.gz
cubeb-226d383c136b8586b69b74f420a5afc8b243cf49.zip
OSS (FreeBSD/illumos) backend (#600)
* ossaudio (FreeBSD currently) backend The ossaudio backend was rewritten from the sunaudio backend. * Fix leaking of mixer_fd in oss_enumerate_devices * Both input/output can be detected at the same DSP * Update on device detection during enumeration * Remove spurious calls to SNDCTL_CARDINFO * Fix string allocation detection in oss_enumerate_devices * On FreeBSD, try to open and probe the device's capability in oss_enumerate_devices * Rewrite oss_io_routine() to implement correctly for all stream directions * Add preferred device detection for FreeBSD * Emits stable devid from oss_enumerate_devices on FreeBSD * Fix SNDCTL_DSP_GETI/OSPACE calls in oss_stream_init * Use /dev/sndstat instead on FreeBSD for oss_enumerate_devices * Unify both play and record nfr and take the minimum value between them * Fix allocating both input/output buffers to input/output-only streams * Fix clipping issue * Fix misuse of input_device and output_device in oss_stream_init * Fix builds on Illumos * Code refactoring on oss_enumerate_devices * Improve oss_io_routine for the case when the stream is in both direction * Use fragsize instead of total buffer size for number of frames * Probe OSS audio availability in cubeb_init when backend is not specified * Fix scan-build10 report on src/cubeb_oss.c:285 * Add __DragonFly__ pp macro testing along-side __FreeBSD__ * Move oss_init in default_init[] table right below alsa to respect POLA * Fix use-after-free of s->mutex in oss_stream_destroy * Fix inconsistent indentation * Remove blocks_written from cubeb_stream * Add LL integer suffix to make 0x80000000 more clear * Add parsing of /dev/sndstat for hw.snd.verbose > 0 in FreeBSD * Do device setup in the order of channels, format and speed, according to developer/callorder.html of OSS v4.x API reference * Add proper latency_frame support for oss_stream_init * Mark close brackets and close braces NUL in oss_sndstat_line_parse * Search close brackets and close braces from the end of line in oss_sndstat_line_parse * Use 32 frames for each fragments in oss_calc_frag_params * Drop unnecessary #include <sys/sysctl.h> * Add support for cubeb_channel_layout * Compilation and scan-build fixes: * Fix warnings in oss_chn_from_cubeb * Include cubeb_mixer.h to have cubeb_channel_layout_nb_channels * Fix potential resource leakage in input_stream_layout->layout does not match input_stream_params->channels. (The same for output_stream_layout) * Classify cubeb_stream_params::layout and cubeb_stream_params::channels mismatch as CUBEB_ERROR_INVALID_PARAMETER * AUDIODEVICE can now override /dev/dsp AUDIODEVICE was chosen to match the sndio backend. * Change environment variable AUDIODEVICE to AUDIO_DEVICE in oss backend * Change the format of cubeb_device_info's friendly_name This avoids name collision when setting media.cubeb.output_device in about:config of Firefox. However, I think it makes more sense on Firefox's side to use device_id instead. * Fix warning of a missing %d in LOG() in oss_io_routine * Do not enable OSS compilation when SOUND_VERSION < 0x040000 * Add mutex to serialize access to cubeb_strings in cubeb_oss's cubeb_context * Change the calculation of fragsize/nfr to match stream latency, with 2 fragments available. By HPS * Now reuse the same thread for audiostream * Redo the duplex logic * Restructure the use of buffers * Fix problems dealing with draining * Revert "Fix problems dealing with draining" This reverts commit 30301ba101f8acadd1ff985cbf7eef48cef5ee36. * Revert "Restructure the use of buffers" This reverts commit 9823ca889ca407a202e7588229514b4b8cd99ec7. * Revert "Revert "Restructure the use of buffers"" This reverts commit 10dc869b852daea5ede3173ebaf76414562fbe93. * Revert "Revert "Fix problems dealing with draining"" This reverts commit 9d19b1094428ed960ef7683df9bee9fe769d78ac. * Make sure there are at least s->nfr available before calling data_cb * Kill a signed/unsigned comparison * Add The FreeBSD Foundation copyright line for recent commits * Fix race condition when restarting a stream by HPS Originally, when stopping a stream and immediately starting the stream again, the start call might be lost. * Do not signal the doorbell_cv again when a stream is already stopped This is redundant. * Revert "Do not signal the doorbell_cv again when a stream is already stopped" This reverts commit fbdf753fc720923336a979997ff85eeb9666b83b. * Make state changes look more similar to pulse backend This also allows drained/short-input stream to be resumed without stopping it first. * Remove a spurious s->running = false
-rw-r--r--CMakeLists.txt12
-rw-r--r--cmake/compile_tests/oss_is_v4.c10
-rw-r--r--src/cubeb.c10
-rw-r--r--src/cubeb_oss.c1259
4 files changed, 1291 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 349ba8e..5aeb622 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -199,6 +199,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/cmake/compile_tests/oss_is_v4.c b/cmake/compile_tests/oss_is_v4.c
new file mode 100644
index 0000000..a46bb15
--- /dev/null
+++ b/cmake/compile_tests/oss_is_v4.c
@@ -0,0 +1,10 @@
+#include <sys/soundcard.h>
+
+#if SOUND_VERSION < 0x040000
+# error "OSSv4 is not available in sys/soundcard.h"
+#endif
+
+int main()
+{
+ return 0;
+}
diff --git a/src/cubeb.c b/src/cubeb.c
index 8d077bd..74c17db 100644
--- a/src/cubeb.c
+++ b/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
@@ -166,6 +169,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#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)
init_oneshot = audiotrack_init;
@@ -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
diff --git a/src/cubeb_oss.c b/src/cubeb_oss.c
new file mode 100644
index 0000000..a95a4dd
--- /dev/null
+++ b/src/cubeb_oss.c
@@ -0,0 +1,1259 @@
+/*
+ * Copyright © 2019-2020 Nia Alarie <[email protected]>
+ * Copyright © 2020 Ka Ho Ng <[email protected]>
+ * 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 <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <poll.h>
+#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;
+ 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->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_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};