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