[Previous] [Contents] [Next]

Controlling volume and balance

The Mixer

The Mixer is responsible for controlling the analog portions of a soundcard. Specifically, this involves mixing or combining multiple analog sound sources for output and input. The Neutrino Audio API allows control of these operations by the manipulation of basic mixer elements. Several element types exist in the specification, but the most useful are:

The API also defines mixer groups that are logical collections of elements that allow for a more abstracted control of the mixer. For example, we can manipulate the "CD" group to increase the volume, mute it, and also select the "CD" source for recording. Although it's possible to access and control all mixer elements directly, most applications will just control groups.

Controlling the Mixer with groups

To control a group, an application has to

The following code fragment from the wave.c example in the appendix demonstrates how to do this:

    if ((rtn = snd_mixer_open (&mixer_handle, card, setup.mixer_device)) < 0)
    {
        fprintf (stderr, "snd_mixer_open failed: %s\n", snd_strerror (rtn));
        return -1;
    }
...
    if (snd_mixer_group_read (mixer_handle, &group) < 0)
        fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
...
    if (snd_mixer_group_write (mixer_handle, &group) < 0)
        fprintf (stderr, "snd_mixer_group_write failed: %s\n", snd_strerror (rtn));
...
    rtn = snd_mixer_close (mixer_handle);

The Group Structure

typedef struct
{
    int32_t     type;
    int8_t      name[32];
    int32_t     index;
}       snd_mixer_gid_t;

typedef struct snd_mixer_group_s
{
    snd_mixer_gid_t gid;
    uint32_t    caps;
    uint32_t    channels;
    int32_t     min, max;
    union
    {
        int32_t values[32];
        struct
        {
            int32_t     front_left;
            int32_t     front_right;
            int32_t     front_center;
            int32_t     rear_left;
            int32_t     rear_right;
            int32_t     woofer;
        }       names;
    }       volume;
    uint32_t    mute;
    uint32_t    capture;
}       snd_mixer_group_t;

The group is identified by the gid structure. It must be set before calling snd_mixer_group_read(). The members of the structure include:

name
Name of the group.
index
Identify the exact group if two or more groups share the same name.
type
0 (Currently, the only valid type.)
caps
A bit field that describes the types of functions the group can control. Valid values are:
SND_MIXER_GRPCAP_CAPTURE
The group can be selected for capture.
SND_MIXER_GRPCAP_JOINTLY_MUTE
All channels must have the same mute state, they aren't controlled individually.
SND_MIXER_GRPCAP_JOINTLY_VOLUME
All channels must have the same volume, they aren't controlled individually.
SND_MIXER_GRPCAP_MUTE
The group has mute capabilities.
SND_MIXER_GRPCAP_SUBCHANNEL
The group is on a PCM subchannel and therefore is transient in nature. It's destroyed when the PCM channel is closed.
SND_MIXER_GRPCAP_VOLUME
The group has volume control capabilities.
channel
A bit field representing the channels this group controls.
min and max
Provide the minimum and maximum limits for the volume members.
volume
Volume levels for each channel in the group.
must
A bitfield with 1 bit for each channel in the group.
capture
A bitfield with 1 bit for each member in the group.

Finding the group for an Open PCM Handle

One problem with mixer groups is to find the one that's controlling the data that you're playing out of the PCM device. The PCM API function, snd_pcm_plugin_setup(), is useful because it returns the mixer device number (there can be more than one mixer per soundcard), and the group that best controls the PCM data. The group returned may be one of:

The rule is: the lowest supported group is returned by snd_pcm_plugin_setup(), but some soundcard hardware may not support subchannel controls. To resolve this, use the setup structure to set a pointer to where you want the group identification stored. The mixer device number used to open the mixer is returned. The following code fragment from the wave.c example in the appendix demonstrates how to do this:

memset (&setup, 0, sizeof (setup));
setup.channel = SND_PCM_CHANNEL_PLAYBACK;
setup.mixer_gid = &group.gid;
if ((rtn = snd_pcm_plugin_setup (pcm_handle, &setup)) < 0)
    fprintf (stderr, "snd_pcm_plugin_setup failed: %s\n", snd_strerror (rtn));

Notification of Mixer Events

Many applications may attempt to control a mixer group concurrently. As a result, it's essential that all applications are notified of any changes. This is how mixer events are used:

An example of these callbacks is the "group changed callback" that's called whenever a change to a group is made. If an application makes a change it doesn't receive a group change event notification, but all other applications do!

Here's a short example of how this works:

int mixer_update ()
{
    snd_mixer_callbacks_t callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.group = mixer_callback_group;

    snd_mixer_read (mixer_handle, &callbacks);
    return (0);
}

static void mixer_callback_group (void *private_data, int cmd, snd_mixer_gid_t *gid)
{
    switch (cmd)
    {
    case SND_MIXER_READ_GROUP_VALUE:
        if( strcmp(gid->name, group.gid.name)==0 && gid->index==group.gid.index)
        // read the changed group
    if (snd_mixer_group_read (mixer_handle, &group) < 0)
        fprintf (stderr, "snd_mixer_group_read failed: %s\n", snd_strerror (rtn));
        break;
    }
}

Note: Mixer events are queued for every opened mixer handle, so your application must clear these events using snd_mixer_read(). Failure to do so, results in all snd_mixer_group_write(). calls failing.


[Previous] [Contents] [Next]