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 58static audio_info_t info, oinfo; 59static const char *device; 60static int audiofd; 61static int aflag, fflag; 62int verbose; 63static int monitor_gain, omonitor_gain; 64static int gain; 65static int balance; 66static int port; 67static char *encoding_str; 68static struct track_info ti; 69static struct timeval record_time; 70static struct timeval start_time; 71static int no_time_limit = 1; 72 73static void (*conv_func) (u_char *, int); 74 75static void usage (void) __dead; 76static int timeleft (struct timeval *, struct timeval *); 77static void cleanup (int) __dead; 78static void rewrite_header (void); 79static void stop (int); 80 81static void stop (int sig) 82{ 83 no_time_limit = 0; 84 timerclear(&record_time); 85} 86 87int 88main(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 370int 371timeleft(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 382void 383cleanup(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 401static void 402rewrite_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 413static void 414usage(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