summaryrefslogtreecommitdiffstats
path: root/src/oss.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/oss.c')
-rw-r--r--src/oss.c858
1 files changed, 858 insertions, 0 deletions
diff --git a/src/oss.c b/src/oss.c
new file mode 100644
index 0000000..d09f8aa
--- /dev/null
+++ b/src/oss.c
@@ -0,0 +1,858 @@
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "sydney.h"
+#include "common.h"
+#include "macro.h"
+#include "malloc.h"
+#include "converter.h"
+#include "driver.h"
+#include "thread.h"
+#include "bufferq.h"
+
+#define DEFAULT_DEVICE "/dev/dsp"
+#define DRIVER_NAME "oss"
+
+typedef struct oss_stream oss_stream_t;
+#define OSS_STREAM(x) ((oss_stream_t*) (x->private))
+
+struct oss_stream {
+ sa_stream_t *parent;
+ int fd;
+ pcm_attrs_t real_pcm_attrs;
+ sa_converter_t converter_read, converter_write;
+ size_t read_fragment_size, write_fragment_size;
+ unsigned read_nfragments, write_nfragments;
+ sa_thread_t *thread;
+ int socket_fds[2];
+ sa_bufferq_t bufferq;
+};
+
+static int simple_log2(int v) {
+ int k = 0;
+
+ for (;;) {
+ v >>= 1;
+ if (!v) break;
+ k++;
+ }
+
+ return k;
+}
+
+static size_t fixup_bps(size_t s, size_t bps1, size_t bps2) {
+ return (s*bps2)/bps1;
+}
+
+int driver_open(sa_stream_t *s) {
+ oss_stream_t *oss;
+ char *n;
+ int f, arg, bs, r, phase, i, found, suggested, fs, l, m;
+ unsigned c;
+ size_t real_bps = 0, bps;
+ int loops = 0;
+
+ static const int format_map[_SA_PCM_FORMAT_MAX] = {
+ [SA_PCM_FORMAT_U8] = AFMT_U8,
+ [SA_PCM_FORMAT_ULAW] = AFMT_MU_LAW,
+ [SA_PCM_FORMAT_ALAW] = AFMT_A_LAW,
+ [SA_PCM_FORMAT_S16_LE] = AFMT_S16_LE,
+ [SA_PCM_FORMAT_S16_BE] = AFMT_S16_BE,
+ [SA_PCM_FORMAT_S24_LE] = AFMT_S16_NE, /* OSS doesn't know this format, hence we pick the best we can */
+ [SA_PCM_FORMAT_S24_BE] = AFMT_S16_NE,
+ [SA_PCM_FORMAT_S32_LE] = AFMT_S16_NE,
+ [SA_PCM_FORMAT_S32_BE] = AFMT_S16_NE,
+ [SA_PCM_FORMAT_FLOAT32_LE] = AFMT_S16_NE,
+ [SA_PCM_FORMAT_FLOAT32_BE] = AFMT_S16_NE
+ };
+ static const int try_rates[] = { 8000, 16000, 32000, 44100, 48000, 96000, 192000 };
+
+ if (s->driver && strcmp(s->driver, DRIVER_NAME))
+ return SA_ERROR_NO_DRIVER;
+
+ if (!(s->private = oss = sa_new0(oss_stream_t, 1)))
+ return SA_ERROR_OOM;
+
+ oss->parent = s;
+ oss->socket_fds[0] = oss->socket_fds[1] = -1;
+
+ n = s->device ? s->device : DEFAULT_DEVICE;
+ if (!s->codec)
+ bps = s->pcm_frame_size * s->pcm_attrs.rate;
+
+ for (;;) {
+ /* We need to loop here, because we have to call SETFRAGMENT
+ * as first ioctl after the open, at a point where we
+ * don't now yet the sample type, freq and the number of
+ * channels we actually settled on. Hence we have to loop
+ * here: if the sampling format is too far off we have to call
+ * SETFRAGMENT again which can do only after reopening the
+ * device again. */
+
+ if ((oss->fd = open(n, s->mode == SA_MODE_RDONLY ? O_RDONLY : (s->mode == SA_MODE_WRONLY ? O_WRONLY : O_RDWR) | O_NOCTTY | O_NONBLOCK)) < 0) {
+
+ if (errno == ENODEV || errno == ENOENT)
+ r = SA_ERROR_NO_DEVICE;
+ else
+ r = SA_ERROR_SYSTEM;
+
+ goto fail;
+ }
+
+ fcntl(oss->fd, F_SETFL, fcntl(oss->fd, F_GETFL) & ~O_NONBLOCK); /* FIXME*/
+
+ if (!s->device) {
+ if (!(n = sa_strdup(n))) {
+ r = SA_ERROR_OOM;
+ goto fail;
+ }
+
+ s->device = n;
+ }
+
+ /* Set fragment settings */
+ if (s->mode & SA_MODE_WRONLY) {
+ bs = s->write_lower_watermark;
+ }
+
+ if (s->mode & SA_MODE_RDONLY) {
+ int rbs;
+
+ rbs = s->read_upper_watermark;
+
+ if (s->mode & SA_MODE_WRONLY)
+ bs = bs > rbs ? bs : rbs;
+ else
+ bs = rbs;
+ }
+
+ if (!s->codec && real_bps)
+ bs = fixup_bps(bs, bps, real_bps);
+
+ fs = bs/4;
+ l = simple_log2(fs);
+ if (l < 1) l = 1;
+ m = (bs+(1<<l)-1)/(1<<l);
+ if (m < 2) m = 2;
+ if (m > 0x7FFF) m = 0x7FFF;
+
+ printf("Asking OSS for: %u fragments, %u fragsize\n", m, 1 << l);
+
+ arg = (m << 16) | l;
+
+ ioctl(oss->fd, SNDCTL_DSP_SETFRAGMENT, &arg);
+ /* We ignore errors on this call, since it's merely a hint anyway */
+
+ if (s->codec) {
+
+ if (strcmp(s->codec, SA_CODEC_AC3) == 0)
+ f = AFMT_AC3;
+ else if (strcmp(s->codec, SA_CODEC_MPEG) == 0)
+ f = AFMT_MPEG;
+ else {
+ r = SA_ERROR_NO_CODEC;
+ goto fail;
+ }
+
+ } else
+ f = format_map[s->pcm_attrs.format];
+
+ bs = 0;
+
+ for (;;) {
+ arg = f;
+
+ if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &arg) < 0) {
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (arg == f)
+ break;
+
+ /* Hmm, the device doesn't support what we're looking for,
+ * let's try our luck */
+
+ if (f == AFMT_S16_LE && !bs) {
+ f = AFMT_S16_BE;
+ bs = 1;
+ } else if (f == AFMT_S16_BE && !bs) {
+ f = AFMT_S16_LE;
+ bs = 1;
+ } else if (f == AFMT_S16_LE || f == AFMT_S16_BE) {
+ f = AFMT_MU_LAW;
+ bs = 0;
+ } else if (f == AFMT_MU_LAW && !bs) {
+ f = AFMT_A_LAW;
+ bs = 1;
+ } else if (f == AFMT_A_LAW && !bs) {
+ f = AFMT_MU_LAW;
+ bs = 1;
+ } else if (f == AFMT_A_LAW || f == AFMT_MU_LAW) {
+ f = AFMT_U8;
+ } else if (f == AFMT_AC3 || f == AFMT_MPEG) {
+ r = SA_ERROR_NO_CODEC;
+ goto fail;
+ } else {
+ r = SA_ERROR_NO_PCM_FORMAT;
+ goto fail;
+ }
+ }
+
+ if (!s->codec) {
+
+ switch (f) {
+ case AFMT_MU_LAW:
+ oss->real_pcm_attrs.format = SA_PCM_FORMAT_ULAW;
+ break;
+
+ case AFMT_A_LAW:
+ oss->real_pcm_attrs.format = SA_PCM_FORMAT_ALAW;
+ break;
+
+ case AFMT_U8:
+ oss->real_pcm_attrs.format = SA_PCM_FORMAT_U8;
+ break;
+
+ case AFMT_S16_LE:
+ oss->real_pcm_attrs.format = SA_PCM_FORMAT_S16_LE;
+ break;
+
+ case AFMT_S16_BE:
+ oss->real_pcm_attrs.format = SA_PCM_FORMAT_S16_BE;
+ break;
+
+ default:
+ sa_assert_not_reached();
+ }
+
+
+ found = 0;
+
+ if (s->adjust_nchannels >= 0) {
+
+ /* First try more channels ... */
+ for (c = s->pcm_attrs.nchannels; c < 8 || c == s->pcm_attrs.nchannels; c ++) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0) {
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* ... then try less channels */
+ if (!found) {
+ for (c = s->pcm_attrs.nchannels - 1; c > 0; c --) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0) {
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ } else {
+
+ /* First try less channels ... */
+ for (c = s->pcm_attrs.nchannels; c > 0; c --) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0) {
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* ... then try more channels */
+ if (!found) {
+ for (c = s->pcm_attrs.nchannels + 1; c < 8; c ++) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0) {
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!found) {
+ errno = EIO;
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ oss->real_pcm_attrs.nchannels = c;
+
+ if (!(oss->real_pcm_attrs.channel_map = sa_new(sa_channel_t, c))) {
+ r = SA_ERROR_OOM;
+ goto fail;
+ }
+
+ switch (c) {
+ case 8:
+ oss->real_pcm_attrs.channel_map[7] = SA_CHANNEL_REAR_RIGHT;
+ case 7:
+ oss->real_pcm_attrs.channel_map[6] = SA_CHANNEL_REAR_LEFT;
+ case 6:
+ oss->real_pcm_attrs.channel_map[5] = SA_CHANNEL_FRONT_LEFT;
+ case 5:
+ oss->real_pcm_attrs.channel_map[4] = SA_CHANNEL_FRONT_RIGHT;
+ case 4:
+ oss->real_pcm_attrs.channel_map[3] = SA_CHANNEL_LFE;
+ case 3:
+ oss->real_pcm_attrs.channel_map[2] = SA_CHANNEL_CENTER;
+ case 2:
+ oss->real_pcm_attrs.channel_map[1] = SA_CHANNEL_RIGHT;
+ oss->real_pcm_attrs.channel_map[0] = SA_CHANNEL_LEFT;
+ break;
+ case 1:
+ oss->real_pcm_attrs.channel_map[0] = SA_CHANNEL_MONO;
+ break;
+ }
+
+ r = s->pcm_attrs.rate;
+
+ if (r < 8000)
+ r = 8000;
+
+ suggested = 0;
+ phase = 0;
+
+ for (;;) {
+ arg = r;
+
+ if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &arg) < 0) {
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ sa_assert(arg > 0);
+
+ if (arg >= r*0.95 || arg <= r *1.05)
+ break;
+
+ if (arg > suggested)
+ suggested = arg;
+
+ if (s->adjust_rate >= 0) {
+
+ if (phase == 0) {
+ /* Find the next higher sample rate to try */
+
+ for (i = 0; i < (int) SA_ELEMENTSOF(try_rates); i++) {
+ /* Yes, we could optimize a little here */
+
+ if (try_rates[i] > r) {
+ r = try_rates[i];
+ break;
+ }
+ }
+
+
+ if (i == SA_ELEMENTSOF(try_rates)) {
+ phase = 1;
+ r = s->pcm_attrs.rate;
+ }
+ }
+
+ if (phase == 1) {
+ /* Find the next lower sample rate to try */
+
+ for (i = SA_ELEMENTSOF(try_rates); i > 0; i--) {
+ if (suggested > try_rates[i-1] && suggested < r) {
+ r = suggested;
+ break;
+ } else if (try_rates[i-1] < r) {
+ r = try_rates[i-1];
+ break;
+ }
+ }
+
+ sa_assert(i > 0);
+ }
+
+ } else {
+
+ if (phase == 0) {
+ /* Find the next lower sample rate to try */
+
+ for (i = SA_ELEMENTSOF(try_rates); i > 0; i--) {
+
+ if (try_rates[i-1] < r) {
+ r = try_rates[i-1];
+ break;
+ }
+ }
+
+ if (i == 0) {
+ phase = 1;
+ r = s->pcm_attrs.rate;
+ }
+ }
+
+ if (phase == 1) {
+ /* Find the next higher sample rate to try */
+
+ for (i = 0; i < (int) SA_ELEMENTSOF(try_rates); i++) {
+ if (suggested > r && suggested < try_rates[i]) {
+ r = suggested;
+ break;
+ } else if (try_rates[i] < r) {
+ r = try_rates[i];
+ break;
+ }
+ }
+
+ sa_assert(i < (int) SA_ELEMENTSOF(try_rates));
+ }
+ }
+
+ }
+
+ oss->real_pcm_attrs.rate = r;
+
+ printf("Chosen: %u channels, %uHz, format=%u\n", oss->real_pcm_attrs.nchannels, oss->real_pcm_attrs.rate, oss->real_pcm_attrs.format);
+
+ real_bps = oss->real_pcm_attrs.nchannels * oss->real_pcm_attrs.rate * sa_get_pcm_sample_size(oss->real_pcm_attrs.format);
+
+ if (real_bps != bps && loops < 1) {
+ loops++;
+
+ sa_free(oss->real_pcm_attrs.channel_map);
+ oss->real_pcm_attrs.channel_map = NULL;
+
+ close(oss->fd);
+
+ printf("bps changed, retrying...\n");
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ /* First, let's try GETBLKSIZE */
+ if (ioctl(oss->fd, SNDCTL_DSP_GETBLKSIZE, &arg) >= 0) {
+ if (s->mode & SA_MODE_RDONLY) {
+ oss->read_fragment_size = arg;
+ oss->read_nfragments = 2;
+ }
+
+ if (s->mode & SA_MODE_WRONLY) {
+ oss->write_fragment_size = arg;
+ oss->write_nfragments = 2;
+ }
+ }
+
+ /* Now, let's use GETxSPACE */
+ if (s->mode & SA_MODE_RDONLY) {
+ audio_buf_info info;
+
+ if (ioctl(oss->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) {
+ oss->read_fragment_size = info.fragsize;
+ oss->read_nfragments = info.fragstotal;
+ }
+ }
+
+ if (s->mode & SA_MODE_WRONLY) {
+ audio_buf_info info;
+
+ if (ioctl(oss->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
+ oss->write_fragment_size = info.fragsize;
+ oss->write_nfragments = info.fragstotal;
+ }
+ }
+
+ if (s->mode & SA_MODE_WRONLY && (oss->write_fragment_size <= 0 || oss->write_nfragments <= 1)) {
+ errno = EIO;
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (s->mode & SA_MODE_RDONLY && (oss->read_fragment_size <= 0 || oss->read_nfragments <= 1)) {
+ errno = EIO;
+ r = SA_ERROR_SYSTEM;
+ goto fail;
+ }
+
+ if (!s->codec) {
+
+ if (s->adjust_nchannels != 0) {
+ sa_channel_t *cm;
+
+ if (!(cm = sa_newdup(sa_channel_t, oss->real_pcm_attrs.channel_map, oss->real_pcm_attrs.nchannels))) {
+ r = SA_ERROR_OOM;
+ goto fail;
+ }
+
+ sa_free(s->pcm_attrs.channel_map);
+ s->pcm_attrs.channel_map = cm;
+
+ s->pcm_attrs.nchannels = oss->real_pcm_attrs.nchannels;
+ }
+ if (s->adjust_rate != 0)
+ s->pcm_attrs.rate = oss->real_pcm_attrs.rate;
+ if (s->adjust_pcm_format != 0)
+ s->pcm_attrs.format = oss->real_pcm_attrs.format;
+
+ if (s->mode & SA_MODE_RDONLY)
+ if ((r = sa_converter_init(&oss->converter_read, &oss->real_pcm_attrs, &s->pcm_attrs, s->dynamic_rate_enabled)) < 0)
+ goto fail;
+
+ if (s->mode & SA_MODE_WRONLY)
+ if ((r = sa_converter_init(&oss->converter_write, &s->pcm_attrs, &oss->real_pcm_attrs, s->dynamic_rate_enabled)) < 0)
+ goto fail;
+ }
+
+ if (s->adjust_watermarks) {
+
+ if (s->mode & SA_MODE_RDONLY) {
+ s->read_lower_watermark = oss->read_fragment_size;
+ s->read_upper_watermark = oss->read_fragment_size * oss->read_nfragments;
+ }
+
+ if (s->mode & SA_MODE_WRONLY) {
+ s->write_lower_watermark = oss->write_fragment_size;
+ s->write_upper_watermark = oss->write_fragment_size * oss->write_nfragments;
+ }
+ }
+
+ if (s->mode & SA_MODE_RDONLY)
+ printf("Chosen for read: %u fragments, %u fragsize\n", oss->read_nfragments, oss->read_fragment_size);
+
+ if (s->mode & SA_MODE_WRONLY)
+ printf("Chosen for write: %u fragments, %u fragsize\n", oss->write_nfragments, oss->write_fragment_size);
+
+ if (sa_bufferq_init(&oss->bufferq, s->pcm_attrs.nchannels, s->pcm_sample_size) < 0)
+ goto fail;
+
+ return SA_SUCCESS;
+
+fail:
+ driver_destroy(s);
+ return r;
+}
+
+int driver_destroy(sa_stream_t *s) {
+ oss_stream_t *oss = OSS_STREAM(s);
+
+ if (oss) {
+
+ if (oss->thread)
+ driver_stop_thread(s);
+
+ if (oss->fd >= 0)
+ close(oss->fd);
+
+ sa_bufferq_done(&oss->bufferq);
+
+ sa_free(oss->real_pcm_attrs.channel_map);
+ sa_converter_done(&oss->converter_read);
+ sa_converter_done(&oss->converter_write);
+
+ sa_free(oss);
+ }
+
+ return SA_SUCCESS;
+}
+
+static int feed(sa_stream_t *s) {
+ oss_stream_t *oss = OSS_STREAM(s);
+ size_t w;
+ int ret;
+
+ sa_assert(s);
+
+ for (;;) {
+ if ((ret = driver_get_write_size(s, &w)) < 0)
+ return ret;
+
+ while (w > 0) {
+ void* i[1];
+ size_t nbytes;
+ uint8_t *d;
+ int drop;
+
+ sa_bufferq_get(&oss->bufferq, i, &nbytes);
+
+ if (nbytes) {
+ void **dst;
+ size_t *stride;
+
+ if ((ret = sa_converter_go_interleaved(&oss->converter_write, i[0], &dst, &stride, 1, &nbytes)))
+ return ret;
+
+ d = dst[0];
+ drop = 1;
+
+ s->state = SA_STATE_RUNNING;
+
+ } else {
+ nbytes = w > oss->write_fragment_size ? oss->write_fragment_size : w;
+
+ if (!(d = sa_converter_get_zero_buffer(&oss->converter_write, nbytes)))
+ return SA_ERROR_OOM;
+
+ drop = s->xrun_mode == SA_XRUN_MODE_SPIN;
+
+ if (s->xrun_mode == SA_XRUN_MODE_STOP)
+ s->state = SA_STATE_STOPPED;
+ }
+
+ while (nbytes > 0) {
+ ssize_t l;
+
+ if ((l = write(oss->fd, d, nbytes)) < 0)
+ return SA_ERROR_SYSTEM;
+
+ sa_assert(l > 0);
+
+ nbytes -= l;
+ d += l;
+
+ w -= 1;
+
+ if (drop)
+ sa_bufferq_drop(&oss->bufferq, l);
+ }
+ }
+ }
+
+ return SA_SUCCESS;
+}
+
+enum {
+ POLLFD_OSS_FD,
+ POLLFD_SOCKET_FD,
+ POLLFD_MAX
+};
+
+static void thread_func(void *data) {
+ struct pollfd pollfds[POLLFD_MAX];
+ sa_stream_t *s = data;
+ oss_stream_t *oss = OSS_STREAM(s);
+ sigset_t mask;
+
+ sigfillset(&mask);
+ pthread_sigmask(SIG_BLOCK, &mask, NULL);
+
+ if (s->callback) {
+ s->event = SA_EVENT_INIT_THREAD;
+ if (s->callback(s, SA_EVENT_INIT_THREAD) < 0)
+ return;
+ }
+
+ memset(pollfds, 0, sizeof(pollfds));
+
+ pollfds[POLLFD_SOCKET_FD].fd = oss->socket_fds[0];
+ pollfds[POLLFD_SOCKET_FD].events = POLLIN;
+
+ pollfds[POLLFD_OSS_FD].fd = oss->fd;
+ pollfds[POLLFD_OSS_FD].events = ((s->mode & SA_MODE_RDONLY) ? POLLIN : 0) | ((s->mode & SA_MODE_WRONLY) ? POLLOUT : 0);
+
+ for (;;) {
+ int ret;
+ if (poll(pollfds, POLLFD_MAX, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ if (s->callback) {
+ s->event = SA_EVENT_ERROR;
+ s->error = SA_ERROR_SYSTEM;
+ s->callback(s, SA_EVENT_ERROR);
+ }
+ break;
+ }
+
+ if (pollfds[POLLFD_SOCKET_FD].revents)
+ break;
+
+ if (pollfds[POLLFD_OSS_FD].revents & (POLLERR|POLLHUP|POLLNVAL)) {
+
+ if (s->callback) {
+ s->event = SA_EVENT_ERROR;
+ s->error = SA_ERROR_SYSTEM;
+ errno = EIO;
+ s->callback(s, SA_EVENT_ERROR);
+ }
+ break;
+ }
+
+ if (s->callback) {
+ s->event = SA_EVENT_REQUEST_IO;
+ if (s->callback(s, SA_EVENT_REQUEST_IO) < 0)
+ break;
+ }
+
+ if ((ret = feed(s)) < 0) {
+ if (s->callback) {
+ s->event = SA_EVENT_ERROR;
+ s->error = ret;
+ s->callback(s, SA_EVENT_ERROR);
+ }
+ break;
+ }
+ }
+}
+
+int driver_start_thread(sa_stream_t *s, sa_event_callback_t callback) {
+ oss_stream_t *oss = OSS_STREAM(s);
+ sa_return_val_if_fail(!oss->thread, SA_ERROR_STATE);
+
+ s->callback = callback;
+
+ if ((socketpair(AF_UNIX, SOCK_DGRAM, 0, oss->socket_fds)) < 0)
+ return SA_ERROR_SYSTEM;
+
+ if (!(oss->thread = sa_thread_new(thread_func, s)))
+ return SA_ERROR_OOM;
+
+ return SA_SUCCESS;
+}
+
+int driver_stop_thread(sa_stream_t *s) {
+ oss_stream_t *oss = OSS_STREAM(s);
+ sa_return_val_if_fail(oss->thread, SA_ERROR_STATE);
+ sa_return_val_if_fail(oss->thread != sa_thread_self(), SA_ERROR_STATE);
+
+ if (oss->socket_fds[0] >= 0)
+ close(oss->socket_fds[0]);
+
+ if (oss->socket_fds[1] >= 0)
+ close(oss->socket_fds[1]);
+
+ if (oss->thread)
+ sa_thread_free(oss->thread);
+
+ oss->thread = NULL;
+ oss->socket_fds[0] = oss->socket_fds[1] = -1;
+
+ return SA_SUCCESS;
+}
+
+int driver_change_read_volume(sa_stream_t *s, const int32_t vol[]) {
+ oss_stream_t *oss = OSS_STREAM(s);
+
+ sa_return_val_if_fail(!s->codec, SA_ERROR_NOT_SUPPORTED);
+
+ sa_converter_set_volume(&oss->converter_read, vol);
+
+ return SA_SUCCESS;
+}
+
+int driver_change_write_volume(sa_stream_t *s, const int32_t vol[]) {
+ oss_stream_t *oss = OSS_STREAM(s);
+
+ sa_return_val_if_fail(!s->codec, SA_ERROR_NOT_SUPPORTED);
+
+ sa_converter_set_volume(&oss->converter_write, vol);
+
+ return SA_SUCCESS;
+}
+
+int driver_change_rate(sa_stream_t *s, unsigned rate) {
+ oss_stream_t *oss = OSS_STREAM(s);
+
+ if (s->mode & SA_MODE_RDONLY)
+ sa_converter_set_ratio(&oss->converter_read, oss->real_pcm_attrs.rate, s->pcm_attrs.rate);
+ if (s->mode & SA_MODE_WRONLY)
+ sa_converter_set_ratio(&oss->converter_write, s->pcm_attrs.rate, oss->real_pcm_attrs.rate);
+
+ return SA_SUCCESS;
+}
+
+int driver_get_state(sa_stream_t *s, sa_state_t *state) {
+ *state = s->state;
+ return SA_SUCCESS;
+}
+
+int driver_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_read(sa_stream_t *s, void *data, size_t nbytes) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) {
+ oss_stream_t *oss = OSS_STREAM(s);
+ int ret, ret2;
+
+ if ((ret = sa_bufferq_push(&oss->bufferq, 0, data, nbytes, offset, whence, SA_BUFFERQ_ITEM_STATIC)))
+ return ret;
+
+ ret = feed(s);
+ ret2 = sa_bufferq_realloc(&oss->bufferq);
+
+ return ret ? ret : ret2;
+}
+
+int driver_read_ni(sa_stream_t *s, unsigned channel, void *data, size_t nbytes) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_pwrite_ni(sa_stream_t *s, unsigned channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_get_read_size(sa_stream_t *s, size_t *size) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_get_write_size(sa_stream_t *s, size_t *size) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_resume(sa_stream_t *s) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_pause(sa_stream_t *s) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_drain(sa_stream_t *s) {
+ oss_stream_t *oss = OSS_STREAM(s);
+ sa_return_val_if_fail(!oss->thread, SA_ERROR_STATE);
+
+ if (ioctl(oss->fd, SNDCTL_DSP_SYNC, NULL) < 0)
+ return SA_ERROR_SYSTEM;
+
+ return SA_SUCCESS;
+}
+
+/* Unsupported operations */
+
+int driver_change_device(sa_stream_t *s, const char *device) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int driver_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size) {
+ return SA_ERROR_NOT_SUPPORTED;
+}