1 /* $NetBSD: record.c,v 1.22 2002/01/31 00:03:24 augustss Exp $ */ 2 3 /* 4 * Copyright (c) 1999 Matthew R. Green 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 /* 32 * SunOS compatible audiorecord(1) 33 */ 34 35 #include <sys/types.h> 36 #include <sys/audioio.h> 37 #include <sys/ioctl.h> 38 #include <sys/time.h> 39 #include <sys/uio.h> 40 41 #include <err.h> 42 #include <fcntl.h> 43 #include <paths.h> 44 #include <signal.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include "libaudio.h" 51 #include "auconv.h" 52 53 audio_info_t info, oinfo; 54 ssize_t total_size = -1; 55 const char *device; 56 const char *ctldev; 57 int format = AUDIO_FORMAT_SUN; 58 char *header_info; 59 char default_info[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; 60 int audiofd, ctlfd, outfd; 61 int qflag, aflag, fflag; 62 int verbose; 63 int monitor_gain, omonitor_gain; 64 int gain; 65 int balance; 66 int port; 67 int encoding; 68 char *encoding_str; 69 int precision; 70 int sample_rate; 71 int channels; 72 struct timeval record_time; 73 struct timeval start_time; /* XXX because that's what gettimeofday returns */ 74 75 void (*conv_func) (u_char *, size_t); 76 77 void usage (void); 78 int main (int, char *[]); 79 int timeleft (struct timeval *, struct timeval *); 80 void cleanup (int) __attribute__((__noreturn__)); 81 int write_header_sun (void **, size_t *, int *); 82 int write_header_wav (void **, size_t *, int *); 83 void write_header (void); 84 void rewrite_header (void); 85 86 int 87 main(argc, argv) 88 int argc; 89 char *argv[]; 90 { 91 char *buffer; 92 size_t len, bufsize; 93 int ch, no_time_limit = 1; 94 95 while ((ch = getopt(argc, argv, "ab:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) { 96 switch (ch) { 97 case 'a': 98 aflag++; 99 break; 100 case 'b': 101 decode_int(optarg, &balance); 102 if (balance < 0 || balance > 63) 103 errx(1, "balance must be between 0 and 63\n"); 104 break; 105 case 'C': 106 ctldev = optarg; 107 break; 108 case 'F': 109 format = audio_format_from_str(optarg); 110 if (format < 0) 111 errx(1, "Unknown audio format; " 112 "supported formats: \"sun\" and \"wav\""); 113 break; 114 case 'c': 115 decode_int(optarg, &channels); 116 if (channels < 0 || channels > 16) 117 errx(1, "channels must be between 0 and 16\n"); 118 break; 119 case 'd': 120 device = optarg; 121 break; 122 case 'e': 123 encoding_str = optarg; 124 break; 125 case 'f': 126 fflag++; 127 break; 128 case 'i': 129 header_info = optarg; 130 break; 131 case 'm': 132 decode_int(optarg, &monitor_gain); 133 if (monitor_gain < 0 || monitor_gain > 255) 134 errx(1, "monitor volume must be between 0 and 255\n"); 135 break; 136 case 'P': 137 decode_int(optarg, &precision); 138 if (precision != 4 && precision != 8 && 139 precision != 16 && precision != 24 && 140 precision != 32) 141 errx(1, "precision must be between 4, 8, 16, 24 or 32"); 142 break; 143 case 'p': 144 len = strlen(optarg); 145 146 if (strncmp(optarg, "mic", len) == 0) 147 port |= AUDIO_MICROPHONE; 148 else if (strncmp(optarg, "cd", len) == 0 || 149 strncmp(optarg, "internal-cd", len) == 0) 150 port |= AUDIO_CD; 151 else if (strncmp(optarg, "line", len) == 0) 152 port |= AUDIO_LINE_IN; 153 else 154 errx(1, 155 "port must be `cd', `internal-cd', `mic', or `line'"); 156 break; 157 case 'q': 158 qflag++; 159 break; 160 case 's': 161 decode_int(optarg, &sample_rate); 162 if (sample_rate < 0 || sample_rate > 48000 * 2) /* XXX */ 163 errx(1, "sample rate must be between 0 and 96000\n"); 164 break; 165 case 't': 166 no_time_limit = 0; 167 decode_time(optarg, &record_time); 168 break; 169 case 'V': 170 verbose++; 171 break; 172 case 'v': 173 decode_int(optarg, &gain); 174 if (gain < 0 || gain > 255) 175 errx(1, "volume must be between 0 and 255\n"); 176 break; 177 /* case 'h': */ 178 default: 179 usage(); 180 /* NOTREACHED */ 181 } 182 } 183 argc -= optind; 184 argv += optind; 185 186 /* 187 * open the audio device, and control device 188 */ 189 if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL && 190 (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */ 191 device = _PATH_SOUND; 192 if (ctldev == NULL && (ctldev = getenv("AUDIOCTLDEVICE")) == NULL) 193 ctldev = _PATH_AUDIOCTL; 194 195 audiofd = open(device, O_RDONLY); 196 if (audiofd < 0 && device == _PATH_SOUND) { 197 device = _PATH_SOUND0; 198 ctldev = _PATH_AUDIOCTL0; 199 audiofd = open(device, O_WRONLY); 200 } 201 if (audiofd < 0) 202 err(1, "failed to open %s", device); 203 ctlfd = open(ctldev, O_RDWR); 204 if (ctlfd < 0) 205 err(1, "failed to open %s", ctldev); 206 207 /* 208 * work out the buffer size to use, and allocate it. also work out 209 * what the old monitor gain value is, so that we can reset it later. 210 */ 211 if (ioctl(ctlfd, AUDIO_GETINFO, &oinfo) < 0) 212 err(1, "failed to get audio info"); 213 bufsize = oinfo.record.buffer_size; 214 if (bufsize < 32 * 1024) 215 bufsize = 32 * 1024; 216 omonitor_gain = oinfo.monitor_gain; 217 218 buffer = malloc(bufsize); 219 if (buffer == NULL) 220 err(1, "couldn't malloc buffer of %d size", (int)bufsize); 221 222 /* 223 * open the output file 224 */ 225 if (argc != 1) 226 usage(); 227 if (argv[0][0] != '-' && argv[0][1] != '\0') { 228 outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666); 229 if (outfd < 0) 230 err(1, "could not open %s", *argv); 231 } else 232 outfd = STDOUT_FILENO; 233 234 /* 235 * convert the encoding string into a value. 236 */ 237 if (encoding_str) { 238 encoding = audio_enc_to_val(encoding_str); 239 if (encoding == -1) 240 errx(1, "unknown encoding, bailing..."); 241 } 242 else 243 encoding = AUDIO_ENCODING_ULAW; 244 245 /* 246 * set up audio device for recording with the speified parameters 247 */ 248 AUDIO_INITINFO(&info); 249 250 /* 251 * for these, get the current values for stuffing into the header 252 */ 253 #define SETINFO(x) if (x) info.record.x = x; else x = oinfo.record.x 254 SETINFO (sample_rate); 255 SETINFO (channels); 256 SETINFO (precision); 257 SETINFO (encoding); 258 SETINFO (gain); 259 SETINFO (port); 260 SETINFO (balance); 261 #undef SETINFO 262 263 if (monitor_gain) 264 info.monitor_gain = monitor_gain; 265 else 266 monitor_gain = oinfo.monitor_gain; 267 268 info.mode = AUMODE_RECORD; 269 if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0) 270 err(1, "failed to reset audio info"); 271 272 signal(SIGINT, cleanup); 273 write_header(); 274 total_size = 0; 275 276 (void)gettimeofday(&start_time, NULL); 277 while (no_time_limit || timeleft(&start_time, &record_time)) { 278 if (read(audiofd, buffer, bufsize) != bufsize) 279 err(1, "read failed"); 280 if (conv_func) 281 (*conv_func)(buffer, bufsize); 282 if (write(outfd, buffer, bufsize) != bufsize) 283 err(1, "write failed"); 284 total_size += bufsize; 285 } 286 cleanup(0); 287 } 288 289 int 290 timeleft(start_tvp, record_tvp) 291 struct timeval *start_tvp; 292 struct timeval *record_tvp; 293 { 294 struct timeval now, diff; 295 296 (void)gettimeofday(&now, NULL); 297 timersub(&now, start_tvp, &diff); 298 timersub(record_tvp, &diff, &now); 299 300 return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0)); 301 } 302 303 void 304 cleanup(signo) 305 int signo; 306 { 307 308 close(audiofd); 309 rewrite_header(); 310 close(outfd); 311 if (omonitor_gain) { 312 AUDIO_INITINFO(&info); 313 info.monitor_gain = omonitor_gain; 314 if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0) 315 err(1, "failed to reset audio info"); 316 } 317 close(ctlfd); 318 exit(0); 319 } 320 321 int 322 write_header_sun(hdrp, lenp, leftp) 323 void **hdrp; 324 size_t *lenp; 325 int *leftp; 326 { 327 static int warned = 0; 328 static sun_audioheader auh; 329 int sunenc, oencoding = encoding; 330 331 if (encoding == AUDIO_ENCODING_ULINEAR_LE) { 332 if (precision == 16) 333 conv_func = swap_bytes; 334 else if (precision == 32) 335 conv_func = swap_bytes32; 336 if (conv_func) 337 encoding = AUDIO_ENCODING_SLINEAR_BE; 338 } else if (encoding == AUDIO_ENCODING_ULINEAR_BE) { 339 if (precision == 16) 340 conv_func = change_sign16_be; 341 else if (precision == 32) 342 conv_func = change_sign32_be; 343 if (conv_func) 344 encoding = AUDIO_ENCODING_SLINEAR_BE; 345 } else if (encoding == AUDIO_ENCODING_SLINEAR_LE) { 346 if (precision == 16) 347 conv_func = change_sign16_swap_bytes_le; 348 else if (precision == 32) 349 conv_func = change_sign32_swap_bytes_le; 350 if (conv_func) 351 encoding = AUDIO_ENCODING_SLINEAR_BE; 352 } 353 354 /* if we can't express this as a Sun header, don't write any */ 355 if (audio_encoding_to_sun(encoding, precision, &sunenc) != 0) { 356 if (!qflag && !warned) 357 warnx("failed to convert to sun encoding from %d:%d; " 358 "Sun audio header not written", 359 oencoding, precision); 360 conv_func = 0; 361 warned = 1; 362 return -1; 363 } 364 365 auh.magic = htonl(AUDIO_FILE_MAGIC); 366 if (outfd == STDOUT_FILENO) 367 auh.data_size = htonl(AUDIO_UNKNOWN_SIZE); 368 else 369 auh.data_size = htonl(total_size); 370 auh.encoding = htonl(sunenc); 371 auh.sample_rate = htonl(sample_rate); 372 auh.channels = htonl(channels); 373 if (header_info) { 374 int len, infolen; 375 376 infolen = ((len = strlen(header_info)) + 7) & 0xfffffff8; 377 *leftp = infolen - len; 378 auh.hdr_size = htonl(sizeof(auh) + infolen); 379 } else { 380 *leftp = sizeof(default_info); 381 auh.hdr_size = htonl(sizeof(auh) + *leftp); 382 } 383 *(sun_audioheader **)hdrp = &auh; 384 *lenp = sizeof auh; 385 return 0; 386 } 387 388 int 389 write_header_wav(hdrp, lenp, leftp) 390 void **hdrp; 391 size_t *lenp; 392 int *leftp; 393 { 394 /* 395 * WAV header we write looks like this: 396 * 397 * bytes purpose 398 * 0-3 "RIFF" 399 * 4-7 file length (minus 8) 400 * 8-15 "WAVEfmt " 401 * 16-19 format size 402 * 20-21 format tag 403 * 22-23 number of channels 404 * 24-27 sample rate 405 * 28-31 average bytes per second 406 * 32-33 block alignment 407 * 34-35 bits per sample 408 * 409 * then for ULAW and ALAW outputs, we have an extended chunk size 410 * and a WAV "fact" to add: 411 * 412 * 36-37 length of extension (== 0) 413 * 38-41 "fact" 414 * 42-45 fact size 415 * 46-49 number of samples written 416 * 50-53 "data" 417 * 54-57 data length 418 * 58- raw audio data 419 * 420 * for PCM outputs we have just the data remaining: 421 * 422 * 36-39 "data" 423 * 40-43 data length 424 * 44- raw audio data 425 * 426 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@ 427 */ 428 char wavheaderbuf[64], *p = wavheaderbuf; 429 const char *riff = "RIFF", 430 *wavefmt = "WAVEfmt ", 431 *fact = "fact", 432 *data = "data"; 433 u_int32_t filelen, fmtsz, sps, abps, factsz = 4, nsample, datalen; 434 u_int16_t fmttag, nchan, align, bps, extln = 0; 435 436 if (header_info) 437 warnx("header information not supported for WAV"); 438 *leftp = NULL; 439 440 switch (precision) { 441 case 8: 442 bps = 8; 443 break; 444 case 16: 445 bps = 16; 446 break; 447 case 32: 448 bps = 32; 449 break; 450 default: 451 { 452 static int warned = 0; 453 454 if (warned == 0) { 455 warnx("can not support precision of %d\n", precision); 456 warned = 1; 457 } 458 } 459 return (-1); 460 } 461 462 switch (encoding) { 463 case AUDIO_ENCODING_ULAW: 464 fmttag = WAVE_FORMAT_MULAW; 465 fmtsz = 18; 466 align = channels; 467 break; 468 case AUDIO_ENCODING_ALAW: 469 fmttag = WAVE_FORMAT_ALAW; 470 fmtsz = 18; 471 align = channels; 472 break; 473 /* 474 * we could try to support RIFX but it seems to be more portable 475 * to output little-endian data for WAV files. 476 */ 477 case AUDIO_ENCODING_ULINEAR_BE: 478 if (bps == 16) 479 conv_func = change_sign16_swap_bytes_be; 480 else if (bps == 32) 481 conv_func = change_sign32_swap_bytes_be; 482 goto fmt_pcm; 483 case AUDIO_ENCODING_SLINEAR_BE: 484 if (bps == 16) 485 conv_func = swap_bytes; 486 else if (bps == 32) 487 conv_func = swap_bytes32; 488 goto fmt_pcm; 489 case AUDIO_ENCODING_ULINEAR_LE: 490 if (bps == 16) 491 conv_func = change_sign16_le; 492 else if (bps == 32) 493 conv_func = change_sign32_le; 494 /* FALLTHROUGH */ 495 case AUDIO_ENCODING_SLINEAR_LE: 496 case AUDIO_ENCODING_PCM16: 497 fmt_pcm: 498 fmttag = WAVE_FORMAT_PCM; 499 fmtsz = 16; 500 align = channels * (bps / 8); 501 break; 502 default: 503 { 504 static int warned = 0; 505 506 if (warned == 0) { 507 warnx("can not support encoding of %s\n", wav_enc_from_val(encoding)); 508 warned = 1; 509 } 510 } 511 return (-1); 512 } 513 514 nchan = channels; 515 sps = sample_rate; 516 517 /* data length */ 518 if (outfd == STDOUT_FILENO) 519 datalen = 0; 520 else 521 datalen = total_size; 522 523 /* file length */ 524 filelen = 4 + (8 + fmtsz) + (8 + datalen); 525 if (fmttag != WAVE_FORMAT_PCM) 526 filelen += 8 + factsz; 527 528 abps = (double)align*sample_rate / (double)1 + 0.5; 529 530 nsample = (datalen / bps) / sample_rate; 531 532 /* 533 * now we've calculated the info, write it out! 534 */ 535 #define put32(x) do { \ 536 u_int32_t _f; \ 537 putle32(_f, (x)); \ 538 memcpy(p, &_f, 4); \ 539 } while (0) 540 #define put16(x) do { \ 541 u_int16_t _f; \ 542 putle16(_f, (x)); \ 543 memcpy(p, &_f, 2); \ 544 } while (0) 545 memcpy(p, riff, 4); 546 p += 4; /* 4 */ 547 put32(filelen); 548 p += 4; /* 8 */ 549 memcpy(p, wavefmt, 8); 550 p += 8; /* 16 */ 551 put32(fmtsz); 552 p += 4; /* 20 */ 553 put16(fmttag); 554 p += 2; /* 22 */ 555 put16(nchan); 556 p += 2; /* 24 */ 557 put32(sps); 558 p += 4; /* 28 */ 559 put32(abps); 560 p += 4; /* 32 */ 561 put16(align); 562 p += 2; /* 34 */ 563 put16(bps); 564 p += 2; /* 36 */ 565 /* NON PCM formats have an extended chunk; write it */ 566 if (fmttag != WAVE_FORMAT_PCM) { 567 put16(extln); 568 p += 2; /* 38 */ 569 memcpy(p, fact, 4); 570 p += 4; /* 42 */ 571 put32(factsz); 572 p += 4; /* 46 */ 573 put32(nsample); 574 p += 4; /* 50 */ 575 } 576 memcpy(p, data, 4); 577 p += 4; /* 40/54 */ 578 put32(datalen); 579 p += 4; /* 44/58 */ 580 #undef put32 581 #undef put16 582 583 *hdrp = wavheaderbuf; 584 *lenp = (p - wavheaderbuf); 585 586 return 0; 587 } 588 589 void 590 write_header() 591 { 592 struct iovec iv[3]; 593 int veclen, left, tlen; 594 void *hdr; 595 size_t hdrlen; 596 597 switch (format) { 598 case AUDIO_FORMAT_SUN: 599 if (write_header_sun(&hdr, &hdrlen, &left) != 0) 600 return; 601 break; 602 case AUDIO_FORMAT_WAV: 603 if (write_header_wav(&hdr, &hdrlen, &left) != 0) 604 return; 605 break; 606 case AUDIO_FORMAT_NONE: 607 return; 608 default: 609 errx(1, "unknown audio format"); 610 } 611 612 veclen = 0; 613 tlen = 0; 614 615 if (hdrlen != 0) { 616 iv[veclen].iov_base = hdr; 617 iv[veclen].iov_len = hdrlen; 618 tlen += iv[veclen++].iov_len; 619 } 620 if (header_info) { 621 iv[veclen].iov_base = header_info; 622 iv[veclen].iov_len = (int)strlen(header_info) + 1; 623 tlen += iv[veclen++].iov_len; 624 } 625 if (left) { 626 iv[veclen].iov_base = default_info; 627 iv[veclen].iov_len = left; 628 tlen += iv[veclen++].iov_len; 629 } 630 631 if (tlen == 0) 632 return; 633 634 if (writev(outfd, iv, veclen) != tlen) 635 err(1, "could not write audio header"); 636 } 637 638 void 639 rewrite_header() 640 { 641 642 /* can't do this here! */ 643 if (outfd == STDOUT_FILENO) 644 return; 645 646 if (lseek(outfd, SEEK_SET, 0) < 0) 647 err(1, "could not seek to start of file for header rewrite"); 648 write_header(); 649 } 650 651 void 652 usage() 653 { 654 655 fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n", 656 getprogname()); 657 fprintf(stderr, "Options:\n\t" 658 "-C audio control device\n\t" 659 "-F format\n\t" 660 "-b balance (0-63)\n\t" 661 "-c channels\n\t" 662 "-d audio device\n\t" 663 "-e encoding\n\t" 664 "-i header information\n\t" 665 "-m monitor volume\n\t" 666 "-P precision bits (4, 8, 16, 24 or 32)\n\t" 667 "-p input port\n\t" 668 "-s sample rate\n\t" 669 "-t recording time\n\t" 670 "-v volume\n"); 671 exit(EXIT_FAILURE); 672 } 673