Subject: Re: audio/mixer controls
To: None <brook@biology.nmsu.edu>
From: Chris Wareham <chris.wareham@iosystems.co.uk>
List: netbsd-help
Date: 08/12/2004 10:07:46
This is a multi-part message in MIME format.
--------------050407070308040107090206
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

brook@biology.nmsu.edu wrote:
> I am porting an audio application (rat4) to NetBSD.  Its GUI provides
> one output volume control, one input volume control, and one loopback
> volume control.  I am mapping these controls to appropriate mixer
> devices through a lookup table of possible devices; the first match in
> the table is the device used for the GUI controls.
> 
> Not being completely familiar with all the manifestations of NetBSD
> audio drivers, I am a bit unsure about what should be in the lookup
> tables and in what order the entries should appear.  Currently, I am
> using the following:
> 
>      static struct mixer_devices mixer_outputs_master [] = {
>        { AudioCoutputs, AudioNmaster },
>        { NULL, NULL }
>      };
> 
>      static struct mixer_devices mixer_record_volume [] = {
>        { AudioCrecord, AudioNmicrophone },
>        { AudioCinputs, AudioNmicrophone },
>        { AudioCrecord, AudioNvolume },
>        { NULL, NULL }
>      };
> 
>      static struct mixer_devices mixer_loopback_volume [] = {
>        { AudioCinputs, AudioNdac },
>        { NULL, NULL }
>      };
> 
> Does anyone have any guidance for a plausible hierarchy of mixer
> devices to try for each of the GUI controls?  
> 
> By the way, another similar application (using the OSS audio system)
> maps the input volume control to the record.mic mixer device and the
> output volume control to the set of {inputs.speaker, inputs.aux,
> inputs.dac} mixer devices.
> 
> Any design suggestions are welcome, especially those that come with
> some logical reasoning.
> 
> Thanks for your help.
> 
> Cheers,
> Brook
> 

If you don't mind using the OSS emulation, then you can "walk" the
available mixer controls, checking to see which are recording sources. I
have written a simple GUI mixer that does that, and the relevant code is
attached.

Chris

--------------050407070308040107090206
Content-Type: text/plain;
 name="functions.h"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="functions.h"

/*
 * Copyright (c) 2004 Chris Wareham.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef FUNCTIONS_H
#define FUNCTIONS_H

typedef struct Device {
	int id, left_value, right_value;
	char *name;
	int stereo : 1;
	int record : 1;
	int record_enabled : 1;
} Device;

typedef struct Mixer {
	int fd, ndevices;
	Device *devices;
	int excl_input : 1;
} Mixer;

Mixer *open_mixer(const char *);
int set_mixer_value(Mixer *, int, int, int);
int set_mixer_record(Mixer *, int, int);
void close_mixer(Mixer *mixer);
void dump_mixer(Mixer *mixer);

#endif /* !FUNCTIONS_H */

--------------050407070308040107090206
Content-Type: text/plain;
 name="functions.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="functions.c"

/*
 * Copyright (c) 2004 Chris Wareham.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/ioctl.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#ifdef __NetBSD__
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "functions.h"

static char *device_labels[] = SOUND_DEVICE_LABELS;

static void strip(char *);

Mixer *
open_mixer(const char *device)
{
	int fd, devmask, recmask, recsrc, caps, stereodevs, value, i;
	Mixer *mixer;

	if ((fd = open(device, O_WRONLY)) < 0) {
		fprintf(stderr, "Unable to open %s: %s\n", device, strerror(errno));
		return NULL;
	}

	mixer = calloc(1, sizeof(Mixer));
	mixer->fd = fd;
	mixer->devices = calloc(SOUND_MIXER_NRDEVICES, sizeof(Device));

	if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
		fprintf(stderr, "Unable to get device mask: %s\n", strerror(errno));
		return mixer;
	}

	if (ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask) < 0) {
		fprintf(stderr, "Unable to get record mask: %s\n", strerror(errno));
		return mixer;
	}

	if (ioctl(fd, SOUND_MIXER_READ_RECSRC, &recsrc) < 0) {
		fprintf(stderr, "Unable to get record sources: %s\n", strerror(errno));
		return mixer;
	}

	if (ioctl(fd, SOUND_MIXER_READ_CAPS, &caps) < 0) {
		fprintf(stderr, "Unable to get capabilities: %s\n", strerror(errno));
		return mixer;
	}

	if (ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) < 0) {
		fprintf(stderr, "Unable to get stereo devices: %s\n", strerror(errno));
		return mixer;
	}

	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
		if (devmask & (1 << i)) {
			mixer->devices[mixer->ndevices].id = i;
			mixer->devices[mixer->ndevices].name = strdup(device_labels[i]);
			strip(mixer->devices[mixer->ndevices].name);

			if (ioctl(fd, MIXER_READ(i), &value) < 0) {
				fprintf(stderr, "Unable to read value of %s: %s\n",
					device_labels[i], strerror(errno));
			} else {
				mixer->devices[mixer->ndevices].left_value = value & 0xff;
				mixer->devices[mixer->ndevices].right_value = (value >> 8) & 0xff;
			}

			if (stereodevs & (1 << i))
				mixer->devices[mixer->ndevices].stereo = 1;

			if (recmask & (1 << i)) {
				mixer->devices[mixer->ndevices].record = 1;
				if (recsrc & (1 << i))
					mixer->devices[mixer->ndevices].record_enabled = 1;
			}

			mixer->ndevices++;
		}
	}

	mixer->excl_input = (caps & SOUND_CAP_EXCL_INPUT) ? 1 : 0;

	return mixer;
}

int
set_mixer_value(Mixer *mixer, int id, int left_value, int right_value)
{
	int value, i;

	value = (right_value << 8) | left_value;

	if (ioctl(mixer->fd, MIXER_WRITE(id), &value) < 0) {
		fprintf(stderr, "Unable to write value of %s: %s\n",
			device_labels[id], strerror(errno));
		return 1;
	}

	for (i = 0; i < mixer->ndevices; i++) {
		if (mixer->devices[i].id == id) {
			mixer->devices->left_value = left_value;
			mixer->devices->right_value = right_value;
			break;
		}
	}

	return 0;
}

int
set_mixer_record(Mixer *mixer, int id, int state)
{
	int recsrc, i;

	if (mixer->excl_input) {
		recsrc = state ? (1 << id) : 0;
	} else {
		if (ioctl(mixer->fd, SOUND_MIXER_READ_RECSRC, &recsrc) < 0) {
			fprintf(stderr, "Unable to get record sources: %s\n", strerror(errno));
			return 1;
		}
		if (state)
			recsrc |= (1 << id);
		else
			recsrc &= ~(1 << id);
	}

	if (ioctl(mixer->fd, SOUND_MIXER_WRITE_RECSRC, &recsrc) < 0) {
		fprintf(stderr, "Unable to set record sources: %s\n", strerror(errno));
		return 1;
	}

	for (i = 0; i < mixer->ndevices; i++) {
		if (mixer->devices[i].id == id) {
			mixer->devices->record_enabled = state ? 1 : 0;
			if (!mixer->excl_input)
				break;
		} else if (mixer->excl_input && state) {
			mixer->devices->record_enabled = 0;
		}
	}

	return 0;
}

void
close_mixer(Mixer *mixer)
{
	int i;

	close(mixer->fd);
	for (i = 0; i < mixer->ndevices; i++)
		free(mixer->devices[i].name);
	free(mixer->devices);
	free(mixer);
}

void
dump_mixer(Mixer *mixer)
{
	int i;

	for (i = 0; i < mixer->ndevices; i++) {
		printf("%d - %s value %d:%d", mixer->devices[i].id, mixer->devices[i].name,
			mixer->devices[i].left_value, mixer->devices[i].right_value);
		if (mixer->devices[i].stereo)
			printf(", stereo");
		if (mixer->devices[i].record)
			printf(", record%s", mixer->devices[i].record_enabled ? " (enabled)" : "");
		printf("\n");
	}
}

void
strip(char *str)
{
	int i;

	for (i = strlen(str); i && isspace(str[i - 1]); i--)
		str[i - 1] = '\0';
}

--------------050407070308040107090206--