Author: Gary Mills Date: Tue Sep 22 13:16:06 2020 -0500 bug #9732: pulseaudio always uses the first audio device diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index 048822c..1146666 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -32,6 +32,8 @@ * */ +#define HAVE_OSSV4 + #ifdef HAVE_CONFIG_H #include #endif @@ -122,7 +124,18 @@ struct userdata { int mode; int mixer_fd; + +#ifdef HAVE_OSSV4 + int mixer_dev; + int mixer_sink_control; + int mixer_source_control; + int mixer_cmax; + int mixer_dsp_fd; + oss_mixext sink_mixext; + oss_mixext source_mixext; +#else int mixer_devmask; +#endif int nfrags, frag_size, orig_frag_size; @@ -199,8 +212,13 @@ static void trigger(struct userdata *u, pa_sink_state_t sink_state, pa_source_st } else { if (enable_bits) +#ifdef HAVE_OSSV4 + if (ioctl(u->fd, SNDCTL_DSP_SYNC, NULL) < 0) + pa_log_warn("SNDCTL_DSP_SYNC: %s", pa_cstrerror(errno)); +#else if (ioctl(u->fd, SNDCTL_DSP_POST, NULL) < 0) pa_log_warn("SNDCTL_DSP_POST: %s", pa_cstrerror(errno)); +#endif if (!quick) { /* @@ -819,6 +837,160 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_ return 0; } +#ifdef HAVE_OSSV4 + +static const char * +mixer_ext_type_get_name (int type) +{ + switch (type) { + case MIXT_DEVROOT: + return "Device root entry"; + case MIXT_GROUP: + return "Controller group"; + case MIXT_ONOFF: + return "On/Off switch"; + case MIXT_ENUM: + return "Enumeration control"; + case MIXT_MONOSLIDER: + return "Mono slider (0-255)"; + case MIXT_STEREOSLIDER: + return "Stereo slider (0-255)"; + case MIXT_MESSAGE: + return "Textual message"; + case MIXT_MONOVU: + return "Mono VU meter value"; + case MIXT_STEREOVU: + return "Stereo VU meter value"; + case MIXT_MONOPEAK: + return "Mono VU meter peak value"; + case MIXT_STEREOPEAK: + return "Stereo VU meter peak value"; + case MIXT_RADIOGROUP: + return "Radio button group"; + case MIXT_MARKER: + /* Separator between normal and extension entries */ + return "Separator"; + case MIXT_VALUE: + return "Decimal value entry"; + case MIXT_HEXVALUE: + return "Hex value entry"; + case MIXT_SLIDER: + return "Mono slider (31-bit value range)"; + case MIXT_3D: + return "3D"; + case MIXT_MONOSLIDER16: + return "Mono slider (0-32767)"; + case MIXT_STEREOSLIDER16: + return "Stereo slider (0-32767)"; + case MIXT_MUTE: + return "Mute switch"; + default: + break; + } + + return "Unknown"; +} + +void +mixer_showflags (int flags) +{ + struct + { + int flag; + char nick[16]; + } all_flags[] = { + /* first the important ones */ + { + MIXF_MAINVOL, "MAINVOL"}, { + MIXF_PCMVOL, "PCMVOL"}, { + MIXF_RECVOL, "RECVOL"}, { + MIXF_MONVOL, "MONVOL"}, { + MIXF_DESCR, "DESCR"}, + + /* now the rest in the right order */ + { + MIXF_READABLE, "READABLE"}, { + MIXF_WRITEABLE, "WRITABLE"}, { + MIXF_POLL, "POLL"}, { + MIXF_HZ, "HZ"}, { + MIXF_STRING, "STRING"}, { + MIXF_DYNAMIC, "DYNAMIC"}, { + MIXF_OKFAIL, "OKFAIL"}, { + MIXF_FLAT, "FLAT"}, { + MIXF_LEGACY, "LEGACY"}, { + MIXF_CENTIBEL, "CENTIBEL"}, { + MIXF_DECIBEL, "DECIBEL"}, { + MIXF_WIDE, "WIDE"} + }; + int num_flags = (sizeof (all_flags) / sizeof ((all_flags)[0])); + int i; + + if (flags == 0) { + pa_log_debug (" flags : None"); + return; + } + + for (i=0; i < num_flags; i++) { + if ((flags & all_flags[i].flag)) { + pa_log_debug (" flag : %s", all_flags[i].nick); + flags &= ~all_flags[i].flag; /* unset */ + } + } + + /* Unknown flags? */ + if (flags != 0) { + pa_log_debug (" flag : ????"); + } + + return; +} + +static void sink_get_volume(pa_sink *s) { + struct userdata *u; + + pa_assert_se(u = s->userdata); + + if (pa_oss_get_volume(u->mixer_dsp_fd, &(u->sink_mixext), &s->sample_spec, &s->real_volume) >= 0) + return; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); +} + +static void sink_set_volume(pa_sink *s) { + struct userdata *u; + + pa_assert_se(u = s->userdata); + + if (pa_oss_set_volume(u->mixer_dsp_fd, &(u->sink_mixext), &s->sample_spec, &s->real_volume) >= 0) + return; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); +} + +static void source_get_volume(pa_source *s) { + struct userdata *u; + + pa_assert_se(u = s->userdata); + + if (pa_oss_get_volume(u->mixer_dsp_fd, &(u->source_mixext), &s->sample_spec, &s->real_volume) >= 0) + return; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); +} + +static void source_set_volume(pa_source *s) { + struct userdata *u; + + pa_assert_se(u = s->userdata); + + if (pa_oss_set_volume(u->mixer_dsp_fd, &(u->source_mixext), &s->sample_spec, &s->real_volume) >= 0) + return; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); +} + +#else + static void sink_get_volume(pa_sink *s) { struct userdata *u; @@ -889,6 +1061,7 @@ static void source_set_volume(pa_source *s) { if (u->mixer_devmask & SOUND_MASK_MIC) (void) pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_MIC, &s->sample_spec, &s->real_volume); } +#endif static void thread_func(void *userdata) { struct userdata *u = userdata; @@ -1179,6 +1352,11 @@ fail: int pa__init(pa_module*m) { +#ifdef HAVE_OSSV4 + struct oss_sysinfo si = { {0,}, }; + struct oss_mixerinfo mi = { 0, }; +#endif + struct audio_buf_info info; struct userdata *u = NULL; const char *dev; @@ -1278,7 +1456,9 @@ int pa__init(pa_module*m) { m->userdata = u; u->fd = fd; u->mixer_fd = -1; +#ifndef HAVE_OSSV4 u->mixer_devmask = 0; +#endif u->use_getospace = u->use_getispace = true; u->use_getodelay = true; u->mode = mode; @@ -1452,6 +1632,179 @@ int pa__init(pa_module*m) { if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) { bool do_close = true; +#ifdef HAVE_OSSV4 +#define IGNORE_DEV 1 + int i; + + u->mixer_dsp_fd = -1; + + if (ioctl (u->mixer_fd, SNDCTL_SYSINFO, &si) < 0) { + pa_log_debug ("SNDCTL_SYSINFO failed"); + goto fail; + } + + i = pa_oss_get_device_number(u->device_name); + if (i >= 0 && i < si.nummixers) { + mi.dev = i; + if (ioctl (u->mixer_fd, SNDCTL_MIXERINFO, &mi) < 0) { + pa_log_debug("SNDCTL_MIXERINFO failed"); + goto fail; + } + } + + if (i < si.nummixers) { + struct stat sbuf; + + if ((stat(mi.devnode, &sbuf) != 0) || + ((sbuf.st_mode & S_IFCHR) == 0)) { + pa_log("Failed to get mixer dsp device."); + i = si.nummixers; + } + } + + if (i < si.nummixers && + (u->mixer_dsp_fd = pa_oss_open_mixer(mi.devnode)) >= 0) { + + /* Will cause for loop to exit if not filled in by OSS */ + u->mixer_cmax = -1; + if (ioctl(u->mixer_dsp_fd, SNDCTL_MIX_NREXT, &u->mixer_cmax) < 0) { + pa_log("Failed to get max control."); + goto fail; + } + + pa_log_debug ("Opened mixer device %d with %d controls\n", + mi.dev, mi.nrext); + + u->mixer_sink_control = -1; + u->mixer_source_control = -1; + + for (i=0; i < u->mixer_cmax; i++) { + memset (&(u->sink_mixext), 0, sizeof (oss_mixext)); + +#ifdef IGNORE_DEV + /* This will cause dev to be ignored */ + u->sink_mixext.dev = -1; +#else + u->sink_mixext.dev = mi.dev; +#endif + + /* + * The real way to pick a control on a mixer is with this + * number. Note that control numbers are unique across all + * mixers. So dev can just be ignored. When dev is included + * it will only be used to check for correct dev from + * userland. But it will not be used to select a mixer. + */ + u->sink_mixext.ctrl = i; + + pa_log_debug ("Control %d", u->sink_mixext.ctrl); + + if (ioctl (u->mixer_dsp_fd, SNDCTL_MIX_EXTINFO, + &(u->sink_mixext)) < 0) { + pa_log_debug ("SNDCTL_MIX_EXTINFO failed"); + continue; + } + + pa_log_debug (" name : %s", u->sink_mixext.extname); + pa_log_debug (" type : %s (%d)", + mixer_ext_type_get_name (u->sink_mixext.type), + u->sink_mixext.type); + pa_log_debug (" maxval : %d", u->sink_mixext.maxvalue); + pa_log_debug (" parent : %d", u->sink_mixext.parent); + mixer_showflags (u->sink_mixext.flags); + + if ((u->sink_mixext.flags & MIXF_PCMVOL)) { + pa_log_debug ("First PCM control: %d", i); + u->mixer_sink_control = i; + break; + } + + /* + * Note that MIXF_MAINVOL may not be an exclusive single + * control. For example on AudioHD there will be one for each + * output jack (Green, Black, Orange...). So to really do a + * master volume you would need to do all MIXF_MAINVOL at the + * same time... + */ + if (((u->sink_mixext.flags & MIXF_MAINVOL)) && + u->mixer_sink_control == -1) { + pa_log_debug ("First main volume control: %d", i); + u->mixer_sink_control = i; + } + } + + if (u->mixer_sink_control != -1) { + pa_log_debug ("Setting OSS sink callbacks."); + pa_sink_set_get_volume_callback(u->sink, sink_get_volume); + pa_sink_set_set_volume_callback(u->sink, sink_set_volume); + u->sink->n_volume_steps = 101; + do_close = false; + } else { + pa_log_debug ("Not setting OSS sink callbacks."); + } + + for (i=0; i < mi.nrext; i++) { + memset (&(u->source_mixext), 0, sizeof (oss_mixext)); +#ifdef IGNORE_DEV + /* This will cause dev to be ignored */ + u->source_mixext.dev = -1; +#else + u->source_mixext.dev = mi.dev; +#endif + /* + * The real way to pick a control on a mixer is with this + * number. Note that control numbers are unique across all + * mixers. So dev can just be ignored. When dev is included + * it will only be used to check for correct dev from userland. + * But it will not be used to select a mixer. + */ + u->source_mixext.ctrl = i; + + pa_log_debug ("Control %d", u->source_mixext.ctrl); + + if (ioctl (u->mixer_dsp_fd, SNDCTL_MIX_EXTINFO, + &(u->source_mixext)) == -1) { + pa_log_debug ("SNDCTL_MIX_EXTINFO failed"); + continue; + } + + pa_log_debug (" name : %s", u->source_mixext.extname); + pa_log_debug (" type : %s (%d)", + mixer_ext_type_get_name (u->source_mixext.type), + u->source_mixext.type); + pa_log_debug (" maxval : %d", u->source_mixext.maxvalue); + pa_log_debug (" parent : %d", u->source_mixext.parent); + mixer_showflags (u->source_mixext.flags); + + /* + * There may be more then one MIXF_RECVOL on a mixer. In fact + * for audioHD the can be three (line-in, mix-in, and cd-in). + * For a master gain it may be good to adjust all... + */ + if ((u->source_mixext.flags & MIXF_RECVOL)) { + pa_log_debug ("First REC control: %d", i); + u->mixer_source_control = i; + break; + } + } + + if (u->mixer_source_control != -1) { + pa_log_debug ("Setting OSS source callbacks."); + pa_source_set_get_volume_callback(u->source, source_get_volume); + pa_source_set_set_volume_callback(u->source, source_set_volume); + u->source->n_volume_steps = 101; + do_close = false; + } else { + pa_log_debug ("Not setting OSS source callbacks."); + } + + if (do_close == true) { + pa_close(u->mixer_dsp_fd); + u->mixer_dsp_fd = -1; + } + } + +#else if (ioctl(u->mixer_fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0) pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno)); else { @@ -1471,11 +1824,14 @@ int pa__init(pa_module*m) { do_close = false; } } +#endif if (do_close) { pa_close(u->mixer_fd); u->mixer_fd = -1; +#ifndef HAVE_OSSV4 u->mixer_devmask = 0; +#endif } } @@ -1595,6 +1951,10 @@ void pa__done(pa_module*m) { if (u->fd >= 0) pa_close(u->fd); +#ifdef HAVE_OSSV4 + if (u->mixer_dsp_fd >= 0) + pa_close(u->mixer_dsp_fd); +#endif if (u->mixer_fd >= 0) pa_close(u->mixer_fd); diff --git a/src/modules/oss/oss-util.c b/src/modules/oss/oss-util.c index 80b6c8c..69c702a 100644 --- a/src/modules/oss/oss-util.c +++ b/src/modules/oss/oss-util.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -90,10 +91,7 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { } success: - if (ioctl(fd, FIONBIO, &nonblock_io) < 0) { - pa_log("FIONBIO: %s", pa_cstrerror(errno)); - goto fail; - } + pa_make_fd_nonblock(fd); t = pa_sprintf_malloc( "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", @@ -256,6 +254,190 @@ int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { return 0; } +#ifdef HAVE_OSSV4 +static int +oss4_mixer_slider_pack_volume (oss_mixext *mixext, int channels, const pa_cvolume *volume) +{ + int val = 0; + int lvol = 0; + int rvol = 0; + int lval = 0; + int rval = 0; + + switch (mixext->type) { + case MIXT_MONOSLIDER: + case MIXT_MONOSLIDER16: + case MIXT_SLIDER: + lvol = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; + val = (lvol*mixext->maxvalue)/PA_VOLUME_NORM; + break; + + case MIXT_STEREOSLIDER: + lvol = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; + lval = (lvol*mixext->maxvalue)/PA_VOLUME_NORM; + if (channels >= 2) { + rvol = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1]; + rval = (rvol*mixext->maxvalue)/PA_VOLUME_NORM; + } + + val = ((rval & 0xff) << 8) | (lval & 0xff); + break; + + case MIXT_STEREOSLIDER16: + lvol = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; + lval = (lvol*mixext->maxvalue)/PA_VOLUME_NORM; + if (channels >= 2) { + rvol = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1]; + rval = (rvol*mixext->maxvalue)/PA_VOLUME_NORM; + } + + val = ((rval & 0xffff) << 16) | (lval & 0xffff); + + break; + + default: + return 0; + } + + return val; +} + +static void +oss4_mixer_slider_unpack_volume (oss_mixext *mixext, int v, pa_cvolume * volume) +{ + uint32_t val, vol; + + val = (uint32_t) v; + switch (mixext->type) { + case MIXT_SLIDER: + vol = val; + volume->values[0] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + if (volume->channels >= 2) { + volume->values[1] = volume->values[0]; + } + break; + case MIXT_MONOSLIDER: + /* oss repeats the value in the upper bits, as if it was stereo */ + vol = val & 0x00ff; + volume->values[0] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + if (volume->channels >= 2) { + volume->values[1] = volume->values[0]; + } + break; + case MIXT_MONOSLIDER16: + /* oss repeats the value in the upper bits, as if it was stereo */ + vol = val & 0x0000ffff; + volume->values[0] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + if (volume->channels >= 2) { + volume->values[1] = volume->values[0]; + } + break; + case MIXT_STEREOSLIDER: + vol = (val & 0x00ff); + volume->values[0] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + if (volume->channels >= 2) { + vol = (val & 0xff00) >> 8; + volume->values[1] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + } + break; + case MIXT_STEREOSLIDER16: + vol = (val & 0x0000ffff); + volume->values[0] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + if (volume->channels >= 2) { + vol = (val & 0xffff0000) >> 16; + volume->values[1] = PA_CLAMP_VOLUME((vol * PA_VOLUME_NORM) / mixext->maxvalue); + } + break; + default: + return; + } +} + +static int +oss4_mixer_get_control_val (int fd, oss_mixext *mixext, int *val) +{ + oss_mixer_value ossval = { 0, }; + + /* ossval.dev = mixext->dev; */ + ossval.dev = -1; /* if -1 on entry then is ignored */ + /* + * The real way to pick a control on a mixer is with this number. + * Note that control numbers are uniq across all mixers. So dev + * can just be ignored. When dev is included it will only be used + * to check for correct dev from userland. But it will not be used + * to select a mixer. + */ + ossval.ctrl = mixext->ctrl; + ossval.timestamp = mixext->timestamp; + + if (ioctl (fd, SNDCTL_MIX_READ, &ossval) == -1) { + pa_log_debug ("SNDCTL_MIX_READ failed"); + *val = 0; + return -1; + } + + *val = ossval.value; + pa_log_debug ("got value 0x%08x from %s", *val, mixext->extname); + return 0; +} + +static int +oss4_mixer_set_control_val (int fd, oss_mixext *mixext, int val) +{ + oss_mixer_value ossval = { 0, }; + + /* ossval.dev = mixext->dev; */ + ossval.dev = -1; /* if -1 on entry then is ignored */ + /* + * The real way to pick a control on a mixer is with this number. + * Note that control numbers are uniq across all mixers. So dev + * can just be ignored. When dev is included it will only be used + * to check for correct dev from userland. But it will not be used + * to select a mixer. + */ + ossval.ctrl = mixext->ctrl; + ossval.timestamp = mixext->timestamp; + ossval.value = val; + + if (ioctl (fd, SNDCTL_MIX_WRITE, &ossval) == -1) { + pa_log_debug ("SNDCTL_MIX_WRITE failed"); + return -1; + } + + pa_log_debug ("set value 0x%08x on %s", val, mixext->extname); + return 0; +} + +int pa_oss_get_volume(int fd, oss_mixext *mixext, const pa_sample_spec *ss, pa_cvolume *volume) { + int v = 0; + + if (oss4_mixer_get_control_val (fd, mixext, &v) != 0) { + pa_log_debug ("Getting volume failed"); + return -1; + } + + pa_cvolume_reset(volume, ss->channels); + + oss4_mixer_slider_unpack_volume (mixext, v, volume); + + return 0; +} + +int pa_oss_set_volume(int fd, oss_mixext *mixext, const pa_sample_spec *ss, const pa_cvolume *volume) { + int val = 0; + + val = oss4_mixer_slider_pack_volume (mixext, ss->channels, volume); + + if (oss4_mixer_set_control_val (fd, mixext, val) != 0) { + pa_log_debug ("Setting volume failed"); + return -1; + } + + return 0; +} + +#else + int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) { char cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; unsigned vol; @@ -298,6 +480,7 @@ int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, con pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); return 0; } +#endif static int get_device_number(const char *dev) { const char *p; @@ -305,10 +488,16 @@ static int get_device_number(const char *dev) { char *rp = NULL; int r = -1; - if (!(p = rp = pa_readlink(dev))) { - if (errno != EINVAL && errno != ENOLINK) - return -2; - p = dev; + p = strchr(dev, '\0') - 1; + if (*p >= '0' && *p <= '9') { + p = dev; + } else { + /* We need to resolve the symlink */ + if (!(p = rp = pa_readlink(dev))) { + if (errno != EINVAL && errno != ENOLINK) + return -2; + p = dev; + } } /* find the last forward slash */ @@ -333,6 +522,10 @@ static int get_device_number(const char *dev) { return r; } +int pa_oss_get_device_number(const char *dev) { + return (get_device_number(dev)); +} + int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { FILE *f; int n, r = -1; @@ -407,15 +602,17 @@ int pa_oss_open_mixer_for_device(const char *device) { char *fn; int fd; - if ((n = get_device_number(device)) == -2) + if ((n = get_device_number(device)) == -2) { + pa_log_warn ("Cannot find number for device '%s'", device); return -1; + } if (n == -1) - if ((fd = open_mixer("/dev/mixer")) >= 0) + if ((fd = pa_oss_open_mixer("/dev/mixer")) >= 0) return fd; fn = pa_sprintf_malloc("/dev/mixer%i", n); - fd = open_mixer(fn); + fd = pa_oss_open_mixer(fn); pa_xfree(fn); if (fd < 0) diff --git a/src/modules/oss/oss-util.h b/src/modules/oss/oss-util.h index 8af27de..120fc5d 100644 --- a/src/modules/oss/oss-util.h +++ b/src/modules/oss/oss-util.h @@ -29,11 +29,22 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss); int pa_oss_set_fragments(int fd, int frags, int frag_size); +#define HAVE_OSSV4 + +#ifdef HAVE_OSSV4 +int pa_oss_set_volume(int fd, oss_mixext *mixext, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_get_volume(int fd, oss_mixext *mixext, const pa_sample_spec *ss, pa_cvolume *volume); + +#else + int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume); +#endif int pa_oss_get_hw_description(const char *dev, char *name, size_t l); +int pa_oss_get_device_number(const char *dev); int pa_oss_open_mixer_for_device(const char *device); +int pa_oss_open_mixer(const char *device); #endif