mix_ctl.c example

This is a sample application that captures the groups and switches in the mixer.

For information about using this utility, see mix_ctl in the Utilities Reference.

/*
 * $QNXLicenseC:
 * Copyright 2016, QNX Software Systems. All Rights Reserved.
 *
 * You must obtain a written license from and pay applicable license fees to QNX
 * Software Systems before you may reproduce, modify or distribute this software,
 * or any work that includes all or part of this software.   Free development
 * licenses are available for evaluation and non-commercial purposes.  For more
 * information visit http://licensing.qnx.com or email licensing@qnx.com.
 *
 * This file may contain contributions from others.  Please review this entire
 * file for other proprietary rights or license notices, as well as the QNX
 * Development Suite License Guide at http://licensing.qnx.com/license-guide/
 * for other information.
 * $
 */

#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <gulliver.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/termio.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>

#include <sys/asoundlib.h>


//*****************************************************************************
/* *INDENT-OFF* */
#ifdef __USAGE
%C  [Options] Cmds

Options:
    -a[card#:]<dev#>  the card & mixer device number to access
                      OR
    -a[name]          the AFM card name (e.g. voice, icc) to access

Cmds:

    groups  [-d] [-c] [-p] [pattern]
        -d  will print the group details
        -c  will show only groups effecting capture
        -p  will show only groups effecting playback

    group  name  [mute[Y]=off|on]  [capture[Y]=off|on]  [volume[Y]=x|x%]  ...
        - name is the group name quoted if it contains white space
        - the voice number Y (zero-based) is optional and
          restricts the change to the specified voice Y (if possible)

    switches

    switch  name  [value]
        - name is the switch name quoted if it contains white space
#endif
/* *INDENT-ON* */
//*****************************************************************************


void
display_group (snd_mixer_t * mixer_handle, snd_mixer_gid_t * gid, snd_mixer_group_t * group)
{
	int     j;

	printf ("\"%s\",%d - %s \n", gid->name, gid->index,
		group->caps & SND_MIXER_GRPCAP_PLAY_GRP ? "Playback Group" : "Capture Group");

	printf ("\tCapabilities - ");
	if (group->caps & SND_MIXER_GRPCAP_VOLUME)
		printf (" Volume");
	if (group->caps & SND_MIXER_GRPCAP_JOINTLY_MUTE)
		printf (" Jointly-Mute");
	else if (group->caps & SND_MIXER_GRPCAP_MUTE)
		printf (" Mute");
	if (group->caps & SND_MIXER_GRPCAP_JOINTLY_CAPTURE)
		printf (" Jointly-Capture");
	if (group->caps & SND_MIXER_GRPCAP_EXCL_CAPTURE)
		printf (" Exclusive-Capture");
	else if (group->caps & SND_MIXER_GRPCAP_CAPTURE)
		printf (" Capture");
	printf ("\n");

	printf ("\tChannels - ");
	for (j = 0; j <= SND_MIXER_CHN_LAST; j++)
	{
		if (!(group->channels & (1 << j)))
			continue;
		printf ("%s ", snd_mixer_channel_name (j));
	}
	printf ("\n");

	printf ("\tVolume Range - minimum=%i, maximum=%i\n", group->min, group->max);

	for (j = 0; j <= SND_MIXER_CHN_LAST; j++)
	{
		if (!(group->channels & (1 << j)))
			continue;
		printf ("\tChannel %d %-12.12s - %3d (%3d%%) %s %s\n", j,
			snd_mixer_channel_name (j), group->volume.values[j],
			(group->max - group->min) <= 0 ? 0 : 100 * (group->volume.values[j] - group->min)
			/ (group->max - group->min),
			group->mute & (1 << j) ? "Muted" : "", group->capture & (1 << j) ? "Capture" : "");
	}
}


void
display_groups (snd_mixer_t * mixer_handle, int argc, char *argv[])
{
	char    details = 0;
	char    playback_only = 0, capture_only = 0;
	char   *pattern;
	snd_mixer_groups_t groups;
	int     i;
	int     rtn;
	snd_mixer_group_t group;

	optind = 1;
	while ((i = getopt (argc, argv, "cdp")) != EOF)
	{
		switch (i)
		{
		case 'c':
			capture_only = 1;
			playback_only = 0;
			break;
		case 'd':
			details = 1;
			break;
		case 'p':
			capture_only = 0;
			playback_only = 1;
			break;
		}
	}
	pattern = (optind >= argc) ? "*" : argv[optind];

	while (1)
	{
		memset (&groups, 0, sizeof (groups));
		if (snd_mixer_groups (mixer_handle, &groups) < 0)
		{
			fprintf (stderr, "snd_mixer_groups API call - %s", strerror (errno));
		}
		else if (groups.groups == 0)
		{
			fprintf (stderr, "--> No mixer groups to list <-- \n");
			break;
		}

		if (groups.groups_over > 0)
		{
			groups.groups_size = groups.groups_over;
			groups.pgroups =
				(snd_mixer_gid_t *) malloc (sizeof (snd_mixer_gid_t) * groups.groups_size);
			if (groups.pgroups == NULL) {
				fprintf (stderr, "Unable to malloc group array - %s", strerror (errno));
				break;
			}
			groups.groups_over = 0;
			groups.groups = 0;
			if (snd_mixer_groups (mixer_handle, &groups) < 0)
				fprintf (stderr, "No Mixer Groups ");
			if (groups.groups_over > 0)
			{
				free (groups.pgroups);
				continue;
			}
			else
			{
				snd_mixer_sort_gid_table (groups.pgroups, groups.groups_size,
					snd_mixer_default_weights);
				break;
			}
		}
	}

	for (i = 0; i < groups.groups; i++)
	{
		if (fnmatch (pattern, groups.pgroups[i].name, 0) == 0)
		{
			memset (&group, 0, sizeof (group));
			memcpy (&group.gid, &groups.pgroups[i], sizeof (snd_mixer_gid_t));
			if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
				fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));

			if (playback_only && (group.caps & SND_MIXER_GRPCAP_CAP_GRP))
				continue;
			if (capture_only && (group.caps & SND_MIXER_GRPCAP_PLAY_GRP))
				continue;

			if (details)
				display_group (mixer_handle, &groups.pgroups[i], &group);
			else
			{
				printf ("\"%s\",%d%*c - %s \n",
					groups.pgroups[i].name, groups.pgroups[i].index,
					(int)(2 + sizeof (groups.pgroups[i].name) - strlen (groups.pgroups[i].name)), ' ',
					group.caps & SND_MIXER_GRPCAP_PLAY_GRP ? "Playback Group" : "Capture Group");
			}
		}
	}
}


int
find_group_best_match (snd_mixer_t * mixer_handle, snd_mixer_gid_t * gid, snd_mixer_group_t * group)
{
	snd_mixer_groups_t groups;
	int     i;

	while (1)
	{
		memset (&groups, 0, sizeof (groups));
		if (snd_mixer_groups (mixer_handle, &groups) < 0)
		{
			fprintf (stderr, "snd_mixer_groups API call - %s", strerror (errno));
		}
		if (groups.groups_over > 0)
		{
			groups.groups_size = groups.groups_over;
			groups.pgroups =
				(snd_mixer_gid_t *) malloc (sizeof (snd_mixer_gid_t) * groups.groups_size);
			if (groups.pgroups == NULL)
				fprintf (stderr, "Unable to malloc group array - %s", strerror (errno));
			groups.groups_over = 0;
			groups.groups = 0;
			if (snd_mixer_groups (mixer_handle, &groups) < 0)
				fprintf (stderr, "No Mixer Groups ");
			if (groups.groups_over > 0)
			{
				free (groups.pgroups);
				continue;
			}
			else
				break;
		}
	}

	for (i = 0; i < groups.groups; i++)
	{
		if (strcasecmp (gid->name, groups.pgroups[i].name) == 0 &&
			gid->index == groups.pgroups[i].index)
		{
			memset (group, 0, sizeof (*group));
			memcpy (gid, &groups.pgroups[i], sizeof (snd_mixer_gid_t));
			memcpy (&group->gid, &groups.pgroups[i], sizeof (snd_mixer_gid_t));
			if ((snd_mixer_group_read (mixer_handle, group)) < 0)
				return ENOENT;
			else
				return EOK;
		}
	}

	return ENOENT;
}


int
group_option_value (char *option)
{
	char   *ptr;
	int     value;

	if ((ptr = strrchr (option, '=')) != NULL)
	{
		if (*(ptr + 1) == 0)
			value = -2;
		else if (strcasecmp (ptr + 1, "off") == 0)
			value = 0;
		else if (strcasecmp (ptr + 1, "on") == 0)
			value = 1;
		else
			value = atoi (ptr + 1);
	}
	else
		value = -1;
	return (value);
}

void
modify_group (snd_mixer_t * mixer_handle, int argc, char *argv[])
{
	int     optind = 1;
	snd_mixer_gid_t gid;
	char   *ptr;
	int     rtn;
	snd_mixer_group_t group;
	long    channel = 0, j;
	int     value;
	char    modified = 0;

	if (optind >= argc)
	{
		fprintf (stderr, "No Group secified \n");
		return;
	}

	memset (&gid, 0, sizeof (gid));
	ptr = strtok (argv[optind++], ",");
	if (ptr != NULL) {
		strncpy (gid.name, ptr, sizeof (gid.name));
		ptr = strtok (NULL, " ");
    }
	if (ptr != NULL)
		gid.index = atoi (ptr);

	memset (&group, 0, sizeof (group));
	memcpy (&group.gid, &gid, sizeof (snd_mixer_gid_t));
	if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
	{
		if (rtn == -ENXIO)
			rtn = find_group_best_match (mixer_handle, &gid, &group);

		if (rtn != EOK)
		{
			fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
			return;
		}
	}

	/* if we have a value option set the group, write and reread it (to get true driver state) */
	/* some things like capture (MUX) can't be turned off but can only be set on another group */
	while (optind < argc)
	{
		modified = 1;
		if ((value = group_option_value (argv[optind])) < 0)
			printf ("\n\t>>>> Unrecognized option [%s] <<<<\n\n", argv[optind]);
		else if (strncasecmp (argv[optind], "mute", 4) == 0)
		{
			if (argv[optind][4] == '=')
				channel = LONG_MAX;
			else
				channel = atoi (&argv[optind][4]);
			if (channel == LONG_MAX)
				group.mute = value ? LONG_MAX : 0;
			else
			{
				group.mute = value ? group.mute | (1 << channel) : group.mute & ~(1 << channel);
			}
		}
		else if (strncasecmp (argv[optind], "capture", 7) == 0)
		{
			if (argv[optind][7] == '=')
				channel = LONG_MAX;
			else
				channel = atoi (&argv[optind][7]);
			if (channel == LONG_MAX)
				group.capture = value ? LONG_MAX : 0;
			else
				group.capture =
					value ? group.capture | (1 << channel) : group.capture & ~(1 << channel);
		}
		else if (strncasecmp (argv[optind], "volume", 6) == 0)
		{
			if (argv[optind][6] == '=')
				channel = LONG_MAX;
			else
				channel = atoi (&argv[optind][6]);
			if (argv[optind][strlen (argv[optind]) - 1] == '%' && (group.max - group.min) >= 0)
				value = (value * (group.max - group.min)) / 100 + group.min;
			if (value > group.max)
				value = group.max;
			if (value < group.min)
				value = group.min;
			for (j = 0; j <= SND_MIXER_CHN_LAST; j++)
			{
				if (!(group.channels & (1 << j)))
					continue;
				if (channel == LONG_MAX || channel == j)
					group.volume.values[j] = value;
			}
		}
		else if (strncasecmp (argv[optind], "delay", 5) == 0)
		{
			if (argv[optind][5] == '=')
				group.change_duration = value;
			else
				group.change_duration = 50000;
		}
		else
			printf ("\n\t>>>> Unrecognized option [%s] <<<<\n\n", argv[optind]);

		if (channel != LONG_MAX && !(group.channels & (1 << channel)))
			printf ("\n\t>>>> Channel specified [%ld] Not in group <<<<\n\n", channel);
		optind++;
	}

	if (modified)
		if ((rtn = snd_mixer_group_write (mixer_handle, &group)) < 0)
			fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));
	if ((rtn = snd_mixer_group_read (mixer_handle, &group)) < 0)
		fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));

	/* display the current group state */
	display_group (mixer_handle, &gid, &group);
}


void
display_switch (snd_switch_t * sw, char table_formated)
{
	printf ("\"%s\"%*c ", sw->name,
		(int)(table_formated ? sizeof (sw->name) - strlen (sw->name) : 1), ' ');
	switch (sw->type)
	{
	case SND_SW_TYPE_BOOLEAN:
		printf ("%s  %s \n", "BOOLEAN", sw->value.enable ? "on" : "off");
		break;
	case SND_SW_TYPE_BYTE:
		printf ("%s  %d \n", "BYTE   ", sw->value.byte.data);
		break;
	case SND_SW_TYPE_WORD:
		printf ("%s  %d \n", "WORD   ", sw->value.word.data);
		break;
	case SND_SW_TYPE_DWORD:
		printf ("%s  %d \n", "DWORD  ", sw->value.dword.data);
		break;
	case SND_SW_TYPE_LIST:
		{
			int i;
			if (sw->subtype == SND_SW_SUBTYPE_HEXA) {
				printf ("%s  0x%x ", "LIST   ", sw->value.list.data);
				for (i=0; i < sw->value.list.items_cnt; i ++) {
					printf(" 0x%x", sw->value.list.items[i]);
				}
			} else {
				printf ("%s  %d ", "LIST   ", sw->value.list.data);
				for (i=0; i < sw->value.list.items_cnt; i ++) {
					printf(" %d", sw->value.list.items[i]);
				}
			}
			printf("\n");
		}
		break;
	case SND_SW_TYPE_STRING_11:
		printf ("%s  \"%s\" \n", "STRING ",
			sw->value.string_11.strings[sw->value.string_11.selection]);
		break;
	default:
		printf ("%s  %d \n", "?      ", 0);
	}
}


void
display_switches (snd_ctl_t * ctl_handle, int mixer_dev, int argc, char *argv[])
{
	int     i;
	snd_switch_list_t list;
	snd_switch_t sw;
	int     rtn;

	while (1)
	{
		memset (&list, 0, sizeof (list));
		if (snd_ctl_mixer_switch_list (ctl_handle, mixer_dev, &list) < 0)
		{
			fprintf (stderr, "snd_ctl_mixer_switch_list API call - %s", strerror (errno));
		}
		else if (list.switches == 0)
		{
			fprintf (stderr, "--> No mixer switches to list <-- \n");
			break;
		}

		if (list.switches_over > 0)
		{
			list.switches_size = list.switches_over;
			list.pswitches = malloc (sizeof (snd_switch_list_item_t) * list.switches_size);
			if (list.pswitches == NULL)
				fprintf (stderr, "Unable to malloc switch array - %s", strerror (errno));
			list.switches_over = 0;
			list.switches = 0;
			if (snd_ctl_mixer_switch_list (ctl_handle, mixer_dev, &list) < 0)
				fprintf (stderr, "No Switches ");
			if (list.switches_over > 0)
			{
				free (list.pswitches);
				continue;
			}
			else
				break;
		}
	}

	for (i = 0; i < list.switches_size; i++)
	{
		memset (&sw, 0, sizeof (sw));
		strncpy (sw.name, (&list.pswitches[i])->name, sizeof (sw.name));
		if ((rtn = snd_ctl_mixer_switch_read (ctl_handle, mixer_dev, &sw)) < 0)
			fprintf (stderr, "snd_ctl_mixer_switch_read failed: %s\n", snd_strerror (rtn));
		display_switch (&sw, 1);
	}
}


void
modify_switch (snd_ctl_t * ctl_handle, int mixer_dev, int argc, char *argv[])
{
	int     optind = 1;
	snd_switch_t sw;
	int     rtn;
	int     value = 0;
	char   *string = NULL;

	if (optind >= argc)
	{
		fprintf (stderr, "No Switch secified \n");
		return;
	}

	memset (&sw, 0, sizeof (sw));
	strncpy (sw.name, argv[optind++], sizeof (sw.name));
	if ((rtn = snd_ctl_mixer_switch_read (ctl_handle, mixer_dev, &sw)) < 0)
	{
		fprintf (stderr, "snd_ctl_mixer_switch_read failed: %s\n", snd_strerror (rtn));
		return;
	}

	/* if we have a value option set the sw, write and reread it (to get true driver state) */
	if (optind < argc)
	{
		if (strcasecmp (argv[optind], "off") == 0)
			value = 0;
		else if (strcasecmp (argv[optind], "on") == 0)
			value = 1;
		else if (strncasecmp (argv[optind], "0x", 2) == 0)
			value = strtol (argv[optind], NULL, 16);
		else
		{
			value = atoi (argv[optind]);
			string = argv[optind];
		}
		optind++;
		if (sw.type == SND_SW_TYPE_BOOLEAN)
			sw.value.enable = value;
		else if (sw.type == SND_SW_TYPE_BYTE)
			sw.value.byte.data = value;
		else if (sw.type == SND_SW_TYPE_WORD)
			sw.value.word.data = value;
		else if (sw.type == SND_SW_TYPE_DWORD)
			sw.value.dword.data = value;
		else if (sw.type == SND_SW_TYPE_LIST) {
			int i;
			sw.value.list.data = value;
			for (i = optind; i < argc; i ++ ) {
				sw.value.list.items[i-optind] = atoi(argv[i]);
			}
			sw.value.list.items_cnt = argc - optind;
		}
		else if (sw.type == SND_SW_TYPE_STRING_11)
		{
			for (rtn = 0; rtn < sw.value.string_11.strings_cnt; rtn++)
			{
				if (string != NULL && strcasecmp (string, sw.value.string_11.strings[rtn]) == 0)
				{
					sw.value.string_11.selection = rtn;
					break;
				}
			}
			if (rtn == sw.value.string_11.strings_cnt)
			{
				if (string != NULL) {
					fprintf (stderr, "ERROR string \"%s\" NOT IN LIST \n", string);
				} else {
					fprintf (stderr, "ERROR string NULL NOT IN LIST \n");
				}
				snd_ctl_mixer_switch_read (ctl_handle, mixer_dev, &sw);
			}
		}
		if ((rtn = snd_ctl_mixer_switch_write (ctl_handle, mixer_dev, &sw)) < 0)
			fprintf (stderr, "snd_ctl_mixer_switch_write failed: %s\n", snd_strerror (rtn));
		if ((rtn = snd_ctl_mixer_switch_read (ctl_handle, mixer_dev, &sw)) < 0)
			fprintf (stderr, "snd_ctl_mixer_switch_read failed: %s\n", snd_strerror (rtn));
	}

	/* display the current switch state */
	display_switch (&sw, 0);
}


int
main (int argc, char *argv[])
{
	int     c;
	int     card = 0;
	int     dev = 0;
	int     rtn;
	snd_ctl_t *ctl_handle;
	snd_mixer_t *mixer_handle;
	char    name[_POSIX_PATH_MAX] = { 0 };

	optind = 1;
	while ((c = getopt (argc, argv, "a:")) != EOF)
	{
		switch (c)
		{
		case 'a':
			if (strchr (optarg, ':'))
			{
				card = atoi (optarg);
				dev = atoi (strchr (optarg, ':') + 1);
			}
			else if (isalpha (optarg[0]))
				strlcpy (name, optarg, sizeof(name));
			else
				dev = atoi (optarg);
			break;
		default:
			return 1;
		}
	}

	if (name[0] != '\0')
	{
		card = snd_card_name( name );
		printf ("Using %s - card %d\n", name, card);
	}
	else
	{
		printf ("Using card %d device %d \n", card, dev);
	}

	if ((rtn = snd_ctl_open (&ctl_handle, card)) < 0)
	{
		fprintf (stderr, "snd_ctl_open failed: %s\n", snd_strerror (rtn));
		return -1;
	}

	if ((rtn = snd_mixer_open (&mixer_handle, card, dev)) < 0)
	{
		fprintf (stderr, "snd_mixer_open failed: %s\n", snd_strerror (rtn));
		snd_ctl_close (ctl_handle);
		return -1;
	}

	if (optind >= argc)
		display_groups (mixer_handle, argc - optind, argv + optind);
	else if (strcasecmp (argv[optind], "groups") == 0)
		display_groups (mixer_handle, argc - optind, argv + optind);
	else if (strcasecmp (argv[optind], "group") == 0)
		modify_group (mixer_handle, argc - optind, argv + optind);
	else if (strcasecmp (argv[optind], "switches") == 0)
		display_switches (ctl_handle, dev, argc - optind, argv + optind);
	else if (strcasecmp (argv[optind], "switch") == 0)
		modify_switch (ctl_handle, dev, argc - optind, argv + optind);
	else
		fprintf (stderr, "Unknown command specified \n");
	snd_mixer_close (mixer_handle);
	snd_ctl_close (ctl_handle);
	return (0);
}

#if defined(__QNXNTO__) && defined(__USESRCVERSION)
#include <sys/srcversion.h>
__SRCVERSION("$URL$ $Rev$")
#endif