record.c revision 1.56 1 1.56 mlelstv /* $NetBSD: record.c,v 1.56 2022/01/09 06:33:13 mlelstv Exp $ */
2 1.2 mrg
3 1.2 mrg /*
4 1.48 mrg * Copyright (c) 1999, 2002, 2003, 2005, 2010 Matthew R. Green
5 1.2 mrg * All rights reserved.
6 1.2 mrg *
7 1.2 mrg * Redistribution and use in source and binary forms, with or without
8 1.2 mrg * modification, are permitted provided that the following conditions
9 1.2 mrg * are met:
10 1.2 mrg * 1. Redistributions of source code must retain the above copyright
11 1.2 mrg * notice, this list of conditions and the following disclaimer.
12 1.2 mrg * 2. Redistributions in binary form must reproduce the above copyright
13 1.2 mrg * notice, this list of conditions and the following disclaimer in the
14 1.2 mrg * documentation and/or other materials provided with the distribution.
15 1.2 mrg *
16 1.2 mrg * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 1.2 mrg * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 1.2 mrg * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 1.2 mrg * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 1.2 mrg * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 1.2 mrg * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 1.2 mrg * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 1.2 mrg * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 1.2 mrg * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 1.2 mrg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 1.2 mrg * SUCH DAMAGE.
27 1.2 mrg */
28 1.2 mrg
29 1.1 mrg /*
30 1.1 mrg * SunOS compatible audiorecord(1)
31 1.1 mrg */
32 1.33 agc #include <sys/cdefs.h>
33 1.33 agc
34 1.33 agc #ifndef lint
35 1.56 mlelstv __RCSID("$NetBSD: record.c,v 1.56 2022/01/09 06:33:13 mlelstv Exp $");
36 1.33 agc #endif
37 1.33 agc
38 1.1 mrg
39 1.48 mrg #include <sys/param.h>
40 1.1 mrg #include <sys/audioio.h>
41 1.1 mrg #include <sys/ioctl.h>
42 1.1 mrg #include <sys/time.h>
43 1.1 mrg #include <sys/uio.h>
44 1.1 mrg
45 1.1 mrg #include <err.h>
46 1.1 mrg #include <fcntl.h>
47 1.1 mrg #include <paths.h>
48 1.1 mrg #include <signal.h>
49 1.1 mrg #include <stdio.h>
50 1.1 mrg #include <stdlib.h>
51 1.1 mrg #include <string.h>
52 1.1 mrg #include <unistd.h>
53 1.44 lukem #include <util.h>
54 1.1 mrg
55 1.1 mrg #include "libaudio.h"
56 1.19 mrg #include "auconv.h"
57 1.1 mrg
58 1.51 joerg static audio_info_t info, oinfo;
59 1.51 joerg static const char *device;
60 1.53 mrg static int audiofd;
61 1.53 mrg static int aflag, fflag;
62 1.1 mrg int verbose;
63 1.51 joerg static int monitor_gain, omonitor_gain;
64 1.51 joerg static int gain;
65 1.51 joerg static int balance;
66 1.51 joerg static int port;
67 1.51 joerg static char *encoding_str;
68 1.54 mrg static struct track_info ti;
69 1.51 joerg static struct timeval record_time;
70 1.51 joerg static struct timeval start_time;
71 1.56 mlelstv static int no_time_limit = 1;
72 1.51 joerg
73 1.51 joerg static void (*conv_func) (u_char *, int);
74 1.51 joerg
75 1.51 joerg static void usage (void) __dead;
76 1.51 joerg static int timeleft (struct timeval *, struct timeval *);
77 1.51 joerg static void cleanup (int) __dead;
78 1.51 joerg static void rewrite_header (void);
79 1.56 mlelstv static void stop (int);
80 1.56 mlelstv
81 1.56 mlelstv static void stop (int sig)
82 1.56 mlelstv {
83 1.56 mlelstv no_time_limit = 0;
84 1.56 mlelstv timerclear(&record_time);
85 1.56 mlelstv }
86 1.1 mrg
87 1.1 mrg int
88 1.51 joerg main(int argc, char *argv[])
89 1.1 mrg {
90 1.43 mrg u_char *buffer;
91 1.48 mrg size_t len, bufsize = 0;
92 1.55 riastrad ssize_t nread;
93 1.56 mlelstv int ch;
94 1.24 augustss const char *defdevice = _PATH_SOUND;
95 1.1 mrg
96 1.53 mrg /*
97 1.54 mrg * Initialise the track_info.
98 1.53 mrg */
99 1.54 mrg ti.format = AUDIO_FORMAT_DEFAULT;
100 1.54 mrg ti.total_size = -1;
101 1.53 mrg
102 1.48 mrg while ((ch = getopt(argc, argv, "ab:B:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) {
103 1.1 mrg switch (ch) {
104 1.1 mrg case 'a':
105 1.1 mrg aflag++;
106 1.1 mrg break;
107 1.1 mrg case 'b':
108 1.1 mrg decode_int(optarg, &balance);
109 1.1 mrg if (balance < 0 || balance > 63)
110 1.30 grant errx(1, "balance must be between 0 and 63");
111 1.1 mrg break;
112 1.48 mrg case 'B':
113 1.48 mrg bufsize = strsuftoll("read buffer size", optarg,
114 1.48 mrg 1, UINT_MAX);
115 1.48 mrg break;
116 1.1 mrg case 'C':
117 1.23 jdolecek /* Ignore, compatibility */
118 1.1 mrg break;
119 1.17 mrg case 'F':
120 1.54 mrg ti.format = audio_format_from_str(optarg);
121 1.54 mrg if (ti.format < 0)
122 1.27 mrg errx(1, "Unknown audio format; supported "
123 1.27 mrg "formats: \"sun\", \"wav\", and \"none\"");
124 1.17 mrg break;
125 1.1 mrg case 'c':
126 1.54 mrg decode_int(optarg, &ti.channels);
127 1.54 mrg if (ti.channels < 0 || ti.channels > 16)
128 1.30 grant errx(1, "channels must be between 0 and 16");
129 1.1 mrg break;
130 1.1 mrg case 'd':
131 1.1 mrg device = optarg;
132 1.1 mrg break;
133 1.1 mrg case 'e':
134 1.1 mrg encoding_str = optarg;
135 1.1 mrg break;
136 1.1 mrg case 'f':
137 1.1 mrg fflag++;
138 1.1 mrg break;
139 1.1 mrg case 'i':
140 1.54 mrg ti.header_info = optarg;
141 1.1 mrg break;
142 1.1 mrg case 'm':
143 1.17 mrg decode_int(optarg, &monitor_gain);
144 1.17 mrg if (monitor_gain < 0 || monitor_gain > 255)
145 1.30 grant errx(1, "monitor volume must be between 0 and 255");
146 1.1 mrg break;
147 1.1 mrg case 'P':
148 1.54 mrg decode_int(optarg, &ti.precision);
149 1.54 mrg if (ti.precision != 4 && ti.precision != 8 &&
150 1.54 mrg ti.precision != 16 && ti.precision != 24 &&
151 1.54 mrg ti.precision != 32)
152 1.15 minoura errx(1, "precision must be between 4, 8, 16, 24 or 32");
153 1.1 mrg break;
154 1.1 mrg case 'p':
155 1.1 mrg len = strlen(optarg);
156 1.1 mrg
157 1.1 mrg if (strncmp(optarg, "mic", len) == 0)
158 1.1 mrg port |= AUDIO_MICROPHONE;
159 1.1 mrg else if (strncmp(optarg, "cd", len) == 0 ||
160 1.1 mrg strncmp(optarg, "internal-cd", len) == 0)
161 1.1 mrg port |= AUDIO_CD;
162 1.1 mrg else if (strncmp(optarg, "line", len) == 0)
163 1.1 mrg port |= AUDIO_LINE_IN;
164 1.1 mrg else
165 1.1 mrg errx(1,
166 1.1 mrg "port must be `cd', `internal-cd', `mic', or `line'");
167 1.1 mrg break;
168 1.1 mrg case 'q':
169 1.54 mrg ti.qflag++;
170 1.1 mrg break;
171 1.1 mrg case 's':
172 1.54 mrg decode_int(optarg, &ti.sample_rate);
173 1.54 mrg if (ti.sample_rate < 0 || ti.sample_rate > 48000 * 2) /* XXX */
174 1.30 grant errx(1, "sample rate must be between 0 and 96000");
175 1.1 mrg break;
176 1.1 mrg case 't':
177 1.8 mrg no_time_limit = 0;
178 1.1 mrg decode_time(optarg, &record_time);
179 1.1 mrg break;
180 1.1 mrg case 'V':
181 1.1 mrg verbose++;
182 1.1 mrg break;
183 1.1 mrg case 'v':
184 1.17 mrg decode_int(optarg, &gain);
185 1.17 mrg if (gain < 0 || gain > 255)
186 1.30 grant errx(1, "volume must be between 0 and 255");
187 1.1 mrg break;
188 1.1 mrg /* case 'h': */
189 1.1 mrg default:
190 1.1 mrg usage();
191 1.1 mrg /* NOTREACHED */
192 1.1 mrg }
193 1.1 mrg }
194 1.1 mrg argc -= optind;
195 1.1 mrg argv += optind;
196 1.1 mrg
197 1.39 mrg if (argc != 1)
198 1.39 mrg usage();
199 1.39 mrg
200 1.1 mrg /*
201 1.40 mrg * convert the encoding string into a value.
202 1.40 mrg */
203 1.40 mrg if (encoding_str) {
204 1.54 mrg ti.encoding = audio_enc_to_val(encoding_str);
205 1.54 mrg if (ti.encoding == -1)
206 1.40 mrg errx(1, "unknown encoding, bailing...");
207 1.40 mrg }
208 1.40 mrg
209 1.40 mrg /*
210 1.40 mrg * open the output file
211 1.40 mrg */
212 1.42 gson if (argv[0][0] != '-' || argv[0][1] != '\0') {
213 1.40 mrg /* intuit the file type from the name */
214 1.54 mrg if (ti.format == AUDIO_FORMAT_DEFAULT)
215 1.40 mrg {
216 1.40 mrg size_t flen = strlen(*argv);
217 1.40 mrg const char *arg = *argv;
218 1.40 mrg
219 1.40 mrg if (strcasecmp(arg + flen - 3, ".au") == 0)
220 1.54 mrg ti.format = AUDIO_FORMAT_SUN;
221 1.40 mrg else if (strcasecmp(arg + flen - 4, ".wav") == 0)
222 1.54 mrg ti.format = AUDIO_FORMAT_WAV;
223 1.40 mrg }
224 1.54 mrg ti.outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666);
225 1.54 mrg if (ti.outfd < 0)
226 1.40 mrg err(1, "could not open %s", *argv);
227 1.40 mrg } else
228 1.54 mrg ti.outfd = STDOUT_FILENO;
229 1.40 mrg
230 1.40 mrg /*
231 1.34 mrg * open the audio device
232 1.1 mrg */
233 1.6 kleink if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL &&
234 1.6 kleink (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */
235 1.24 augustss device = defdevice;
236 1.1 mrg
237 1.1 mrg audiofd = open(device, O_RDONLY);
238 1.24 augustss if (audiofd < 0 && device == defdevice) {
239 1.22 augustss device = _PATH_SOUND0;
240 1.28 uwe audiofd = open(device, O_RDONLY);
241 1.22 augustss }
242 1.1 mrg if (audiofd < 0)
243 1.1 mrg err(1, "failed to open %s", device);
244 1.1 mrg
245 1.1 mrg /*
246 1.1 mrg * work out the buffer size to use, and allocate it. also work out
247 1.1 mrg * what the old monitor gain value is, so that we can reset it later.
248 1.1 mrg */
249 1.23 jdolecek if (ioctl(audiofd, AUDIO_GETINFO, &oinfo) < 0)
250 1.1 mrg err(1, "failed to get audio info");
251 1.49 jmcneill if (bufsize == 0) {
252 1.48 mrg bufsize = oinfo.record.buffer_size;
253 1.49 jmcneill if (bufsize < 32 * 1024)
254 1.49 jmcneill bufsize = 32 * 1024;
255 1.49 jmcneill }
256 1.17 mrg omonitor_gain = oinfo.monitor_gain;
257 1.1 mrg
258 1.1 mrg buffer = malloc(bufsize);
259 1.1 mrg if (buffer == NULL)
260 1.1 mrg err(1, "couldn't malloc buffer of %d size", (int)bufsize);
261 1.1 mrg
262 1.1 mrg /*
263 1.17 mrg * set up audio device for recording with the speified parameters
264 1.17 mrg */
265 1.17 mrg AUDIO_INITINFO(&info);
266 1.17 mrg
267 1.17 mrg /*
268 1.17 mrg * for these, get the current values for stuffing into the header
269 1.17 mrg */
270 1.53 mrg #define SETINFO2(x, y) if (x) \
271 1.53 mrg info.record.y = x; \
272 1.40 mrg else \
273 1.53 mrg info.record.y = x = oinfo.record.y;
274 1.54 mrg #define SETINFO(x) SETINFO2(ti.x, x)
275 1.53 mrg
276 1.40 mrg SETINFO (sample_rate)
277 1.40 mrg SETINFO (channels)
278 1.40 mrg SETINFO (precision)
279 1.40 mrg SETINFO (encoding)
280 1.53 mrg SETINFO2 (gain, gain)
281 1.53 mrg SETINFO2 (port, port)
282 1.53 mrg SETINFO2 (balance, balance)
283 1.17 mrg #undef SETINFO
284 1.53 mrg #undef SETINFO2
285 1.17 mrg
286 1.17 mrg if (monitor_gain)
287 1.17 mrg info.monitor_gain = monitor_gain;
288 1.17 mrg else
289 1.17 mrg monitor_gain = oinfo.monitor_gain;
290 1.1 mrg
291 1.1 mrg info.mode = AUMODE_RECORD;
292 1.23 jdolecek if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0)
293 1.29 mrg err(1, "failed to set audio info");
294 1.1 mrg
295 1.56 mlelstv signal(SIGINT, stop);
296 1.53 mrg
297 1.54 mrg ti.total_size = 0;
298 1.53 mrg
299 1.54 mrg write_header(&ti);
300 1.54 mrg if (ti.format == AUDIO_FORMAT_NONE)
301 1.53 mrg errx(1, "unable to determine audio format");
302 1.54 mrg conv_func = write_get_conv_func(&ti);
303 1.1 mrg
304 1.27 mrg if (verbose && conv_func) {
305 1.27 mrg const char *s = NULL;
306 1.27 mrg
307 1.27 mrg if (conv_func == swap_bytes)
308 1.27 mrg s = "swap bytes (16 bit)";
309 1.27 mrg else if (conv_func == swap_bytes32)
310 1.27 mrg s = "swap bytes (32 bit)";
311 1.27 mrg else if (conv_func == change_sign16_be)
312 1.27 mrg s = "change sign (big-endian, 16 bit)";
313 1.27 mrg else if (conv_func == change_sign16_le)
314 1.27 mrg s = "change sign (little-endian, 16 bit)";
315 1.27 mrg else if (conv_func == change_sign32_be)
316 1.27 mrg s = "change sign (big-endian, 32 bit)";
317 1.27 mrg else if (conv_func == change_sign32_le)
318 1.27 mrg s = "change sign (little-endian, 32 bit)";
319 1.27 mrg else if (conv_func == change_sign16_swap_bytes_be)
320 1.27 mrg s = "change sign & swap bytes (big-endian, 16 bit)";
321 1.27 mrg else if (conv_func == change_sign16_swap_bytes_le)
322 1.27 mrg s = "change sign & swap bytes (little-endian, 16 bit)";
323 1.27 mrg else if (conv_func == change_sign32_swap_bytes_be)
324 1.27 mrg s = "change sign (big-endian, 32 bit)";
325 1.27 mrg else if (conv_func == change_sign32_swap_bytes_le)
326 1.27 mrg s = "change sign & swap bytes (little-endian, 32 bit)";
327 1.27 mrg
328 1.27 mrg if (s)
329 1.27 mrg fprintf(stderr, "%s: converting, using function: %s\n",
330 1.27 mrg getprogname(), s);
331 1.27 mrg else
332 1.27 mrg fprintf(stderr, "%s: using unnamed conversion "
333 1.27 mrg "function\n", getprogname());
334 1.27 mrg }
335 1.27 mrg
336 1.27 mrg if (verbose)
337 1.27 mrg fprintf(stderr,
338 1.27 mrg "sample_rate=%d channels=%d precision=%d encoding=%s\n",
339 1.27 mrg info.record.sample_rate, info.record.channels,
340 1.27 mrg info.record.precision,
341 1.27 mrg audio_enc_from_val(info.record.encoding));
342 1.32 mrg
343 1.32 mrg if (!no_time_limit && verbose)
344 1.32 mrg fprintf(stderr, "recording for %lu seconds, %lu microseconds\n",
345 1.32 mrg (u_long)record_time.tv_sec, (u_long)record_time.tv_usec);
346 1.27 mrg
347 1.1 mrg (void)gettimeofday(&start_time, NULL);
348 1.8 mrg while (no_time_limit || timeleft(&start_time, &record_time)) {
349 1.55 riastrad if ((nread = read(audiofd, buffer, bufsize)) == -1)
350 1.1 mrg err(1, "read failed");
351 1.55 riastrad if (nread == 0)
352 1.56 mlelstv break;
353 1.18 mrg if (conv_func)
354 1.56 mlelstv (*conv_func)(buffer, nread);
355 1.56 mlelstv if (write(ti.outfd, buffer, nread) != nread)
356 1.1 mrg err(1, "write failed");
357 1.56 mlelstv ti.total_size += nread;
358 1.1 mrg }
359 1.1 mrg cleanup(0);
360 1.1 mrg }
361 1.1 mrg
362 1.1 mrg int
363 1.51 joerg timeleft(struct timeval *start_tvp, struct timeval *record_tvp)
364 1.1 mrg {
365 1.1 mrg struct timeval now, diff;
366 1.1 mrg
367 1.1 mrg (void)gettimeofday(&now, NULL);
368 1.7 dmcmahil timersub(&now, start_tvp, &diff);
369 1.7 dmcmahil timersub(record_tvp, &diff, &now);
370 1.1 mrg
371 1.1 mrg return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0));
372 1.1 mrg }
373 1.1 mrg
374 1.1 mrg void
375 1.51 joerg cleanup(int signo)
376 1.1 mrg {
377 1.1 mrg
378 1.1 mrg rewrite_header();
379 1.54 mrg close(ti.outfd);
380 1.17 mrg if (omonitor_gain) {
381 1.1 mrg AUDIO_INITINFO(&info);
382 1.17 mrg info.monitor_gain = omonitor_gain;
383 1.23 jdolecek if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0)
384 1.1 mrg err(1, "failed to reset audio info");
385 1.1 mrg }
386 1.23 jdolecek close(audiofd);
387 1.44 lukem if (signo != 0) {
388 1.44 lukem (void)raise_default_signal(signo);
389 1.44 lukem }
390 1.1 mrg exit(0);
391 1.1 mrg }
392 1.1 mrg
393 1.51 joerg static void
394 1.51 joerg rewrite_header(void)
395 1.1 mrg {
396 1.1 mrg
397 1.1 mrg /* can't do this here! */
398 1.54 mrg if (ti.outfd == STDOUT_FILENO)
399 1.1 mrg return;
400 1.54 mrg if (lseek(ti.outfd, (off_t)0, SEEK_SET) == (off_t)-1)
401 1.1 mrg err(1, "could not seek to start of file for header rewrite");
402 1.54 mrg write_header(&ti);
403 1.1 mrg }
404 1.1 mrg
405 1.51 joerg static void
406 1.51 joerg usage(void)
407 1.1 mrg {
408 1.14 cgd
409 1.14 cgd fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n",
410 1.14 cgd getprogname());
411 1.4 mrg fprintf(stderr, "Options:\n\t"
412 1.50 wiz "-B buffer size\n\t"
413 1.4 mrg "-b balance (0-63)\n\t"
414 1.4 mrg "-c channels\n\t"
415 1.4 mrg "-d audio device\n\t"
416 1.4 mrg "-e encoding\n\t"
417 1.35 wiz "-F format\n\t"
418 1.4 mrg "-i header information\n\t"
419 1.4 mrg "-m monitor volume\n\t"
420 1.35 wiz "-P precision (4, 8, 16, 24, or 32 bits)\n\t"
421 1.4 mrg "-p input port\n\t"
422 1.4 mrg "-s sample rate\n\t"
423 1.4 mrg "-t recording time\n\t"
424 1.4 mrg "-v volume\n");
425 1.9 kleink exit(EXIT_FAILURE);
426 1.1 mrg }
427