xref: /netbsd-src/usr.bin/audio/record/record.c (revision 27578b9aac214cc7796ead81dcc5427e79d5f2a0)
1 /*	$NetBSD: record.c,v 1.16 2001/06/07 12:50:29 mrg Exp $	*/
2 
3 /*
4  * Copyright (c) 1999 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  * 3. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 /*
32  * SunOS compatible audiorecord(1)
33  */
34 
35 #include <sys/types.h>
36 #include <sys/audioio.h>
37 #include <sys/ioctl.h>
38 #include <sys/time.h>
39 #include <sys/uio.h>
40 
41 #include <err.h>
42 #include <fcntl.h>
43 #include <paths.h>
44 #include <signal.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #include "libaudio.h"
51 
52 audio_info_t info, oinfo;
53 ssize_t	total_size = -1;
54 char	*device;
55 char	*ctldev;
56 char	*header_info;
57 char	default_info[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' };
58 int	audiofd, ctlfd, outfd;
59 int	qflag, aflag, fflag;
60 int	verbose;
61 int	monvol, omonvol;
62 int	volume;
63 int	balance;
64 int	port;
65 int	encoding;
66 char	*encoding_str;
67 int	precision;
68 int	sample_rate;
69 int	channels;
70 struct timeval record_time;
71 struct timeval start_time;	/* XXX because that's what gettimeofday returns */
72 
73 void usage (void);
74 int main (int, char *[]);
75 int timeleft (struct timeval *, struct timeval *);
76 void cleanup (int) __attribute__((__noreturn__));
77 void write_header (void);
78 void rewrite_header (void);
79 
80 int
81 main(argc, argv)
82 	int argc;
83 	char *argv[];
84 {
85 	char	*buffer;
86 	size_t	len, bufsize;
87 	int	ch, no_time_limit = 1;
88 
89 	while ((ch = getopt(argc, argv, "ab:C:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) {
90 		switch (ch) {
91 		case 'a':
92 			aflag++;
93 			break;
94 		case 'b':
95 			decode_int(optarg, &balance);
96 			if (balance < 0 || balance > 63)
97 				errx(1, "balance must be between 0 and 63\n");
98 			break;
99 		case 'C':
100 			ctldev = optarg;
101 			break;
102 		case 'c':
103 			decode_int(optarg, &channels);
104 			if (channels < 0 || channels > 16)
105 				errx(1, "channels must be between 0 and 16\n");
106 			break;
107 		case 'd':
108 			device = optarg;
109 			break;
110 		case 'e':
111 			encoding_str = optarg;
112 			break;
113 		case 'f':
114 			fflag++;
115 			break;
116 		case 'i':
117 			header_info = optarg;
118 			break;
119 		case 'm':
120 			decode_int(optarg, &monvol);
121 			if (monvol < 0 || monvol > 255)
122 				errx(1, "monitor volume must be between 0 and 255\n");
123 			break;
124 		case 'P':
125 			decode_int(optarg, &precision);
126 			if (precision != 4 && precision != 8 &&
127 			    precision != 16 && precision != 24 &&
128 			    precision != 32)
129 				errx(1, "precision must be between 4, 8, 16, 24 or 32");
130 			break;
131 		case 'p':
132 			len = strlen(optarg);
133 
134 			if (strncmp(optarg, "mic", len) == 0)
135 				port |= AUDIO_MICROPHONE;
136 			else if (strncmp(optarg, "cd", len) == 0 ||
137 			           strncmp(optarg, "internal-cd", len) == 0)
138 				port |= AUDIO_CD;
139 			else if (strncmp(optarg, "line", len) == 0)
140 				port |= AUDIO_LINE_IN;
141 			else
142 				errx(1,
143 			    "port must be `cd', `internal-cd', `mic', or `line'");
144 			break;
145 		case 'q':
146 			qflag++;
147 			break;
148 		case 's':
149 			decode_int(optarg, &sample_rate);
150 			if (sample_rate < 0 || sample_rate > 48000 * 2)	/* XXX */
151 				errx(1, "sample rate must be between 0 and 96000\n");
152 			break;
153 		case 't':
154 			no_time_limit = 0;
155 			decode_time(optarg, &record_time);
156 			break;
157 		case 'V':
158 			verbose++;
159 			break;
160 		case 'v':
161 			decode_int(optarg, &volume);
162 			if (volume < 0 || volume > 255)
163 				errx(1, "volume must be between 0 and 255\n");
164 			break;
165 		/* case 'h': */
166 		default:
167 			usage();
168 			/* NOTREACHED */
169 		}
170 	}
171 	argc -= optind;
172 	argv += optind;
173 
174 	/*
175 	 * open the audio device, and control device
176 	 */
177 	if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL &&
178 	    (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */
179 		device = _PATH_AUDIO;
180 	if (ctldev == NULL && (ctldev = getenv("AUDIOCTLDEVICE")) == NULL)
181 		ctldev = _PATH_AUDIOCTL;
182 
183 	audiofd = open(device, O_RDONLY);
184 	if (audiofd < 0)
185 		err(1, "failed to open %s", device);
186 	ctlfd = open(ctldev, O_RDWR);
187 	if (ctlfd < 0)
188 		err(1, "failed to open %s", ctldev);
189 
190 	/*
191 	 * work out the buffer size to use, and allocate it.  also work out
192 	 * what the old monitor gain value is, so that we can reset it later.
193 	 */
194 	if (ioctl(ctlfd, AUDIO_GETINFO, &oinfo) < 0)
195 		err(1, "failed to get audio info");
196 	bufsize = oinfo.record.buffer_size;
197 	if (bufsize < 32 * 1024)
198 		bufsize = 32 * 1024;
199 	omonvol = oinfo.monitor_gain;
200 
201 	buffer = malloc(bufsize);
202 	if (buffer == NULL)
203 		err(1, "couldn't malloc buffer of %d size", (int)bufsize);
204 
205 	/*
206 	 * open the output file
207 	 */
208 	if (argc != 1)
209 		usage();
210 	if (argv[0][0] != '-' && argv[0][1] != '\0') {
211 		outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666);
212 		if (outfd < 0)
213 			err(1, "could not open %s", *argv);
214 	} else
215 		outfd = STDOUT_FILENO;
216 
217 	/*
218 	 * set up audio device for recording with the speified parameters
219 	 */
220 	AUDIO_INITINFO(&info);
221 
222 	/*
223 	 * for these, get the current values for stuffing into the header
224 	 **/
225 	if (sample_rate)
226 		info.record.sample_rate = sample_rate;
227 	else
228 		sample_rate = oinfo.record.sample_rate;
229 	if (channels)
230 		info.record.channels = channels;
231 	else
232 		channels = oinfo.record.channels;
233 
234 	if (encoding_str) {
235 		encoding = audio_enc_to_val(encoding_str);
236 		if (encoding == -1)
237 			errx(1, "unknown encoding, bailing...");
238 	}
239 	else
240 		encoding = AUDIO_ENCODING_ULAW;
241 
242 	if (precision)
243 		info.record.precision = precision;
244 	if (encoding)
245 		info.record.encoding = encoding;
246 	if (volume)
247 		info.record.gain = volume;
248 	if (port)
249 		info.record.port = port;
250 	if (balance)
251 		info.record.balance = (u_char)balance;
252 	if (monvol)
253 		info.monitor_gain = monvol;
254 
255 	info.mode = AUMODE_RECORD;
256 	if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0)
257 		err(1, "failed to reset audio info");
258 
259 	signal(SIGINT, cleanup);
260 	write_header();
261 	total_size = 0;
262 
263 	(void)gettimeofday(&start_time, NULL);
264 	while (no_time_limit || timeleft(&start_time, &record_time)) {
265 		if (read(audiofd, buffer, bufsize) != bufsize)
266 			err(1, "read failed");
267 		if (write(outfd, buffer, bufsize) != bufsize)
268 			err(1, "write failed");
269 		total_size += bufsize;
270 	}
271 	cleanup(0);
272 }
273 
274 int
275 timeleft(start_tvp, record_tvp)
276 	struct timeval *start_tvp;
277 	struct timeval *record_tvp;
278 {
279 	struct timeval now, diff;
280 
281 	(void)gettimeofday(&now, NULL);
282 	timersub(&now, start_tvp, &diff);
283 	timersub(record_tvp, &diff, &now);
284 
285 	return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0));
286 }
287 
288 void
289 cleanup(signo)
290 	int signo;
291 {
292 
293 	close(audiofd);
294 	rewrite_header();
295 	close(outfd);
296 	if (omonvol) {
297 		AUDIO_INITINFO(&info);
298 		info.monitor_gain = omonvol;
299 		if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0)
300 			err(1, "failed to reset audio info");
301 	}
302 	close(ctlfd);
303 	exit(0);
304 }
305 
306 void
307 write_header()
308 {
309 	static int warned = 0;
310 	sun_audioheader auh;
311 	struct iovec iv[3];
312 	int veclen = 0, left, tlen = 0;
313 	int sunenc;
314 
315 	/* if we can't express this as a Sun header, don't write any */
316 	if (audio_encoding_to_sun(encoding, precision, &sunenc) != 0) {
317 		if (!qflag && !warned)
318 			warnx("failed to convert to sun encoding; "
319 			      "Sun audio header not written");
320 		warned = 1;
321 		return;
322 	}
323 
324 	auh.magic = htonl(AUDIO_FILE_MAGIC);
325 	if (outfd == STDOUT_FILENO)
326 		auh.data_size = htonl(AUDIO_UNKNOWN_SIZE);
327 	else
328 		auh.data_size = htonl(total_size);
329 	auh.encoding = htonl(sunenc);
330 	auh.sample_rate = htonl(sample_rate);
331 	auh.channels = htonl(channels);
332 	if (header_info) {
333 		int 	len, infolen;
334 
335 		infolen = ((len = strlen(header_info)) + 7) & 0xfffffff8;
336 		left = infolen - len;
337 		auh.hdr_size = htonl(sizeof(auh) + infolen);
338 	} else {
339 		left = sizeof(default_info);
340 		auh.hdr_size = htonl(sizeof(auh) + left);
341 	}
342 
343 	iv[veclen].iov_base = &auh;
344 	iv[veclen].iov_len = sizeof(auh);
345 	tlen = iv[veclen++].iov_len;
346 	if (header_info) {
347 		iv[veclen].iov_base = header_info;
348 		iv[veclen].iov_len = (int)strlen(header_info);
349 		tlen += iv[veclen++].iov_len;
350 	}
351 	if (left) {
352 		iv[veclen].iov_base = default_info;
353 		iv[veclen].iov_len = left;
354 		tlen += iv[veclen++].iov_len;
355 	}
356 
357 	if (writev(outfd, iv, veclen) != tlen)
358 		err(1, "could not write audio header");
359 }
360 
361 void
362 rewrite_header()
363 {
364 
365 	/* can't do this here! */
366 	if (outfd == STDOUT_FILENO)
367 		return;
368 
369 	if (lseek(outfd, SEEK_SET, 0) < 0)
370 		err(1, "could not seek to start of file for header rewrite");
371 	write_header();
372 }
373 
374 void
375 usage()
376 {
377 
378 	fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n",
379 	    getprogname());
380 	fprintf(stderr, "Options:\n\t"
381 	    "-C audio control device\n\t"
382 	    "-b balance (0-63)\n\t"
383 	    "-c channels\n\t"
384 	    "-d audio device\n\t"
385 	    "-e encoding\n\t"
386 	    "-i header information\n\t"
387 	    "-m monitor volume\n\t"
388 	    "-P precision bits (4, 8, 16, 24 or 32)\n\t"
389 	    "-p input port\n\t"
390 	    "-s sample rate\n\t"
391 	    "-t recording time\n\t"
392 	    "-v volume\n");
393 	exit(EXIT_FAILURE);
394 }
395