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