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. This version corresponds to QNX Software Development Platform 6.6 io-audio patch (patch ID 4687).

/*
 * $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_JOINTLY_VOLUME)
                printf (" Jointly-Volume");
        else 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 ((rtn = snd_mixer_groups (mixer_handle, &groups)) < 0)
                {
                        fprintf (stderr, "snd_mixer_groups failed: %s\n", snd_strerror (rtn));
                }
                else if (groups.groups == 0)
                {
                        fprintf (stderr, "--> No mixer groups to list <-- \n");
                        break;
                }

                if (groups.groups_over > 0)
                {
                        groups.pgroups =
                                (snd_mixer_gid_t *) malloc (sizeof (snd_mixer_gid_t) * groups.groups);
                        if (groups.pgroups == NULL)
                        {
                                fprintf (stderr, "Unable to malloc group array - %s\n", strerror (errno));
                                groups.groups = 0;
                                break;
                        }
                        groups.groups_size = groups.groups;
                        groups.groups_over = 0;
                        groups.groups = 0;
                        if ((rtn = snd_mixer_groups (mixer_handle, &groups)) < 0)
                        {
                                fprintf (stderr, "snd_mixer_groups failed: %s\n", snd_strerror (rtn));
                                groups.groups = 0;
                                break;
                        }
                        if (groups.groups_over > 0) /* Mixer controls have changed since call above, try again */
                        {
                                free (groups.pgroups);
                                continue;
                        }
                        snd_mixer_sort_gid_table (groups.pgroups, groups.groups, 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 of group %d failed: %s\n", i, snd_strerror (rtn));
                                continue;
                        }

                        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,
                                        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;
        int     rtn;

        while (1)
        {
                memset (&groups, 0, sizeof (groups));
                if ((rtn = snd_mixer_groups (mixer_handle, &groups)) < 0)
                {
                        fprintf (stderr, "snd_mixer_groups failed: %s\n", snd_strerror (rtn));
                }
                else if (groups.groups == 0)
                {
                        break;
                }

                if (groups.groups_over > 0)
                {
                        groups.pgroups =
                                (snd_mixer_gid_t *) malloc (sizeof (snd_mixer_gid_t) * groups.groups);
                        if (groups.pgroups == NULL)
                        {
                                fprintf (stderr, "Unable to malloc group array - %s\n", strerror (errno));
                                groups.groups = 0;
                                break;
                        }
                        groups.groups_size = groups.groups;
                        groups.groups_over = 0;
                        groups.groups = 0;
                        if ((rtn = snd_mixer_groups (mixer_handle, &groups)) < 0)
                        {
                                fprintf (stderr, "snd_mixer_groups failed: %s\n", snd_strerror (rtn));
                                groups.groups = 0;
                                break;
                        }
                        if (groups.groups_over > 0) /* Mixer controls have changed since call above, try again */
                        {
                                free (groups.pgroups);
                                continue;
                        }
                        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;
        uint32_t channel = 0, j;
        int32_t value;
        char    modified = 0;

        if (optind >= argc)
        {
                fprintf (stderr, "No Group specified \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;
                }
        }

        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 ((group.caps & SND_MIXER_GRPCAP_JOINTLY_MUTE) && (group.channels & (1<<channel)))
                                        channel = LONG_MAX;
                        }
                        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 ((group.caps & SND_MIXER_GRPCAP_JOINTLY_VOLUME) && (group.channels & (1<<channel)))
                                        channel = LONG_MAX;
                        }
                        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 [%d] Not in group <<<<\n\n", channel);
                optind++;
        }

        /* 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 */
        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,
                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:
                if (sw->subtype == SND_SW_SUBTYPE_HEXA)
                        printf ("%s  0x%x \n", "LIST   ", sw->value.list.data);
                else
                        printf ("%s  %d \n", "LIST   ", sw->value.list.data);
                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 ((rtn = snd_ctl_mixer_switch_list (ctl_handle, mixer_dev, &list)) < 0)
                {
                        fprintf (stderr, "snd_ctl_mixer_switch_list failed: %s\n", snd_strerror (rtn));
                }
                else if (list.switches == 0)
                {
                        fprintf (stderr, "--> No mixer switches to list <-- \n");
                        break;
                }

                if (list.switches_over > 0)
                {
                        list.pswitches = malloc (sizeof (snd_switch_list_item_t) * list.switches);
                        if (list.pswitches == NULL)
                        {
                                fprintf (stderr, "Unable to malloc switch array - %s\n", strerror (errno));
                                list.switches = 0;
                                break;
                        }
                        list.switches_size = list.switches;
                        list.switches_over = 0;
                        list.switches = 0;
                        if ((rtn = snd_ctl_mixer_switch_list (ctl_handle, mixer_dev, &list)) < 0)
                        {
                                fprintf (stderr, "snd_ctl_mixer_switch_list failed: %s\n", snd_strerror (rtn));
                                list.switches = 0;
                                break;
                        }
                        if (list.switches_over > 0) /* Mixer controls have changed since call above, try again */
                        {
                                free (list.pswitches);
                                continue;
                        }
                        break;
                }
        }

        for (i = 0; i < list.switches; 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 of switch %d failed: %s\n", i, snd_strerror (rtn));
                        continue;
                }
                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 specified \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)
                        sw.value.list.data = value;
                else if (sw.type == SND_SW_TYPE_STRING_11)
                {
                        if (!string)
                        {
                                fprintf (stderr, "string required for switch type");
                        }
                        else
                        {
                                for (rtn = 0; rtn < sw.value.string_11.strings_cnt; rtn++)
                                {
                                        if (strcasecmp (string, sw.value.string_11.strings[rtn]) == 0)
                                        {
                                                sw.value.string_11.selection = rtn;
                                                break;
                                        }
                                }
                                if (rtn == sw.value.string_11.strings_cnt)
                                {
                                        fprintf (stderr, "ERROR string \"%s\" NOT IN LIST \n", string);
                                        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