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