1 1.59 mrg /* $NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg 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.59 mrg __RCSID("$NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg 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.57 mlelstv else if (conv_func == change_sign24_be) 316 1.57 mlelstv s = "change sign (big-endian, 24 bit)"; 317 1.57 mlelstv else if (conv_func == change_sign24_le) 318 1.57 mlelstv s = "change sign (little-endian, 24 bit)"; 319 1.27 mrg else if (conv_func == change_sign32_be) 320 1.27 mrg s = "change sign (big-endian, 32 bit)"; 321 1.27 mrg else if (conv_func == change_sign32_le) 322 1.27 mrg s = "change sign (little-endian, 32 bit)"; 323 1.27 mrg else if (conv_func == change_sign16_swap_bytes_be) 324 1.27 mrg s = "change sign & swap bytes (big-endian, 16 bit)"; 325 1.59 mrg else if (conv_func == change_sign16_swap_bytes_le) 326 1.27 mrg s = "change sign & swap bytes (little-endian, 16 bit)"; 327 1.59 mrg else if (conv_func == change_sign24_swap_bytes_be) 328 1.57 mlelstv s = "change sign & swap bytes (big-endian, 24 bit)"; 329 1.57 mlelstv else if (conv_func == change_sign24_swap_bytes_le) 330 1.57 mlelstv s = "change sign & swap bytes (little-endian, 24 bit)"; 331 1.27 mrg else if (conv_func == change_sign32_swap_bytes_be) 332 1.27 mrg s = "change sign (big-endian, 32 bit)"; 333 1.27 mrg else if (conv_func == change_sign32_swap_bytes_le) 334 1.27 mrg s = "change sign & swap bytes (little-endian, 32 bit)"; 335 1.58 gson 336 1.27 mrg if (s) 337 1.27 mrg fprintf(stderr, "%s: converting, using function: %s\n", 338 1.27 mrg getprogname(), s); 339 1.27 mrg else 340 1.27 mrg fprintf(stderr, "%s: using unnamed conversion " 341 1.27 mrg "function\n", getprogname()); 342 1.27 mrg } 343 1.27 mrg 344 1.27 mrg if (verbose) 345 1.27 mrg fprintf(stderr, 346 1.27 mrg "sample_rate=%d channels=%d precision=%d encoding=%s\n", 347 1.27 mrg info.record.sample_rate, info.record.channels, 348 1.27 mrg info.record.precision, 349 1.27 mrg audio_enc_from_val(info.record.encoding)); 350 1.32 mrg 351 1.32 mrg if (!no_time_limit && verbose) 352 1.32 mrg fprintf(stderr, "recording for %lu seconds, %lu microseconds\n", 353 1.32 mrg (u_long)record_time.tv_sec, (u_long)record_time.tv_usec); 354 1.27 mrg 355 1.1 mrg (void)gettimeofday(&start_time, NULL); 356 1.8 mrg while (no_time_limit || timeleft(&start_time, &record_time)) { 357 1.55 riastrad if ((nread = read(audiofd, buffer, bufsize)) == -1) 358 1.1 mrg err(1, "read failed"); 359 1.55 riastrad if (nread == 0) 360 1.56 mlelstv break; 361 1.18 mrg if (conv_func) 362 1.56 mlelstv (*conv_func)(buffer, nread); 363 1.56 mlelstv if (write(ti.outfd, buffer, nread) != nread) 364 1.1 mrg err(1, "write failed"); 365 1.56 mlelstv ti.total_size += nread; 366 1.1 mrg } 367 1.1 mrg cleanup(0); 368 1.1 mrg } 369 1.1 mrg 370 1.1 mrg int 371 1.51 joerg timeleft(struct timeval *start_tvp, struct timeval *record_tvp) 372 1.1 mrg { 373 1.1 mrg struct timeval now, diff; 374 1.1 mrg 375 1.1 mrg (void)gettimeofday(&now, NULL); 376 1.7 dmcmahil timersub(&now, start_tvp, &diff); 377 1.7 dmcmahil timersub(record_tvp, &diff, &now); 378 1.1 mrg 379 1.1 mrg return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0)); 380 1.1 mrg } 381 1.1 mrg 382 1.1 mrg void 383 1.51 joerg cleanup(int signo) 384 1.1 mrg { 385 1.1 mrg 386 1.1 mrg rewrite_header(); 387 1.54 mrg close(ti.outfd); 388 1.17 mrg if (omonitor_gain) { 389 1.1 mrg AUDIO_INITINFO(&info); 390 1.17 mrg info.monitor_gain = omonitor_gain; 391 1.23 jdolecek if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0) 392 1.1 mrg err(1, "failed to reset audio info"); 393 1.1 mrg } 394 1.23 jdolecek close(audiofd); 395 1.44 lukem if (signo != 0) { 396 1.44 lukem (void)raise_default_signal(signo); 397 1.44 lukem } 398 1.1 mrg exit(0); 399 1.1 mrg } 400 1.1 mrg 401 1.51 joerg static void 402 1.51 joerg rewrite_header(void) 403 1.1 mrg { 404 1.1 mrg 405 1.1 mrg /* can't do this here! */ 406 1.54 mrg if (ti.outfd == STDOUT_FILENO) 407 1.1 mrg return; 408 1.54 mrg if (lseek(ti.outfd, (off_t)0, SEEK_SET) == (off_t)-1) 409 1.1 mrg err(1, "could not seek to start of file for header rewrite"); 410 1.54 mrg write_header(&ti); 411 1.1 mrg } 412 1.1 mrg 413 1.51 joerg static void 414 1.51 joerg usage(void) 415 1.1 mrg { 416 1.14 cgd 417 1.14 cgd fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n", 418 1.14 cgd getprogname()); 419 1.4 mrg fprintf(stderr, "Options:\n\t" 420 1.50 wiz "-B buffer size\n\t" 421 1.4 mrg "-b balance (0-63)\n\t" 422 1.4 mrg "-c channels\n\t" 423 1.4 mrg "-d audio device\n\t" 424 1.4 mrg "-e encoding\n\t" 425 1.35 wiz "-F format\n\t" 426 1.4 mrg "-i header information\n\t" 427 1.4 mrg "-m monitor volume\n\t" 428 1.35 wiz "-P precision (4, 8, 16, 24, or 32 bits)\n\t" 429 1.4 mrg "-p input port\n\t" 430 1.4 mrg "-s sample rate\n\t" 431 1.4 mrg "-t recording time\n\t" 432 1.4 mrg "-v volume\n"); 433 1.9 kleink exit(EXIT_FAILURE); 434 1.1 mrg } 435