From 6493a5f501c3dfe158554020d4a6a10329273cbd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Jun 2005 17:17:13 +0000 Subject: Add OSS mixer <-> ALSA control plugin Added the external OSS mixer <-> ALSA control plugin. --- Makefile.am | 2 +- configure.in | 4 +- ctl/Makefile.am | 2 + ctl/oss/Makefile.am | 10 ++ ctl/oss/ctl_oss.c | 452 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 467 insertions(+), 3 deletions(-) create mode 100644 ctl/Makefile.am create mode 100644 ctl/oss/Makefile.am create mode 100644 ctl/oss/ctl_oss.c diff --git a/Makefile.am b/Makefile.am index 7784abf..e72ac58 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = pcm +SUBDIRS = pcm ctl EXTRA_DIST = cvscompile version AUTOMAKE_OPTIONS = foreign diff --git a/configure.in b/configure.in index 39d1cd7..cb24ce1 100644 --- a/configure.in +++ b/configure.in @@ -21,6 +21,6 @@ AC_OUTPUT([ pcm/Makefile pcm/oss/Makefile pcm/jack/Makefile + ctl/Makefile + ctl/oss/Makefile ]) - - diff --git a/ctl/Makefile.am b/ctl/Makefile.am new file mode 100644 index 0000000..8c1ac17 --- /dev/null +++ b/ctl/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = oss + diff --git a/ctl/oss/Makefile.am b/ctl/oss/Makefile.am new file mode 100644 index 0000000..f1c0e04 --- /dev/null +++ b/ctl/oss/Makefile.am @@ -0,0 +1,10 @@ +asound_module_ctl_oss_LTLIBRARIES = libasound_module_ctl_oss.la + +asound_module_ctl_ossdir = $(libdir)/alsa-lib + +AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ +libasound_module_ctl_oss_la_SOURCES = ctl_oss.c +libasound_module_ctl_oss_la_LDFLAGS = -module -avoid-version -export-dynamic +libasound_module_ctl_oss_la_LIBADD = @ALSA_LIBS@ + +EXTRA_DIST = README diff --git a/ctl/oss/ctl_oss.c b/ctl/oss/ctl_oss.c new file mode 100644 index 0000000..a7cfa8f --- /dev/null +++ b/ctl/oss/ctl_oss.c @@ -0,0 +1,452 @@ +/* + * ALSA <-> OSS mixer control plugin + * + * Copyright (c) 2005 by Takashi Iwai + * + * 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 + */ + +/* + * TODO: implement the pseudo poll with thread (and pipe as pollfd)? + */ + +#include +#include +#include +#include +#include + +typedef struct snd_ctl_oss { + snd_ctl_ext_t ext; + char *device; + int fd; + int exclusive_input; + int stereo_mask; + int num_vol_ctls; + int vol_ctl[SOUND_MIXER_NRDEVICES]; + int num_rec_items; + int rec_item[SOUND_MIXER_NRDEVICES]; +} snd_ctl_oss_t; + +static const char *vol_devices[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = "Master Playback Volume", + [SOUND_MIXER_BASS] = "Tone Control - Bass", + [SOUND_MIXER_TREBLE] = "Tone Control - Treble", + [SOUND_MIXER_SYNTH] = "Synth Playback Volume", + [SOUND_MIXER_PCM] = "PCM Playback Volume", + [SOUND_MIXER_SPEAKER] = "PC Speaker Playback Volume", + [SOUND_MIXER_LINE] = "Line Playback Volume", + [SOUND_MIXER_MIC] = "Mic Playback Volume", + [SOUND_MIXER_CD] = "CD Playback Volume", + [SOUND_MIXER_IMIX] = "Monitor Mix Playback Volume", + [SOUND_MIXER_ALTPCM] = "Headphone Playback Volume", + [SOUND_MIXER_RECLEV] = "Capture Volume", + [SOUND_MIXER_IGAIN] = "Capture Volume", + [SOUND_MIXER_OGAIN] = "Playback Volume", + [SOUND_MIXER_LINE1] = "Aux Playback Volume", + [SOUND_MIXER_LINE2] = "Aux1 Playback Volume", + [SOUND_MIXER_LINE3] = "Line1 Playback Volume", + [SOUND_MIXER_DIGITAL1] = "IEC958 Playback Volume", + [SOUND_MIXER_DIGITAL2] = "Digital Playback Volume", + [SOUND_MIXER_DIGITAL3] = "Digital1 Playback Volume", + [SOUND_MIXER_PHONEIN] = "Phone Playback Volume", + [SOUND_MIXER_PHONEOUT] = "Master Mono Playback Volume", + [SOUND_MIXER_VIDEO] = "Video Playback Volume", + [SOUND_MIXER_RADIO] = "Radio Playback Volume", + [SOUND_MIXER_MONITOR] = "Monitor Playback Volume", +}; + +static const char *rec_devices[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = "Mix Capture Switch", + [SOUND_MIXER_SYNTH] = "Synth Capture Switch", + [SOUND_MIXER_PCM] = "PCM Capture Switch", + [SOUND_MIXER_LINE] = "Line Capture Switch", + [SOUND_MIXER_MIC] = "Mic Capture Switch", + [SOUND_MIXER_CD] = "CD Capture Switch", + [SOUND_MIXER_LINE1] = "Aux Capture Switch", + [SOUND_MIXER_LINE2] = "Aux1 Capture Switch", + [SOUND_MIXER_LINE3] = "Line1 Capture Switch", + [SOUND_MIXER_DIGITAL1] = "IEC958 Capture Switch", + [SOUND_MIXER_DIGITAL2] = "Digital Capture Switch", + [SOUND_MIXER_DIGITAL3] = "Digital1 Capture Switch", + [SOUND_MIXER_PHONEIN] = "Phone Capture Switch", + [SOUND_MIXER_VIDEO] = "Video Capture Switch", + [SOUND_MIXER_RADIO] = "Radio Capture Switch", +}; + +static const char *rec_items[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = "Mix", + [SOUND_MIXER_SYNTH] = "Synth", + [SOUND_MIXER_PCM] = "PCM", + [SOUND_MIXER_LINE] = "Line", + [SOUND_MIXER_MIC] = "Mic", + [SOUND_MIXER_CD] = "CD", + [SOUND_MIXER_LINE1] = "Aux", + [SOUND_MIXER_LINE2] = "Aux1", + [SOUND_MIXER_LINE3] = "Line1", + [SOUND_MIXER_DIGITAL1] = "IEC958", + [SOUND_MIXER_DIGITAL2] = "Digital", + [SOUND_MIXER_DIGITAL3] = "Digital1", + [SOUND_MIXER_PHONEIN] = "Phone", + [SOUND_MIXER_VIDEO] = "Video", + [SOUND_MIXER_RADIO] = "Radio", +}; + +static void oss_close(snd_ctl_ext_t *ext) +{ + snd_ctl_oss_t *oss = ext->private_data; + + close(oss->fd); + free(oss->device); + free(oss); +} + +static int oss_elem_count(snd_ctl_ext_t *ext) +{ + snd_ctl_oss_t *oss = ext->private_data; + int num; + + num = oss->num_vol_ctls; + if (oss->exclusive_input) + num++; + else if (oss->num_rec_items) + num += oss->num_rec_items; + return num; +} + +static int oss_elem_list(snd_ctl_ext_t *ext, unsigned int offset, snd_ctl_elem_id_t *id) +{ + snd_ctl_oss_t *oss = ext->private_data; + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + if (offset < oss->num_vol_ctls) + snd_ctl_elem_id_set_name(id, vol_devices[oss->vol_ctl[offset]]); + else if (oss->exclusive_input) + snd_ctl_elem_id_set_name(id, "Capture Source"); + else { + offset -= oss->num_vol_ctls; + snd_ctl_elem_id_set_name(id, rec_devices[oss->rec_item[offset]]); + } + return 0; +} + +#define OSS_KEY_DEVICE_MASK 0x1f +#define OSS_KEY_CAPTURE_FLAG (1 << 8) +#define OSS_KEY_CAPTURE_MUX (1 << 16) + +static snd_ctl_ext_key_t oss_find_elem(snd_ctl_ext_t *ext, + const snd_ctl_elem_id_t *id) +{ + snd_ctl_oss_t *oss = ext->private_data; + const char *name; + int i, key; + + name = snd_ctl_elem_id_get_name(id); + if (! strcmp(name, "Capture Source")) { + if (oss->exclusive_input) + return OSS_KEY_CAPTURE_MUX; + else + return SND_CTL_EXT_KEY_NOT_FOUND; + } + for (i = 0; i < oss->num_vol_ctls; i++) { + key = oss->vol_ctl[i]; + if (! strcmp(name, vol_devices[key])) + return key; + } + for (i = 0; i < oss->num_rec_items; i++) { + key = oss->rec_item[i]; + if (! strcmp(name, rec_devices[key])) + return key | OSS_KEY_CAPTURE_FLAG; + } + return SND_CTL_EXT_KEY_NOT_FOUND; +} + +static int oss_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + int *type, unsigned int *acc, unsigned int *count) +{ + snd_ctl_oss_t *oss = ext->private_data; + + if (key == OSS_KEY_CAPTURE_MUX) { + *type = SND_CTL_ELEM_TYPE_ENUMERATED; + *acc = SND_CTL_EXT_ACCESS_READWRITE; + *count = 1; + } else if (key & OSS_KEY_CAPTURE_FLAG) { + *type = SND_CTL_ELEM_TYPE_BOOLEAN; + *acc = SND_CTL_EXT_ACCESS_READWRITE; + *count = 1; + } else { + *type = SND_CTL_ELEM_TYPE_INTEGER; + *acc = SND_CTL_EXT_ACCESS_READWRITE; + if (oss->stereo_mask & (1 << key)) + *count = 2; + else + *count = 1; + } + return 0; +} + +static int oss_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *imin, long *imax, long *istep) +{ + *istep = 0; + *imin = 0; + *imax = 100; + return 0; +} + +static int oss_get_enumerated_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int *items) +{ + snd_ctl_oss_t *oss = ext->private_data; + + *items = oss->num_rec_items; + return 0; +} + +static int oss_get_enumerated_name(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int item, char *name, size_t name_max_len) +{ + snd_ctl_oss_t *oss = ext->private_data; + + if (item >= oss->num_rec_items) + return -EINVAL; + item = oss->rec_item[item]; + strncpy(name, rec_items[item], name_max_len - 1); + name[name_max_len - 1] = 0; + return 0; +} + +static int oss_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value) +{ + snd_ctl_oss_t *oss = ext->private_data; + int val; + + if (key & OSS_KEY_CAPTURE_FLAG) { + key &= OSS_KEY_DEVICE_MASK; + if (ioctl(oss->fd, SOUND_MIXER_READ_RECSRC, &val) < 0) + return -errno; + *value = (val & (1 << key)) ? 1 : 0; + } else { + if (ioctl(oss->fd, MIXER_READ(key), &val) < 0) + return -errno; + *value = val & 0xff; + if (oss->stereo_mask & (1 << key)) + value[1] = (val >> 8) & 0xff; + } + return 0; +} + +static int oss_read_enumerated(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned int *items) +{ + snd_ctl_oss_t *oss = ext->private_data; + int i, val; + + *items = 0; + if (ioctl(oss->fd, SOUND_MIXER_READ_RECSRC, &val) < 0) + return -errno; + for (i = 0; i < oss->num_rec_items; i++) { + if (val & (1 << oss->rec_item[i])) { + *items = i; + break; + } + } + return 0; +} + +static int oss_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value) +{ + snd_ctl_oss_t *oss = ext->private_data; + int val, oval; + + if (key & OSS_KEY_CAPTURE_FLAG) { + key &= OSS_KEY_DEVICE_MASK; + if (ioctl(oss->fd, SOUND_MIXER_READ_RECSRC, &oval) < 0) + return -errno; + if (*value) + val = oval | (1 << key); + else + val = oval & ~(1 << key); + if (oval == val) + return 0; + if (ioctl(oss->fd, SOUND_MIXER_WRITE_RECSRC, &val) < 0) + return -errno; + return 1; + } else { + val = *value; + if (oss->stereo_mask & (1 << key)) + val |= value[1] << 8; + if (ioctl(oss->fd, MIXER_READ(key), &oval) < 0) + return -errno; + if (oval == val) + return 0; + if (ioctl(oss->fd, MIXER_WRITE(key), &val) < 0) + return -errno; + return 1; + } +} + +static int oss_write_enumerated(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + unsigned int *items) +{ + snd_ctl_oss_t *oss = ext->private_data; + int val, oval; + + if (ioctl(oss->fd, SOUND_MIXER_READ_RECSRC, &oval) < 0) + return -errno; + val = 1 << oss->rec_item[*items]; + if (val == oval) + return 0; + if (ioctl(oss->fd, SOUND_MIXER_WRITE_RECSRC, &val) < 0) + return -errno; + return 1; +} + +static int oss_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, + unsigned int *event_mask) +{ + return -EAGAIN; +} + +static snd_ctl_ext_callback_t oss_ext_callback = { + .close = oss_close, + .elem_count = oss_elem_count, + .elem_list = oss_elem_list, + .find_elem = oss_find_elem, + .get_attribute = oss_get_attribute, + .get_integer_info = oss_get_integer_info, + .get_enumerated_info = oss_get_enumerated_info, + .get_enumerated_name = oss_get_enumerated_name, + .read_integer = oss_read_integer, + .read_enumerated = oss_read_enumerated, + .write_integer = oss_write_integer, + .write_enumerated = oss_write_enumerated, + .read_event = oss_read_event, +}; + + +SND_CTL_PLUGIN_DEFINE_FUNC(oss) +{ + snd_config_iterator_t it, next; + const char *device = "/dev/mixer"; + struct mixer_info mixinfo; + int i, err, val; + snd_ctl_oss_t *oss; + + snd_config_for_each(it, next, conf) { + snd_config_t *n = snd_config_iterator_entry(it); + 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); + oss->fd = -1; + if (oss->device == NULL) { + SNDERR("cannot allocate"); + free(oss); + return -ENOMEM; + } + oss->fd = open(device, O_RDWR); + if (oss->fd < 0) { + err = -errno; + SNDERR("Cannot open device %s", device); + goto error; + } + + if (ioctl(oss->fd, SOUND_MIXER_INFO, &mixinfo) < 0) { + err = -errno; + SNDERR("Cannot get mixer info for device %s", device); + goto error; + } + + oss->ext.version = SND_CTL_EXT_VERSION; + oss->ext.card_idx = 0; /* FIXME */ + strncpy(oss->ext.id, mixinfo.id, sizeof(oss->ext.id) - 1); + strcpy(oss->ext.driver, "OSS-Emulation"); + strncpy(oss->ext.name, mixinfo.name, sizeof(oss->ext.name) - 1); + strncpy(oss->ext.longname, mixinfo.name, sizeof(oss->ext.longname) - 1); + strncpy(oss->ext.mixername, mixinfo.name, sizeof(oss->ext.mixername) - 1); + oss->ext.poll_fd = -1; + oss->ext.callback = &oss_ext_callback; + oss->ext.private_data = oss; + + oss->num_vol_ctls = 0; + val = 0; + if (ioctl(oss->fd, SOUND_MIXER_READ_DEVMASK, &val) < 0) + perror("ctl_oss: DEVMASK error"); + else { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((val & (1 << i)) && vol_devices[i]) + oss->vol_ctl[oss->num_vol_ctls++] = i; + } + } + + if (ioctl(oss->fd, SOUND_MIXER_READ_STEREODEVS, &oss->stereo_mask) < 0) + perror("ctl_oss: STEREODEVS error"); + val = 0; + if (ioctl(oss->fd, SOUND_MIXER_READ_CAPS, &val) < 0) + perror("ctl_oss: MIXER_CAPS error"); + else if (val & SOUND_CAP_EXCL_INPUT) + oss->exclusive_input = 1; + + oss->num_rec_items = 0; + val = 0; + if (ioctl(oss->fd, SOUND_MIXER_READ_RECMASK, &val) < 0) + perror("ctl_oss: MIXER_RECMASK error"); + else { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (val & (1 << i)) { + if (oss->exclusive_input) { + if (! rec_items[i]) + continue; + } else { + if (! rec_devices[i]) + continue; + } + oss->rec_item[oss->num_rec_items++] = i; + } + } + } + if (! oss->num_rec_items) + oss->exclusive_input = 0; + + err = snd_ctl_ext_create(&oss->ext, name, mode); + if (err < 0) + goto error; + + *handlep = oss->ext.handle; + return 0; + + error: + if (oss->fd >= 0) + close(oss->fd); + free(oss->device); + free(oss); + return err; +} + +SND_CTL_PLUGIN_SYMBOL(oss); -- cgit