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