summaryrefslogtreecommitdiffstats
path: root/oss.c
diff options
context:
space:
mode:
Diffstat (limited to 'oss.c')
-rw-r--r--oss.c424
1 files changed, 424 insertions, 0 deletions
diff --git a/oss.c b/oss.c
new file mode 100644
index 0000000..f39571c
--- /dev/null
+++ b/oss.c
@@ -0,0 +1,424 @@
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#include "sydney.h"
+#include "common.h"
+#include "macro.h"
+#include "malloc.h"
+
+#define DEFAULT_DEVICE "/dev/dsp"
+#define DRIVER_NAME "oss"
+
+typedef struct oss_device oss_device_t;
+#define OSS_DEVICE(x) ((oss_device_t*) (x))
+
+struct oss_device {
+ sa_device_t parent;
+ int fd;
+
+ unsigned real_rate;
+ unsigned real_nchannels;
+ sa_pcm_format_t real_pcm_format;
+};
+
+int device_create_opaque(sa_device_t **dev, const char *client_name, sa_mode_t mode, const char *codec) {
+ int error;
+
+ if ((error = device_alloc_opaque(dev, sizeof(oss_device_t), client_name, mode, codec)))
+ return error;
+
+ OSS_DEVICE(*dev)->fd = -1;
+
+ return SA_SUCCESS;
+}
+
+int device_create_pcm(sa_device_t **dev, const char *client_name, sa_mode_t mode, sa_pcm_format_t format, unsigned rate, unsigned channels) {
+ int error;
+
+ if ((error = device_alloc_pcm(dev, sizeof(oss_device_t), client_name, mode, format, rate, channels)))
+ return error;
+
+ OSS_DEVICE(*dev)->fd = -1;
+
+ return SA_SUCCESS;
+}
+
+int device_open(sa_device_t *dev) {
+ oss_device_t *oss = OSS_DEVICE(dev);
+ char *n;
+ int f, arg, bs, r, phase, i, found, suggested;
+ unsigned c;
+
+ 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 (dev->driver && strcmp(dev->driver, DRIVER_NAME))
+ return SA_ERROR_NO_DRIVER;
+
+ sa_assert(oss->fd < 0);
+
+ n = dev->device ? dev->device : DEFAULT_DEVICE;
+ if ((oss->fd = open(n, dev->mode == SA_MODE_RDONLY ? O_RDONLY : (dev->mode == SA_MODE_WRONLY ? O_WRONLY : O_RDWR) | O_NOCTTY | O_NONBLOCK)) < 0) {
+
+ if (errno == ENODEV || errno == ENOENT)
+ return SA_ERROR_NO_DEVICE;
+
+ return SA_ERROR_SYSTEM;
+ }
+
+ if (!dev->device) {
+ if (!(n = sa_strdup(n)))
+ return SA_ERROR_OOM;
+
+ dev->device = n;
+ }
+
+ if (dev->codec) {
+
+ if (strcmp(dev->codec, SA_CODEC_AC3) == 0)
+ f = AFMT_AC3;
+ else if (strcmp(dev->codec, SA_CODEC_MPEG) == 0)
+ f = AFMT_MPEG;
+ else
+ return SA_ERROR_NO_CODEC;
+
+ } else
+ f = format_map[dev->pcm_format];
+
+ bs = 0;
+
+ for (;;) {
+ arg = f;
+
+ if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &arg) < 0)
+ return SA_ERROR_SYSTEM;
+
+ 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)
+ return SA_ERROR_NO_CODEC;
+ else
+ return SA_ERROR_NO_PCM_FORMAT;
+ }
+
+ if (!dev->codec) {
+
+ switch (f) {
+ case AFMT_MU_LAW:
+ oss->real_pcm_format = SA_PCM_FORMAT_ULAW;
+ break;
+
+ case AFMT_A_LAW:
+ oss->real_pcm_format = SA_PCM_FORMAT_ALAW;
+ break;
+
+ case AFMT_U8:
+ oss->real_pcm_format = SA_PCM_FORMAT_U8;
+ break;
+
+ case AFMT_S16_LE:
+ oss->real_pcm_format = SA_PCM_FORMAT_S16_LE;
+ break;
+
+ case AFMT_S16_BE:
+ oss->real_pcm_format = SA_PCM_FORMAT_S16_BE;
+ break;
+
+ default:
+ sa_assert_not_reached();
+ }
+
+
+ found = 0;
+
+ if (dev->adjust_nchannels >= 0) {
+
+ /* First try more channels ... */
+ for (c = dev->nchannels; c < 16 || c == dev->nchannels; c ++) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0)
+ return SA_ERROR_SYSTEM;
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* ... then try less channels */
+ if (!found) {
+ for (c = dev->nchannels - 1; c > 0; c --) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0)
+ return SA_ERROR_SYSTEM;
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ } else {
+
+ /* First try less channels ... */
+ for (c = dev->nchannels; c > 0; c --) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0)
+ return SA_ERROR_SYSTEM;
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* ... then try more channels */
+ if (!found) {
+ for (c = dev->nchannels + 1; c < 16; c ++) {
+ arg = c;
+ if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &arg) < 0)
+ return SA_ERROR_SYSTEM;
+
+ if (arg == (int) c) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!found) {
+ errno = EIO;
+ return SA_ERROR_SYSTEM;
+ }
+
+ oss->real_nchannels = c;
+
+ r = dev->rate;
+ suggested = 0;
+ phase = 0;
+
+ for (;;) {
+ arg = r;
+
+ if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &arg) < 0)
+ return SA_ERROR_SYSTEM;
+
+ sa_assert(arg > 0);
+
+ if (arg >= r*0.95 || arg <= r *1.05)
+ break;
+
+ if (arg > suggested)
+ suggested = arg;
+
+ if (dev->adjust_rate >= 0) {
+
+ if (phase == 0) {
+ /* Find the next higher sample rate to try */
+
+ for (i = 0; i < (int) elementsof(try_rates); i++) {
+ /* Yes, we could optimize a little here */
+
+ if (try_rates[i] > r) {
+ r = try_rates[i];
+ break;
+ }
+ }
+
+
+ if (i == elementsof(try_rates)) {
+ phase = 1;
+ r = dev->rate;
+ }
+ }
+
+ if (phase == 1) {
+ /* Find the next lower sample rate to try */
+
+ for (i = 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 = elementsof(try_rates); i > 0; i--) {
+
+ if (try_rates[i-1] < r) {
+ r = try_rates[i-1];
+ break;
+ }
+ }
+
+ if (i == 0) {
+ phase = 1;
+ r = dev->rate;
+ }
+ }
+
+ if (phase == 1) {
+ /* Find the next higher sample rate to try */
+
+ for (i = 0; i < (int) 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) elementsof(try_rates));
+ }
+ }
+
+ }
+
+ oss->real_rate = r;
+
+ printf("Chosen: %u channels, %uHz, format=%u\n", oss->real_nchannels, oss->real_rate, oss->real_pcm_format);
+
+ if (dev->adjust_nchannels != 0)
+ dev->nchannels = oss->real_nchannels;
+ if (dev->adjust_rate != 0)
+ dev->rate = oss->real_rate;
+ if (dev->pcm_format != 0)
+ dev->pcm_format = oss->real_pcm_format;
+ }
+
+ return SA_SUCCESS;
+}
+
+int device_destroy(sa_device_t *dev) {
+ oss_device_t *oss = OSS_DEVICE(dev);
+
+ if (oss->fd >= 0)
+ close(oss->fd);
+
+ device_free(dev);
+ return SA_SUCCESS;
+}
+
+int device_start_thread(sa_device_t *dev, sa_event_callback_t *callback) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_change_input_volume(sa_device_t *dev, int *vol) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_change_output_volume(sa_device_t *dev, int *vol) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_change_sampling_rate(sa_device_t *dev, unsigned rate) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_get_state(sa_device_t *dev, sa_state_t *state) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_get_position(sa_device_t *dev, sa_position_t position, int64_t *pos) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_pread(sa_device_t *dev, void *data, size_t nbytes, int64_t offset, sa_seek_t whence) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_pwrite(sa_device_t *dev, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_pread_ni(sa_device_t *dev, unsigned channel, void *data, size_t nbytes, int64_t offset, sa_seek_t whence) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_pwrite_ni(sa_device_t *dev, unsigned channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_get_read_size(sa_device_t *dev, size_t *size) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_get_write_size(sa_device_t *dev, size_t *size) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_resume(sa_device_t *dev) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_pause(sa_device_t *dev) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_drain(sa_device_t *dev) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+/* Unsupported operations */
+
+int device_change_device(sa_device_t *dev, const char *device_name) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_change_client_name(sa_device_t *dev, const char *client_name) {
+ return SA_ERROR_NOT_SUPPORTED;
+}
+
+int device_change_stream_name(sa_device_t *dev, const char *stream_name) {
+ return SA_ERROR_NOT_SUPPORTED;
+}