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