1 /* $NetBSD: record.c,v 1.46 2008/05/29 14:51:27 mrg Exp $ */
4 * Copyright (c) 1999, 2002 Matthew R. Green
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SunOS compatible audiorecord(1)
32 #include <sys/cdefs.h>
35 __RCSID("$NetBSD: record.c,v 1.46 2008/05/29 14:51:27 mrg Exp $");
39 #include <sys/types.h>
40 #include <sys/audioio.h>
41 #include <sys/ioctl.h>
58 audio_info_t info
, oinfo
;
59 ssize_t total_size
= -1;
61 int format
= AUDIO_FORMAT_DEFAULT
;
63 char default_info
[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' };
65 int qflag
, aflag
, fflag
;
67 int monitor_gain
, omonitor_gain
;
76 struct timeval record_time
;
77 struct timeval start_time
;
79 void (*conv_func
) (u_char
*, int);
82 int main (int, char *[]);
83 int timeleft (struct timeval
*, struct timeval
*);
84 void cleanup (int) __dead
;
85 int write_header_sun (void **, size_t *, int *);
86 int write_header_wav (void **, size_t *, int *);
87 void write_header (void);
88 void rewrite_header (void);
97 int ch
, no_time_limit
= 1;
98 const char *defdevice
= _PATH_SOUND
;
100 while ((ch
= getopt(argc
, argv
, "ab:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) {
106 decode_int(optarg
, &balance
);
107 if (balance
< 0 || balance
> 63)
108 errx(1, "balance must be between 0 and 63");
111 /* Ignore, compatibility */
114 format
= audio_format_from_str(optarg
);
116 errx(1, "Unknown audio format; supported "
117 "formats: \"sun\", \"wav\", and \"none\"");
120 decode_int(optarg
, &channels
);
121 if (channels
< 0 || channels
> 16)
122 errx(1, "channels must be between 0 and 16");
128 encoding_str
= optarg
;
134 header_info
= optarg
;
137 decode_int(optarg
, &monitor_gain
);
138 if (monitor_gain
< 0 || monitor_gain
> 255)
139 errx(1, "monitor volume must be between 0 and 255");
142 decode_int(optarg
, &precision
);
143 if (precision
!= 4 && precision
!= 8 &&
144 precision
!= 16 && precision
!= 24 &&
146 errx(1, "precision must be between 4, 8, 16, 24 or 32");
149 len
= strlen(optarg
);
151 if (strncmp(optarg
, "mic", len
) == 0)
152 port
|= AUDIO_MICROPHONE
;
153 else if (strncmp(optarg
, "cd", len
) == 0 ||
154 strncmp(optarg
, "internal-cd", len
) == 0)
156 else if (strncmp(optarg
, "line", len
) == 0)
157 port
|= AUDIO_LINE_IN
;
160 "port must be `cd', `internal-cd', `mic', or `line'");
166 decode_int(optarg
, &sample_rate
);
167 if (sample_rate
< 0 || sample_rate
> 48000 * 2) /* XXX */
168 errx(1, "sample rate must be between 0 and 96000");
172 decode_time(optarg
, &record_time
);
178 decode_int(optarg
, &gain
);
179 if (gain
< 0 || gain
> 255)
180 errx(1, "volume must be between 0 and 255");
195 * convert the encoding string into a value.
198 encoding
= audio_enc_to_val(encoding_str
);
200 errx(1, "unknown encoding, bailing...");
204 encoding
= AUDIO_ENCODING_ULAW
;
208 * open the output file
210 if (argv
[0][0] != '-' || argv
[0][1] != '\0') {
211 /* intuit the file type from the name */
212 if (format
== AUDIO_FORMAT_DEFAULT
)
214 size_t flen
= strlen(*argv
);
215 const char *arg
= *argv
;
217 if (strcasecmp(arg
+ flen
- 3, ".au") == 0)
218 format
= AUDIO_FORMAT_SUN
;
219 else if (strcasecmp(arg
+ flen
- 4, ".wav") == 0)
220 format
= AUDIO_FORMAT_WAV
;
222 outfd
= open(*argv
, O_CREAT
|(aflag
? O_APPEND
: O_TRUNC
)|O_WRONLY
, 0666);
224 err(1, "could not open %s", *argv
);
226 outfd
= STDOUT_FILENO
;
229 * open the audio device
231 if (device
== NULL
&& (device
= getenv("AUDIODEVICE")) == NULL
&&
232 (device
= getenv("AUDIODEV")) == NULL
) /* Sun compatibility */
235 audiofd
= open(device
, O_RDONLY
);
236 if (audiofd
< 0 && device
== defdevice
) {
237 device
= _PATH_SOUND0
;
238 audiofd
= open(device
, O_RDONLY
);
241 err(1, "failed to open %s", device
);
244 * work out the buffer size to use, and allocate it. also work out
245 * what the old monitor gain value is, so that we can reset it later.
247 if (ioctl(audiofd
, AUDIO_GETINFO
, &oinfo
) < 0)
248 err(1, "failed to get audio info");
249 bufsize
= oinfo
.record
.buffer_size
;
250 if (bufsize
< 32 * 1024)
252 omonitor_gain
= oinfo
.monitor_gain
;
254 buffer
= malloc(bufsize
);
256 err(1, "couldn't malloc buffer of %d size", (int)bufsize
);
259 * set up audio device for recording with the speified parameters
261 AUDIO_INITINFO(&info
);
264 * for these, get the current values for stuffing into the header
266 #define SETINFO(x) if (x) \
269 info.record.x = x = oinfo.record.x;
270 SETINFO (sample_rate
)
280 info
.monitor_gain
= monitor_gain
;
282 monitor_gain
= oinfo
.monitor_gain
;
284 info
.mode
= AUMODE_RECORD
;
285 if (ioctl(audiofd
, AUDIO_SETINFO
, &info
) < 0)
286 err(1, "failed to set audio info");
288 signal(SIGINT
, cleanup
);
292 if (verbose
&& conv_func
) {
293 const char *s
= NULL
;
295 if (conv_func
== swap_bytes
)
296 s
= "swap bytes (16 bit)";
297 else if (conv_func
== swap_bytes32
)
298 s
= "swap bytes (32 bit)";
299 else if (conv_func
== change_sign16_be
)
300 s
= "change sign (big-endian, 16 bit)";
301 else if (conv_func
== change_sign16_le
)
302 s
= "change sign (little-endian, 16 bit)";
303 else if (conv_func
== change_sign32_be
)
304 s
= "change sign (big-endian, 32 bit)";
305 else if (conv_func
== change_sign32_le
)
306 s
= "change sign (little-endian, 32 bit)";
307 else if (conv_func
== change_sign16_swap_bytes_be
)
308 s
= "change sign & swap bytes (big-endian, 16 bit)";
309 else if (conv_func
== change_sign16_swap_bytes_le
)
310 s
= "change sign & swap bytes (little-endian, 16 bit)";
311 else if (conv_func
== change_sign32_swap_bytes_be
)
312 s
= "change sign (big-endian, 32 bit)";
313 else if (conv_func
== change_sign32_swap_bytes_le
)
314 s
= "change sign & swap bytes (little-endian, 32 bit)";
317 fprintf(stderr
, "%s: converting, using function: %s\n",
320 fprintf(stderr
, "%s: using unnamed conversion "
321 "function\n", getprogname());
326 "sample_rate=%d channels=%d precision=%d encoding=%s\n",
327 info
.record
.sample_rate
, info
.record
.channels
,
328 info
.record
.precision
,
329 audio_enc_from_val(info
.record
.encoding
));
331 if (!no_time_limit
&& verbose
)
332 fprintf(stderr
, "recording for %lu seconds, %lu microseconds\n",
333 (u_long
)record_time
.tv_sec
, (u_long
)record_time
.tv_usec
);
335 (void)gettimeofday(&start_time
, NULL
);
336 while (no_time_limit
|| timeleft(&start_time
, &record_time
)) {
337 if ((size_t)read(audiofd
, buffer
, bufsize
) != bufsize
)
338 err(1, "read failed");
340 (*conv_func
)(buffer
, bufsize
);
341 if ((size_t)write(outfd
, buffer
, bufsize
) != bufsize
)
342 err(1, "write failed");
343 total_size
+= bufsize
;
349 timeleft(start_tvp
, record_tvp
)
350 struct timeval
*start_tvp
;
351 struct timeval
*record_tvp
;
353 struct timeval now
, diff
;
355 (void)gettimeofday(&now
, NULL
);
356 timersub(&now
, start_tvp
, &diff
);
357 timersub(record_tvp
, &diff
, &now
);
359 return (now
.tv_sec
> 0 || (now
.tv_sec
== 0 && now
.tv_usec
> 0));
370 AUDIO_INITINFO(&info
);
371 info
.monitor_gain
= omonitor_gain
;
372 if (ioctl(audiofd
, AUDIO_SETINFO
, &info
) < 0)
373 err(1, "failed to reset audio info");
377 (void)raise_default_signal(signo
);
383 write_header_sun(hdrp
, lenp
, leftp
)
388 static int warned
= 0;
389 static sun_audioheader auh
;
390 int sunenc
, oencoding
= encoding
;
392 /* only perform conversions if we don't specify the encoding */
394 case AUDIO_ENCODING_ULINEAR_LE
:
395 #if BYTE_ORDER == LITTLE_ENDIAN
396 case AUDIO_ENCODING_ULINEAR
:
399 conv_func
= change_sign16_swap_bytes_le
;
400 else if (precision
== 32)
401 conv_func
= change_sign32_swap_bytes_le
;
403 encoding
= AUDIO_ENCODING_SLINEAR_BE
;
406 case AUDIO_ENCODING_ULINEAR_BE
:
407 #if BYTE_ORDER == BIG_ENDIAN
408 case AUDIO_ENCODING_ULINEAR
:
411 conv_func
= change_sign16_be
;
412 else if (precision
== 32)
413 conv_func
= change_sign32_be
;
415 encoding
= AUDIO_ENCODING_SLINEAR_BE
;
418 case AUDIO_ENCODING_SLINEAR_LE
:
419 #if BYTE_ORDER == LITTLE_ENDIAN
420 case AUDIO_ENCODING_SLINEAR
:
423 conv_func
= swap_bytes
;
424 else if (precision
== 32)
425 conv_func
= swap_bytes32
;
427 encoding
= AUDIO_ENCODING_SLINEAR_BE
;
430 #if BYTE_ORDER == BIG_ENDIAN
431 case AUDIO_ENCODING_SLINEAR
:
432 encoding
= AUDIO_ENCODING_SLINEAR_BE
;
437 /* if we can't express this as a Sun header, don't write any */
438 if (audio_encoding_to_sun(encoding
, precision
, &sunenc
) != 0) {
439 if (!qflag
&& !warned
) {
440 const char *s
= audio_enc_from_val(oencoding
);
444 warnx("failed to convert to sun encoding from %s "
445 "(precision %d);\nSun audio header not written",
448 format
= AUDIO_FORMAT_NONE
;
454 auh
.magic
= htonl(AUDIO_FILE_MAGIC
);
455 if (outfd
== STDOUT_FILENO
)
456 auh
.data_size
= htonl(AUDIO_UNKNOWN_SIZE
);
457 else if (total_size
!= -1)
458 auh
.data_size
= htonl(total_size
);
461 auh
.encoding
= htonl(sunenc
);
462 auh
.sample_rate
= htonl(sample_rate
);
463 auh
.channels
= htonl(channels
);
467 infolen
= ((len
= strlen(header_info
)) + 7) & 0xfffffff8;
468 *leftp
= infolen
- len
;
469 auh
.hdr_size
= htonl(sizeof(auh
) + infolen
);
471 *leftp
= sizeof(default_info
);
472 auh
.hdr_size
= htonl(sizeof(auh
) + *leftp
);
474 *(sun_audioheader
**)hdrp
= &auh
;
480 write_header_wav(hdrp
, lenp
, leftp
)
486 * WAV header we write looks like this:
490 * 4-7 file length (minus 8)
494 * 22-23 number of channels
496 * 28-31 average bytes per second
497 * 32-33 block alignment
498 * 34-35 bits per sample
500 * then for ULAW and ALAW outputs, we have an extended chunk size
501 * and a WAV "fact" to add:
503 * 36-37 length of extension (== 0)
506 * 46-49 number of samples written
511 * for PCM outputs we have just the data remaining:
517 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
519 char wavheaderbuf
[64], *p
= wavheaderbuf
;
520 const char *riff
= "RIFF",
521 *wavefmt
= "WAVEfmt ",
524 u_int32_t filelen
, fmtsz
, sps
, abps
, factsz
= 4, nsample
, datalen
;
525 u_int16_t fmttag
, nchan
, align
, bps
, extln
= 0;
528 warnx("header information not supported for WAV");
543 static int warned
= 0;
546 warnx("can not support precision of %d", precision
);
554 case AUDIO_ENCODING_ULAW
:
555 fmttag
= WAVE_FORMAT_MULAW
;
560 case AUDIO_ENCODING_ALAW
:
561 fmttag
= WAVE_FORMAT_ALAW
;
567 * we could try to support RIFX but it seems to be more portable
568 * to output little-endian data for WAV files.
570 case AUDIO_ENCODING_ULINEAR_BE
:
571 #if BYTE_ORDER == BIG_ENDIAN
572 case AUDIO_ENCODING_ULINEAR
:
575 conv_func
= change_sign16_swap_bytes_be
;
577 conv_func
= change_sign32_swap_bytes_be
;
580 case AUDIO_ENCODING_SLINEAR_BE
:
581 #if BYTE_ORDER == BIG_ENDIAN
582 case AUDIO_ENCODING_SLINEAR
:
585 conv_func
= change_sign8
;
587 conv_func
= swap_bytes
;
589 conv_func
= swap_bytes32
;
592 case AUDIO_ENCODING_ULINEAR_LE
:
593 #if BYTE_ORDER == LITTLE_ENDIAN
594 case AUDIO_ENCODING_ULINEAR
:
597 conv_func
= change_sign16_le
;
599 conv_func
= change_sign32_le
;
602 case AUDIO_ENCODING_SLINEAR_LE
:
603 case AUDIO_ENCODING_PCM16
:
604 #if BYTE_ORDER == LITTLE_ENDIAN
605 case AUDIO_ENCODING_SLINEAR
:
608 conv_func
= change_sign8
;
610 fmttag
= WAVE_FORMAT_PCM
;
612 align
= channels
* (bps
/ 8);
617 static int warned
= 0;
620 const char *s
= wav_enc_from_val(encoding
);
623 warnx("can not support encoding of %s", s
);
625 warnx("can not support encoding of %d", encoding
);
629 format
= AUDIO_FORMAT_NONE
;
637 if (outfd
== STDOUT_FILENO
)
639 else if (total_size
!= -1)
640 datalen
= total_size
;
645 filelen
= 4 + (8 + fmtsz
) + (8 + datalen
);
646 if (fmttag
!= WAVE_FORMAT_PCM
)
647 filelen
+= 8 + factsz
;
649 abps
= (double)align
*sample_rate
/ (double)1 + 0.5;
651 nsample
= (datalen
/ bps
) / sample_rate
;
654 * now we've calculated the info, write it out!
656 #define put32(x) do { \
661 #define put16(x) do { \
670 memcpy(p
, wavefmt
, 8);
686 /* NON PCM formats have an extended chunk; write it */
687 if (fmttag
!= WAVE_FORMAT_PCM
) {
704 *hdrp
= wavheaderbuf
;
705 *lenp
= (p
- wavheaderbuf
);
714 int veclen
, left
, tlen
;
719 case AUDIO_FORMAT_DEFAULT
:
720 case AUDIO_FORMAT_SUN
:
721 if (write_header_sun(&hdr
, &hdrlen
, &left
) != 0)
724 case AUDIO_FORMAT_WAV
:
725 if (write_header_wav(&hdr
, &hdrlen
, &left
) != 0)
728 case AUDIO_FORMAT_NONE
:
731 errx(1, "unknown audio format");
738 iv
[veclen
].iov_base
= hdr
;
739 iv
[veclen
].iov_len
= hdrlen
;
740 tlen
+= iv
[veclen
++].iov_len
;
743 iv
[veclen
].iov_base
= header_info
;
744 iv
[veclen
].iov_len
= (int)strlen(header_info
) + 1;
745 tlen
+= iv
[veclen
++].iov_len
;
748 iv
[veclen
].iov_base
= default_info
;
749 iv
[veclen
].iov_len
= left
;
750 tlen
+= iv
[veclen
++].iov_len
;
756 if (writev(outfd
, iv
, veclen
) != tlen
)
757 err(1, "could not write audio header");
764 /* can't do this here! */
765 if (outfd
== STDOUT_FILENO
)
768 if (lseek(outfd
, SEEK_SET
, 0) < 0)
769 err(1, "could not seek to start of file for header rewrite");
777 fprintf(stderr
, "Usage: %s [-afhqV] [options] {files ...|-}\n",
779 fprintf(stderr
, "Options:\n\t"
780 "-b balance (0-63)\n\t"
782 "-d audio device\n\t"
785 "-i header information\n\t"
786 "-m monitor volume\n\t"
787 "-P precision (4, 8, 16, 24, or 32 bits)\n\t"
790 "-t recording time\n\t"