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