Home | History | Annotate | Line # | Download | only in libntp
audio.c revision 1.1.1.8
      1 /*
      2  * audio.c - audio interface for reference clock audio drivers
      3  */
      4 #ifdef HAVE_CONFIG_H
      5 # include <config.h>
      6 #endif
      7 
      8 #if defined(HAVE_SYS_AUDIOIO_H) || defined(HAVE_SUN_AUDIOIO_H) || \
      9     defined(HAVE_SYS_SOUNDCARD_H) || defined(HAVE_MACHINE_SOUNDCARD_H)
     10 
     11 #include "audio.h"
     12 #include "ntp_stdlib.h"
     13 #include "ntp_syslog.h"
     14 #ifdef HAVE_UNISTD_H
     15 # include <unistd.h>
     16 #endif
     17 #include <stdio.h>
     18 #include "ntp_string.h"
     19 
     20 #ifdef HAVE_SYS_AUDIOIO_H
     21 # include <sys/audioio.h>
     22 #endif /* HAVE_SYS_AUDIOIO_H */
     23 
     24 #ifdef HAVE_SUN_AUDIOIO_H
     25 # include <sys/ioccom.h>
     26 # include <sun/audioio.h>
     27 #endif /* HAVE_SUN_AUDIOIO_H */
     28 
     29 #ifdef HAVE_SYS_IOCTL_H
     30 # include <sys/ioctl.h>
     31 #endif /* HAVE_SYS_IOCTL_H */
     32 
     33 #include <fcntl.h>
     34 
     35 #ifdef HAVE_MACHINE_SOUNDCARD_H
     36 # include <machine/soundcard.h>
     37 # define PCM_STYLE_SOUND
     38 #else
     39 # ifdef HAVE_SYS_SOUNDCARD_H
     40 #  include <sys/soundcard.h>
     41 #  define PCM_STYLE_SOUND
     42 # endif
     43 #endif
     44 
     45 #ifdef PCM_STYLE_SOUND
     46 # include <ctype.h>
     47 #endif
     48 
     49 /*
     50  * Global variables
     51  */
     52 #ifdef HAVE_SYS_AUDIOIO_H
     53 static struct audio_device device; /* audio device ident */
     54 #endif /* HAVE_SYS_AUDIOIO_H */
     55 #ifdef PCM_STYLE_SOUND
     56 # define INIT_FILE "/etc/ntp.audio"
     57 int agc =	SOUND_MIXER_WRITE_RECLEV; /* or IGAIN or LINE */
     58 int monitor =	SOUND_MIXER_WRITE_VOLUME; /* or OGAIN */
     59 int devmask = 0;
     60 int recmask = 0;
     61 char cf_c_dev[100], cf_i_dev[100], cf_agc[100], cf_monitor[100];
     62 
     63 const char *m_names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
     64 #else /* not PCM_STYLE_SOUND */
     65 static struct audio_info info;	/* audio device info */
     66 #endif /* not PCM_STYLE_SOUND */
     67 static int ctl_fd;		/* audio control file descriptor */
     68 
     69 #ifdef PCM_STYLE_SOUND
     70 static void audio_config_read (int, const char **, const char **);
     71 static int  mixer_name (const char *, int);
     72 
     73 
     74 int
     75 mixer_name(
     76 	const char *m_name,
     77 	int m_mask
     78 	)
     79 {
     80 	int i;
     81 
     82 	for (i = 0; i < SOUND_MIXER_NRDEVICES; ++i)
     83 		if (((1 << i) & m_mask)
     84 		    && !strcmp(m_names[i], m_name))
     85 			break;
     86 
     87 	return (SOUND_MIXER_NRDEVICES == i)
     88 	    ? -1
     89 	    : i
     90 	    ;
     91 }
     92 
     93 
     94 /*
     95  * Check:
     96  *
     97  * /etc/ntp.audio#	where # is the unit number
     98  * /etc/ntp.audio.#	where # is the unit number
     99  * /etc/ntp.audio
    100  *
    101  * for contents of the form:
    102  *
    103  * idev /dev/input_device
    104  * cdev /dev/control_device
    105  * agc pcm_input_device {igain,line,line1,...}
    106  * monitor pcm_monitor_device {ogain,...}
    107  *
    108  * The device names for the "agc" and "monitor" keywords
    109  * can be found by running either the "mixer" program or the
    110  * util/audio-pcm program.
    111  *
    112  * Great hunks of this subroutine were swiped from refclock_oncore.c
    113  */
    114 static void
    115 audio_config_read(
    116 	int unit,
    117 	const char **c_dev,	/* Control device */
    118 	const char **i_dev	/* input device */
    119 	)
    120 {
    121 	FILE *fd;
    122 	char device[20], line[100], ab[100];
    123 
    124 	snprintf(device, sizeof(device), "%s%d", INIT_FILE, unit);
    125 	if ((fd = fopen(device, "r")) == NULL) {
    126 		printf("audio_config_read: <%s> NO\n", device);
    127 		snprintf(device, sizeof(device), "%s.%d", INIT_FILE,
    128 			 unit);
    129 		if ((fd = fopen(device, "r")) == NULL) {
    130 			printf("audio_config_read: <%s> NO\n", device);
    131 			snprintf(device, sizeof(device), "%s",
    132 				 INIT_FILE);
    133 			if ((fd = fopen(device, "r")) == NULL) {
    134 				printf("audio_config_read: <%s> NO\n",
    135 				       device);
    136 				return;
    137 			}
    138 		}
    139 	}
    140 	printf("audio_config_read: reading <%s>\n", device);
    141 	while (fgets(line, sizeof line, fd)) {
    142 		char *cp, *cc, *ca;
    143 		int i;
    144 
    145 		/* Remove comments */
    146 		if ((cp = strchr(line, '#')))
    147 			*cp = '\0';
    148 
    149 		/* Remove any trailing spaces */
    150 		for (i = strlen(line);
    151 		     i > 0 && isascii((unsigned char)line[i - 1]) && isspace((unsigned char)line[i - 1]);
    152 			)
    153 			line[--i] = '\0';
    154 
    155 		/* Remove leading space */
    156 		for (cc = line; *cc && isascii((unsigned char)*cc) && isspace((unsigned char)*cc); cc++)
    157 			continue;
    158 
    159 		/* Stop if nothing left */
    160 		if (!*cc)
    161 			continue;
    162 
    163 		/* Uppercase the command and find the arg */
    164 		for (ca = cc; *ca; ca++) {
    165 			if (isascii((unsigned char)*ca)) {
    166 				if (islower((unsigned char)*ca)) {
    167 					*ca = toupper((unsigned char)*ca);
    168 				} else if (isspace((unsigned char)*ca) || (*ca == '='))
    169 					break;
    170 			}
    171 		}
    172 
    173 		/* Remove space (and possible =) leading the arg */
    174 		for (; *ca && isascii((unsigned char)*ca) && (isspace((unsigned char)*ca) || (*ca == '=')); ca++)
    175 			continue;
    176 
    177 		if (!strncmp(cc, "IDEV", 4) &&
    178 		    1 == sscanf(ca, "%99s", ab)) {
    179 			strlcpy(cf_i_dev, ab, sizeof(cf_i_dev));
    180 			printf("idev <%s>\n", ab);
    181 		} else if (!strncmp(cc, "CDEV", 4) &&
    182 			   1 == sscanf(ca, "%99s", ab)) {
    183 			strlcpy(cf_c_dev, ab, sizeof(cf_c_dev));
    184 			printf("cdev <%s>\n", ab);
    185 		} else if (!strncmp(cc, "AGC", 3) &&
    186 			   1 == sscanf(ca, "%99s", ab)) {
    187 			strlcpy(cf_agc, ab, sizeof(cf_agc));
    188 			printf("agc <%s> %d\n", ab, i);
    189 		} else if (!strncmp(cc, "MONITOR", 7) &&
    190 			   1 == sscanf(ca, "%99s", ab)) {
    191 			strlcpy(cf_monitor, ab, sizeof(cf_monitor));
    192 			printf("monitor <%s> %d\n", ab, mixer_name(ab, -1));
    193 		}
    194 	}
    195 	fclose(fd);
    196 	return;
    197 }
    198 #endif /* PCM_STYLE_SOUND */
    199 
    200 /*
    201  * audio_init - open and initialize audio device
    202  *
    203  * This code works with SunOS 4.x, Solaris 2.x, and PCM; however, it is
    204  * believed generic and applicable to other systems with a minor twid
    205  * or two. All it does is open the device, set the buffer size (Solaris
    206  * only), preset the gain and set the input port. It assumes that the
    207  * codec sample rate (8000 Hz), precision (8 bits), number of channels
    208  * (1) and encoding (ITU-T G.711 mu-law companded) have been set by
    209  * default.
    210  */
    211 int
    212 audio_init(
    213 	const char *dname,	/* device name */
    214 	int	bufsiz,		/* buffer size */
    215 	int	unit		/* device unit (0-3) */
    216 	)
    217 {
    218 #ifdef PCM_STYLE_SOUND
    219 # define ACTL_DEV	"/dev/mixer%d"
    220 	char actl_dev[30];
    221 # ifdef HAVE_STRUCT_SND_SIZE
    222 	struct snd_size s_size;
    223 # endif
    224 # ifdef AIOGFMT
    225 	snd_chan_param s_c_p;
    226 # endif
    227 #endif
    228 	int fd;
    229 	int rval;
    230 	const char *actl =
    231 #ifdef PCM_STYLE_SOUND
    232 		actl_dev
    233 #else
    234 		"/dev/audioctl"
    235 #endif
    236 		;
    237 
    238 #ifdef PCM_STYLE_SOUND
    239 	snprintf(actl_dev, sizeof(actl_dev), ACTL_DEV, unit);
    240 
    241 	audio_config_read(unit, &actl, &dname);
    242 	/* If we have values for cf_c_dev or cf_i_dev, use them. */
    243 	if (*cf_c_dev)
    244 		actl = cf_c_dev;
    245 	if (*cf_i_dev)
    246 		dname = cf_i_dev;
    247 #endif
    248 
    249 	/*
    250 	 * Open audio device
    251 	 */
    252 	fd = open(dname, O_RDWR | O_NONBLOCK, 0777);
    253 	if (fd < 0) {
    254 		msyslog(LOG_ERR, "audio_init: %s %m", dname);
    255 		return (fd);
    256 	}
    257 
    258 	/*
    259 	 * Open audio control device.
    260 	 */
    261 	ctl_fd = open(actl, O_RDWR);
    262 	if (ctl_fd < 0) {
    263 		msyslog(LOG_ERR, "audio_init: invalid control device <%s>",
    264 		    actl);
    265 		close(fd);
    266 		return(ctl_fd);
    267 	}
    268 
    269 	/*
    270 	 * Set audio device parameters.
    271 	 */
    272 #ifdef PCM_STYLE_SOUND
    273 	printf("audio_init: <%s> bufsiz %d\n", dname, bufsiz);
    274 	rval = fd;
    275 
    276 # ifdef HAVE_STRUCT_SND_SIZE
    277 	if (ioctl(fd, AIOGSIZE, &s_size) == -1)
    278 	    printf("audio_init: AIOGSIZE: %s\n", strerror(errno));
    279 	else
    280 	    printf("audio_init: orig: play_size %d, rec_size %d\n",
    281 		s_size.play_size, s_size.rec_size);
    282 
    283 	s_size.play_size = s_size.rec_size = bufsiz;
    284 	printf("audio_init: want: play_size %d, rec_size %d\n",
    285 	       s_size.play_size, s_size.rec_size);
    286 
    287 	if (ioctl(fd, AIOSSIZE, &s_size) == -1)
    288 	    printf("audio_init: AIOSSIZE: %s\n", strerror(errno));
    289 	else
    290 	    printf("audio_init: set:  play_size %d, rec_size %d\n",
    291 		s_size.play_size, s_size.rec_size);
    292 # endif /* HAVE_STRUCT_SND_SIZE */
    293 
    294 # ifdef SNDCTL_DSP_SETFRAGMENT
    295 	{
    296 		int tmp = (16 << 16) + 6; /* 16 fragments, each 2^6 bytes */
    297 		if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
    298 		    printf("audio_init: SNDCTL_DSP_SETFRAGMENT: %s\n",
    299 			   strerror(errno));
    300 	}
    301 # endif /* SNDCTL_DSP_SETFRAGMENT */
    302 
    303 # ifdef AIOGFMT
    304 	if (ioctl(fd, AIOGFMT, &s_c_p) == -1)
    305 	    printf("audio_init: AIOGFMT: %s\n", strerror(errno));
    306 	else
    307 	    printf("audio_init: play_rate %lu, rec_rate %lu, play_format %#lx, rec_format %#lx\n",
    308 		s_c_p.play_rate, s_c_p.rec_rate, s_c_p.play_format, s_c_p.rec_format);
    309 # endif
    310 
    311 	/* Grab the device and record masks */
    312 
    313 	if (ioctl(ctl_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
    314 	    printf("SOUND_MIXER_READ_DEVMASK: %s\n", strerror(errno));
    315 	if (ioctl(ctl_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
    316 	    printf("SOUND_MIXER_READ_RECMASK: %s\n", strerror(errno));
    317 
    318 	/* validate and set any specified config file stuff */
    319 	if (cf_agc[0] != '\0') {
    320 		int i;
    321 
    322 		/* recmask */
    323 		i = mixer_name(cf_agc, recmask);
    324 		if (i >= 0)
    325 			agc = MIXER_WRITE(i);
    326 		else
    327 			printf("input %s not in recmask %#x\n",
    328 			       cf_agc, recmask);
    329 	}
    330 
    331 	if (cf_monitor[0] != '\0') {
    332 		int i;
    333 
    334 		/* devmask */
    335 		i = mixer_name(cf_monitor, devmask);
    336 		if (i >= 0)
    337 			monitor = MIXER_WRITE(i);
    338 		else
    339 			printf("monitor %s not in devmask %#x\n",
    340 			       cf_monitor, devmask);
    341 	}
    342 
    343 #else /* not PCM_STYLE_SOUND */
    344 	AUDIO_INITINFO(&info);
    345 	info.play.gain = AUDIO_MAX_GAIN;
    346 	info.play.port = AUDIO_SPEAKER;
    347 # ifdef HAVE_SYS_AUDIOIO_H
    348 	info.record.buffer_size = bufsiz;
    349 # endif /* HAVE_SYS_AUDIOIO_H */
    350 	rval = ioctl(ctl_fd, (int)AUDIO_SETINFO, (char *)&info);
    351 	if (rval < 0) {
    352 		msyslog(LOG_ERR, "audio: invalid control device parameters");
    353 		close(ctl_fd);
    354 		close(fd);
    355 		return(rval);
    356 	}
    357 	rval = fd;
    358 #endif /* not PCM_STYLE_SOUND */
    359 	return (rval);
    360 }
    361 
    362 
    363 /*
    364  * audio_gain - adjust codec gains and port
    365  */
    366 int
    367 audio_gain(
    368 	int gain,		/* volume level (gain) 0-255 */
    369 	int mongain,		/* input to output mix (monitor gain) 0-255 */
    370 	int port		/* selected I/O port: 1 mic/2 line in */
    371 	)
    372 {
    373 	int rval;
    374 	static int o_mongain = -1;
    375 	static int o_port = -1;
    376 
    377 #ifdef PCM_STYLE_SOUND
    378 	int l, r;
    379 
    380 # ifdef GCC
    381 	rval = 0;		/* GCC thinks rval is used uninitialized */
    382 # endif
    383 
    384 	r = l = 100 * gain / 255;	/* Normalize to 0-100 */
    385 # ifdef DEBUG
    386 	if (debug > 1)
    387 		printf("audio_gain: gain %d/%d\n", gain, l);
    388 # endif
    389 #if 0	/* not a good idea to do this; connector wiring dependency */
    390 	/* figure out what channel(s) to use. just nuke right for now. */
    391 	r = 0 ; /* setting to zero nicely mutes the channel */
    392 #endif
    393 	l |= r << 8;
    394 	if (cf_agc[0] != '\0')
    395 		rval = ioctl(ctl_fd, agc, &l);
    396 	else
    397 		rval = ioctl(ctl_fd
    398 			    , (2 == port)
    399 				? SOUND_MIXER_WRITE_LINE
    400 				: SOUND_MIXER_WRITE_MIC
    401 			    , &l);
    402 	if (-1 == rval) {
    403 		printf("audio_gain: agc write: %s\n", strerror(errno));
    404 		return rval;
    405 	}
    406 
    407 	if (o_mongain != mongain) {
    408 		r = l = 100 * mongain / 255;    /* Normalize to 0-100 */
    409 # ifdef DEBUG
    410 		if (debug > 1)
    411 			printf("audio_gain: mongain %d/%d\n", mongain, l);
    412 # endif
    413 		l |= r << 8;
    414 		if (cf_monitor[0] != '\0')
    415 			rval = ioctl(ctl_fd, monitor, &l );
    416 		else
    417 			rval = ioctl(ctl_fd, SOUND_MIXER_WRITE_VOLUME,
    418 				     &l);
    419 		if (-1 == rval) {
    420 			printf("audio_gain: mongain write: %s\n",
    421 			       strerror(errno));
    422 			return (rval);
    423 		}
    424 		o_mongain = mongain;
    425 	}
    426 
    427 	if (o_port != port) {
    428 # ifdef DEBUG
    429 		if (debug > 1)
    430 			printf("audio_gain: port %d\n", port);
    431 # endif
    432 		l = (1 << ((port == 2) ? SOUND_MIXER_LINE : SOUND_MIXER_MIC));
    433 		rval = ioctl(ctl_fd, SOUND_MIXER_WRITE_RECSRC, &l);
    434 		if (rval == -1) {
    435 			printf("SOUND_MIXER_WRITE_RECSRC: %s\n",
    436 			       strerror(errno));
    437 			return (rval);
    438 		}
    439 # ifdef DEBUG
    440 		if (debug > 1) {
    441 			if (ioctl(ctl_fd, SOUND_MIXER_READ_RECSRC, &l) == -1)
    442 				printf("SOUND_MIXER_WRITE_RECSRC: %s\n",
    443 				       strerror(errno));
    444 			else
    445 				printf("audio_gain: recsrc is %d\n", l);
    446 		}
    447 # endif
    448 		o_port = port;
    449 	}
    450 #else /* not PCM_STYLE_SOUND */
    451 	ioctl(ctl_fd, (int)AUDIO_GETINFO, (char *)&info);
    452 	info.record.encoding = AUDIO_ENCODING_ULAW;
    453 	info.record.error = 0;
    454 	info.record.gain = gain;
    455 	if (o_mongain != mongain)
    456 		o_mongain = info.monitor_gain = mongain;
    457 	if (o_port != port)
    458 		o_port = info.record.port = port;
    459 	rval = ioctl(ctl_fd, (int)AUDIO_SETINFO, (char *)&info);
    460 	if (rval < 0) {
    461 		msyslog(LOG_ERR, "audio_gain: %m");
    462 		return (rval);
    463 	}
    464 	rval = info.record.error;
    465 #endif /* not PCM_STYLE_SOUND */
    466 	return (rval);
    467 }
    468 
    469 
    470 /*
    471  * audio_show - display audio parameters
    472  *
    473  * This code doesn't really do anything, except satisfy curiousity and
    474  * verify the ioctl's work.
    475  */
    476 void
    477 audio_show(void)
    478 {
    479 #ifdef PCM_STYLE_SOUND
    480 	int recsrc = 0;
    481 
    482 	printf("audio_show: ctl_fd %d\n", ctl_fd);
    483 	if (ioctl(ctl_fd, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
    484 	    printf("SOUND_MIXER_READ_RECSRC: %s\n", strerror(errno));
    485 
    486 #else /* not PCM_STYLE_SOUND */
    487 # ifdef HAVE_SYS_AUDIOIO_H
    488 	ioctl(ctl_fd, (int)AUDIO_GETDEV, &device);
    489 	printf("audio: name %s, version %s, config %s\n",
    490 	    device.name, device.version, device.config);
    491 # endif /* HAVE_SYS_AUDIOIO_H */
    492 	ioctl(ctl_fd, (int)AUDIO_GETINFO, (char *)&info);
    493 	printf(
    494 	    "audio: rate %d, chan %d, prec %d, code %d, gain %d, mon %d, port %d\n",
    495 	    info.record.sample_rate, info.record.channels,
    496 	    info.record.precision, info.record.encoding,
    497 	    info.record.gain, info.monitor_gain, info.record.port);
    498 	printf(
    499 	    "audio: samples %d, eof %d, pause %d, error %d, waiting %d, balance %d\n",
    500 	    info.record.samples, info.record.eof,
    501 	    info.record.pause, info.record.error,
    502 	    info.record.waiting, info.record.balance);
    503 #endif /* not PCM_STYLE_SOUND */
    504 }
    505 #else
    506 int audio_bs;
    507 #endif /* HAVE_{SYS_AUDIOIO,SUN_AUDIOIO,MACHINE_SOUNDCARD,SYS_SOUNDCARD}_H */
    508