/*** This file is part of libsydney. Copyright 2007-2008 Lennart Poettering libsydney is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. libsydney is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with libsydney. If not, see . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MACHINE_SOUNDCARD_H # include #else # ifdef HAVE_SOUNDCARD_H # include # else # include # endif #endif #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" struct private { sa_stream *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 *thread; int socket_fds[2]; sa_bufferq_t bufferq; void *cdata; }; #define PRIVATE(c) ((struct private *) ((c)->private)) static inline unsigned ulog2(unsigned n) { if (n <= 1) return 0; #if __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) return 8U * (unsigned) sizeof(unsigned) - (unsigned) __builtin_clz(n) - 1; #else { unsigned r = 0; for (;;) { n = n >> 1; if (!n) return r; r++; } } #endif } static size_t fixup_bps(size_t s, size_t bps1, size_t bps2) { return (s*bps2)/bps1; } int driver_open(sa_stream *s) { struct private *p; 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 && !sa_streq(s->driver, "oss")) return SA_ERROR_NODRIVER; if (!(s->private = p = sa_new0(struct private, 1))) return SA_ERROR_OOM; p->parent = s; p->socket_fds[0] = p->socket_fds[1] = -1; n = s->device ? s->device : (char*) 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 ((p->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_NODEVICE; else r = SA_ERROR_SYSTEM; goto fail; } fcntl(p->fd, F_SETFL, fcntl(p->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; */ bs = 1024*10; /* FIXME */ } if (s->mode & SA_MODE_RDONLY) { int rbs; /* rbs = s->read_upper_watermark; */ bs = 1024*10; /* FIXME */ if (s->mode & SA_MODE_WRONLY) bs = bs > rbs ? bs : rbs; else bs = rbs; } if (!s->codec && real_bps) bs = (int) fixup_bps((size_t) bs, bps, real_bps); fs = bs/4; l = ulog2(fs); if (l < 1) l = 1; m = (bs+(1< 0x7FFF) m = 0x7FFF; printf("Asking OSS for: %u fragments, %u fragsize\n", m, 1 << l); arg = (m << 16) | l; ioctl(p->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_NOCODEC; goto fail; } } else f = format_map[s->pcm_attrs.format]; bs = 0; for (;;) { arg = f; if (ioctl(p->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_NOCODEC; goto fail; } else { r = SA_ERROR_NOPCMFORMAT; goto fail; } } if (!s->codec) { switch (f) { case AFMT_MU_LAW: p->real_pcm_attrs.format = SA_PCM_FORMAT_ULAW; break; case AFMT_A_LAW: p->real_pcm_attrs.format = SA_PCM_FORMAT_ALAW; break; case AFMT_U8: p->real_pcm_attrs.format = SA_PCM_FORMAT_U8; break; case AFMT_S16_LE: p->real_pcm_attrs.format = SA_PCM_FORMAT_S16_LE; break; case AFMT_S16_BE: p->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 = (int) c; if (ioctl(p->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 = (int) c; if (ioctl(p->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 = (int) c; if (ioctl(p->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(p->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; } p->real_pcm_attrs.nchannels = c; if (!(p->real_pcm_attrs.channel_map = sa_new(sa_channel_t, c))) { r = SA_ERROR_OOM; goto fail; } switch (c) { case 8: p->real_pcm_attrs.channel_map[7] = SA_CHANNEL_REAR_RIGHT; case 7: p->real_pcm_attrs.channel_map[6] = SA_CHANNEL_REAR_LEFT; case 6: p->real_pcm_attrs.channel_map[5] = SA_CHANNEL_FRONT_LEFT; case 5: p->real_pcm_attrs.channel_map[4] = SA_CHANNEL_FRONT_RIGHT; case 4: p->real_pcm_attrs.channel_map[3] = SA_CHANNEL_LFE; case 3: p->real_pcm_attrs.channel_map[2] = SA_CHANNEL_CENTER; case 2: p->real_pcm_attrs.channel_map[1] = SA_CHANNEL_RIGHT; p->real_pcm_attrs.channel_map[0] = SA_CHANNEL_LEFT; break; case 1: p->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(p->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)); } } } p->real_pcm_attrs.rate = r; printf("Chosen: %u channels, %uHz, format=%u\n", p->real_pcm_attrs.nchannels, p->real_pcm_attrs.rate, p->real_pcm_attrs.format); real_bps = p->real_pcm_attrs.nchannels * p->real_pcm_attrs.rate * sa_get_pcm_sample_size(p->real_pcm_attrs.format); if (real_bps != bps && loops < 1) { loops++; sa_free(p->real_pcm_attrs.channel_map); p->real_pcm_attrs.channel_map = NULL; close(p->fd); printf("bps changed, retrying...\n"); continue; } } break; } /* First, let's try GETBLKSIZE */ if (ioctl(p->fd, SNDCTL_DSP_GETBLKSIZE, &arg) >= 0) { if (s->mode & SA_MODE_RDONLY) { p->read_fragment_size = arg; p->read_nfragments = 2; } if (s->mode & SA_MODE_WRONLY) { p->write_fragment_size = arg; p->write_nfragments = 2; } } /* Now, let's use GETxSPACE */ if (s->mode & SA_MODE_RDONLY) { audio_buf_info info; if (ioctl(p->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { p->read_fragment_size = info.fragsize; p->read_nfragments = info.fragstotal; } } if (s->mode & SA_MODE_WRONLY) { audio_buf_info info; if (ioctl(p->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { p->write_fragment_size = info.fragsize; p->write_nfragments = info.fragstotal; } } if (s->mode & SA_MODE_WRONLY && (p->write_fragment_size <= 0 || p->write_nfragments <= 1)) { errno = EIO; r = SA_ERROR_SYSTEM; goto fail; } if (s->mode & SA_MODE_RDONLY && (p->read_fragment_size <= 0 || p->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, p->real_pcm_attrs.channel_map, p->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 = p->real_pcm_attrs.nchannels; } if (s->adjust_rate != 0) s->pcm_attrs.rate = p->real_pcm_attrs.rate; if (s->adjust_pcm_format != 0) s->pcm_attrs.format = p->real_pcm_attrs.format; if (s->mode & SA_MODE_RDONLY) if ((r = sa_converter_init(&p->converter_read, &p->real_pcm_attrs, &s->pcm_attrs, s->dynamic_rate_enabled)) < 0) goto fail; if (s->mode & SA_MODE_WRONLY) if ((r = sa_converter_init(&p->converter_write, &s->pcm_attrs, &p->real_pcm_attrs, s->dynamic_rate_enabled)) < 0) goto fail; } /* if (s->adjust_watermarks) { */ /* if (s->mode & SA_MODE_RDONLY) { */ /* s->read_lower_watermark = p->read_fragment_size; */ /* s->read_upper_watermark = p->read_fragment_size * p->read_nfragments; */ /* } */ /* if (s->mode & SA_MODE_WRONLY) { */ /* s->write_lower_watermark = p->write_fragment_size; */ /* s->write_upper_watermark = p->write_fragment_size * p->write_nfragments; */ /* } */ /* } */ if (s->mode & SA_MODE_RDONLY) printf("Chosen for read: %u fragments, %lu fragsize\n", p->read_nfragments, (unsigned long) p->read_fragment_size); if (s->mode & SA_MODE_WRONLY) printf("Chosen for write: %u fragments, %lu fragsize\n", p->write_nfragments, (unsigned long) p->write_fragment_size); if (sa_bufferq_init(&p->bufferq, s->ni_enabled ? s->pcm_attrs.nchannels : 1) < 0) goto fail; if (!(p->cdata = sa_new(void*, s->ni_enabled ? s->pcm_attrs.nchannels : 1))) goto fail; return SA_SUCCESS; fail: driver_destroy(s); return r; } int driver_destroy(sa_stream *s) { struct private *p = PRIVATE(s); if (p) { if (p->thread) sa_thread_free(p->thread); if (p->fd >= 0) close(p->fd); sa_bufferq_done(&p->bufferq); sa_free(p->real_pcm_attrs.channel_map); sa_converter_done(&p->converter_read); sa_converter_done(&p->converter_write); sa_free(p->cdata); sa_free(p); } return SA_SUCCESS; } static int feed(sa_stream *s) { struct private *p = PRIVATE(s); size_t w; int ret; sa_assert(s); for (;;) { if ((ret = driver_get_write_size(s, &w)) < 0) return ret; while (w > 0) { size_t nbytes; uint8_t *d; sa_bool_t drop; void *cdata; sa_bufferq_get(&p->bufferq, cdata, &nbytes); if (nbytes > 0) { void **dst; size_t *stride; if (s->ni_enabled) { /* if ((ret = sa_converter_go(&p->converter_write, cdata, &dst, &stride, */ } else { if ((ret = sa_converter_go_interleaved(&p->converter_write, cdata, &dst, &stride, 1, &nbytes))) goto fail; d = dst[0]; } drop = TRUE; s->state = SA_STATE_RUNNING; } else { nbytes = w > p->write_fragment_size ? p->write_fragment_size : w; if (!(d = sa_converter_get_zero_buffer(&p->converter_write, nbytes))) { ret = SA_ERROR_OOM; goto fail; } 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(p->fd, d, nbytes)) < 0) { ret = SA_ERROR_SYSTEM; goto fail; } sa_assert(l > 0); nbytes -= l; d += l; w -= 1; if (drop) sa_bufferq_drop(&p->bufferq, l); } } } ret = SA_SUCCESS; fail: /* sa_free(i); */ return ret; } enum { POLLFD_OSS_FD, POLLFD_SOCKET_FD, POLLFD_MAX }; static void thread_func(void *data) { struct pollfd pollfds[POLLFD_MAX]; sa_stream *s = data; struct private *p = PRIVATE(s); sigset_t mask; sa_assert_se(sigfillset(&mask) == 0); sa_assert_se(pthread_sigmask(SIG_BLOCK, &mask, NULL) == 0); 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 = p->socket_fds[0]; pollfds[POLLFD_SOCKET_FD].events = POLLIN; pollfds[POLLFD_OSS_FD].fd = p->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 *s, sa_event_callback_t callback) { */ /* struct private *p = PRIVATE(s); */ /* sa_return_val_if_fail(!p->thread, SA_ERROR_STATE); */ /* s->callback = callback; */ /* if ((socketpair(AF_UNIX, SOCK_DGRAM, 0, p->socket_fds)) < 0) */ /* return SA_ERROR_SYSTEM; */ /* if (!(p->thread = sa_thread_new(thread_func, s))) */ /* return SA_ERROR_OOM; */ /* return SA_SUCCESS; */ /* } */ /* int driver_stop_thread(sa_stream *s) { */ /* struct private *p = PRIVATE(s); */ /* sa_return_val_if_fail(p->thread, SA_ERROR_STATE); */ /* sa_return_val_if_fail(p->thread != sa_thread_self(), SA_ERROR_STATE); */ /* if (p->socket_fds[0] >= 0) */ /* close(p->socket_fds[0]); */ /* if (p->socket_fds[1] >= 0) */ /* close(p->socket_fds[1]); */ /* if (p->thread) */ /* sa_thread_free(p->thread); */ /* p->thread = NULL; */ /* p->socket_fds[0] = p->socket_fds[1] = -1; */ /* return SA_SUCCESS; */ /* } */ int driver_change_read_volume(sa_stream *s, const int32_t vol[]) { struct private *p = PRIVATE(s); sa_return_val_if_fail(!s->codec, SA_ERROR_NOTSUPPORTED); sa_converter_set_volume(&p->converter_read, vol); return SA_SUCCESS; } int driver_change_write_volume(sa_stream *s, const int32_t vol[]) { struct private *p = PRIVATE(s); sa_return_val_if_fail(!s->codec, SA_ERROR_NOTSUPPORTED); sa_converter_set_volume(&p->converter_write, vol); return SA_SUCCESS; } int driver_change_pcm_rate(sa_stream *s, unsigned rate) { struct private *p = PRIVATE(s); if (s->mode & SA_MODE_RDONLY) sa_converter_set_ratio(&p->converter_read, p->real_pcm_attrs.rate, s->pcm_attrs.rate); if (s->mode & SA_MODE_WRONLY) sa_converter_set_ratio(&p->converter_write, s->pcm_attrs.rate, p->real_pcm_attrs.rate); return SA_SUCCESS; } int driver_get_state(sa_stream *s, sa_state_t *state) { *state = s->state; return SA_SUCCESS; } int driver_get_position(sa_stream *s, sa_position_t position, int64_t *pos) { return SA_ERROR_NOTSUPPORTED; } int driver_read(sa_stream *s, void *data, size_t nbytes) { return SA_ERROR_NOTSUPPORTED; } int driver_pwrite(sa_stream *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) { struct private *p = PRIVATE(s); int ret, ret2; if ((ret = sa_bufferq_push(&p->bufferq, 0, data, nbytes, offset, whence, SA_BUFFERQ_ITEM_STATIC))) return ret; ret = feed(s); ret2 = sa_bufferq_realloc(&p->bufferq); return ret ? ret : ret2; } int driver_read_ni(sa_stream *s, unsigned channel, void *data, size_t nbytes) { return SA_ERROR_NOTSUPPORTED; } int driver_pwrite_ni(sa_stream *s, unsigned channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) { return SA_ERROR_NOTSUPPORTED; } int driver_get_read_size(sa_stream *s, size_t *size) { return SA_ERROR_NOTSUPPORTED; } int driver_get_write_size(sa_stream *s, size_t *size) { return SA_ERROR_NOTSUPPORTED; } int driver_resume(sa_stream *s) { return SA_ERROR_NOTSUPPORTED; } int driver_pause(sa_stream *s) { return SA_ERROR_NOTSUPPORTED; } int driver_drain(sa_stream *s) { struct private *p = PRIVATE(s); sa_return_val_if_fail(!p->thread, SA_ERROR_STATE); if (ioctl(p->fd, SNDCTL_DSP_SYNC, NULL) < 0) return SA_ERROR_SYSTEM; return SA_SUCCESS; } /* Unsupported operations */ int driver_change_device(sa_stream *s, const char *device) { return SA_ERROR_NOTSUPPORTED; }