record.c revision 1.17 1 /* $NetBSD: record.c,v 1.17 2002/01/15 08:19:38 mrg 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
52 audio_info_t info, oinfo;
53 ssize_t total_size = -1;
54 char *device;
55 char *ctldev;
56 int format = AUDIO_FORMAT_SUN;
57 char *header_info;
58 char default_info[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' };
59 int audiofd, ctlfd, outfd;
60 int qflag, aflag, fflag;
61 int verbose;
62 int monitor_gain, omonitor_gain;
63 int gain;
64 int balance;
65 int port;
66 int encoding;
67 char *encoding_str;
68 int precision;
69 int sample_rate;
70 int channels;
71 struct timeval record_time;
72 struct timeval start_time; /* XXX because that's what gettimeofday returns */
73
74 void usage (void);
75 int main (int, char *[]);
76 int timeleft (struct timeval *, struct timeval *);
77 void cleanup (int) __attribute__((__noreturn__));
78 int write_header_sun (void **, size_t *, int *);
79 int write_header_wav (void **, size_t *, int *);
80 void write_header (void);
81 void rewrite_header (void);
82
83 int
84 main(argc, argv)
85 int argc;
86 char *argv[];
87 {
88 char *buffer;
89 size_t len, bufsize;
90 int ch, no_time_limit = 1;
91
92 while ((ch = getopt(argc, argv, "ab:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) {
93 switch (ch) {
94 case 'a':
95 aflag++;
96 break;
97 case 'b':
98 decode_int(optarg, &balance);
99 if (balance < 0 || balance > 63)
100 errx(1, "balance must be between 0 and 63\n");
101 break;
102 case 'C':
103 ctldev = optarg;
104 break;
105 case 'F':
106 format = audio_format_from_str(optarg);
107 if (format < 0)
108 errx(1, "Unknown audio format; "
109 "supported formats: \"sun\" and \"wav\"");
110 break;
111 case 'c':
112 decode_int(optarg, &channels);
113 if (channels < 0 || channels > 16)
114 errx(1, "channels must be between 0 and 16\n");
115 break;
116 case 'd':
117 device = optarg;
118 break;
119 case 'e':
120 encoding_str = optarg;
121 break;
122 case 'f':
123 fflag++;
124 break;
125 case 'i':
126 header_info = optarg;
127 break;
128 case 'm':
129 decode_int(optarg, &monitor_gain);
130 if (monitor_gain < 0 || monitor_gain > 255)
131 errx(1, "monitor volume must be between 0 and 255\n");
132 break;
133 case 'P':
134 decode_int(optarg, &precision);
135 if (precision != 4 && precision != 8 &&
136 precision != 16 && precision != 24 &&
137 precision != 32)
138 errx(1, "precision must be between 4, 8, 16, 24 or 32");
139 break;
140 case 'p':
141 len = strlen(optarg);
142
143 if (strncmp(optarg, "mic", len) == 0)
144 port |= AUDIO_MICROPHONE;
145 else if (strncmp(optarg, "cd", len) == 0 ||
146 strncmp(optarg, "internal-cd", len) == 0)
147 port |= AUDIO_CD;
148 else if (strncmp(optarg, "line", len) == 0)
149 port |= AUDIO_LINE_IN;
150 else
151 errx(1,
152 "port must be `cd', `internal-cd', `mic', or `line'");
153 break;
154 case 'q':
155 qflag++;
156 break;
157 case 's':
158 decode_int(optarg, &sample_rate);
159 if (sample_rate < 0 || sample_rate > 48000 * 2) /* XXX */
160 errx(1, "sample rate must be between 0 and 96000\n");
161 break;
162 case 't':
163 no_time_limit = 0;
164 decode_time(optarg, &record_time);
165 break;
166 case 'V':
167 verbose++;
168 break;
169 case 'v':
170 decode_int(optarg, &gain);
171 if (gain < 0 || gain > 255)
172 errx(1, "volume must be between 0 and 255\n");
173 break;
174 /* case 'h': */
175 default:
176 usage();
177 /* NOTREACHED */
178 }
179 }
180 argc -= optind;
181 argv += optind;
182
183 /*
184 * open the audio device, and control device
185 */
186 if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL &&
187 (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */
188 device = _PATH_AUDIO;
189 if (ctldev == NULL && (ctldev = getenv("AUDIOCTLDEVICE")) == NULL)
190 ctldev = _PATH_AUDIOCTL;
191
192 audiofd = open(device, O_RDONLY);
193 if (audiofd < 0)
194 err(1, "failed to open %s", device);
195 ctlfd = open(ctldev, O_RDWR);
196 if (ctlfd < 0)
197 err(1, "failed to open %s", ctldev);
198
199 /*
200 * work out the buffer size to use, and allocate it. also work out
201 * what the old monitor gain value is, so that we can reset it later.
202 */
203 if (ioctl(ctlfd, AUDIO_GETINFO, &oinfo) < 0)
204 err(1, "failed to get audio info");
205 bufsize = oinfo.record.buffer_size;
206 if (bufsize < 32 * 1024)
207 bufsize = 32 * 1024;
208 omonitor_gain = oinfo.monitor_gain;
209
210 buffer = malloc(bufsize);
211 if (buffer == NULL)
212 err(1, "couldn't malloc buffer of %d size", (int)bufsize);
213
214 /*
215 * open the output file
216 */
217 if (argc != 1)
218 usage();
219 if (argv[0][0] != '-' && argv[0][1] != '\0') {
220 outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666);
221 if (outfd < 0)
222 err(1, "could not open %s", *argv);
223 } else
224 outfd = STDOUT_FILENO;
225
226 /*
227 * convert the encoding string into a value.
228 */
229 if (encoding_str) {
230 encoding = audio_enc_to_val(encoding_str);
231 if (encoding == -1)
232 errx(1, "unknown encoding, bailing...");
233 }
234 else
235 encoding = AUDIO_ENCODING_ULAW;
236
237 /*
238 * set up audio device for recording with the speified parameters
239 */
240 AUDIO_INITINFO(&info);
241
242 /*
243 * for these, get the current values for stuffing into the header
244 */
245 #define SETINFO(x) if (x) info.record.x = x; else x = oinfo.record.x
246 SETINFO (sample_rate);
247 SETINFO (channels);
248 SETINFO (precision);
249 SETINFO (encoding);
250 SETINFO (gain);
251 SETINFO (port);
252 SETINFO (balance);
253 #undef SETINFO
254
255 if (monitor_gain)
256 info.monitor_gain = monitor_gain;
257 else
258 monitor_gain = oinfo.monitor_gain;
259
260 info.mode = AUMODE_RECORD;
261 if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0)
262 err(1, "failed to reset audio info");
263
264 signal(SIGINT, cleanup);
265 write_header();
266 total_size = 0;
267
268 (void)gettimeofday(&start_time, NULL);
269 while (no_time_limit || timeleft(&start_time, &record_time)) {
270 if (read(audiofd, buffer, bufsize) != bufsize)
271 err(1, "read failed");
272 if (write(outfd, buffer, bufsize) != bufsize)
273 err(1, "write failed");
274 total_size += bufsize;
275 }
276 cleanup(0);
277 }
278
279 int
280 timeleft(start_tvp, record_tvp)
281 struct timeval *start_tvp;
282 struct timeval *record_tvp;
283 {
284 struct timeval now, diff;
285
286 (void)gettimeofday(&now, NULL);
287 timersub(&now, start_tvp, &diff);
288 timersub(record_tvp, &diff, &now);
289
290 return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0));
291 }
292
293 void
294 cleanup(signo)
295 int signo;
296 {
297
298 close(audiofd);
299 rewrite_header();
300 close(outfd);
301 if (omonitor_gain) {
302 AUDIO_INITINFO(&info);
303 info.monitor_gain = omonitor_gain;
304 if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0)
305 err(1, "failed to reset audio info");
306 }
307 close(ctlfd);
308 exit(0);
309 }
310
311 int
312 write_header_sun(hdrp, lenp, leftp)
313 void **hdrp;
314 size_t *lenp;
315 int *leftp;
316 {
317 static int warned = 0;
318 static sun_audioheader auh;
319 int sunenc;
320
321 /* if we can't express this as a Sun header, don't write any */
322 if (audio_encoding_to_sun(encoding, precision, &sunenc) != 0) {
323 if (!qflag && !warned)
324 warnx("failed to convert to sun encoding from %d:%d; "
325 "Sun audio header not written",
326 encoding, precision);
327 warned = 1;
328 return -1;
329 }
330
331 auh.magic = htonl(AUDIO_FILE_MAGIC);
332 if (outfd == STDOUT_FILENO)
333 auh.data_size = htonl(AUDIO_UNKNOWN_SIZE);
334 else
335 auh.data_size = htonl(total_size);
336 auh.encoding = htonl(sunenc);
337 auh.sample_rate = htonl(sample_rate);
338 auh.channels = htonl(channels);
339 if (header_info) {
340 int len, infolen;
341
342 infolen = ((len = strlen(header_info)) + 7) & 0xfffffff8;
343 *leftp = infolen - len;
344 auh.hdr_size = htonl(sizeof(auh) + infolen);
345 } else {
346 *leftp = sizeof(default_info);
347 auh.hdr_size = htonl(sizeof(auh) + *leftp);
348 }
349 *(sun_audioheader **)hdrp = &auh;
350 *lenp = sizeof auh;
351 return 0;
352 }
353
354 int
355 write_header_wav(hdrp, lenp, leftp)
356 void **hdrp;
357 size_t *lenp;
358 int *leftp;
359 {
360 /*
361 * WAV header we write looks like this:
362 *
363 * bytes purpose
364 * 0-3 "RIFF"
365 * 4-7 file length (minus 8)
366 * 8-15 "WAVEfmt "
367 * 16-19 format size
368 * 20-21 format tag
369 * 22-23 number of channels
370 * 24-27 sample rate
371 * 28-31 average bytes per second
372 * 32-33 block alignment
373 * 34-35 bits per sample
374 *
375 * then for ULAW and ALAW outputs, we have an extended chunk size
376 * and a WAV "fact" to add:
377 *
378 * 36-37 length of extension (== 0)
379 * 38-41 "fact"
380 * 42-45 fact size
381 * 46-49 number of samples written
382 * 50-53 "data"
383 * 54-57 data length
384 * 58- raw audio data
385 *
386 * for PCM outputs we have just the data remaining:
387 *
388 * 36-39 "data"
389 * 40-43 data length
390 * 44- raw audio data
391 *
392 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
393 */
394 char wavheaderbuf[64], *p = wavheaderbuf;
395 char *riff = "RIFF", *wavefmt = "WAVEfmt ", *fact = "fact", *data = "data";
396 u_int32_t filelen, fmtsz, sps, abps, factsz = 4, nsample, datalen;
397 u_int16_t fmttag, nchan, align, bps, extln = 0;
398 static int ewarned = 0, pwarned = 0;
399
400 if (header_info)
401 warnx("header information not supported for WAV");
402 *leftp = NULL;
403
404 switch (precision) {
405 case 8:
406 bps = 8;
407 break;
408 case 16:
409 bps = 16;
410 break;
411 case 32:
412 bps = 32;
413 break;
414 default:
415 if (pwarned == 0) {
416 warnx("can not support precision of %d\n", precision);
417 pwarned = 1;
418 }
419 return (-1);
420 }
421
422 switch (encoding) {
423 case AUDIO_ENCODING_ULAW:
424 fmttag = WAVE_FORMAT_MULAW;
425 fmtsz = 18;
426 align = channels;
427 break;
428 case AUDIO_ENCODING_ALAW:
429 fmttag = WAVE_FORMAT_ALAW;
430 fmtsz = 18;
431 align = channels;
432 break;
433 case AUDIO_ENCODING_PCM16:
434 fmttag = WAVE_FORMAT_PCM;
435 fmtsz = 16;
436 align = channels * (bps / 8);
437 break;
438 default:
439 if (ewarned == 0) {
440 warnx("can not support encoding of %s\n", wav_enc_from_val(encoding));
441 ewarned = 1;
442 }
443 return (-1);
444 }
445
446 nchan = channels;
447 sps = sample_rate;
448
449 /* data length */
450 if (outfd == STDOUT_FILENO)
451 datalen = 0;
452 else
453 datalen = total_size;
454
455 /* file length */
456 filelen = 4 + (8 + fmtsz) + (8 + datalen);
457 if (fmttag != WAVE_FORMAT_PCM)
458 filelen += 8 + factsz;
459
460 abps = (double)align*sample_rate / (double)1 + 0.5;
461
462 nsample = (datalen / bps) / sample_rate;
463
464 /*
465 * now we've calculated the info, write it out!
466 */
467 #define put32(x) do { \
468 u_int32_t _f; \
469 putle32(_f, (x)); \
470 memcpy(p, &_f, 4); \
471 } while (0)
472 #define put16(x) do { \
473 u_int16_t _f; \
474 putle16(_f, (x)); \
475 memcpy(p, &_f, 2); \
476 } while (0)
477 memcpy(p, riff, 4);
478 p += 4; /* 4 */
479 put32(filelen);
480 p += 4; /* 8 */
481 memcpy(p, wavefmt, 8);
482 p += 8; /* 16 */
483 put32(fmtsz);
484 p += 4; /* 20 */
485 put16(fmttag);
486 p += 2; /* 22 */
487 put16(nchan);
488 p += 2; /* 24 */
489 put32(sps);
490 p += 4; /* 28 */
491 put32(abps);
492 p += 4; /* 32 */
493 put16(align);
494 p += 2; /* 34 */
495 put16(bps);
496 p += 2; /* 36 */
497 /* NON PCM formats have an extended chunk; write it */
498 if (fmttag != WAVE_FORMAT_PCM) {
499 put16(extln);
500 p += 2; /* 38 */
501 memcpy(p, fact, 4);
502 p += 4; /* 42 */
503 put32(factsz);
504 p += 4; /* 46 */
505 put32(nsample);
506 p += 4; /* 50 */
507 }
508 memcpy(p, data, 4);
509 p += 4; /* 40/54 */
510 put32(datalen);
511 p += 4; /* 44/58 */
512 #undef put32
513 #undef put16
514
515 *hdrp = wavheaderbuf;
516 *lenp = (p - wavheaderbuf);
517
518 return 0;
519 }
520
521 void
522 write_header()
523 {
524 struct iovec iv[3];
525 int veclen, left, tlen;
526 void *hdr;
527 size_t hdrlen;
528
529 switch (format) {
530 case AUDIO_FORMAT_SUN:
531 if (write_header_sun(&hdr, &hdrlen, &left) != 0)
532 return;
533 break;
534 case AUDIO_FORMAT_WAV:
535 if (write_header_wav(&hdr, &hdrlen, &left) != 0)
536 return;
537 break;
538 case AUDIO_FORMAT_NONE:
539 return;
540 default:
541 errx(1, "unknown audio format");
542 }
543
544 veclen = 0;
545 tlen = 0;
546
547 if (hdrlen != 0) {
548 iv[veclen].iov_base = hdr;
549 iv[veclen].iov_len = hdrlen;
550 tlen += iv[veclen++].iov_len;
551 }
552 if (header_info) {
553 iv[veclen].iov_base = header_info;
554 iv[veclen].iov_len = (int)strlen(header_info) + 1;
555 tlen += iv[veclen++].iov_len;
556 }
557 if (left) {
558 iv[veclen].iov_base = default_info;
559 iv[veclen].iov_len = left;
560 tlen += iv[veclen++].iov_len;
561 }
562
563 if (tlen == 0)
564 return;
565
566 if (writev(outfd, iv, veclen) != tlen)
567 err(1, "could not write audio header");
568 }
569
570 void
571 rewrite_header()
572 {
573
574 /* can't do this here! */
575 if (outfd == STDOUT_FILENO)
576 return;
577
578 if (lseek(outfd, SEEK_SET, 0) < 0)
579 err(1, "could not seek to start of file for header rewrite");
580 write_header();
581 }
582
583 void
584 usage()
585 {
586
587 fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n",
588 getprogname());
589 fprintf(stderr, "Options:\n\t"
590 "-C audio control device\n\t"
591 "-F format\n\t"
592 "-b balance (0-63)\n\t"
593 "-c channels\n\t"
594 "-d audio device\n\t"
595 "-e encoding\n\t"
596 "-i header information\n\t"
597 "-m monitor volume\n\t"
598 "-P precision bits (4, 8, 16, 24 or 32)\n\t"
599 "-p input port\n\t"
600 "-s sample rate\n\t"
601 "-t recording time\n\t"
602 "-v volume\n");
603 exit(EXIT_FAILURE);
604 }
605