diff options
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | common.c | 523 | ||||
-rw-r--r-- | common.h | 58 | ||||
-rw-r--r-- | driver.h | 29 | ||||
-rw-r--r-- | macro.h | 33 | ||||
-rw-r--r-- | malloc.c | 13 | ||||
-rw-r--r-- | malloc.h | 16 | ||||
-rw-r--r-- | oss.c | 424 | ||||
-rw-r--r-- | resample.c | 164 | ||||
-rw-r--r-- | sydney.h | 341 | ||||
-rw-r--r-- | test-sine.c | 27 |
12 files changed, 1642 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ab247e0 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +CFLAGS=-Wall -O0 -g -W -Wno-unused-parameter + +all: common.o malloc.o test-sine.o oss.o + $(CC) $(CFLAGS) -o $@ $^ + +*.o: *.h + +clean: + rm -f *.o @@ -0,0 +1,5 @@ +* Replace all "input", "output" with "read", "write" +* Vielleicht client_name nicht erzwingen, stattdessen /proc/self/exename nehmen o.ä. +* s/sa_device/sa_stream/ +* adjust channel map + diff --git a/common.c b/common.c new file mode 100644 index 0000000..b7a92d7 --- /dev/null +++ b/common.c @@ -0,0 +1,523 @@ +#include "sydney.h" +#include "macro.h" +#include "malloc.h" +#include "common.h" +#include "driver.h" + +int sa_device_create_opaque(sa_device_t **dev, const char *client_name, sa_mode_t mode, const char *codec) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(client_name, SA_ERROR_INVALID); + sa_return_val_if_fail(mode == SA_MODE_RDONLY || mode == SA_MODE_WRONLY || mode == SA_MODE_RDWR, SA_ERROR_INVALID); + sa_return_val_if_fail(codec, SA_ERROR_INVALID); + + return device_create_opaque(dev, client_name, mode, codec); +} + +int sa_device_create_pcm(sa_device_t **dev, const char *client_name, sa_mode_t mode, sa_pcm_format_t format, unsigned rate, unsigned channels) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(client_name, SA_ERROR_INVALID); + sa_return_val_if_fail(mode == SA_MODE_RDONLY || mode == SA_MODE_WRONLY || mode == SA_MODE_RDWR, SA_ERROR_INVALID); + sa_return_val_if_fail(format < SA_PCM_FORMAT_MAX, SA_ERROR_INVALID); + sa_return_val_if_fail(rate > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(channels > 0, SA_ERROR_INVALID); + + return device_create_pcm(dev, client_name, mode, format, rate, channels); +} + +int sa_device_open(sa_device_t *dev) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + return device_open(dev); +} + +int sa_device_destroy(sa_device_t *dev) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + + return device_destroy(dev); +} + +int sa_device_set_write_lower_watermark(sa_device_t *dev, size_t size) { + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(size > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->write_lower_watermark = size; + return SA_SUCCESS; +} + +int sa_device_set_read_lower_watermark(sa_device_t *dev, size_t size) { + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(size > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->read_lower_watermark = size; + return SA_SUCCESS; +} + +int sa_device_set_write_upper_watermark(sa_device_t *dev, size_t size) { + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(size > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->write_upper_watermark = size; + return SA_SUCCESS; +} + +int sa_device_set_read_upper_watermark(sa_device_t *dev, size_t size) { + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(size > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->read_upper_watermark = size; + return SA_SUCCESS; +} + +int sa_device_set_channel_map(sa_device_t *dev, const sa_channel_t *map) { + const sa_channel_t *c; + sa_channel_t *m; + unsigned n; + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(map, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + + for (c = map, n = dev->nchannels; n > 0; c++, n--) + if (*c >= SA_CHANNEL_MAX) + return SA_ERROR_INVALID; + + if (!(m = sa_memdup(map, sizeof(sa_channel_t) * dev->nchannels))) + return SA_ERROR_OOM; + + sa_free(dev->channel_map); + dev->channel_map = m; + + return SA_SUCCESS; +} + +int sa_device_set_xrun_mode(sa_device_t *dev, sa_xrun_mode_t mode) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(mode == SA_XRUN_MODE_STOP || mode == SA_XRUN_MODE_SPIN, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->xrun_mode = mode; + return SA_SUCCESS; +} + +int sa_device_set_ni(sa_device_t *dev, int enable) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + + dev->ni_enabled = !!enable; + return SA_SUCCESS; +} + +int sa_device_set_dsr(sa_device_t *dev, int enable) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + + dev->dsr_enabled = !!enable; + return SA_SUCCESS; +} + +int sa_device_set_driver(sa_device_t *dev, const char *driver) { + char *d; + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(driver, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + if (!(d = sa_strdup(driver))) + return SA_ERROR_OOM; + + sa_free(dev->driver); + dev->driver = d; + + return SA_SUCCESS; +} + +int sa_device_start_thread(sa_device_t *dev, sa_event_callback_t *callback) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(callback, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + return device_start_thread(dev, callback); +} + +int sa_device_change_device(sa_device_t *dev, const char *device_name) { + char *d; + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(device_name, SA_ERROR_INVALID); + + if (dev->state == SA_STATE_INIT) { + if (!(d = sa_strdup(device_name))) + return SA_ERROR_OOM; + + sa_free(dev->device); + dev->device = d; + + return SA_SUCCESS; + } + + return device_change_device(dev, device_name); +} + +int sa_device_change_input_volume(sa_device_t *dev, int *vol) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(vol, SA_ERROR_INVALID); + + if (dev->state == SA_STATE_INIT) { + dev->input_volume = *vol; + return SA_SUCCESS; + } + + return device_change_input_volume(dev, vol); +} + +int sa_device_change_output_volume(sa_device_t *dev, int *vol) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(vol, SA_ERROR_INVALID); + + if (dev->state == SA_STATE_INIT) { + dev->output_volume = *vol; + return SA_SUCCESS; + } + + return device_change_output_volume(dev, vol); +} + +int sa_device_change_sampling_rate(sa_device_t *dev, unsigned rate) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(rate > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + sa_return_val_if_fail(dev->dsr_enabled || dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + if (dev->state == SA_STATE_INIT) { + dev->rate = rate; + return SA_SUCCESS; + } + + return device_change_sampling_rate(dev, rate); +} + +int sa_device_change_client_name(sa_device_t *dev, const char *client_name) { + char *n; + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(client_name, SA_ERROR_INVALID); + + if (dev->state == SA_STATE_INIT) { + if (!(n = sa_strdup(client_name))) + return SA_ERROR_OOM; + + sa_free(dev->client_name); + dev->client_name = n; + + return SA_SUCCESS; + } + + return device_change_client_name(dev, client_name); +} + +int sa_device_change_stream_name(sa_device_t *dev, const char *stream_name) { + char *n; + + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(stream_name, SA_ERROR_INVALID); + + if (dev->state == SA_STATE_INIT) { + if (!(n = sa_strdup(stream_name))) + return SA_ERROR_OOM; + + sa_free(dev->stream_name); + dev->stream_name = n; + + return SA_SUCCESS; + } + + return device_change_stream_name(dev, stream_name); +} + +int sa_device_change_user_data(sa_device_t *dev, void *value) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + + dev->user_data = value; + return SA_SUCCESS; +} + +int sa_device_adjust_rate(sa_device_t *dev, sa_adjust_t direction) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->adjust_rate = direction; + return SA_SUCCESS; +} + +int sa_device_adjust_nchannels(sa_device_t *dev, sa_adjust_t direction) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->adjust_rate = direction; + return SA_SUCCESS; +} + +int sa_device_adjust_pcm_format(sa_device_t *dev, sa_adjust_t direction) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_INIT, SA_ERROR_STATE); + + dev->adjust_pcm_format = direction; + return SA_SUCCESS; +} + +int sa_device_get_state(sa_device_t *dev, sa_state_t *state) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(state, SA_ERROR_INVALID); + + return device_get_state(dev, state); +} + +int sa_device_get_sampling_rate(sa_device_t *dev, unsigned *rate) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(rate, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + + *rate = dev->rate; + return SA_SUCCESS; +} + +int sa_device_get_nchannels(sa_device_t *dev, int *nchannels) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(nchannels, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + + *nchannels = dev->nchannels; + return SA_SUCCESS; +} + +int sa_device_get_pcm_format(sa_device_t *dev, sa_pcm_format_t *pcm_format) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(pcm_format, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->codec, SA_ERROR_STATE); + + *pcm_format = dev->pcm_format; + return SA_SUCCESS; +} + +int sa_device_get_user_data(sa_device_t *dev, void **value) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(value, SA_ERROR_INVALID); + + *value = dev->user_data; + return SA_SUCCESS; +} + +int sa_device_get_event_error(sa_device_t *dev, sa_error_t *error) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(error, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->event == SA_EVENT_ERROR, SA_ERROR_STATE); + + *error = dev->error; + return SA_SUCCESS; +} + +int sa_device_get_event_notify(sa_device_t *dev, sa_notify_t *notify) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(notify, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->event == SA_EVENT_NOTIFY, SA_ERROR_STATE); + + *notify = dev->notify; + return SA_SUCCESS; +} + +int sa_device_get_position(sa_device_t *dev, sa_position_t position, int64_t *pos) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(pos, SA_ERROR_INVALID); + sa_return_val_if_fail(position < SA_POSITION_MAX, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_get_position(dev, position, pos); +} + +int sa_device_read(sa_device_t *dev, void *data, size_t nbytes) { + return sa_device_pread(dev, data, nbytes, 0, SA_SEEK_RELATIVE); +} + +int sa_device_write(sa_device_t *dev, const void *data, size_t nbytes) { + return sa_device_pwrite(dev, data, nbytes, 0, SA_SEEK_RELATIVE); +} + +int sa_device_read_ni(sa_device_t *dev, unsigned channel, void *data, size_t nbytes) { + return sa_device_pread_ni(dev, channel, data, nbytes, 0, SA_SEEK_RELATIVE); +} + +int sa_device_write_ni(sa_device_t *dev, unsigned channel, const void *data, size_t nbytes) { + return sa_device_pwrite_ni(dev, channel, data, nbytes, 0, SA_SEEK_RELATIVE); +} + +int sa_device_pread(sa_device_t *dev, void *data, size_t nbytes, int64_t offset, sa_seek_t whence) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(data, SA_ERROR_INVALID); + sa_return_val_if_fail(nbytes > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(whence == SA_SEEK_RELATIVE || whence == SA_SEEK_ABSOLUTE || whence == SA_SEEK_RELATIVE_END, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->ni_enabled, SA_ERROR_STATE); + sa_return_val_if_fail(dev->mode & SA_MODE_RDONLY, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_pread(dev, data, nbytes, offset, whence); +} + +int sa_device_pwrite(sa_device_t *dev, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(data, SA_ERROR_INVALID); + sa_return_val_if_fail(nbytes > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(whence == SA_SEEK_RELATIVE || whence == SA_SEEK_ABSOLUTE || whence == SA_SEEK_RELATIVE_END, SA_ERROR_INVALID); + sa_return_val_if_fail(!dev->ni_enabled, SA_ERROR_STATE); + sa_return_val_if_fail(dev->mode & SA_MODE_WRONLY, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_pwrite(dev, data, nbytes, offset, whence); +} + +int sa_device_pread_ni(sa_device_t *dev, unsigned channel, void *data, size_t nbytes, int64_t offset, sa_seek_t whence) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(data, SA_ERROR_INVALID); + sa_return_val_if_fail(nbytes > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(whence == SA_SEEK_RELATIVE || whence == SA_SEEK_ABSOLUTE || whence == SA_SEEK_RELATIVE_END, SA_ERROR_INVALID); + sa_return_val_if_fail(channel < dev->nchannels, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->ni_enabled, SA_ERROR_STATE); + sa_return_val_if_fail(dev->mode & SA_MODE_RDONLY, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_pread_ni(dev, channel, data, nbytes, offset, whence); +} + +int sa_device_pwrite_ni(sa_device_t *dev, unsigned channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(data, SA_ERROR_INVALID); + sa_return_val_if_fail(nbytes > 0, SA_ERROR_INVALID); + sa_return_val_if_fail(whence == SA_SEEK_RELATIVE || whence == SA_SEEK_ABSOLUTE || whence == SA_SEEK_RELATIVE_END, SA_ERROR_INVALID); + sa_return_val_if_fail(channel < dev->nchannels, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->ni_enabled, SA_ERROR_STATE); + sa_return_val_if_fail(dev->mode & SA_MODE_WRONLY, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_pwrite_ni(dev, channel, data, nbytes, offset, whence); +} + +int sa_device_get_read_size(sa_device_t *dev, size_t *size) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(size, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->mode & SA_MODE_RDONLY, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_get_read_size(dev, size); +} + +int sa_device_get_write_size(sa_device_t *dev, size_t *size) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(size, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->mode & SA_MODE_WRONLY, SA_ERROR_STATE); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_get_write_size(dev, size); +} + +int sa_device_resume(sa_device_t *dev) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_resume(dev); +} + +int sa_device_pause(sa_device_t *dev) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_pause(dev); +} + +int sa_device_drain(sa_device_t *dev) { + sa_return_val_if_fail(dev, SA_ERROR_INVALID); + sa_return_val_if_fail(dev->state == SA_STATE_RUNNING || dev->state == SA_STATE_STOPPED, SA_ERROR_STATE); + + return device_drain(dev); +} + +sa_device_t *device_alloc(size_t total) { + sa_device_t *d; + + sa_assert(total >= sizeof(sa_device_t)); + + if (!(d = sa_malloc0(total))) + return NULL; + + /* All fields a carefully chosen in a way that initializing them + * NUL bytes is sufficient */ + + return d; +} + +void device_free(sa_device_t *d) { + sa_free(d->codec); + sa_free(d->driver); + sa_free(d->device); + sa_free(d->channel_map); + sa_free(d->client_name); + sa_free(d->stream_name); + sa_free(d); +} + +int device_alloc_opaque(sa_device_t **dev, size_t total, const char *client_name, sa_mode_t mode, const char *codec) { + int error; + + if (!(*dev = device_alloc(total))) + return SA_ERROR_OOM; + + (*dev)->mode = mode; + + if (!((*dev)->codec = sa_strdup(codec))) { + device_free(*dev); + return SA_ERROR_OOM; + } + + if ((error = sa_device_change_client_name(*dev, client_name))) { + device_free(*dev); + return error; + } + + return SA_SUCCESS; +} + +int device_alloc_pcm(sa_device_t **dev, size_t total, const char *client_name, sa_mode_t mode, sa_pcm_format_t format, unsigned rate, unsigned nchannels) { + int error; + + if (!(*dev = device_alloc(total))) + return SA_ERROR_OOM; + + (*dev)->mode = mode; + (*dev)->pcm_format = format; + (*dev)->nchannels = nchannels; + + if ((error = sa_device_change_sampling_rate(*dev, rate))) { + device_free(*dev); + return error; + } + + if ((error = sa_device_change_client_name(*dev, client_name))) { + device_free(*dev); + return error; + } + + return SA_SUCCESS; +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..1500ef1 --- /dev/null +++ b/common.h @@ -0,0 +1,58 @@ +#ifndef foocommonh +#define foocommonh + +#include "sydney.h" + +#define elementsof(x) (sizeof(x)/sizeof((x)[0])) + +struct sa_device { + sa_mode_t mode; + + sa_pcm_format_t pcm_format; + unsigned rate; + unsigned nchannels; + + char *codec; + + char *client_name; + char *stream_name; + + size_t read_lower_watermark; + size_t read_upper_watermark; + size_t write_lower_watermark; + size_t write_upper_watermark; + + sa_channel_t *channel_map; + + sa_xrun_mode_t xrun_mode; + int ni_enabled; + int dsr_enabled; + + sa_event_callback_t event_callback; + + char *device; + char *driver; + + int input_volume; + int output_volume; + + void *user_data; + + sa_state_t state; + + sa_adjust_t adjust_rate; + sa_adjust_t adjust_nchannels; + sa_adjust_t adjust_pcm_format; + + sa_error_t error; + sa_notify_t notify; + sa_event_t event; +}; + +sa_device_t *device_alloc(size_t total); +void device_free(sa_device_t *d); + +int device_alloc_pcm(sa_device_t **dev, size_t total, const char *client_name, sa_mode_t mode, sa_pcm_format_t format, unsigned rate, unsigned channels); +int device_alloc_opaque(sa_device_t **dev, size_t total, const char *client_name, sa_mode_t mode, const char *codec); + +#endif diff --git a/driver.h b/driver.h new file mode 100644 index 0000000..49bebbd --- /dev/null +++ b/driver.h @@ -0,0 +1,29 @@ +#ifndef foodriverhfoo +#define foodriverhfoo + +#include "sydney.h" + +int device_create_opaque(sa_device_t **dev, const char *client_name, sa_mode_t mode, const char *codec); +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 device_open(sa_device_t *dev); +int device_destroy(sa_device_t *dev); +int device_start_thread(sa_device_t *dev, sa_event_callback_t *callback); +int device_change_device(sa_device_t *dev, const char *device_name); +int device_change_input_volume(sa_device_t *dev, int *vol); +int device_change_output_volume(sa_device_t *dev, int *vol); +int device_change_sampling_rate(sa_device_t *dev, unsigned rate); +int device_change_client_name(sa_device_t *dev, const char *client_name); +int device_change_stream_name(sa_device_t *dev, const char *stream_name); +int device_get_state(sa_device_t *dev, sa_state_t *state); +int device_get_position(sa_device_t *dev, sa_position_t position, int64_t *pos); +int device_pread(sa_device_t *dev, void *data, size_t nbytes, int64_t offset, sa_seek_t whence); +int device_pwrite(sa_device_t *dev, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence); +int device_pread_ni(sa_device_t *dev, unsigned channel, void *data, size_t nbytes, int64_t offset, sa_seek_t whence); +int device_pwrite_ni(sa_device_t *dev, unsigned channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence); +int device_get_read_size(sa_device_t *dev, size_t *size); +int device_get_write_size(sa_device_t *dev, size_t *size); +int device_resume(sa_device_t *dev); +int device_pause(sa_device_t *dev); +int device_drain(sa_device_t *dev); + +#endif @@ -0,0 +1,33 @@ +#ifndef foomacrohfoo +#define foomacrohfoo + +#include <stdio.h> +#include <assert.h> + +#ifdef __GNUC__ +#define PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +#define PRETTY_FUNCTION "" +#endif + +#define sa_return_if_fail(expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, PRETTY_FUNCTION ": Assertion <" #expr "> failed.\n"); \ + return; \ + } \ + } while(0) + +#define sa_return_val_if_fail(expr, val) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: Assertion <" #expr "> failed.\n", PRETTY_FUNCTION ); \ + return (val); \ + } \ + } while(0) + +#define sa_assert assert + +#define sa_assert_not_reached() sa_assert(!"Should not be reached.") + +#endif diff --git a/malloc.c b/malloc.c new file mode 100644 index 0000000..080eb2f --- /dev/null +++ b/malloc.c @@ -0,0 +1,13 @@ +#include <string.h> + +#include "malloc.h" + +void* sa_memdup(const void* p, size_t size) { + void *r; + + if (!(r = malloc(size))) + return NULL; + + memcpy(r, p, size); + return r; +} diff --git a/malloc.h b/malloc.h new file mode 100644 index 0000000..df459ed --- /dev/null +++ b/malloc.h @@ -0,0 +1,16 @@ +#ifndef foomallochfoo +#define foomallochfoo + +#include <stdlib.h> +#include <string.h> + +#define sa_malloc malloc +#define sa_free free +#define sa_malloc0(size) calloc(1, (size)) +#define sa_strdup strdup + +void* sa_memdup(const void* p, size_t size); + +#define sa_new(t, n) ((t*) sa_malloc(sizeof(t)*n)) + +#endif @@ -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; +} diff --git a/resample.c b/resample.c new file mode 100644 index 0000000..f69e6d6 --- /dev/null +++ b/resample.c @@ -0,0 +1,164 @@ +#include "sydney.h" + +#define MAX_OPERATIONS 10 + +typedef struct buffer_spec buffer_spec_t; + +struct buffer_spec { + void *data; + size_t size; + size_t max_size; +}; + +typedef struct operation operation_t; + +typedef void (*operation_execute_t)(operation_t *o, buffer_spec_t *source); + +struct operation { + operation_execute_t execute; + + struct buffer_spec result; + + size_t sample + unsigned nchannels; +}; + +typedef struct pipeline pipeline_t; + +struct pipeline { + operation_t operations[MAX_OPERATIONS]; +}; + +static int native_pcm_format(pcm_format_t f) { + + /* Sample formats we know to handle natively */ + + return + f == SA_PCM_FORMAT_S16_NE || + f == SA_PCM_FORMAT_S32_NE || + f == SA_PCM_FORMAT_FLOAT32_NE; + +} + +/* Steps: prefmt -> volume -> remap -> resample -> postfmt */ + +int build_pipeline_output( + sa_device_t *dev, + sa_pipeline_t *pipeline, + sa_pcm_format format, + unsigned rate, + unsigned nchannels, + const sa_channel_t map[]) { + + int *channel_map_table; + unsigned u, t; + int prefmt_required; + int sum_required = 0; + int o = 0; + pcm_format_t work_format; + operation_execute_t func; + + sa_assert(dev); + sa_assert(pipeline); + + if (!(channel_map_table = sa_new(int, nchannels * dev->nchannels))) + return SA_ERROR_OOM; + + for (u = 0; u < nchannels; u++) { + unsigned k = 0; + + for (t = 0; t < dev->nchannels; t++) { + + if (map[u] == map[t]) + channel_map_table[u * dev->nchannels + k++] += t; + } + + if (k > 1) + sum_required = 1; + + channel_map_table[k] = (unsigned) -1; + } + + prefmt_required = + sum_required || + dev->output_volume != 0 || + dev->rate != rate || + dev->dsr_enabled; + + if (native_pcm_format(dev->pcm_format)) + prefmt_required = 0; + + if (prefmt_required) { + + switch (dev->pcm_format) { + case SA_PCM_FORMAT_U8: + case SA_PCM_FORMAT_ULAW: + case SA_PCM_FORMAT_ALAW: + case SA_PCM_FORMAT_S16_LE: + case SA_PCM_FORMAT_S16_BE: + work_format = SA_PCM_FORMAT_S16_NE; + break; + + case SA_PCM_FORMAT_S24_LE: + case SA_PCM_FORMAT_S24_BE: + case SA_PCM_FORMAT_S32_LE: + case SA_PCM_FORMAT_S32_BE: + work_format = SA_PCM_FORMAT_S32_NE; + break; + + case SA_PCM_FORMAT_FLOAT32_LE: + case SA_PCM_FORMAT_FLOAT32_BE: + work_format = SA_PCM_FORMAT_FLOAT32_NE; + break; + } + + sa_assert(native_pcm_format(work_format)); + + func = get_format_converter(dev->pcm_format, work_format); + pipeline.operations[o].execute = func; + pipeline.operations[o].buffer.data = NULL; + pipeline.operations[o].buffer.stride = dev->nchannels * get_sample_size(dev->work_format); + pipeline.operations[o]. + o++; + + } else + work_format = dev->pcm_format; + + if (dev->output_volume != 0) { + pipeline.operations[o].execute = get_scaler(work_format); + pipeline.operations[o].buffer.data = NULL; + o++; + } + + if (dev->rate != rate || dev->dsr_enabled) { + pipeline.operations[o].execute = get_resampler(work_format); + pipeline.operations[o].buffer.data = NULL; + o++; + } + + if (work_format != pcm_format) { + pipeline.operations[o].execute = get_format_converter(work_format, pcm_format); + pipeline.operations[o].buffer.data = NULL; + o++; + } + + pipeline.operations[o].execute = NULL; + + + +} + +void execute_pipeline(sa_device_t *dev, pipeline_t *pipeline, const buffer_spec_t *buffer, buffer_spec_t *output) { + sa_operation_t *o; + + sa_assert(dev); + sa_assert(pipeline); + sa_assert(buffer); + + for (o = pipeline->operations; o->execute; o++) { + o->execute(o, buffer); + buffer = &o->buffer; + } + + *output = *buffer; +} diff --git a/sydney.h b/sydney.h new file mode 100644 index 0000000..7f70add --- /dev/null +++ b/sydney.h @@ -0,0 +1,341 @@ +#ifndef foosydneyhfoo +#define foosydneyhfoo + +/* Requirements: + +- In sync mode, the device will automatically write data so that an initial read causes writes +of zeros to be issued to that one can do "while (1); {read(); write()} + +- All functions are thread-safe and can be called in any thread context. None of the functions is +async-signal safe. + +- It is assumed that duplex streams have a single clock (synchronised) +*/ + +#include <sys/types.h> +#include <sys/param.h> +#include <inttypes.h> + +/* Detect byte order, based on sys/param.h */ +#if (defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN)) || defined(WIN32) +# define SA_LITTLE_ENDIAN +# undef SA_BIG_ENDIAN +#elif (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) +# undef SA_LITTLE_ENDIAN +# define SA_BIG_ENDIAN +#else +# error "Cannot determine byte order!" +#endif + +typedef struct sa_device sa_device_t; + +/** Volume that corresponds to muted in/out */ +#define SA_VOLUME_MUTED 0x80000000 + +/** Ways to express seek offsets for pread/pwrite */ +typedef enum { + SA_SEEK_RELATIVE, + SA_SEEK_ABSOLUTE, + SA_SEEK_RELATIVE_END, +} sa_seek_t; + +/** Supported formats */ +typedef enum { + SA_PCM_FORMAT_U8, + SA_PCM_FORMAT_ULAW, + SA_PCM_FORMAT_ALAW, + SA_PCM_FORMAT_S16_LE, + SA_PCM_FORMAT_S16_BE, + SA_PCM_FORMAT_S24_LE, + SA_PCM_FORMAT_S24_BE, + SA_PCM_FORMAT_S32_LE, + SA_PCM_FORMAT_S32_BE, + SA_PCM_FORMAT_FLOAT32_LE, + SA_PCM_FORMAT_FLOAT32_BE, + SA_PCM_FORMAT_MAX +} sa_pcm_format_t; + +/* Native endianness definitions for PCM */ +#ifdef SA_LITTLE_ENDIAN +#define SA_PCM_FORMAT_S16_NE SA_PCM_FORMAT_S16_LE +#define SA_PCM_FORMAT_S24_NE SA_PCM_FORMAT_S24_LE +#define SA_PCM_FORMAT_S32_NE SA_PCM_FORMAT_S32_LE +#define SA_PCM_FORMAT_FLOAT32_NE SA_PCM_FORMAT_FLOAT32_LE +#else +#define SA_PCM_FORMAT_S16_NE SA_PCM_FORMAT_S16_BE +#define SA_PCM_FORMAT_S24_NE SA_PCM_FORMAT_S24_BE +#define SA_PCM_FORMAT_S32_NE SA_PCM_FORMAT_S32_BE +#define SA_PCM_FORMAT_FLOAT32_NE SA_PCM_FORMAT_FLOAT32_BE +#endif + +#define SA_CODEC_MPEG "mpeg" +#define SA_CODEC_AC3 "ac3" +#define SA_CODEC_GSM "gsm" +#define SA_CODEC_VORBIS "vorbis" +#define SA_CODEC_SPEEX "speex" + +/** Device opening modes */ +typedef enum { + SA_MODE_RDONLY = 1, + SA_MODE_WRONLY = 2, + SA_MODE_RDWR = 3 +} sa_mode_t; + +/** Error codes */ +typedef enum { + SA_SUCCESS = 0, + SA_ERROR_NOT_SUPPORTED = -1, + SA_ERROR_INVALID = -2, + SA_ERROR_STATE = -3, + SA_ERROR_OOM = -4, + SA_ERROR_NO_DEVICE = -5, + SA_ERROR_NO_DRIVER = -6, + SA_ERROR_NO_CODEC = -7, + SA_ERROR_NO_PCM_FORMAT = -7, + SA_ERROR_SYSTEM = -8 +} sa_error_t; + +/** Possible events for notifications */ +typedef enum { + SA_NOTIFY_REQUEST_STOP, + SA_NOTIFY_REQUEST_START, + SA_NOTIFY_VOLUME_CHANGED_IN, + SA_NOTIFY_VOLUME_CHANGED_OUT, + SA_NOTIFY_DEVICE_CHANGED +} sa_notify_t; + +/** Classes of events */ +typedef enum { + SA_EVENT_REQUEST_IO, + SA_EVENT_INIT_THREAD, + SA_EVENT_NOTIFY, + SA_EVENT_ERROR +} sa_event_t; + +/** List of sample position queries */ +typedef enum { + SA_POSITION_WRITE_DELAY, + SA_POSITION_WRITE_HARDWARE, + SA_POSITION_WRITE_SOFTWARE, + SA_POSITION_READ_DELAY, + SA_POSITION_READ_HARDWARE, + SA_POSITION_READ_SOFTWARE, + SA_POSITION_DUPLEX_DELAY, + SA_POSITION_MAX +} sa_position_t; + +/* Channel positions */ +typedef enum { + SA_CHANNEL_MONO, + SA_CHANNEL_LEFT, + SA_CHANNEL_RIGHT, + SA_CHANNEL_CENTER, + SA_CHANNEL_FRONT_LEFT, + SA_CHANNEL_FRONT_RIGHT, + SA_CHANNEL_FRONT_CENTER, + SA_CHANNEL_REAR_LEFT, + SA_CHANNEL_REAR_RIGHT, + SA_CHANNEL_REAR_CENTER, + SA_CHANNEL_LFE, + SA_CHANNEL_FRONT_LEFT_OF_CENTER, + SA_CHANNEL_FRONT_RIGHT_OF_CENTER, + SA_CHANNEL_SIDE_LEFT, + SA_CHANNEL_SIDE_RIGHT, + SA_CHANNEL_TOP_CENTER, + SA_CHANNEL_TOP_FRONT_LEFT, + SA_CHANNEL_TOP_FRONT_RIGHT, + SA_CHANNEL_TOP_FRONT_CENTER, + SA_CHANNEL_TOP_REAR_LEFT, + SA_CHANNEL_TOP_REAR_RIGHT, + SA_CHANNEL_TOP_REAR_CENTER, + SA_CHANNEL_AUX0, + SA_CHANNEL_AUX1, + SA_CHANNEL_AUX2, + SA_CHANNEL_AUX3, + SA_CHANNEL_AUX4, + SA_CHANNEL_AUX5, + SA_CHANNEL_AUX6, + SA_CHANNEL_AUX7, + SA_CHANNEL_AUX8, + SA_CHANNEL_AUX9, + SA_CHANNEL_AUX10, + SA_CHANNEL_AUX11, + SA_CHANNEL_AUX12, + SA_CHANNEL_AUX13, + SA_CHANNEL_AUX14, + SA_CHANNEL_AUX15, + SA_CHANNEL_AUX16, + SA_CHANNEL_AUX17, + SA_CHANNEL_AUX18, + SA_CHANNEL_AUX19, + SA_CHANNEL_AUX20, + SA_CHANNEL_AUX21, + SA_CHANNEL_AUX22, + SA_CHANNEL_AUX23, + SA_CHANNEL_AUX24, + SA_CHANNEL_AUX25, + SA_CHANNEL_AUX26, + SA_CHANNEL_AUX27, + SA_CHANNEL_AUX28, + SA_CHANNEL_AUX29, + SA_CHANNEL_AUX30, + SA_CHANNEL_AUX31, + SA_CHANNEL_MAX +} sa_channel_t; + +typedef enum { + SA_STATE_INIT, + SA_STATE_RUNNING, + SA_STATE_STOPPED, + /* put more stuff */ +} sa_state_t; + +typedef enum { + SA_XRUN_MODE_STOP, + SA_XRUN_MODE_SPIN +} sa_xrun_mode_t; + +typedef enum { + SA_ADJUST_UP = 1, + SA_ADJUST_DOWN = -1, + SA_ADJUST_NONE = 0, +} sa_adjust_t; + +/** Main callback function */ +typedef int (*sa_event_callback_t)(sa_device_t *dev, sa_event_t event); + +/** Create an opaque (e.g. AC3) codec stream */ +int sa_device_create_opaque(sa_device_t **dev, const char *client_name, sa_mode_t mode, const char *codec); + +/** Normal way to open a PCM device */ +int sa_device_create_pcm(sa_device_t **dev, const char *client_name, sa_mode_t mode, sa_pcm_format_t format, unsigned rate, unsigned nchannels); + +/** Initialise the device */ +int sa_device_open(sa_device_t *dev); + +/** Close/destroy everything */ +int sa_device_destroy(sa_device_t *dev); + +/* "Soft" params */ +int sa_device_set_write_lower_watermark(sa_device_t *dev, size_t size); +int sa_device_set_read_lower_watermark(sa_device_t *dev, size_t size); + +int sa_device_set_write_upper_watermark(sa_device_t *dev, size_t size); +int sa_device_set_read_upper_watermark(sa_device_t *dev, size_t size); + +/** Set the mapping between channels and the loudspeakers */ +int sa_device_set_channel_map(sa_device_t *dev, const sa_channel_t map[]); + +/** Whether xruns cause the card to reset */ +int sa_device_set_xrun_mode(sa_device_t *dev, sa_xrun_mode_t mode); + +/** Set the device to non-interleaved mode */ +int sa_device_set_ni(sa_device_t *dev, int enable); + +/** Require dynamic sample rate */ +int sa_device_set_dsr(sa_device_t *dev, int enable); + +/** Select driver */ +int sa_device_set_driver(sa_device_t *dev, const char *driver); + +/** Start callback */ +int sa_device_start_thread(sa_device_t *dev, sa_event_callback_t *callback); + +/** Change the device connected to the stream */ +int sa_device_change_device(sa_device_t *dev, const char *device_name); + +/** volume in hundreths of dB's*/ +int sa_device_change_input_volume(sa_device_t *dev, int *vol); + +/** volume in hundreths of dB's*/ +int sa_device_change_output_volume(sa_device_t *dev, int *vol); + +/** Change the sampling rate */ +int sa_device_change_sampling_rate(sa_device_t *dev, unsigned rate); + +/** Change the name of the client application using the device */ +int sa_device_change_client_name(sa_device_t *dev, const char *client_name); + +/** Change the name of the stream being sent */ +int sa_device_change_stream_name(sa_device_t *dev, const char *stream_name); + +/** Associate opaque user data */ +int sa_device_change_user_data(sa_device_t *dev, void *value); + +/* Hardware-related. This is implementation-specific and hardware specific. */ +int sa_device_adjust_rate(sa_device_t *dev, sa_adjust_t direction); + +int sa_device_adjust_nchannels(sa_device_t *dev, sa_adjust_t direction); + +int sa_device_adjust_pcm_format(sa_device_t *dev, sa_adjust_t direction); + +/* Query functions */ + +/** Get current state of the audio device */ +int sa_device_get_state(sa_device_t *dev, sa_state_t *state); + +/** Get current sampling rate */ +int sa_device_get_sampling_rate(sa_device_t *dev, unsigned *rate); + +/** Get number of channels */ +int sa_device_get_nchannels(sa_device_t *dev, int *nchannels); + +/** Get format being used */ +int sa_device_get_pcm_format(sa_device_t *dev, sa_pcm_format_t *format); + +/** Get opaque pointer associated to the device */ +int sa_device_get_user_data(sa_device_t *dev, void **value); + +/** Obtain the error code */ +int sa_device_get_event_error(sa_device_t *dev, sa_error_t *error); + +/** Obtain the notification code */ +int sa_device_get_event_notify(sa_device_t *dev, sa_notify_t *notify); + +/** sync/timing */ +int sa_device_get_position(sa_device_t *dev, sa_position_t position, int64_t *pos); + + + + +/* Blocking IO calls */ + +/** Interleaved capture function */ +int sa_device_read(sa_device_t *dev, void *data, size_t nbytes); +/** Interleaved playback function */ +int sa_device_write(sa_device_t *dev, const void *data, size_t nbytes); + +/** Non-interleaved capture function */ +int sa_device_read_ni(sa_device_t *dev, unsigned channel, void *data, size_t nbytes); +/** Non-interleaved playback function */ +int sa_device_write_ni(sa_device_t *dev, unsigned channel, const void *data, size_t nbytes); + +/** Interleaved capture function with seek offset */ +int sa_device_pread(sa_device_t *dev, void *data, size_t nbytes, int64_t offset, sa_seek_t whence); +/** Interleaved playback function with seek offset */ +int sa_device_pwrite(sa_device_t *dev, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence); + +/** Non-interleaved capture function with seek offset */ +int sa_device_pread_ni(sa_device_t *dev, unsigned channel, void *data, size_t nbytes, int64_t offset, sa_seek_t whence); +/** Non-interleaved playback function with seek offset */ +int sa_device_pwrite_ni(sa_device_t *dev, unsigned channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence); + + +/** Query how much can be read without blocking */ +int sa_device_get_read_size(sa_device_t *dev, size_t *size); +/** Query how much can be written without blocking */ +int sa_device_get_write_size(sa_device_t *dev, size_t *size); + + +/* Control/xrun */ + +/** Resume playing after a pause */ +int sa_device_resume(sa_device_t *dev); + +/** Pause audio playback (do not empty the buffer) */ +int sa_device_pause(sa_device_t *dev); + +/** Block until all audio has been played */ +int sa_device_drain(sa_device_t *dev); + +#endif diff --git a/test-sine.c b/test-sine.c new file mode 100644 index 0000000..ae9ac9e --- /dev/null +++ b/test-sine.c @@ -0,0 +1,27 @@ +#include <assert.h> + +#include "sydney.h" + +#define ASSERT_SUCCESS(x) assert(x == SA_SUCCESS) + +#define FREQ 440 + +int main(int argc, char *argv[]) { + + sa_device_t *dev; + float data[4] = { 0.0, 1.0, 0.0, -1.0 }; + int i; + + ASSERT_SUCCESS(sa_device_create_pcm(&dev, argv[0], SA_MODE_WRONLY, SA_PCM_FORMAT_FLOAT32_NE, FREQ * 4, 1)); + ASSERT_SUCCESS(sa_device_change_device(dev, "/dev/dsp1")); + ASSERT_SUCCESS(sa_device_open(dev)); + + for (i = 0; i < 10; i++) + ASSERT_SUCCESS(sa_device_write(dev, data, sizeof(data))); + + ASSERT_SUCCESS(sa_device_drain(dev)); + + ASSERT_SUCCESS(sa_device_destroy(dev)); + + return 0; +} |