xref: /netbsd-src/usr.bin/audio/record/record.c (revision 707adea33919471657fb2c7dc8eaf8e19df9aa1a)
1*707adea3Smrg /*	$NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg Exp $	*/
2e0c321f2Smrg 
3e0c321f2Smrg /*
4ebcfca8bSmrg  * Copyright (c) 1999, 2002, 2003, 2005, 2010 Matthew R. Green
5e0c321f2Smrg  * All rights reserved.
6e0c321f2Smrg  *
7e0c321f2Smrg  * Redistribution and use in source and binary forms, with or without
8e0c321f2Smrg  * modification, are permitted provided that the following conditions
9e0c321f2Smrg  * are met:
10e0c321f2Smrg  * 1. Redistributions of source code must retain the above copyright
11e0c321f2Smrg  *    notice, this list of conditions and the following disclaimer.
12e0c321f2Smrg  * 2. Redistributions in binary form must reproduce the above copyright
13e0c321f2Smrg  *    notice, this list of conditions and the following disclaimer in the
14e0c321f2Smrg  *    documentation and/or other materials provided with the distribution.
15e0c321f2Smrg  *
16e0c321f2Smrg  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17e0c321f2Smrg  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18e0c321f2Smrg  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19e0c321f2Smrg  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20e0c321f2Smrg  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21e0c321f2Smrg  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22e0c321f2Smrg  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23e0c321f2Smrg  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24e0c321f2Smrg  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25e0c321f2Smrg  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26e0c321f2Smrg  * SUCH DAMAGE.
27e0c321f2Smrg  */
28e0c321f2Smrg 
29140c9e18Smrg /*
30140c9e18Smrg  * SunOS compatible audiorecord(1)
31140c9e18Smrg  */
32c0db2196Sagc #include <sys/cdefs.h>
33c0db2196Sagc 
34c0db2196Sagc #ifndef lint
35*707adea3Smrg __RCSID("$NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg Exp $");
36c0db2196Sagc #endif
37c0db2196Sagc 
38140c9e18Smrg 
39ebcfca8bSmrg #include <sys/param.h>
40140c9e18Smrg #include <sys/audioio.h>
41140c9e18Smrg #include <sys/ioctl.h>
42140c9e18Smrg #include <sys/time.h>
43140c9e18Smrg #include <sys/uio.h>
44140c9e18Smrg 
45140c9e18Smrg #include <err.h>
46140c9e18Smrg #include <fcntl.h>
47140c9e18Smrg #include <paths.h>
48140c9e18Smrg #include <signal.h>
49140c9e18Smrg #include <stdio.h>
50140c9e18Smrg #include <stdlib.h>
51140c9e18Smrg #include <string.h>
52140c9e18Smrg #include <unistd.h>
535d427989Slukem #include <util.h>
54140c9e18Smrg 
55140c9e18Smrg #include "libaudio.h"
567aab3eccSmrg #include "auconv.h"
57140c9e18Smrg 
58c36a7298Sjoerg static audio_info_t info, oinfo;
59c36a7298Sjoerg static const char *device;
600c2e0646Smrg static int	audiofd;
610c2e0646Smrg static int	aflag, fflag;
62140c9e18Smrg int	verbose;
63c36a7298Sjoerg static int	monitor_gain, omonitor_gain;
64c36a7298Sjoerg static int	gain;
65c36a7298Sjoerg static int	balance;
66c36a7298Sjoerg static int	port;
67c36a7298Sjoerg static char	*encoding_str;
68a0e3e391Smrg static struct track_info ti;
69c36a7298Sjoerg static struct timeval record_time;
70c36a7298Sjoerg static struct timeval start_time;
718a61065fSmlelstv static int no_time_limit = 1;
72140c9e18Smrg 
73c36a7298Sjoerg static void (*conv_func) (u_char *, int);
74829efa09Smrg 
75c36a7298Sjoerg static void usage (void) __dead;
76c36a7298Sjoerg static int timeleft (struct timeval *, struct timeval *);
77c36a7298Sjoerg static void cleanup (int) __dead;
78c36a7298Sjoerg static void rewrite_header (void);
798a61065fSmlelstv static void stop (int);
808a61065fSmlelstv 
stop(int sig)818a61065fSmlelstv static void stop (int sig)
828a61065fSmlelstv {
838a61065fSmlelstv 	no_time_limit = 0;
848a61065fSmlelstv 	timerclear(&record_time);
858a61065fSmlelstv }
86140c9e18Smrg 
87140c9e18Smrg int
main(int argc,char * argv[])88c36a7298Sjoerg main(int argc, char *argv[])
89140c9e18Smrg {
90bc3f0566Smrg 	u_char	*buffer;
91ebcfca8bSmrg 	size_t	len, bufsize = 0;
9236d4e80cSriastradh 	ssize_t	nread;
938a61065fSmlelstv 	int	ch;
94b5d25cc5Saugustss 	const char *defdevice = _PATH_SOUND;
95140c9e18Smrg 
960c2e0646Smrg 	/*
97a0e3e391Smrg 	 * Initialise the track_info.
980c2e0646Smrg 	 */
99a0e3e391Smrg 	ti.format = AUDIO_FORMAT_DEFAULT;
100a0e3e391Smrg 	ti.total_size = -1;
1010c2e0646Smrg 
102ebcfca8bSmrg 	while ((ch = getopt(argc, argv, "ab:B:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) {
103140c9e18Smrg 		switch (ch) {
104140c9e18Smrg 		case 'a':
105140c9e18Smrg 			aflag++;
106140c9e18Smrg 			break;
107140c9e18Smrg 		case 'b':
108140c9e18Smrg 			decode_int(optarg, &balance);
109140c9e18Smrg 			if (balance < 0 || balance > 63)
110eda9e509Sgrant 				errx(1, "balance must be between 0 and 63");
111140c9e18Smrg 			break;
112ebcfca8bSmrg 		case 'B':
113ebcfca8bSmrg 			bufsize = strsuftoll("read buffer size", optarg,
114ebcfca8bSmrg 					     1, UINT_MAX);
115ebcfca8bSmrg 			break;
116140c9e18Smrg 		case 'C':
117ef0bbb2aSjdolecek 			/* Ignore, compatibility */
118140c9e18Smrg 			break;
11937188d08Smrg 		case 'F':
120a0e3e391Smrg 			ti.format = audio_format_from_str(optarg);
121a0e3e391Smrg 			if (ti.format < 0)
12216189b5aSmrg 				errx(1, "Unknown audio format; supported "
12316189b5aSmrg 				    "formats: \"sun\", \"wav\", and \"none\"");
12437188d08Smrg 			break;
125140c9e18Smrg 		case 'c':
126a0e3e391Smrg 			decode_int(optarg, &ti.channels);
127a0e3e391Smrg 			if (ti.channels < 0 || ti.channels > 16)
128eda9e509Sgrant 				errx(1, "channels must be between 0 and 16");
129140c9e18Smrg 			break;
130140c9e18Smrg 		case 'd':
131140c9e18Smrg 			device = optarg;
132140c9e18Smrg 			break;
133140c9e18Smrg 		case 'e':
134140c9e18Smrg 			encoding_str = optarg;
135140c9e18Smrg 			break;
136140c9e18Smrg 		case 'f':
137140c9e18Smrg 			fflag++;
138140c9e18Smrg 			break;
139140c9e18Smrg 		case 'i':
140a0e3e391Smrg 			ti.header_info = optarg;
141140c9e18Smrg 			break;
142140c9e18Smrg 		case 'm':
14337188d08Smrg 			decode_int(optarg, &monitor_gain);
14437188d08Smrg 			if (monitor_gain < 0 || monitor_gain > 255)
145eda9e509Sgrant 				errx(1, "monitor volume must be between 0 and 255");
146140c9e18Smrg 			break;
147140c9e18Smrg 		case 'P':
148a0e3e391Smrg 			decode_int(optarg, &ti.precision);
149a0e3e391Smrg 			if (ti.precision != 4 && ti.precision != 8 &&
150a0e3e391Smrg 			    ti.precision != 16 && ti.precision != 24 &&
151a0e3e391Smrg 			    ti.precision != 32)
152399af9e4Sminoura 				errx(1, "precision must be between 4, 8, 16, 24 or 32");
153140c9e18Smrg 			break;
154140c9e18Smrg 		case 'p':
155140c9e18Smrg 			len = strlen(optarg);
156140c9e18Smrg 
157140c9e18Smrg 			if (strncmp(optarg, "mic", len) == 0)
158140c9e18Smrg 				port |= AUDIO_MICROPHONE;
159140c9e18Smrg 			else if (strncmp(optarg, "cd", len) == 0 ||
160140c9e18Smrg 			           strncmp(optarg, "internal-cd", len) == 0)
161140c9e18Smrg 				port |= AUDIO_CD;
162140c9e18Smrg 			else if (strncmp(optarg, "line", len) == 0)
163140c9e18Smrg 				port |= AUDIO_LINE_IN;
164140c9e18Smrg 			else
165140c9e18Smrg 				errx(1,
166140c9e18Smrg 			    "port must be `cd', `internal-cd', `mic', or `line'");
167140c9e18Smrg 			break;
168140c9e18Smrg 		case 'q':
169a0e3e391Smrg 			ti.qflag++;
170140c9e18Smrg 			break;
171140c9e18Smrg 		case 's':
172a0e3e391Smrg 			decode_int(optarg, &ti.sample_rate);
173a0e3e391Smrg 			if (ti.sample_rate < 0 || ti.sample_rate > 48000 * 2)	/* XXX */
174eda9e509Sgrant 				errx(1, "sample rate must be between 0 and 96000");
175140c9e18Smrg 			break;
176140c9e18Smrg 		case 't':
177b3a888e9Smrg 			no_time_limit = 0;
178140c9e18Smrg 			decode_time(optarg, &record_time);
179140c9e18Smrg 			break;
180140c9e18Smrg 		case 'V':
181140c9e18Smrg 			verbose++;
182140c9e18Smrg 			break;
183140c9e18Smrg 		case 'v':
18437188d08Smrg 			decode_int(optarg, &gain);
18537188d08Smrg 			if (gain < 0 || gain > 255)
186eda9e509Sgrant 				errx(1, "volume must be between 0 and 255");
187140c9e18Smrg 			break;
188140c9e18Smrg 		/* case 'h': */
189140c9e18Smrg 		default:
190140c9e18Smrg 			usage();
191140c9e18Smrg 			/* NOTREACHED */
192140c9e18Smrg 		}
193140c9e18Smrg 	}
194140c9e18Smrg 	argc -= optind;
195140c9e18Smrg 	argv += optind;
196140c9e18Smrg 
197906f130fSmrg 	if (argc != 1)
198906f130fSmrg 		usage();
199906f130fSmrg 
200140c9e18Smrg 	/*
2018ce77e55Smrg 	 * convert the encoding string into a value.
2028ce77e55Smrg 	 */
2038ce77e55Smrg 	if (encoding_str) {
204a0e3e391Smrg 		ti.encoding = audio_enc_to_val(encoding_str);
205a0e3e391Smrg 		if (ti.encoding == -1)
2068ce77e55Smrg 			errx(1, "unknown encoding, bailing...");
2078ce77e55Smrg 	}
2088ce77e55Smrg 
2098ce77e55Smrg 	/*
2108ce77e55Smrg 	 * open the output file
2118ce77e55Smrg 	 */
212dcf9144aSgson 	if (argv[0][0] != '-' || argv[0][1] != '\0') {
2138ce77e55Smrg 		/* intuit the file type from the name */
214a0e3e391Smrg 		if (ti.format == AUDIO_FORMAT_DEFAULT)
2158ce77e55Smrg 		{
2168ce77e55Smrg 			size_t flen = strlen(*argv);
2178ce77e55Smrg 			const char *arg = *argv;
2188ce77e55Smrg 
2198ce77e55Smrg 			if (strcasecmp(arg + flen - 3, ".au") == 0)
220a0e3e391Smrg 				ti.format = AUDIO_FORMAT_SUN;
2218ce77e55Smrg 			else if (strcasecmp(arg + flen - 4, ".wav") == 0)
222a0e3e391Smrg 				ti.format = AUDIO_FORMAT_WAV;
2238ce77e55Smrg 		}
224a0e3e391Smrg 		ti.outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666);
225a0e3e391Smrg 		if (ti.outfd < 0)
2268ce77e55Smrg 			err(1, "could not open %s", *argv);
2278ce77e55Smrg 	} else
228a0e3e391Smrg 		ti.outfd = STDOUT_FILENO;
2298ce77e55Smrg 
2308ce77e55Smrg 	/*
231dd10ebe3Smrg 	 * open the audio device
232140c9e18Smrg 	 */
23387316718Skleink 	if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL &&
23487316718Skleink 	    (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */
235b5d25cc5Saugustss 		device = defdevice;
236140c9e18Smrg 
237140c9e18Smrg 	audiofd = open(device, O_RDONLY);
238b5d25cc5Saugustss 	if (audiofd < 0 && device == defdevice) {
239d1c426feSaugustss 		device = _PATH_SOUND0;
240770c3134Suwe 		audiofd = open(device, O_RDONLY);
241d1c426feSaugustss 	}
242140c9e18Smrg 	if (audiofd < 0)
243140c9e18Smrg 		err(1, "failed to open %s", device);
244140c9e18Smrg 
245140c9e18Smrg 	/*
246140c9e18Smrg 	 * work out the buffer size to use, and allocate it.  also work out
247140c9e18Smrg 	 * what the old monitor gain value is, so that we can reset it later.
248140c9e18Smrg 	 */
249ef0bbb2aSjdolecek 	if (ioctl(audiofd, AUDIO_GETINFO, &oinfo) < 0)
250140c9e18Smrg 		err(1, "failed to get audio info");
25194f6c577Sjmcneill 	if (bufsize == 0) {
252140c9e18Smrg 		bufsize = oinfo.record.buffer_size;
253140c9e18Smrg 		if (bufsize < 32 * 1024)
254140c9e18Smrg 			bufsize = 32 * 1024;
25594f6c577Sjmcneill 	}
25637188d08Smrg 	omonitor_gain = oinfo.monitor_gain;
257140c9e18Smrg 
258140c9e18Smrg 	buffer = malloc(bufsize);
259140c9e18Smrg 	if (buffer == NULL)
260140c9e18Smrg 		err(1, "couldn't malloc buffer of %d size", (int)bufsize);
261140c9e18Smrg 
262140c9e18Smrg 	/*
26337188d08Smrg 	 * set up audio device for recording with the speified parameters
26437188d08Smrg 	 */
26537188d08Smrg 	AUDIO_INITINFO(&info);
26637188d08Smrg 
26737188d08Smrg 	/*
26837188d08Smrg 	 * for these, get the current values for stuffing into the header
26937188d08Smrg 	 */
2700c2e0646Smrg #define SETINFO2(x, y)	if (x) \
2710c2e0646Smrg 				info.record.y = x; \
2728ce77e55Smrg 			else \
2730c2e0646Smrg 				info.record.y = x = oinfo.record.y;
274a0e3e391Smrg #define SETINFO(x)	SETINFO2(ti.x, x)
2750c2e0646Smrg 
2768ce77e55Smrg 	SETINFO (sample_rate)
2778ce77e55Smrg 	SETINFO (channels)
2788ce77e55Smrg 	SETINFO (precision)
2798ce77e55Smrg 	SETINFO (encoding)
2800c2e0646Smrg 	SETINFO2 (gain, gain)
2810c2e0646Smrg 	SETINFO2 (port, port)
2820c2e0646Smrg 	SETINFO2 (balance, balance)
28337188d08Smrg #undef SETINFO
2840c2e0646Smrg #undef SETINFO2
28537188d08Smrg 
28637188d08Smrg 	if (monitor_gain)
28737188d08Smrg 		info.monitor_gain = monitor_gain;
28837188d08Smrg 	else
28937188d08Smrg 		monitor_gain = oinfo.monitor_gain;
290140c9e18Smrg 
291140c9e18Smrg 	info.mode = AUMODE_RECORD;
292ef0bbb2aSjdolecek 	if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0)
293c5c02584Smrg 		err(1, "failed to set audio info");
294140c9e18Smrg 
2958a61065fSmlelstv 	signal(SIGINT, stop);
2960c2e0646Smrg 
297a0e3e391Smrg 	ti.total_size = 0;
2980c2e0646Smrg 
299a0e3e391Smrg 	write_header(&ti);
300a0e3e391Smrg 	if (ti.format == AUDIO_FORMAT_NONE)
3010c2e0646Smrg 		errx(1, "unable to determine audio format");
302a0e3e391Smrg 	conv_func = write_get_conv_func(&ti);
303140c9e18Smrg 
30416189b5aSmrg 	if (verbose && conv_func) {
30516189b5aSmrg 		const char *s = NULL;
30616189b5aSmrg 
30716189b5aSmrg 		if (conv_func == swap_bytes)
30816189b5aSmrg 			s = "swap bytes (16 bit)";
30916189b5aSmrg 		else if (conv_func == swap_bytes32)
31016189b5aSmrg 			s = "swap bytes (32 bit)";
31116189b5aSmrg 		else if (conv_func == change_sign16_be)
31216189b5aSmrg 			s = "change sign (big-endian, 16 bit)";
31316189b5aSmrg 		else if (conv_func == change_sign16_le)
31416189b5aSmrg 			s = "change sign (little-endian, 16 bit)";
3153375d27cSmlelstv 		else if (conv_func == change_sign24_be)
3163375d27cSmlelstv 			s = "change sign (big-endian, 24 bit)";
3173375d27cSmlelstv 		else if (conv_func == change_sign24_le)
3183375d27cSmlelstv 			s = "change sign (little-endian, 24 bit)";
31916189b5aSmrg 		else if (conv_func == change_sign32_be)
32016189b5aSmrg 			s = "change sign (big-endian, 32 bit)";
32116189b5aSmrg 		else if (conv_func == change_sign32_le)
32216189b5aSmrg 			s = "change sign (little-endian, 32 bit)";
32316189b5aSmrg 		else if (conv_func == change_sign16_swap_bytes_be)
32416189b5aSmrg 			s = "change sign & swap bytes (big-endian, 16 bit)";
325*707adea3Smrg 		else if (conv_func == change_sign16_swap_bytes_le)
32616189b5aSmrg 			s = "change sign & swap bytes (little-endian, 16 bit)";
327*707adea3Smrg 		else if (conv_func == change_sign24_swap_bytes_be)
3283375d27cSmlelstv 			s = "change sign & swap bytes (big-endian, 24 bit)";
3293375d27cSmlelstv 		else if (conv_func == change_sign24_swap_bytes_le)
3303375d27cSmlelstv 			s = "change sign & swap bytes (little-endian, 24 bit)";
33116189b5aSmrg 		else if (conv_func == change_sign32_swap_bytes_be)
33216189b5aSmrg 			s = "change sign (big-endian, 32 bit)";
33316189b5aSmrg 		else if (conv_func == change_sign32_swap_bytes_le)
33416189b5aSmrg 			s = "change sign & swap bytes (little-endian, 32 bit)";
33516189b5aSmrg 
33616189b5aSmrg 		if (s)
33716189b5aSmrg 			fprintf(stderr, "%s: converting, using function: %s\n",
33816189b5aSmrg 			    getprogname(), s);
33916189b5aSmrg 		else
34016189b5aSmrg 			fprintf(stderr, "%s: using unnamed conversion "
34116189b5aSmrg 					"function\n", getprogname());
34216189b5aSmrg 	}
34316189b5aSmrg 
34416189b5aSmrg 	if (verbose)
34516189b5aSmrg 		fprintf(stderr,
34616189b5aSmrg 		   "sample_rate=%d channels=%d precision=%d encoding=%s\n",
34716189b5aSmrg 		   info.record.sample_rate, info.record.channels,
34816189b5aSmrg 		   info.record.precision,
34916189b5aSmrg 		   audio_enc_from_val(info.record.encoding));
35016189b5aSmrg 
351c0f9ed08Smrg 	if (!no_time_limit && verbose)
352c0f9ed08Smrg 		fprintf(stderr, "recording for %lu seconds, %lu microseconds\n",
353c0f9ed08Smrg 		    (u_long)record_time.tv_sec, (u_long)record_time.tv_usec);
354c0f9ed08Smrg 
355140c9e18Smrg 	(void)gettimeofday(&start_time, NULL);
356b3a888e9Smrg 	while (no_time_limit || timeleft(&start_time, &record_time)) {
35736d4e80cSriastradh 		if ((nread = read(audiofd, buffer, bufsize)) == -1)
358140c9e18Smrg 			err(1, "read failed");
35936d4e80cSriastradh 		if (nread == 0)
3608a61065fSmlelstv 			break;
361829efa09Smrg 		if (conv_func)
3628a61065fSmlelstv 			(*conv_func)(buffer, nread);
3638a61065fSmlelstv 		if (write(ti.outfd, buffer, nread) != nread)
364140c9e18Smrg 			err(1, "write failed");
3658a61065fSmlelstv 		ti.total_size += nread;
366140c9e18Smrg 	}
367140c9e18Smrg 	cleanup(0);
368140c9e18Smrg }
369140c9e18Smrg 
370140c9e18Smrg int
timeleft(struct timeval * start_tvp,struct timeval * record_tvp)371c36a7298Sjoerg timeleft(struct timeval *start_tvp, struct timeval *record_tvp)
372140c9e18Smrg {
373140c9e18Smrg 	struct timeval now, diff;
374140c9e18Smrg 
375140c9e18Smrg 	(void)gettimeofday(&now, NULL);
376df5a9e3cSdmcmahill 	timersub(&now, start_tvp, &diff);
377df5a9e3cSdmcmahill 	timersub(record_tvp, &diff, &now);
378140c9e18Smrg 
379140c9e18Smrg 	return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0));
380140c9e18Smrg }
381140c9e18Smrg 
382140c9e18Smrg void
cleanup(int signo)383c36a7298Sjoerg cleanup(int signo)
384140c9e18Smrg {
385140c9e18Smrg 
386140c9e18Smrg 	rewrite_header();
387a0e3e391Smrg 	close(ti.outfd);
38837188d08Smrg 	if (omonitor_gain) {
389140c9e18Smrg 		AUDIO_INITINFO(&info);
39037188d08Smrg 		info.monitor_gain = omonitor_gain;
391ef0bbb2aSjdolecek 		if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0)
392140c9e18Smrg 			err(1, "failed to reset audio info");
393140c9e18Smrg 	}
394ef0bbb2aSjdolecek 	close(audiofd);
3955d427989Slukem 	if (signo != 0) {
3965d427989Slukem 		(void)raise_default_signal(signo);
3975d427989Slukem 	}
398140c9e18Smrg 	exit(0);
399140c9e18Smrg }
400140c9e18Smrg 
401c36a7298Sjoerg static void
rewrite_header(void)402c36a7298Sjoerg rewrite_header(void)
403140c9e18Smrg {
404140c9e18Smrg 
405140c9e18Smrg 	/* can't do this here! */
406a0e3e391Smrg 	if (ti.outfd == STDOUT_FILENO)
407140c9e18Smrg 		return;
408a0e3e391Smrg 	if (lseek(ti.outfd, (off_t)0, SEEK_SET) == (off_t)-1)
409140c9e18Smrg 		err(1, "could not seek to start of file for header rewrite");
410a0e3e391Smrg 	write_header(&ti);
411140c9e18Smrg }
412140c9e18Smrg 
413c36a7298Sjoerg static void
usage(void)414c36a7298Sjoerg usage(void)
415140c9e18Smrg {
416a8ec668dScgd 
417a8ec668dScgd 	fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n",
418a8ec668dScgd 	    getprogname());
419aefa214cSmrg 	fprintf(stderr, "Options:\n\t"
420ebcfca8bSmrg 	    "-B buffer size\n\t"
42190bb3515Swiz 	    "-b balance (0-63)\n\t"
422aefa214cSmrg 	    "-c channels\n\t"
423aefa214cSmrg 	    "-d audio device\n\t"
424aefa214cSmrg 	    "-e encoding\n\t"
4250402922aSwiz 	    "-F format\n\t"
426aefa214cSmrg 	    "-i header information\n\t"
427aefa214cSmrg 	    "-m monitor volume\n\t"
4280402922aSwiz 	    "-P precision (4, 8, 16, 24, or 32 bits)\n\t"
429aefa214cSmrg 	    "-p input port\n\t"
430aefa214cSmrg 	    "-s sample rate\n\t"
431aefa214cSmrg 	    "-t recording time\n\t"
432aefa214cSmrg 	    "-v volume\n");
433c9954566Skleink 	exit(EXIT_FAILURE);
434140c9e18Smrg }
435