diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | configure.in | 21 | ||||
-rw-r--r-- | pcm/Makefile.am | 2 | ||||
-rw-r--r-- | pcm/jack/Makefile.am | 8 | ||||
-rw-r--r-- | pcm/jack/pcm_jack.c | 463 | ||||
-rw-r--r-- | pcm/oss/Makefile.am | 8 | ||||
-rw-r--r-- | pcm/oss/pcm_oss.c | 422 |
7 files changed, 926 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..8430c35 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = pcm +AUTOMAKE_OPTIONS = foreign diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..35dcbd7 --- /dev/null +++ b/configure.in @@ -0,0 +1,21 @@ +AC_INIT(pcm/oss/pcm_oss.c) +AM_INIT_AUTOMAKE(alsa-plugins, 1.0.8) +AC_PREFIX_DEFAULT(/usr) + +AC_PROG_CC +AC_PROG_INSTALL +AC_DISABLE_STATIC +AM_PROG_LIBTOOL +AC_HEADER_STDC + +PKG_CHECK_MODULES(ALSA, alsa >= 1.0.8) +PKG_CHECK_MODULES(JACK, jack >= 0.99) + +AC_OUTPUT([ + Makefile + pcm/Makefile + pcm/oss/Makefile + pcm/jack/Makefile +]) + + diff --git a/pcm/Makefile.am b/pcm/Makefile.am new file mode 100644 index 0000000..9f0dd9a --- /dev/null +++ b/pcm/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = oss jack + diff --git a/pcm/jack/Makefile.am b/pcm/jack/Makefile.am new file mode 100644 index 0000000..63dc500 --- /dev/null +++ b/pcm/jack/Makefile.am @@ -0,0 +1,8 @@ +asound_module_pcm_jack_LTLIBRARIES = libasound_module_pcm_jack.la + +asound_module_pcm_jackdir = $(libdir)/alsa-lib + +AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ @JACK_CFLAGS@ +libasound_module_pcm_jack_la_SOURCES = pcm_jack.c +libasound_module_pcm_jack_la_LDFLAGS = -module -avoid-version -export-dynamic +libasound_module_pcm_jack_la_LIBADD = @ALSA_LIBS@ @JACK_LIBS@ diff --git a/pcm/jack/pcm_jack.c b/pcm/jack/pcm_jack.c new file mode 100644 index 0000000..ad2465f --- /dev/null +++ b/pcm/jack/pcm_jack.c @@ -0,0 +1,463 @@ +/* + * PCM - JACK plugin + * Copyright (c) 2003 by Maarten de Boer <mdeboer@iua.upf.es> + * + * + * This library 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. + * + * This program 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <byteswap.h> +#include <sys/shm.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <jack/jack.h> +#include <alsa/asoundlib.h> +#include <alsa/pcm_external.h> + +typedef enum _jack_format { + SND_PCM_JACK_FORMAT_RAW +} snd_pcm_jack_format_t; + +typedef struct { + snd_pcm_ioplug_t io; + + int fd; + int activated; /* jack is activated? */ + + char** playback_ports; + char** capture_ports; + unsigned int playback_ports_n; + unsigned int capture_ports_n; + unsigned int hw_ptr; + unsigned int sample_bits; + + unsigned int channels; + snd_pcm_channel_area_t *areas; + + jack_port_t **ports; + jack_client_t *client; +} snd_pcm_jack_t; + +static void snd_pcm_jack_free(snd_pcm_jack_t *jack) +{ + if (jack) { + unsigned int k; + if (jack->client) + jack_client_close(jack->client); + if (jack->playback_ports) { + for (k = 0; k < jack->playback_ports_n; k++) + if (jack->playback_ports[k]) + free(jack->playback_ports[k]); + free(jack->playback_ports); + } + if (jack->capture_ports) { + for (k = 0; k < jack->capture_ports_n; k++) + if (jack->capture_ports[k]) + free(jack->capture_ports[k]); + free(jack->capture_ports); + } + if (jack->areas) + free(jack->areas); + free(jack); + } +} + +static int snd_pcm_jack_close(snd_pcm_ioplug_t *io) +{ + snd_pcm_jack_t *jack = io->private_data; + snd_pcm_jack_free(jack); + return 0; +} + +static int snd_pcm_jack_poll_revents(snd_pcm_ioplug_t *io, + struct pollfd *pfds, unsigned int nfds, + unsigned short *revents) +{ + char buf[1]; + + assert(pfds && nfds == 1 && revents); + + read(pfds[0].fd, buf, 1); + + *revents = pfds[0].revents; + return 0; +} + +static snd_pcm_sframes_t snd_pcm_jack_pointer(snd_pcm_ioplug_t *io) +{ + snd_pcm_jack_t *jack = io->private_data; + return jack->hw_ptr; +} + +static int +snd_pcm_jack_process_cb(jack_nframes_t nframes, snd_pcm_ioplug_t *io) +{ + snd_pcm_jack_t *jack = io->private_data; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t xfer = 0; + char buf[1]; + unsigned int channel; + + for (channel = 0; channel < io->channels; channel++) { + jack->areas[channel].addr = + jack_port_get_buffer (jack->ports[channel], nframes); + jack->areas[channel].first = 0; + jack->areas[channel].step = jack->sample_bits; + } + + if (io->state != SND_PCM_STATE_RUNNING) { + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + for (channel = 0; channel < io->channels; channel++) + snd_pcm_area_silence(&jack->areas[channel], 0, nframes, io->format); + return 0; + } + } + + areas = snd_pcm_ioplug_mmap_areas(io); + + while (xfer < nframes) { + snd_pcm_uframes_t frames = nframes - xfer; + snd_pcm_uframes_t offset = jack->hw_ptr; + snd_pcm_uframes_t cont = io->buffer_size - offset; + + if (cont < frames) + frames = cont; + + for (channel = 0; channel < io->channels; channel++) { + if (io->stream == SND_PCM_STREAM_PLAYBACK) + snd_pcm_area_copy(&jack->areas[channel], xfer, &areas[channel], offset, frames, io->format); + else + snd_pcm_area_copy(&areas[channel], offset, &jack->areas[channel], xfer, frames, io->format); + } + + jack->hw_ptr += frames; + jack->hw_ptr %= io->buffer_size; + xfer += frames; + } + + write(jack->fd, buf, 1); /* for polling */ + + return 0; +} + +static int snd_pcm_jack_prepare(snd_pcm_ioplug_t *io) +{ + unsigned int i; + + snd_pcm_jack_t *jack = io->private_data; + + jack->hw_ptr = 0; + + jack->ports = calloc(io->channels, sizeof(jack_port_t*)); + + for (i = 0; i < io->channels; i++) { + char port_name[32]; + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + + sprintf(port_name,"out_%03d\n",i); + jack->ports[i] = jack_port_register (jack->client, port_name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + } else { + sprintf(port_name,"in__%03d\n",i); + jack->ports[i] = jack_port_register (jack->client, port_name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + } + } + + jack_set_process_callback (jack->client, + (JackProcessCallback)snd_pcm_jack_process_cb, io); + return 0; +} + +static int snd_pcm_jack_start(snd_pcm_ioplug_t *io) +{ + snd_pcm_jack_t *jack = io->private_data; + unsigned int i; + + if (jack_activate (jack->client)) + return -EIO; + + jack->activated = 1; + + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + for (i = 0; i < io->channels && i < jack->playback_ports_n; i++) { + if ( jack->playback_ports[i]) { + if (jack_connect (jack->client, + jack_port_name (jack->ports[i]), + jack->playback_ports[i])) { + fprintf(stderr, "cannot connect %s to %s\n", + jack_port_name(jack->ports[i]), + jack->playback_ports[i]); + return -EIO; + } + } + } + } else { + for (i = 0; i < io->channels && i < jack->capture_ports_n; i++) { + if ( jack->capture_ports[i]) { + if (jack_connect (jack->client, + jack->capture_ports[i], + jack_port_name (jack->ports[i]))) { + fprintf(stderr, "cannot connect %s to %s\n", + jack->capture_ports[i], + jack_port_name(jack->ports[i])); + return -EIO; + } + } + } + } + + return 0; +} + +static int snd_pcm_jack_stop(snd_pcm_ioplug_t *io) +{ +#if 0 + snd_pcm_jack_t *jack = io->private_data; + + if (jack->activated) { + printf("deactivate\n"); + jack_deactivate(jack->client); + printf("deactivate done\n"); + jack->activated = 0; + } + { + unsigned i; + for (i = 0; i < io->channels; i++) { + if (jack->ports[i]) { + jack_port_unregister(jack->client, jack->ports[i]); + jack->ports[i] = NULL; + } + } + } +#endif + return 0; +} + +static snd_pcm_ioplug_callback_t jack_pcm_callback = { + .close = snd_pcm_jack_close, + .start = snd_pcm_jack_start, + .stop = snd_pcm_jack_stop, + .pointer = snd_pcm_jack_pointer, + .prepare = snd_pcm_jack_prepare, + .poll_revents = snd_pcm_jack_poll_revents, +}; + +#define ARRAY_SIZE(ary) (sizeof(ary)/sizeof(ary[0])) + +static int jack_set_hw_constraint(snd_pcm_jack_t *jack) +{ + unsigned int access_list[] = { + SND_PCM_ACCESS_MMAP_INTERLEAVED, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + SND_PCM_ACCESS_RW_INTERLEAVED, + SND_PCM_ACCESS_RW_NONINTERLEAVED + }; + unsigned int format = SND_PCM_FORMAT_FLOAT; + unsigned int rate = jack_get_sample_rate(jack->client); + int err; + + jack->sample_bits = snd_pcm_format_physical_width(format); + if ((err = snd_pcm_ioplug_set_param_list(&jack->io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_SIZE(access_list), access_list)) < 0 || + (err = snd_pcm_ioplug_set_param_list(&jack->io, SND_PCM_IOPLUG_HW_FORMAT, + 1, &format)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&jack->io, SND_PCM_IOPLUG_HW_CHANNELS, + jack->channels, jack->channels)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&jack->io, SND_PCM_IOPLUG_HW_RATE, + rate, rate)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&jack->io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + 128, 64*1024)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&jack->io, SND_PCM_IOPLUG_HW_PERIODS, + 2, 64)) < 0) + return err; + + return 0; +} + +static int parse_ports(snd_config_t *conf, char*** ret_ports, int *ret_n) +{ + snd_config_iterator_t i, next; + char** ports = NULL; + unsigned int cnt = 0; + unsigned int channel; + + if (conf) { + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + cnt++; + } + ports = calloc(cnt,sizeof(char*)); + for (channel = 0; channel < cnt; channel++) + ports[channel] = NULL; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + const char *port; + + if (snd_config_get_id(n, &id) < 0) + continue; + + channel = atoi(id); + + if (snd_config_get_string(n, &port) < 0) + continue; + + ports[channel] = port ? strdup(port) : NULL; + } + } + *ret_ports = ports; + *ret_n = cnt; + return 0; +} + +static int snd_pcm_jack_open(snd_pcm_t **pcmp, const char *name, + snd_config_t *playback_conf, + snd_config_t *capture_conf, + snd_pcm_stream_t stream, int mode) +{ + snd_pcm_jack_t *jack; + int err; + int fd[2]; + static unsigned int num = 0; + char jack_client_name[32]; + + assert(pcmp); + jack = calloc(1, sizeof(snd_pcm_jack_t)); + if (!jack) + return -ENOMEM; + + err = parse_ports(playback_conf, &jack->playback_ports, &jack->playback_ports_n); + if (err) { + snd_pcm_jack_free(jack); + return err; + } + + err = parse_ports(capture_conf, &jack->capture_ports, &jack->capture_ports_n); + if (err) { + snd_pcm_jack_free(jack); + return err; + } + + if (stream == SND_PCM_STREAM_PLAYBACK) + jack->channels = jack->playback_ports_n; + else + jack->channels = jack->capture_ports_n; + + if (jack->channels == 0) { + SNDERR("define the %s_ports section\n", stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + snd_pcm_jack_free(jack); + return -EINVAL; + } + + if (snprintf(jack_client_name, sizeof(jack_client_name), "alsa%s.%d.%d", + stream == SND_PCM_STREAM_PLAYBACK ? "P" : "C", getpid(), num++) + >= (int)sizeof(jack_client_name)) { + fprintf(stderr, "%s: WARNING: JACK client name '%s' truncated to %d characters, might not be unique\n", + __func__, jack_client_name, (int)strlen(jack_client_name)); + } + + jack->client = jack_client_new(jack_client_name); + + if (jack->client==0) { + snd_pcm_jack_free(jack); + return -ENOENT; + } + + jack->areas = calloc(jack->channels, sizeof(snd_pcm_channel_area_t)); + if (! jack->areas) { + snd_pcm_jack_free(jack); + return -ENOMEM; + } + + socketpair(AF_LOCAL, SOCK_STREAM, 0, fd); + + jack->fd = fd[0]; + + jack->io.name = "ALSA -> JACK PCM Plugin"; + jack->io.callback = &jack_pcm_callback; + jack->io.private_data = jack; + jack->io.poll_fd = fd[1]; + jack->io.poll_events = POLLIN; + jack->io.mmap_rw = 1; + + err = snd_pcm_ioplug_create(&jack->io, name, stream, mode); + if (err < 0) { + snd_pcm_jack_free(jack); + return err; + } + + err = jack_set_hw_constraint(jack); + if (err < 0) { + snd_pcm_ioplug_delete(&jack->io); + return err; + } + + *pcmp = jack->io.pcm; + + return 0; +} + + +SND_PCM_PLUGIN_DEFINE_FUNC(jack) +{ + snd_config_iterator_t i, next; + snd_config_t *playback_conf = NULL; + snd_config_t *capture_conf = NULL; + int err; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) + continue; + if (strcmp(id, "playback_ports") == 0) { + if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + playback_conf = n; + continue; + } + if (strcmp(id, "capture_ports") == 0) { + if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + capture_conf = n; + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + err = snd_pcm_jack_open(pcmp, name, + playback_conf, + capture_conf, + stream, mode); + + return err; +} + +SND_PCM_PLUGIN_SYMBOL(jack); diff --git a/pcm/oss/Makefile.am b/pcm/oss/Makefile.am new file mode 100644 index 0000000..ac38a87 --- /dev/null +++ b/pcm/oss/Makefile.am @@ -0,0 +1,8 @@ +asound_module_pcm_oss_LTLIBRARIES = libasound_module_pcm_oss.la + +asound_module_pcm_ossdir = $(libdir)/alsa-lib + +AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ +libasound_module_pcm_oss_la_SOURCES = pcm_oss.c +libasound_module_pcm_oss_la_LDFLAGS = -module -avoid-version -export-dynamic +libasound_module_pcm_oss_la_LIBADD = @ALSA_LIBS@ diff --git a/pcm/oss/pcm_oss.c b/pcm/oss/pcm_oss.c new file mode 100644 index 0000000..5628a2e --- /dev/null +++ b/pcm/oss/pcm_oss.c @@ -0,0 +1,422 @@ +/* + * OSS -> ALSA I/O plugin + * + * Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de> + * + * This library 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. + * + * This program 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdio.h> +#include <sys/ioctl.h> +#include <alsa/asoundlib.h> +#include <alsa/pcm_external.h> +#include <linux/soundcard.h> + +typedef struct snd_pcm_oss { + snd_pcm_ioplug_t io; + char *device; + int fd; + int fragment_set; + int caps; + int format; + unsigned int period_shift; + unsigned int periods; + unsigned int frame_bytes; +} snd_pcm_oss_t; + +static snd_pcm_sframes_t oss_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + snd_pcm_oss_t *oss = io->private_data; + const char *buf; + ssize_t result; + + /* we handle only an interleaved buffer */ + buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8; + size *= oss->frame_bytes; + result = write(oss->fd, buf, size); + if (result <= 0) + return result; + return result / oss->frame_bytes; +} + +static snd_pcm_sframes_t oss_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + snd_pcm_oss_t *oss = io->private_data; + char *buf; + ssize_t result; + + /* we handle only an interleaved buffer */ + buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8; + size *= oss->frame_bytes; + result = read(oss->fd, buf, size); + if (result <= 0) + return result; + return result / oss->frame_bytes; +} + +static snd_pcm_sframes_t oss_pointer(snd_pcm_ioplug_t *io) +{ + snd_pcm_oss_t *oss = io->private_data; + struct count_info info; + int ptr; + + if (ioctl(oss->fd, io->stream == SND_PCM_STREAM_PLAYBACK ? + SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &info) < 0) { + fprintf(stderr, "*** OSS: oss_pointer error\n"); + return 0; + } + ptr = snd_pcm_bytes_to_frames(io->pcm, info.ptr); + return ptr; +} + +static int oss_start(snd_pcm_ioplug_t *io) +{ + snd_pcm_oss_t *oss = io->private_data; + int tmp = io->stream == SND_PCM_STREAM_PLAYBACK ? + PCM_ENABLE_OUTPUT : PCM_ENABLE_INPUT; + + if (ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) { + fprintf(stderr, "*** OSS: trigger failed\n"); + if (io->stream == SND_PCM_STREAM_CAPTURE) + /* fake read to trigger */ + read(oss->fd, &tmp, 0); + } + return 0; +} + +static int oss_stop(snd_pcm_ioplug_t *io) +{ + snd_pcm_oss_t *oss = io->private_data; + int tmp = 0; + + ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &tmp); + return 0; +} + +static int oss_drain(snd_pcm_ioplug_t *io) +{ + snd_pcm_oss_t *oss = io->private_data; + + if (io->stream == SND_PCM_STREAM_PLAYBACK) + ioctl(oss->fd, SNDCTL_DSP_SYNC); + return 0; +} + +static int oss_prepare(snd_pcm_ioplug_t *io) +{ + snd_pcm_oss_t *oss = io->private_data; + int tmp; + + ioctl(oss->fd, SNDCTL_DSP_RESET); + + tmp = io->channels; + if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0) { + perror("SNDCTL_DSP_CHANNELS"); + return -EINVAL; + } + tmp = oss->format; + if (ioctl(oss->fd, SNDCTL_DSP_SETFMT, &tmp) < 0) { + perror("SNDCTL_DSP_SETFMT"); + return -EINVAL; + } + tmp = io->rate; + if (ioctl(oss->fd, SNDCTL_DSP_SPEED, &tmp) < 0 || + tmp > io->rate * 1.01 || tmp < io->rate * 0.99) { + perror("SNDCTL_DSP_SPEED"); + return -EINVAL; + } + return 0; +} + +static int oss_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) +{ + snd_pcm_oss_t *oss = io->private_data; + int i, tmp, err; + unsigned int period_bytes; + long oflags, flags; + + oss->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8; + switch (io->format) { + case SND_PCM_FORMAT_U8: + oss->format = AFMT_U8; + break; + case SND_PCM_FORMAT_S16_LE: + oss->format = AFMT_S16_LE; + break; + case SND_PCM_FORMAT_S16_BE: + oss->format = AFMT_S16_BE; + break; + default: + fprintf(stderr, "*** OSS: unsupported format %s\n", snd_pcm_format_name(io->format)); + return -EINVAL; + } + period_bytes = io->period_size * oss->frame_bytes; + oss->period_shift = 0; + for (i = 31; i >= 4; i--) { + if (period_bytes & (1U << i)) { + oss->period_shift = i; + break; + } + } + if (! oss->period_shift) { + fprintf(stderr, "*** OSS: invalid period size %d\n", (int)io->period_size); + return -EINVAL; + } + + _retry: + tmp = oss->period_shift | (oss->periods << 16); + if (ioctl(oss->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) { + if (! oss->fragment_set) { + perror("SNDCTL_DSP_SETFRAGMENT"); + fprintf(stderr, "*** period shift = %d, periods = %d\n", oss->period_shift, oss->periods); + return -EINVAL; + } + /* OSS has no proper way to reinitialize the fragments */ + /* try to reopen the device */ + close(oss->fd); + oss->fd = open(oss->device, io->stream == SND_PCM_STREAM_PLAYBACK ? + O_WRONLY : O_RDONLY); + if (oss->fd < 0) { + err = -errno; + SNDERR("Cannot reopen the device %s", oss->device); + return err; + } + io->poll_fd = oss->fd; + io->poll_events = io->stream == SND_PCM_STREAM_PLAYBACK ? + POLLOUT : POLLIN; + snd_pcm_ioplug_reinit_status(io); + oss->fragment_set = 0; + goto _retry; + } + oss->fragment_set = 1; + + if ((flags = fcntl(oss->fd, F_GETFL)) < 0) { + err = -errno; + perror("F_GETFL"); + } else { + oflags = flags; + if (io->nonblock) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (flags != oflags && + fcntl(oss->fd, F_SETFL, flags) < 0) { + err = -errno; + perror("F_SETFL"); + } + } + + return 0; +} + +#define ARRAY_SIZE(ary) (sizeof(ary)/sizeof(ary[0])) + +static int oss_hw_constraint(snd_pcm_oss_t *oss) +{ + snd_pcm_ioplug_t *io = &oss->io; + snd_pcm_access_t access; + unsigned int nformats; + unsigned int format[5]; + unsigned int nchannels; + unsigned int channel[6]; + /* period bytes must be power of two */ + static unsigned int period_bytes_list[] = { + 1U<<8, 1U<<9, 1U<<10, 1U<<11, 1U<<12, 1U<<13, 1U<<14, 1U<<15, + 1U<<16, 1U<<17, 1U<<18, 1U<<19, 1U<<20, 1U<<21, 1U<<22, 1U<<23 + }; + /* some apps (e.g. quake3) prefer periods to be power of two */ + static unsigned int periods_list[] = { + 1U<<1, 1U<<2, 1U<<3, 1U<<4, 1U<<5, 1U<<6, 1U<<7, 1U<<8, + 1U<<9, 1U<<10, 1U<<11, 1U<<12, + }; + int i, err, tmp; + + /* check trigger */ + oss->caps = 0; + if (ioctl(oss->fd, SNDCTL_DSP_GETCAPS, &oss->caps) >= 0) { + if (! (oss->caps & DSP_CAP_TRIGGER)) + fprintf(stderr, "*** OSS: trigger is not supported!\n"); + } + + /* access type - rw interleaved only */ + access = SND_PCM_ACCESS_RW_INTERLEAVED; + if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, + 1, &access)) < 0) + return err; + + /* supported formats */ + tmp = 0; + ioctl(oss->fd, SNDCTL_DSP_GETFMTS, &tmp); + nformats = 0; + if (tmp & AFMT_U8) + format[nformats++] = SND_PCM_FORMAT_U8; + if (tmp & AFMT_S16_LE) + format[nformats++] = SND_PCM_FORMAT_S16_LE; + if (tmp & AFMT_S16_BE) + format[nformats++] = SND_PCM_FORMAT_S16_BE; + if (tmp & AFMT_MU_LAW) + format[nformats++] = SND_PCM_FORMAT_MU_LAW; + if (! nformats) + format[nformats++] = SND_PCM_FORMAT_S16; + if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, + nformats, format)) < 0) + return err; + + /* supported channels */ + nchannels = 0; + for (i = 0; i < 6; i++) { + tmp = i + 1; + if (ioctl(oss->fd, SNDCTL_DSP_CHANNELS, &tmp) >= 0) + channel[nchannels++] = tmp; + } + if (! nchannels) /* assume 2ch stereo */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, + 2, 2); + else + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS, + nchannels, channel); + if (err < 0) + return err; + + /* supported rates */ + /* FIXME: should query? */ + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 480000); + if (err < 0) + return err; + + /* period size (in power of two) */ + if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + ARRAY_SIZE(period_bytes_list), + period_bytes_list)) < 0) + return err; + /* periods */ + /* err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 1<<1, 1<<12); */ + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIODS, + ARRAY_SIZE(periods_list), + periods_list); + if (err < 0) + return err; + + return 0; +} + + +static int oss_close(snd_pcm_ioplug_t *io) +{ + snd_pcm_oss_t *oss = io->private_data; + + close(oss->fd); + free(oss->device); + free(oss); + return 0; +} + +static snd_pcm_ioplug_callback_t oss_playback_callback = { + .start = oss_start, + .stop = oss_stop, + .transfer = oss_write, + .pointer = oss_pointer, + .close = oss_close, + .hw_params = oss_hw_params, + .prepare = oss_prepare, + .drain = oss_drain, +}; + +static snd_pcm_ioplug_callback_t oss_capture_callback = { + .start = oss_start, + .stop = oss_stop, + .transfer = oss_read, + .pointer = oss_pointer, + .close = oss_close, + .hw_params = oss_hw_params, + .prepare = oss_prepare, + .drain = oss_drain, +}; + + +SND_PCM_PLUGIN_DEFINE_FUNC(oss) +{ + snd_config_iterator_t i, next; + const char *device = "/dev/dsp"; + int err; + snd_pcm_oss_t *oss; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) + continue; + if (strcmp(id, "device") == 0) { + if (snd_config_get_string(n, &device) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + oss = calloc(1, sizeof(*oss)); + oss->device = strdup(device); + if (oss->device == NULL) { + SNDERR("cannot allocate"); + free(oss); + return -ENOMEM; + } + oss->fd = open(device, stream == SND_PCM_STREAM_PLAYBACK ? + O_WRONLY : O_RDONLY); + if (oss->fd < 0) { + err = -errno; + SNDERR("Cannot open device %s", device); + goto error; + } + oss->io.name = "ALSA -> OSS PCM Plugin"; + oss->io.poll_fd = oss->fd; + oss->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; + oss->io.mmap_rw = 0; + oss->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &oss_playback_callback : &oss_capture_callback; + oss->io.private_data = oss; + + err = snd_pcm_ioplug_create(&oss->io, name, stream, mode); + if (err < 0) + goto error; + + if ((err = oss_hw_constraint(oss)) < 0) { + snd_pcm_ioplug_delete(&oss->io); + return err; + } + + *pcmp = oss->io.pcm; + return 0; + + error: + if (oss->fd >= 0) + close(oss->fd); + free(oss->device); + free(oss); + return err; +} + +SND_PCM_PLUGIN_SYMBOL(oss); |