Audio control API, part 4: switch ports to sndio API

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Audio control API, part 4: switch ports to sndio API

Alexandre Ratchov-2
There are two ports only using the kernel-based API, this diff updates
them to use new libsndio-based API:
  - sysutils/tray-app
  - audio/gqmpeg

As a side effect, this fixes gqmpeg crashes, makes these programs use
the right device, and make them work on many uaudio(4) and envy(4)
devices with no hardware master level knob. Also when one program
changes the volume, slider of other programs are properly updated.

enjoy,

Index: audio/gqmpeg/Makefile
===================================================================
RCS file: /cvs/ports/audio/gqmpeg/Makefile,v
retrieving revision 1.64
diff -u -p -u -p -r1.64 Makefile
--- audio/gqmpeg/Makefile 12 Jul 2019 20:43:33 -0000 1.64
+++ audio/gqmpeg/Makefile 4 Feb 2020 22:28:34 -0000
@@ -3,7 +3,7 @@
 COMMENT= front-end to various audio players
 
 DISTNAME= gqmpeg-0.91.1
-REVISION= 14
+REVISION= 15
 CATEGORIES= audio
 
 HOMEPAGE= http://gqmpeg.sourceforge.net/
Index: audio/gqmpeg/patches/patch-src_Makefile_in
===================================================================
RCS file: audio/gqmpeg/patches/patch-src_Makefile_in
diff -N audio/gqmpeg/patches/patch-src_Makefile_in
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ audio/gqmpeg/patches/patch-src_Makefile_in 4 Feb 2020 22:28:34 -0000
@@ -0,0 +1,14 @@
+$OpenBSD$
+
+Index: src/Makefile.in
+--- src/Makefile.in.orig
++++ src/Makefile.in
+@@ -342,7 +342,7 @@ gqmpeg_SOURCES = \
+ $(module_mpg123) $(module_xmp) $(module_ogg123) $(module_radio)
+
+
+-gqmpeg_LDADD = $(GTK_LIBS) $(LIBPNG)
++gqmpeg_LDADD = $(GTK_LIBS) $(LIBPNG) -lsndio
+
+ EXTRA_DIST = \
+ $(extra_SLIK) \
Index: audio/gqmpeg/patches/patch-src_mixer_c
===================================================================
RCS file: /cvs/ports/audio/gqmpeg/patches/patch-src_mixer_c,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 patch-src_mixer_c
--- audio/gqmpeg/patches/patch-src_mixer_c 14 Oct 2007 14:12:42 -0000 1.2
+++ audio/gqmpeg/patches/patch-src_mixer_c 4 Feb 2020 22:28:34 -0000
@@ -1,39 +1,287 @@
 $OpenBSD: patch-src_mixer_c,v 1.2 2007/10/14 14:12:42 jasper Exp $
---- src/mixer.c.orig Tue Sep 10 16:16:26 2002
-+++ src/mixer.c Sun Oct 14 15:47:27 2007
-@@ -285,7 +285,11 @@ void mixer_init(gint init_device_id)
-
-   mixer_device = getenv("MIXERDEVICE");
-   if (mixer_device == NULL)
-+#ifdef __OpenBSD__
-+    mixer_device = "/dev/mixer";
-+#else
-     mixer_device = "/dev/mixer0";
-+#endif
+Index: src/mixer.c
+--- src/mixer.c.orig
++++ src/mixer.c
+@@ -39,10 +39,16 @@
+ #include <sys/soundcard.h>
+ #endif
 
-   if ((fd = open(mixer_device, O_RDWR)) == -1) {
-     perror(mixer_device);
-@@ -362,7 +366,11 @@ static void mixer_set_vol(DeviceData *device, gint vol
-
-   mixer_device = getenv("MIXERDEVICE");
-   if (mixer_device == NULL)
-+#ifdef __OpenBSD__
-+    mixer_device = "/dev/mixer";
-+#else
-     mixer_device = "/dev/mixer0";
-+#endif
+-#if defined(__NetBSD__) || defined(__OpenBSD__)
++#if defined(__NetBSD__)
+ #include <sys/audioio.h>
+ #endif
 
-   if ((fd = open(mixer_device, O_RDWR)) == -1) {
-     perror(mixer_device);
-@@ -406,7 +414,11 @@ static gint mixer_get_vol(DeviceData *device)
-
-   mixer_device = getenv("MIXERDEVICE");
-   if (mixer_device == NULL)
-+#ifdef __OpenBSD__
-+    mixer_device = "/dev/mixer";
-+#else
-     mixer_device = "/dev/mixer0";
++#if defined(__OpenBSD__)
++#include <poll.h>
++#include <sndio.h>
++#include "display.h"
 +#endif
++
+ #if defined(sun) && defined(__svr4__)
+ #include <sys/audioio.h>
+ #endif
+@@ -267,11 +273,11 @@ static gint mixer_get_vol(DeviceData *device)
+
+ /*
+  *--------------------------------------------------------------------
+- * NetBSD and OpenBSD
++ * NetBSD
+  *--------------------------------------------------------------------
+  */
+
+-#elif defined(__NetBSD__) || defined(__OpenBSD__)
++#elif defined(__NetBSD__)
+
+ mixer_devinfo_t *infos;
+ mixer_ctrl_t *values;
+@@ -442,6 +448,241 @@ static gint mixer_get_vol(DeviceData *device)
+
+ /*
+  *--------------------------------------------------------------------
++ * OpenBSD
++ *--------------------------------------------------------------------
++ */
++
++#elif defined(__OpenBSD__)
++
++struct control {
++ struct control *next;
++ unsigned int addr;
++ unsigned int value;
++};
++
++static struct control *controls;
++static struct sioctl_hdl *hdl;
++static struct pollfd *pfds;
++static int initialized;
++
++/*
++ * new control registered
++ */
++static void ondesc(void *unused, struct sioctl_desc *d, int val)
++{
++ struct control *i, **pi;
++
++ if (d == NULL)
++ return;
++
++ /*
++ * delete existing control with the same address
++ */
++ for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
++ if (d->addr == i->addr) {
++ *pi = i->next;
++ free(i);
++ break;
++ }
++ }
++
++ /*
++ * SIOCTL_NONE means control was deleted from the device
++ */
++ if (d->type == SIOCTL_NONE)
++ return;
++
++ /*
++ * we're interested in top-level output.level controls only
++ */
++ if (d->group[0] != 0 ||
++    strcmp(d->node0.name, "output") != 0 ||
++    strcmp(d->func, "level") != 0)
++ return;
++
++ i = malloc(sizeof(struct control));
++ if (i == NULL) {
++ perror("malloc");
++ return;
++ }
++
++ i->addr = d->addr;
++ i->value = val;
++ i->next = controls;
++ controls = i;
++
++ if (debug_mode)
++ fprintf(stderr, "found output.level at %d\n", i->addr);
++}
++
++/*
++ * control value changed
++ */
++static void onval(void *unused, unsigned int addr, unsigned int value)
++{
++ struct control *c;
++
++ if (debug_mode)
++ fprintf(stderr, "control %d changed to %d\n", addr, value);
++
++ for (c = controls; ; c = c->next) {
++ if (c == NULL)
++ return;
++ if (c->addr == addr)
++ break;
++ }
++
++ c->value = value;
++
++ if (debug_mode)
++ fprintf(stderr, "refreshing\n");
++ display_set_volume();
++}
++
++/*
++ * Call poll(2), for both gtk and sndio descriptors.
++ */
++int
++do_poll(GPollFD *gtk_pfds, guint gtk_nfds, gint timeout)
++{
++#define MAXFDS 64
++ struct pollfd pfds[MAXFDS], *sioctl_pfds;
++ unsigned int sioctl_nfds;
++ unsigned int i;
++ int revents;
++ int rc;
++
++ for (i = 0; i < gtk_nfds; i++) {
++ pfds[i].fd = gtk_pfds[i].fd;
++ pfds[i].events = gtk_pfds[i].events;
++ }
++ if (hdl != NULL) {
++ sioctl_pfds = pfds + gtk_nfds;
++ sioctl_nfds = sioctl_pollfd(hdl, sioctl_pfds, POLLIN);
++ } else
++ sioctl_nfds = 0;
++
++ rc = poll(pfds, gtk_nfds + sioctl_nfds, timeout);
++ if (rc > 0 && hdl != NULL) {
++ revents = sioctl_revents(hdl, sioctl_pfds);
++ if (revents & POLLHUP) {
++ fprintf(stderr, "Device disconnected\n");
++ sioctl_close(hdl);
++ hdl = NULL;
++ }
++ }
++
++ for (i = 0; i < gtk_nfds; i++)
++ gtk_pfds[i].revents = pfds[i].revents;
++
++ return rc;
++}
++
++void mixer_init(gint init_device_id)
++{
++ if (debug_mode)
++ fprintf(stderr, "mixer, initializing...\n");
++
++ if (initialized) {
++ fprintf(stderr, "mixer, already initialized\n");
++ return;
++ }
++
++ initialized = 1;
++
++ hdl = sioctl_open(SIOCTL_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
++ if (hdl == NULL) {
++ fprintf(stderr, "Cannot open audio control device\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
++ sioctl_close(hdl);
++ fprintf(stderr, "Cannot get mixer information\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++
++ /* register call-back for external volume changes */
++ if (!sioctl_onval(hdl, onval, NULL)) {
++ sioctl_close(hdl);
++ fprintf(stderr, "Cannot get mixer values\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++
++ pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
++ if (pfds == NULL) {
++ sioctl_close(hdl);
++ fprintf(stderr, "Cannot allocate pollfd structures\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++
++ if (controls != NULL) {
++ DeviceData *device = g_new0(DeviceData, 1);
++ device->device_id = 0;
++ device->device_name = "output.level";
++ device->stereo = (controls->next != NULL);
++ device->recordable = 0;
++ device_list = g_list_append(device_list, device);
++ current_device = device_list->data;
++ current_vol = mixer_get_vol(current_device);
++ mixer_enabled = TRUE;
++ } else
++ mixer_enabled = FALSE;
++
++ if (debug_mode)
++ fprintf(stderr, "setting gtk poll function\n");
++ g_main_context_set_poll_func(g_main_context_default(), do_poll);
++
++}
++
++static void mixer_poll(void)
++{
++ int n, nfds;
++
++ nfds = sioctl_pollfd(hdl, pfds, 0);
++ if (nfds > 0) {
++ n = poll(pfds, nfds, 0);
++ if (n >= 0)
++ sioctl_revents(hdl, pfds);
++ }
++}
++
++static void mixer_set_vol(DeviceData *device, gint vol)
++{
++ struct control *c;
++
++ if (hdl == NULL)
++ return;
++
++ for (c = controls; c != NULL; c = c->next) {
++ sioctl_setval(hdl, c->addr, (vol * SIOCTL_VALMAX + 50) / 100);
++ if (debug_mode)
++ fprintf(stderr, "setting %d to %d%%\n", c->addr, vol);
++ }
++}
++
++static gint mixer_get_vol(DeviceData *device)
++{
++ struct control *c;
++ int vol, minvol = 100;
++
++ for (c = controls; c != NULL; c = c->next) {
++ vol = (c->value * 100 + SIOCTL_VALMAX / 2) / SIOCTL_VALMAX;
++ if (vol < minvol)
++ minvol = vol;
++ }
++
++ if (debug_mode)
++ fprintf(stderr, "get volume: %d\n", minvol);
++
++ return minvol;
++}
++
++/*
++ *--------------------------------------------------------------------
+  * Sun (svr4)
+  *--------------------------------------------------------------------
+  */
+@@ -1266,7 +1507,7 @@ gint get_volume(void)
+  * but some platforms did not have it update the volume (mixer_get_vol),
+  * and I am not going to mess with it.
+  */
+-#if defined (linux) || defined (__FreeBSD__)
++#if defined (linux) || defined (__FreeBSD__) || defined (__OpenBSD__)
+ current_vol = mixer_get_vol(current_device);
+ #endif
 
-   if ((fd = open(mixer_device, O_RDWR)) == -1) {
-     perror(mixer_device);
Index: sysutils/tray-app/Makefile
===================================================================
RCS file: /cvs/ports/sysutils/tray-app/Makefile,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 Makefile
--- sysutils/tray-app/Makefile 12 Jul 2019 20:49:53 -0000 1.8
+++ sysutils/tray-app/Makefile 4 Feb 2020 22:28:34 -0000
@@ -5,7 +5,7 @@ ONLY_FOR_ARCHS= ${APM_ARCHS}
 COMMENT= small utilities for X11 system tray: eject, battery, mixer
 
 DISTNAME= tray-app-0.3.1
-REVISION= 0
+REVISION= 1
 
 CATEGORIES= sysutils x11
 
Index: sysutils/tray-app/patches/patch-sound_Makefile
===================================================================
RCS file: /cvs/ports/sysutils/tray-app/patches/patch-sound_Makefile,v
retrieving revision 1.1.1.1
diff -u -p -u -p -r1.1.1.1 patch-sound_Makefile
--- sysutils/tray-app/patches/patch-sound_Makefile 17 Sep 2013 11:21:50 -0000 1.1.1.1
+++ sysutils/tray-app/patches/patch-sound_Makefile 4 Feb 2020 22:28:34 -0000
@@ -1,6 +1,7 @@
 $OpenBSD: patch-sound_Makefile,v 1.1.1.1 2013/09/17 11:21:50 sthen Exp $
---- sound/Makefile.orig Mon Mar 12 08:46:04 2012
-+++ sound/Makefile Tue Sep 17 11:39:18 2013
+Index: sound/Makefile
+--- sound/Makefile.orig
++++ sound/Makefile
 @@ -8,13 +8,13 @@ MAN=
 
  gtk_CFLAGS!= pkg-config --cflags gtk+-2.0
@@ -8,7 +9,8 @@ $OpenBSD: patch-sound_Makefile,v 1.1.1.1
 -CFLAGS= -W -Wall -g -O0 -I../lib $(gtk_CFLAGS)
 +CFLAGS+= -W -Wall -I../lib $(gtk_CFLAGS)
  LDFLAGS= -L../lib $(gtk_LDFLAGS)
- LDADD= -ltrayapp
+-LDADD= -ltrayapp
++LDADD= -ltrayapp -lsndio
 
 -BINDIR=/usr/local/libexec/tray-app
 +BINDIR=${TRUEPREFIX}/libexec/tray-app
Index: sysutils/tray-app/patches/patch-sound_sound_c
===================================================================
RCS file: sysutils/tray-app/patches/patch-sound_sound_c
diff -N sysutils/tray-app/patches/patch-sound_sound_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ sysutils/tray-app/patches/patch-sound_sound_c 4 Feb 2020 22:28:34 -0000
@@ -0,0 +1,511 @@
+$OpenBSD$
+
+Index: sound/sound.c
+--- sound/sound.c.orig
++++ sound/sound.c
+@@ -4,12 +4,13 @@
+  */
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+-#include <sys/audioio.h>
++#include <sndio.h>
+
+ #include <gtk/gtk.h>
+
+ #include <err.h>
+ #include <fcntl.h>
++#include <poll.h>
+ #include <stdlib.h>
+ #include <string.h>
+
+@@ -19,16 +20,19 @@
+ #include "sound-1.xpm"
+ #include "sound-2.xpm"
+
++struct control {
++ struct control *next;
++ unsigned int addr;
++ unsigned int value;
++ int ismute;
++};
++
+ static void usage(const char *prog);
+-static int get_mixer_index(int fd, mixer_devinfo_t *devinfo);
+-static int get_volume(int fd, mixer_devinfo_t *devinfo, u_char *volume);
+-static int set_volume(int fd, mixer_devinfo_t *devinfo, u_char volume);
+-static int get_mute(int fd, mixer_devinfo_t *devinfo, int *mute);
+-static int set_mute(int fd, mixer_devinfo_t *devinfo, int mute);
++static void set_state(int ismute, int mute);
++static void get_state(int *rvolume, int *rmute);
+
+ static void prepare_tooltip(int mute, u_char volume, char *text, size_t sz);
+
+-static gboolean cb_timer(GtkWidget *widget);
+ static void cb_button_toggled(GtkWidget *widget, gpointer data);
+ static void cb_scale_value_changed(GtkScale *scale, GtkAdjustment *adj);
+ static gboolean cb_window_delete(GtkWidget *widget,GdkEvent *event,
+@@ -48,8 +52,8 @@ static void hide_gui(void);
+ static int init_tray(int doinvert);
+ static void set_tray_icon(u_char volume);
+
+-static int mixer_fd;
+-static mixer_devinfo_t outputs_master_dev[2]; /* volume, mute */
++static struct sioctl_hdl *hdl;
++static struct control    *controls;
+
+ static GtkWidget *gui_window = NULL;
+ static GtkObject *gui_adj = NULL;
+@@ -63,9 +67,6 @@ static GdkPixbuf *tray_pixbuf_0 = NULL;
+ static GdkPixbuf *tray_pixbuf_1 = NULL;
+ static GdkPixbuf *tray_pixbuf_2 = NULL;
+
+-static u_char current_volume = 0;
+-static int current_mute = 0;
+-
+ void
+ usage(const char *prog)
+ {
+@@ -76,104 +77,132 @@ usage(const char *prog)
+    prog);
+ }
+
+-static int
+-get_mixer_index(int fd, mixer_devinfo_t *devinfo)
++/*
++ * new control registered
++ */
++static void
++cb_control_desc(void *unused, struct sioctl_desc *d, int val)
+ {
+- int error;
+- int i, outputs_idx;
++ struct control *i, **pi;
++ int ismute;
+
+- i = 0;
+- outputs_idx = -1;
+- devinfo[0].index = 0;
+- for (;;) {
+- error = ioctl(fd, AUDIO_MIXER_DEVINFO, devinfo + i);
+- if (error == -1)
++ if (d == NULL)
++ return;
++
++ /*
++ * delete existing control with the same address
++ */
++ for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
++ if (d->addr == i->addr) {
++ *pi = i->next;
++ free(i);
+ break;
++ }
++ }
+
+- if (i == 0 && devinfo[0].type == AUDIO_MIXER_CLASS &&
+-    strcmp(devinfo[0].label.name, "outputs") == 0)
+- outputs_idx = devinfo[0].index;
+- else if (i == 0 && devinfo[0].type == AUDIO_MIXER_VALUE &&
+-    strcmp(devinfo[0].label.name, "master") == 0 &&
+-    outputs_idx != -1 &&
+-    devinfo[0].mixer_class == outputs_idx) {
+- devinfo[1].index = devinfo[0].index;
+- i++;
+- } else if (i == 1 && devinfo[1].prev == devinfo[0].index &&
+-    devinfo[1].type == AUDIO_MIXER_ENUM &&
+-    strcmp(devinfo[1].label.name, "mute") == 0 &&
+-    devinfo[1].mixer_class == outputs_idx)
+- return (0);
++ /*
++ * SIOCTL_NONE means control was deleted from the device
++ */
++ if (d->type == SIOCTL_NONE)
++ return;
+
+- devinfo[i].index++;
+- }
+- return (-1);
+-}
++ /*
++ * we're interested in top-level output.xxx controls only
++ */
++ if (d->group[0] != 0 || strcmp(d->node0.name, "output") != 0)
++ return;
+
+-static int
+-get_volume(int fd, mixer_devinfo_t *devinfo, u_char *volume)
+-{
+- mixer_ctrl_t mctl;
+- int error;
++ if (strcmp(d->func, "level") == 0)
++ ismute = 0;
++ else if (strcmp(d->func, "mute") == 0)
++ ismute = 1;
++ else
++ return;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = AUDIO_MIXER_VALUE;
+- error = ioctl(fd, AUDIO_MIXER_READ, &mctl);
+- if (error == -1)
+- return (-1);
+- *volume = mctl.un.value.level[0];
+- return (0);
++ i = malloc(sizeof(struct control));
++ if (i == NULL)
++ err(1, "malloc");
++
++ i->addr = d->addr;
++ i->value = val;
++ i->ismute = ismute;
++ i->next = controls;
++ controls = i;
+ }
+
+-static int
+-set_volume(int fd, mixer_devinfo_t *devinfo, u_char volume)
++/*
++ * control value changed
++ */
++static void
++cb_control_value(void *unused, unsigned int addr, unsigned int value)
+ {
+- mixer_ctrl_t mctl;
+- int i, error;
++ GtkWidget *widget = (gpointer)gui_window;
++ struct control *c;
++ int volume, mute;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = devinfo->type;
+- mctl.un.value.num_channels = devinfo->un.v.num_channels;
+- for (i = 0; i < devinfo->un.v.num_channels; i++)
+- mctl.un.value.level[i] = volume;
+- error = ioctl(fd, AUDIO_MIXER_WRITE, &mctl);
+- if (error == -1)
+- return (-1);
+- return (0);
++ for (c = controls; ; c = c->next) {
++ if (c == NULL)
++ return;
++ if (c->addr == addr)
++ break;
++ }
++
++ c->value = value;
++
++ /*
++ * refresh gui state
++ */
++
++ get_state(&volume, &mute);
++
++ set_tray_icon(mute ? 0 : volume);
++
++ /* Move slider. Change "check" button state. */
++ if (widget->window != NULL) {
++ gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
++    SIOCTL_VALMAX - volume);
++ gtk_toggle_button_set_active(
++    GTK_TOGGLE_BUTTON(gui_check), mute);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
++ }
+ }
+
+-static int
+-get_mute(int fd, mixer_devinfo_t *devinfo, int *mute)
++/*
++ * Return current volume and mute states:
++ *   - the returned volume is the maximum volume of all channels.
++ *   - the returned mute set 1 if all channels are muted
++ */
++static void
++get_state(int *rvolume, int *rmute)
+ {
+- mixer_ctrl_t mctl;
+- int error;
+-
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = AUDIO_MIXER_ENUM;
+- error = ioctl(fd, AUDIO_MIXER_READ, &mctl);
+- if (error == -1)
+- return (-1);
+- *mute = mctl.un.ord;
+- return (0);
++ struct control *c;
++ int mute = 0;
++ int volume = SIOCTL_VALMAX;
++
++ for (c = controls; c != NULL; c = c->next) {
++ if (c->ismute) {
++ if (c->value)
++ mute = 1;
++ } else {
++ if (c->value < volume)
++ volume = c->value;
++ }
++ }
++ *rvolume = volume;
++ *rmute = mute;
+ }
+
+-static int
+-set_mute(int fd, mixer_devinfo_t *devinfo, int mute)
++static void
++set_state(int ismute, int value)
+ {
+- mixer_ctrl_t mctl;
+- int error;
++ struct control *c;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = devinfo->type;
+- mctl.un.ord = mute;
+- error = ioctl(fd, AUDIO_MIXER_WRITE, &mctl);
+- if (error == -1)
+- return (-1);
+- return (0);
++ for (c = controls; c != NULL; c = c->next) {
++ if (c->ismute != ismute || c->value == value)
++ continue;
++ c->value = value;
++ sioctl_setval(hdl, c->addr, value);
++ }
+ }
+
+ static void
+@@ -183,62 +212,31 @@ prepare_tooltip(int mute, u_char volume, char *text, s
+ strlcpy(text, "Audio is muted", sz);
+ } else {
+ snprintf(text, sz, "Audio volume: %u%%",
+-    100U * (u_int)volume / AUDIO_MAX_GAIN);
++    100U * (u_int)volume / SIOCTL_VALMAX);
+ }
+ }
+
+-static gboolean
+-cb_timer(GtkWidget *widget)
+-{
+- int mute;
+- u_char volume;
+-
+- if (get_mute(mixer_fd, outputs_master_dev + 1, &mute) == -1)
+- return (TRUE);
+- else if (get_volume(mixer_fd, outputs_master_dev, &volume) == -1)
+- return (TRUE);
+- if (mute != current_mute || volume != current_volume) {
+- set_tray_icon(mute ? 0 : volume);
+-
+- /* Move slider. Change "check" button state. */
+- if (widget->window != NULL) {
+- gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
+-    AUDIO_MAX_GAIN - volume);
+- gtk_toggle_button_set_active(
+-    GTK_TOGGLE_BUTTON(gui_check), mute);
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+- }
+-
+- current_volume = volume;
+- current_mute = mute;
+- }
+-
+- return (TRUE);
+-}
+-
+ static void
+ cb_button_toggled(GtkWidget *widget, gpointer data)
+ {
+- int mute;
++ int mute, volume;
+
+ mute = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+- if (set_mute(mixer_fd, outputs_master_dev + 1, mute) == 0) {
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+- set_tray_icon(mute ? 0 : current_volume);
+- current_mute = mute;
+- }
++ set_state(1, mute);
++ get_state(&volume, &mute);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
++ set_tray_icon(mute ? 0 : volume);
+ }
+
+ static void
+ cb_scale_value_changed(GtkScale *scale, GtkAdjustment *adj)
+ {
+- u_char volume;
++ int mute, volume;
+
+- volume = AUDIO_MAX_GAIN - (u_char)gtk_adjustment_get_value(adj);
+- if (set_volume(mixer_fd, outputs_master_dev, volume) == 0) {
+- set_tray_icon(current_mute ? 0 : volume);
+- current_volume = volume;
+- }
++ volume = SIOCTL_VALMAX - (int)gtk_adjustment_get_value(adj);
++ set_state(0, volume);
++ get_state(&volume, &mute);
++ set_tray_icon(mute ? 0 : volume);
+ }
+
+ static gboolean
+@@ -277,8 +275,10 @@ cb_tray_query_tooltip(GtkStatusIcon *icon, gint x, gin
+     gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data)
+ {
+ char text[30];
++ int volume, mute;
+
+- prepare_tooltip(current_mute, current_volume, text, sizeof(text));
++ get_state(&volume, &mute);
++ prepare_tooltip(mute, volume, text, sizeof(text));
+ gtk_tooltip_set_text(tooltip, text);
+ return (TRUE);
+ }
+@@ -304,8 +304,8 @@ init_gui(void)
+ gtk_window_set_deletable(GTK_WINDOW(gui_window), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(gui_window), FALSE);
+
+- gui_adj = gtk_adjustment_new(0.0, AUDIO_MIN_GAIN,
+-    AUDIO_MAX_GAIN, 1.0, 10.0, 0.0);
++ gui_adj = gtk_adjustment_new(0.0, 0,
++    SIOCTL_VALMAX, 1.0, 10.0, 0.0);
+ if (gui_adj == NULL)
+ return (-1);
+
+@@ -355,12 +355,15 @@ show_gui(void)
+ GdkRectangle area;
+ GtkOrientation orientation;
+ int width, height, x, y;
++ int volume, mute;
+
++ get_state(&volume, &mute);
++
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
+-    AUDIO_MAX_GAIN - current_volume);
++    SIOCTL_VALMAX - volume);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui_check),
+-    current_mute);
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !current_mute);
++    mute);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+ gtk_widget_show_all(GTK_WIDGET(gui_window));
+
+ gtk_status_icon_get_geometry(tray_icon, NULL, &area, &orientation);
+@@ -395,8 +398,7 @@ static int
+ init_tray(int doinvert)
+ {
+ char tooltip[30];
+- u_char volume;
+- int mute;
++ int volume, mute;
+
+ tray_pixbuf = gdk_pixbuf_new_from_xpm_data(sound_xpm);
+ if (tray_pixbuf == NULL)
+@@ -421,13 +423,9 @@ init_tray(int doinvert)
+ tray_icon = gtk_status_icon_new();
+ if (tray_icon == NULL)
+ return (-1);
+- volume = 0;
+- mute = 0;
+- get_volume(mixer_fd, outputs_master_dev, &volume);
+- get_mute(mixer_fd, outputs_master_dev + 1, &mute);
++
++ get_state(&volume, &mute);
+ set_tray_icon(mute ? 0 : volume);
+- current_volume = volume;
+- current_mute = mute;
+
+ prepare_tooltip(mute, volume, tooltip, sizeof(tooltip));
+ gtk_status_icon_set_tooltip_text(tray_icon, tooltip);
+@@ -455,7 +453,43 @@ set_tray_icon(u_char volume)
+ gtk_status_icon_set_from_pixbuf(tray_icon, pb);
+ }
+
++/*
++ * Call poll(2), for both gtk and sndio descriptors.
++ */
+ int
++do_poll(GPollFD *gtk_pfds, guint gtk_nfds, gint timeout)
++{
++#define MAXFDS 64
++ struct pollfd pfds[MAXFDS], *sioctl_pfds;
++ unsigned int sioctl_nfds;
++ unsigned int i;
++ int revents;
++ int rc;
++
++ for (i = 0; i < gtk_nfds; i++) {
++ pfds[i].fd = gtk_pfds[i].fd;
++ pfds[i].events = gtk_pfds[i].events;
++ }
++ if (hdl != NULL) {
++ sioctl_pfds = pfds + gtk_nfds;
++ sioctl_nfds = sioctl_pollfd(hdl, sioctl_pfds, POLLIN);
++ } else
++ sioctl_nfds = 0;
++
++ rc = poll(pfds, gtk_nfds + sioctl_nfds, timeout);
++ if (rc > 0 && hdl != NULL) {
++ revents = sioctl_revents(hdl, sioctl_pfds);
++ if (revents & POLLHUP)
++ errx(1, "Device disconnected\n");
++ }
++
++ for (i = 0; i < gtk_nfds; i++)
++ gtk_pfds[i].revents = pfds[i].revents;
++
++ return rc;
++}
++
++int
+ main(int argc, char **argv)
+ {
+ char *progname;
+@@ -481,30 +515,32 @@ main(int argc, char **argv)
+ }
+ argc -= optind;
+ argv += optind;
+-
+- mixer_fd = open("/dev/mixer", O_RDWR);
+- if (mixer_fd == -1)
++
++ hdl = sioctl_open(SIOCTL_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
++ if (hdl == NULL) {
+ errx(1, "Cannot open mixer device");
+-
+- error = get_mixer_index(mixer_fd, outputs_master_dev);
+- if (error == -1) {
+- close(mixer_fd);
++ }
++ if (!sioctl_ondesc(hdl, cb_control_desc, NULL)) {
++ sioctl_close(hdl);
+ errx(1, "Cannot get mixer information");
+ }
+-
+ error = init_tray(invert_flag);
+ if (error == -1) {
+- close(mixer_fd);
++ sioctl_close(hdl);
+ errx(1, "Cannot initialize notification area");
+ }
+ error = init_gui();
+ if (error == -1) {
+- close(mixer_fd);
++ sioctl_close(hdl);
+ errx(1, "Cannot initialize program window");
+ }
+- g_timeout_add(1000, (GSourceFunc)cb_timer, (gpointer)gui_window);
++
++ /* register call-back for external volume changes */
++ sioctl_onval(hdl, cb_control_value, NULL);
++
++ g_main_context_set_poll_func(g_main_context_default(), do_poll);
+ gtk_main();
+
+- close(mixer_fd);
++ sioctl_close(hdl);
+ return (0);
+ }

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 4: switch ports to sndio API

Alexandre Ratchov-2
On Sun, Feb 09, 2020 at 01:27:27PM +0100, Alexandre Ratchov wrote:

> There are two ports only using the kernel-based API, this diff updates
> them to use new libsndio-based API:
>   - sysutils/tray-app
>   - audio/gqmpeg
>
> As a side effect, this fixes gqmpeg crashes, makes these programs use
> the right device, and make them work on many uaudio(4) and envy(4)
> devices with no hardware master level knob. Also when one program
> changes the volume, slider of other programs are properly updated.
>

I forgot x11/i3status; below is a diff to make it use sndio.

Index: Makefile
===================================================================
RCS file: /cvs/ports/x11/i3status/Makefile,v
retrieving revision 1.58
diff -u -p -r1.58 Makefile
--- Makefile 27 Sep 2019 20:33:22 -0000 1.58
+++ Makefile 12 Feb 2020 13:55:43 -0000
@@ -5,7 +5,7 @@ ONLY_FOR_ARCHS= ${APM_ARCHS}
 COMMENT= generate a statusbar for use with i3/xmobar/dzen2
 
 DISTNAME= i3status-2.13
-REVISION= 1
+REVISION= 2
 CATEGORIES= x11 sysutils
 
 HOMEPAGE= https://i3wm.org/i3status/
@@ -28,7 +28,9 @@ BUILD_DEPENDS= textproc/asciidoc>=8.6.8
 LIB_DEPENDS= devel/libconfuse \
  devel/libyajl
 
-CONFIGURE_STYLE = gnu
+AUTOCONF_VERSION = 2.69
+AUTOMAKE_VERSION = 1.16
+CONFIGURE_STYLE = autoreconf
 SEPARATE_BUILD = Yes
 
 FAKE_FLAGS += sysconfdir=${PREFIX}/share/examples/i3status/
Index: patches/patch-Makefile_am
===================================================================
RCS file: patches/patch-Makefile_am
diff -N patches/patch-Makefile_am
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-Makefile_am 12 Feb 2020 13:55:43 -0000
@@ -0,0 +1,37 @@
+$OpenBSD$
+
+Index: Makefile.am
+--- Makefile.am.orig
++++ Makefile.am
+@@ -1,5 +1,3 @@
+-@CODE_COVERAGE_RULES@
+-
+ echo-version:
+ @echo "@I3STATUS_VERSION@"
+
+@@ -30,6 +28,7 @@ i3status_CFLAGS = \
+ $(PULSE_CFLAGS) \
+ $(NLGENL_CFLAGS) \
+ $(ALSA_CFLAGS) \
++ $(SNDIO_CFLAGS) \
+ $(PTHREAD_CFLAGS)
+
+ i3status_CPPFLAGS = \
+@@ -42,6 +41,7 @@ i3status_LDADD = \
+ $(PULSE_LIBS) \
+ $(NLGENL_LIBS) \
+ $(ALSA_LIBS) \
++ $(SNDIO_LIBS) \
+ $(PTHREAD_LIBS)
+
+ i3status_SOURCES = \
+@@ -69,7 +69,8 @@ i3status_SOURCES = \
+ src/print_wireless_info.c \
+ src/print_file_contents.c \
+ src/process_runs.c \
+- src/pulse.c
++ src/pulse.c \
++ src/sndio.c
+
+ dist_sysconf_DATA = \
+ i3status.conf
Index: patches/patch-Makefile_in
===================================================================
RCS file: patches/patch-Makefile_in
diff -N patches/patch-Makefile_in
--- patches/patch-Makefile_in 6 Jul 2019 20:20:27 -0000 1.1
+++ /dev/null 1 Jan 1970 00:00:00 -0000
@@ -1,15 +0,0 @@
-$OpenBSD: patch-Makefile_in,v 1.1 2019/07/06 20:20:27 jasper Exp $
-
-The CODE_COVERAGE_RULES fragment contains an unmatched "if" clause.
-
-Index: Makefile.in
---- Makefile.in.orig
-+++ Makefile.in
-@@ -1851,7 +1851,6 @@ uninstall-man: uninstall-man1
-
- .PRECIOUS: Makefile
-
--@CODE_COVERAGE_RULES@
-
- echo-version:
- @echo "@I3STATUS_VERSION@"
Index: patches/patch-configure_ac
===================================================================
RCS file: patches/patch-configure_ac
diff -N patches/patch-configure_ac
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-configure_ac 12 Feb 2020 13:55:43 -0000
@@ -0,0 +1,19 @@
+$OpenBSD$
+
+Index: configure.ac
+--- configure.ac.orig
++++ configure.ac
+@@ -91,6 +91,13 @@ case $host_os in
+ ;;
+ esac
+
++# if sndio is available, define USE_SNDIO
++AC_CHECK_HEADER(sndio.h,
++ [AC_CHECK_LIB([sndio], [sio_open], [
++ AC_SUBST(SNDIO_LIBS, "-lsndio")
++ AC_DEFINE([USE_SNDIO], [], [Use sndio])
++ ], [])], [])
++
+ dnl TODO: check for libbsd for GNU/kFreeBSD
+
+ # Checks for programs.
Index: patches/patch-include_i3status_h
===================================================================
RCS file: patches/patch-include_i3status_h
diff -N patches/patch-include_i3status_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-include_i3status_h 12 Feb 2020 13:55:43 -0000
@@ -0,0 +1,13 @@
+$OpenBSD$
+
+Index: include/i3status.h
+--- include/i3status.h.orig
++++ include/i3status.h
+@@ -232,6 +232,7 @@ int volume_pulseaudio(uint32_t sink_idx, const char *s
+ bool description_pulseaudio(uint32_t sink_idx, const char *sink_name, char buffer[MAX_SINK_DESCRIPTION_LEN]);
+ bool pulse_initialize(void);
+ void print_file_contents(yajl_gen json_gen, char *buffer, const char *title, const char *path, const char *format, const char *format_bad, const int max_chars);
++int volume_sndio(void);
+
+ /* socket file descriptor for general purposes */
+ extern int general_socket;
Index: patches/patch-src_print_volume_c
===================================================================
RCS file: patches/patch-src_print_volume_c
diff -N patches/patch-src_print_volume_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-src_print_volume_c 12 Feb 2020 13:55:43 -0000
@@ -0,0 +1,144 @@
+$OpenBSD$
+
+Index: src/print_volume.c
+--- src/print_volume.c.orig
++++ src/print_volume.c
+@@ -21,13 +21,6 @@
+ #include <sys/soundcard.h>
+ #endif
+
+-#ifdef __OpenBSD__
+-#include <fcntl.h>
+-#include <unistd.h>
+-#include <sys/audioio.h>
+-#include <sys/ioctl.h>
+-#endif
+-
+ #include "i3status.h"
+ #include "queue.h"
+
+@@ -145,9 +138,20 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+         /* negative result or NULL description means error, fail PulseAudio attempt */
+     }
+ /* If some other device was specified or PulseAudio is not detected,
+- * proceed to ALSA / OSS */
++ * proceed to sndio / ALSA / OSS */
+ #endif
+
++#ifdef USE_SNDIO
++    int vol;
++    
++ vol = volume_sndio();
++ if (vol != -1) {
++        outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f, "sndio");
++        goto out;
++    }
++/* If sndio is not detected, proceed to ALSA / OSS */
++#endif
++
+ #ifdef __linux__
+     const long MAX_LINEAR_DB_SCALE = 24;
+     int err;
+@@ -248,7 +252,8 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+     snd_mixer_selem_id_free(sid);
+
+ #endif
+-#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
++
++#if defined(__FreeBSD__) || defined(__DragonFly__)
+     char *mixerpath;
+     char defaultmixer[] = "/dev/mixer";
+     int mixfd, vol, devmask = 0;
+@@ -261,84 +266,13 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+         mixerpath = defaultmixer;
+
+     if ((mixfd = open(mixerpath, O_RDWR)) < 0) {
+-#if defined(__OpenBSD__)
+-        warn("audioio: Cannot open mixer");
+-#else
+         warn("OSS: Cannot open mixer");
+-#endif
+         goto out;
+     }
+
+     if (mixer_idx > 0)
+         free(mixerpath);
+
+-#if defined(__OpenBSD__)
+-    int oclass_idx = -1, master_idx = -1, master_mute_idx = -1;
+-    int master_next = AUDIO_MIXER_LAST;
+-    mixer_devinfo_t devinfo, devinfo2;
+-    mixer_ctrl_t vinfo;
+-
+-    devinfo.index = 0;
+-    while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo) >= 0) {
+-        if (devinfo.type != AUDIO_MIXER_CLASS) {
+-            devinfo.index++;
+-            continue;
+-        }
+-        if (strncmp(devinfo.label.name, AudioCoutputs, MAX_AUDIO_DEV_LEN) == 0)
+-            oclass_idx = devinfo.index;
+-
+-        devinfo.index++;
+-    }
+-
+-    devinfo2.index = 0;
+-    while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo2) >= 0) {
+-        if ((devinfo2.type == AUDIO_MIXER_VALUE) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmaster, MAX_AUDIO_DEV_LEN) == 0)) {
+-            master_idx = devinfo2.index;
+-            master_next = devinfo2.next;
+-        }
+-
+-        if ((devinfo2.type == AUDIO_MIXER_ENUM) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmute, MAX_AUDIO_DEV_LEN) == 0))
+-            if (master_next == devinfo2.index)
+-                master_mute_idx = devinfo2.index;
+-
+-        if (master_next != AUDIO_MIXER_LAST)
+-            master_next = devinfo2.next;
+-        devinfo2.index++;
+-    }
+-
+-    if (master_idx == -1)
+-        goto out;
+-
+-    devinfo.index = master_idx;
+-    if (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo) == -1)
+-        goto out;
+-
+-    vinfo.dev = master_idx;
+-    vinfo.type = AUDIO_MIXER_VALUE;
+-    vinfo.un.value.num_channels = devinfo.un.v.num_channels;
+-    if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1)
+-        goto out;
+-
+-    if (AUDIO_MAX_GAIN != 100) {
+-        float avgf = ((float)vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO] / AUDIO_MAX_GAIN) * 100;
+-        vol = (int)avgf;
+-        vol = (avgf - vol < 0.5 ? vol : (vol + 1));
+-    } else {
+-        vol = (int)vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO];
+-    }
+-
+-    vinfo.dev = master_mute_idx;
+-    vinfo.type = AUDIO_MIXER_ENUM;
+-    if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1)
+-        goto out;
+-
+-    if (master_mute_idx != -1 && vinfo.un.ord) {
+-        START_COLOR("color_degraded");
+-        fmt = fmt_muted;
+-        pbval = 0;
+-    }
+-
+-#else
+     if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
+         warn("OSS: Cannot read mixer information");
+         goto out;
+@@ -353,7 +287,6 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+         pbval = 0;
+     }
+
+-#endif
+     outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f, devicename);
+     close(mixfd);
+ #endif
Index: patches/patch-src_sndio_c
===================================================================
RCS file: patches/patch-src_sndio_c
diff -N patches/patch-src_sndio_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-src_sndio_c 12 Feb 2020 13:55:43 -0000
@@ -0,0 +1,173 @@
+$OpenBSD$
+
+Add sndio volume backend.
+
+Index: src/sndio.c
+--- src/sndio.c.orig
++++ src/sndio.c
+@@ -0,0 +1,165 @@
++#include <poll.h>
++#include <sndio.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include "i3status.h"
++
++struct control {
++    struct control *next;
++    unsigned int addr;
++    unsigned int value;
++};
++
++static int initialized;
++static struct sioctl_hdl *hdl;
++static struct control *controls;
++static struct pollfd *pfds;
++
++/*
++ * new control registered or control changed
++ */
++static void ondesc(void *unused, struct sioctl_desc *d, int val)
++{
++    struct control *i, **pi;
++
++    if (d == NULL)
++        return;
++
++    /*
++     * delete existing control with the same address
++     */
++    for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
++        if (d->addr == i->addr) {
++            *pi = i->next;
++            free(i);
++            break;
++        }
++    }
++
++    /*
++     * we're interested in top-level output.level controls only
++     */
++    if (d->type != SIOCTL_NUM ||
++        d->group[0] != 0 ||
++        strcmp(d->node0.name, "output") != 0 ||
++        strcmp(d->func, "level") != 0)
++        return;
++
++    i = malloc(sizeof(struct control));
++    if (i == NULL) {
++        fprintf(stderr, "sndio: failed to allocate control\n");
++        return;
++    }
++
++    i->addr = d->addr;
++    i->value = val;
++    i->next = controls;
++    controls = i;
++}
++
++/*
++ * control value changed
++ */
++static void onval(void *unused, unsigned int addr, unsigned int value)
++{
++    struct control *c;
++
++    for (c = controls; ; c = c->next) {
++        if (c == NULL)
++            return;
++        if (c->addr == addr)
++            break;
++    }
++
++    c->value = value;
++}
++
++static void cleanup(void)
++{
++    struct control *c;
++
++    if (hdl) {
++        sioctl_close(hdl);
++        hdl = NULL;
++    }
++    if (pfds) {
++        free(pfds);
++        pfds = NULL;
++    }
++    while ((c = controls) != NULL) {
++        controls = c->next;
++        free(c);
++    }
++}
++
++static int init(void)
++{
++    /* open device */
++    hdl = sioctl_open(SIOCTL_DEVANY, SIOCTL_READ, 0);
++    if (hdl == NULL) {
++        fprintf(stderr, "sndio: cannot open device\n");
++        goto failed;
++    }
++
++    /* register call-back for control description changes */
++    if (!sioctl_ondesc(hdl, ondesc, NULL)) {
++        fprintf(stderr, "sndio: cannot get description\n");
++        goto failed;
++    }
++
++    /* register call-back for volume changes */
++    if (!sioctl_onval(hdl, onval, NULL)) {
++        fprintf(stderr, "sndio: cannot get values\n");
++        goto failed;
++    }
++
++    /* allocate structures for poll() syscall */
++    pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
++    if (pfds == NULL) {
++        fprintf(stderr, "sndio: cannot allocate pollfd structures\n");
++        goto failed;
++    }
++    return 1;
++failed:
++    cleanup();
++    return 0;
++}
++
++int volume_sndio(void)
++{
++    struct control *c;
++    int n, value;
++
++    if (!initialized) {
++        initialized = 1;
++        init();
++    }
++    if (hdl == NULL)
++        return -1;
++
++    /* check if controls changed */
++    n = sioctl_pollfd(hdl, pfds, POLLIN);
++    if (n > 0) {
++        n = poll(pfds, n, 0);
++        if (n > 0) {
++            if (sioctl_revents(hdl, pfds) & POLLHUP) {
++                fprintf(stderr, "sndio: disconnected\n");
++                cleanup();
++                return -1;
++            }
++        }
++    }
++
++    /*
++     * get control value: as there may be multiple
++     * channels, return the minimum
++     */
++    value = SIOCTL_VALMAX;
++    for (c = controls; c != NULL; c = c->next) {
++        if (c->value < value)
++            value = c->value;
++    }
++
++    return (value * 100 + SIOCTL_VALMAX / 2) / SIOCTL_VALMAX;
++}