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