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