xref: /netbsd-src/usr.bin/audio/record/record.c (revision 707adea33919471657fb2c7dc8eaf8e19df9aa1a)
1 /*	$NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg Exp $	*/
2 
3 /*
4  * Copyright (c) 1999, 2002, 2003, 2005, 2010 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  *
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
26  * SUCH DAMAGE.
27  */
28 
29 /*
30  * SunOS compatible audiorecord(1)
31  */
32 #include <sys/cdefs.h>
33 
34 #ifndef lint
35 __RCSID("$NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg Exp $");
36 #endif
37 
38 
39 #include <sys/param.h>
40 #include <sys/audioio.h>
41 #include <sys/ioctl.h>
42 #include <sys/time.h>
43 #include <sys/uio.h>
44 
45 #include <err.h>
46 #include <fcntl.h>
47 #include <paths.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <util.h>
54 
55 #include "libaudio.h"
56 #include "auconv.h"
57 
58 static audio_info_t info, oinfo;
59 static const char *device;
60 static int	audiofd;
61 static int	aflag, fflag;
62 int	verbose;
63 static int	monitor_gain, omonitor_gain;
64 static int	gain;
65 static int	balance;
66 static int	port;
67 static char	*encoding_str;
68 static struct track_info ti;
69 static struct timeval record_time;
70 static struct timeval start_time;
71 static int no_time_limit = 1;
72 
73 static void (*conv_func) (u_char *, int);
74 
75 static void usage (void) __dead;
76 static int timeleft (struct timeval *, struct timeval *);
77 static void cleanup (int) __dead;
78 static void rewrite_header (void);
79 static void stop (int);
80 
stop(int sig)81 static void stop (int sig)
82 {
83 	no_time_limit = 0;
84 	timerclear(&record_time);
85 }
86 
87 int
main(int argc,char * argv[])88 main(int argc, char *argv[])
89 {
90 	u_char	*buffer;
91 	size_t	len, bufsize = 0;
92 	ssize_t	nread;
93 	int	ch;
94 	const char *defdevice = _PATH_SOUND;
95 
96 	/*
97 	 * Initialise the track_info.
98 	 */
99 	ti.format = AUDIO_FORMAT_DEFAULT;
100 	ti.total_size = -1;
101 
102 	while ((ch = getopt(argc, argv, "ab:B:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) {
103 		switch (ch) {
104 		case 'a':
105 			aflag++;
106 			break;
107 		case 'b':
108 			decode_int(optarg, &balance);
109 			if (balance < 0 || balance > 63)
110 				errx(1, "balance must be between 0 and 63");
111 			break;
112 		case 'B':
113 			bufsize = strsuftoll("read buffer size", optarg,
114 					     1, UINT_MAX);
115 			break;
116 		case 'C':
117 			/* Ignore, compatibility */
118 			break;
119 		case 'F':
120 			ti.format = audio_format_from_str(optarg);
121 			if (ti.format < 0)
122 				errx(1, "Unknown audio format; supported "
123 				    "formats: \"sun\", \"wav\", and \"none\"");
124 			break;
125 		case 'c':
126 			decode_int(optarg, &ti.channels);
127 			if (ti.channels < 0 || ti.channels > 16)
128 				errx(1, "channels must be between 0 and 16");
129 			break;
130 		case 'd':
131 			device = optarg;
132 			break;
133 		case 'e':
134 			encoding_str = optarg;
135 			break;
136 		case 'f':
137 			fflag++;
138 			break;
139 		case 'i':
140 			ti.header_info = optarg;
141 			break;
142 		case 'm':
143 			decode_int(optarg, &monitor_gain);
144 			if (monitor_gain < 0 || monitor_gain > 255)
145 				errx(1, "monitor volume must be between 0 and 255");
146 			break;
147 		case 'P':
148 			decode_int(optarg, &ti.precision);
149 			if (ti.precision != 4 && ti.precision != 8 &&
150 			    ti.precision != 16 && ti.precision != 24 &&
151 			    ti.precision != 32)
152 				errx(1, "precision must be between 4, 8, 16, 24 or 32");
153 			break;
154 		case 'p':
155 			len = strlen(optarg);
156 
157 			if (strncmp(optarg, "mic", len) == 0)
158 				port |= AUDIO_MICROPHONE;
159 			else if (strncmp(optarg, "cd", len) == 0 ||
160 			           strncmp(optarg, "internal-cd", len) == 0)
161 				port |= AUDIO_CD;
162 			else if (strncmp(optarg, "line", len) == 0)
163 				port |= AUDIO_LINE_IN;
164 			else
165 				errx(1,
166 			    "port must be `cd', `internal-cd', `mic', or `line'");
167 			break;
168 		case 'q':
169 			ti.qflag++;
170 			break;
171 		case 's':
172 			decode_int(optarg, &ti.sample_rate);
173 			if (ti.sample_rate < 0 || ti.sample_rate > 48000 * 2)	/* XXX */
174 				errx(1, "sample rate must be between 0 and 96000");
175 			break;
176 		case 't':
177 			no_time_limit = 0;
178 			decode_time(optarg, &record_time);
179 			break;
180 		case 'V':
181 			verbose++;
182 			break;
183 		case 'v':
184 			decode_int(optarg, &gain);
185 			if (gain < 0 || gain > 255)
186 				errx(1, "volume must be between 0 and 255");
187 			break;
188 		/* case 'h': */
189 		default:
190 			usage();
191 			/* NOTREACHED */
192 		}
193 	}
194 	argc -= optind;
195 	argv += optind;
196 
197 	if (argc != 1)
198 		usage();
199 
200 	/*
201 	 * convert the encoding string into a value.
202 	 */
203 	if (encoding_str) {
204 		ti.encoding = audio_enc_to_val(encoding_str);
205 		if (ti.encoding == -1)
206 			errx(1, "unknown encoding, bailing...");
207 	}
208 
209 	/*
210 	 * open the output file
211 	 */
212 	if (argv[0][0] != '-' || argv[0][1] != '\0') {
213 		/* intuit the file type from the name */
214 		if (ti.format == AUDIO_FORMAT_DEFAULT)
215 		{
216 			size_t flen = strlen(*argv);
217 			const char *arg = *argv;
218 
219 			if (strcasecmp(arg + flen - 3, ".au") == 0)
220 				ti.format = AUDIO_FORMAT_SUN;
221 			else if (strcasecmp(arg + flen - 4, ".wav") == 0)
222 				ti.format = AUDIO_FORMAT_WAV;
223 		}
224 		ti.outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666);
225 		if (ti.outfd < 0)
226 			err(1, "could not open %s", *argv);
227 	} else
228 		ti.outfd = STDOUT_FILENO;
229 
230 	/*
231 	 * open the audio device
232 	 */
233 	if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL &&
234 	    (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */
235 		device = defdevice;
236 
237 	audiofd = open(device, O_RDONLY);
238 	if (audiofd < 0 && device == defdevice) {
239 		device = _PATH_SOUND0;
240 		audiofd = open(device, O_RDONLY);
241 	}
242 	if (audiofd < 0)
243 		err(1, "failed to open %s", device);
244 
245 	/*
246 	 * work out the buffer size to use, and allocate it.  also work out
247 	 * what the old monitor gain value is, so that we can reset it later.
248 	 */
249 	if (ioctl(audiofd, AUDIO_GETINFO, &oinfo) < 0)
250 		err(1, "failed to get audio info");
251 	if (bufsize == 0) {
252 		bufsize = oinfo.record.buffer_size;
253 		if (bufsize < 32 * 1024)
254 			bufsize = 32 * 1024;
255 	}
256 	omonitor_gain = oinfo.monitor_gain;
257 
258 	buffer = malloc(bufsize);
259 	if (buffer == NULL)
260 		err(1, "couldn't malloc buffer of %d size", (int)bufsize);
261 
262 	/*
263 	 * set up audio device for recording with the speified parameters
264 	 */
265 	AUDIO_INITINFO(&info);
266 
267 	/*
268 	 * for these, get the current values for stuffing into the header
269 	 */
270 #define SETINFO2(x, y)	if (x) \
271 				info.record.y = x; \
272 			else \
273 				info.record.y = x = oinfo.record.y;
274 #define SETINFO(x)	SETINFO2(ti.x, x)
275 
276 	SETINFO (sample_rate)
277 	SETINFO (channels)
278 	SETINFO (precision)
279 	SETINFO (encoding)
280 	SETINFO2 (gain, gain)
281 	SETINFO2 (port, port)
282 	SETINFO2 (balance, balance)
283 #undef SETINFO
284 #undef SETINFO2
285 
286 	if (monitor_gain)
287 		info.monitor_gain = monitor_gain;
288 	else
289 		monitor_gain = oinfo.monitor_gain;
290 
291 	info.mode = AUMODE_RECORD;
292 	if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0)
293 		err(1, "failed to set audio info");
294 
295 	signal(SIGINT, stop);
296 
297 	ti.total_size = 0;
298 
299 	write_header(&ti);
300 	if (ti.format == AUDIO_FORMAT_NONE)
301 		errx(1, "unable to determine audio format");
302 	conv_func = write_get_conv_func(&ti);
303 
304 	if (verbose && conv_func) {
305 		const char *s = NULL;
306 
307 		if (conv_func == swap_bytes)
308 			s = "swap bytes (16 bit)";
309 		else if (conv_func == swap_bytes32)
310 			s = "swap bytes (32 bit)";
311 		else if (conv_func == change_sign16_be)
312 			s = "change sign (big-endian, 16 bit)";
313 		else if (conv_func == change_sign16_le)
314 			s = "change sign (little-endian, 16 bit)";
315 		else if (conv_func == change_sign24_be)
316 			s = "change sign (big-endian, 24 bit)";
317 		else if (conv_func == change_sign24_le)
318 			s = "change sign (little-endian, 24 bit)";
319 		else if (conv_func == change_sign32_be)
320 			s = "change sign (big-endian, 32 bit)";
321 		else if (conv_func == change_sign32_le)
322 			s = "change sign (little-endian, 32 bit)";
323 		else if (conv_func == change_sign16_swap_bytes_be)
324 			s = "change sign & swap bytes (big-endian, 16 bit)";
325 		else if (conv_func == change_sign16_swap_bytes_le)
326 			s = "change sign & swap bytes (little-endian, 16 bit)";
327 		else if (conv_func == change_sign24_swap_bytes_be)
328 			s = "change sign & swap bytes (big-endian, 24 bit)";
329 		else if (conv_func == change_sign24_swap_bytes_le)
330 			s = "change sign & swap bytes (little-endian, 24 bit)";
331 		else if (conv_func == change_sign32_swap_bytes_be)
332 			s = "change sign (big-endian, 32 bit)";
333 		else if (conv_func == change_sign32_swap_bytes_le)
334 			s = "change sign & swap bytes (little-endian, 32 bit)";
335 
336 		if (s)
337 			fprintf(stderr, "%s: converting, using function: %s\n",
338 			    getprogname(), s);
339 		else
340 			fprintf(stderr, "%s: using unnamed conversion "
341 					"function\n", getprogname());
342 	}
343 
344 	if (verbose)
345 		fprintf(stderr,
346 		   "sample_rate=%d channels=%d precision=%d encoding=%s\n",
347 		   info.record.sample_rate, info.record.channels,
348 		   info.record.precision,
349 		   audio_enc_from_val(info.record.encoding));
350 
351 	if (!no_time_limit && verbose)
352 		fprintf(stderr, "recording for %lu seconds, %lu microseconds\n",
353 		    (u_long)record_time.tv_sec, (u_long)record_time.tv_usec);
354 
355 	(void)gettimeofday(&start_time, NULL);
356 	while (no_time_limit || timeleft(&start_time, &record_time)) {
357 		if ((nread = read(audiofd, buffer, bufsize)) == -1)
358 			err(1, "read failed");
359 		if (nread == 0)
360 			break;
361 		if (conv_func)
362 			(*conv_func)(buffer, nread);
363 		if (write(ti.outfd, buffer, nread) != nread)
364 			err(1, "write failed");
365 		ti.total_size += nread;
366 	}
367 	cleanup(0);
368 }
369 
370 int
timeleft(struct timeval * start_tvp,struct timeval * record_tvp)371 timeleft(struct timeval *start_tvp, struct timeval *record_tvp)
372 {
373 	struct timeval now, diff;
374 
375 	(void)gettimeofday(&now, NULL);
376 	timersub(&now, start_tvp, &diff);
377 	timersub(record_tvp, &diff, &now);
378 
379 	return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0));
380 }
381 
382 void
cleanup(int signo)383 cleanup(int signo)
384 {
385 
386 	rewrite_header();
387 	close(ti.outfd);
388 	if (omonitor_gain) {
389 		AUDIO_INITINFO(&info);
390 		info.monitor_gain = omonitor_gain;
391 		if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0)
392 			err(1, "failed to reset audio info");
393 	}
394 	close(audiofd);
395 	if (signo != 0) {
396 		(void)raise_default_signal(signo);
397 	}
398 	exit(0);
399 }
400 
401 static void
rewrite_header(void)402 rewrite_header(void)
403 {
404 
405 	/* can't do this here! */
406 	if (ti.outfd == STDOUT_FILENO)
407 		return;
408 	if (lseek(ti.outfd, (off_t)0, SEEK_SET) == (off_t)-1)
409 		err(1, "could not seek to start of file for header rewrite");
410 	write_header(&ti);
411 }
412 
413 static void
usage(void)414 usage(void)
415 {
416 
417 	fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n",
418 	    getprogname());
419 	fprintf(stderr, "Options:\n\t"
420 	    "-B buffer size\n\t"
421 	    "-b balance (0-63)\n\t"
422 	    "-c channels\n\t"
423 	    "-d audio device\n\t"
424 	    "-e encoding\n\t"
425 	    "-F format\n\t"
426 	    "-i header information\n\t"
427 	    "-m monitor volume\n\t"
428 	    "-P precision (4, 8, 16, 24, or 32 bits)\n\t"
429 	    "-p input port\n\t"
430 	    "-s sample rate\n\t"
431 	    "-t recording time\n\t"
432 	    "-v volume\n");
433 	exit(EXIT_FAILURE);
434 }
435