record.c revision 1.54 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