#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #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 *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; } #ifdef HAVE_CONFIG_H #include #endif 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; const 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< 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; }