/* $NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg Exp $ */ /* * Copyright (c) 1999, 2002, 2003, 2005, 2010 Matthew R. Green * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * SunOS compatible audiorecord(1) */ #include #ifndef lint __RCSID("$NetBSD: record.c,v 1.59 2024/03/20 20:19:31 mrg Exp $"); #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libaudio.h" #include "auconv.h" static audio_info_t info, oinfo; static const char *device; static int audiofd; static int aflag, fflag; int verbose; static int monitor_gain, omonitor_gain; static int gain; static int balance; static int port; static char *encoding_str; static struct track_info ti; static struct timeval record_time; static struct timeval start_time; static int no_time_limit = 1; static void (*conv_func) (u_char *, int); static void usage (void) __dead; static int timeleft (struct timeval *, struct timeval *); static void cleanup (int) __dead; static void rewrite_header (void); static void stop (int); static void stop (int sig) { no_time_limit = 0; timerclear(&record_time); } int main(int argc, char *argv[]) { u_char *buffer; size_t len, bufsize = 0; ssize_t nread; int ch; const char *defdevice = _PATH_SOUND; /* * Initialise the track_info. */ ti.format = AUDIO_FORMAT_DEFAULT; ti.total_size = -1; while ((ch = getopt(argc, argv, "ab:B:C:F:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) { switch (ch) { case 'a': aflag++; break; case 'b': decode_int(optarg, &balance); if (balance < 0 || balance > 63) errx(1, "balance must be between 0 and 63"); break; case 'B': bufsize = strsuftoll("read buffer size", optarg, 1, UINT_MAX); break; case 'C': /* Ignore, compatibility */ break; case 'F': ti.format = audio_format_from_str(optarg); if (ti.format < 0) errx(1, "Unknown audio format; supported " "formats: \"sun\", \"wav\", and \"none\""); break; case 'c': decode_int(optarg, &ti.channels); if (ti.channels < 0 || ti.channels > 16) errx(1, "channels must be between 0 and 16"); break; case 'd': device = optarg; break; case 'e': encoding_str = optarg; break; case 'f': fflag++; break; case 'i': ti.header_info = optarg; break; case 'm': decode_int(optarg, &monitor_gain); if (monitor_gain < 0 || monitor_gain > 255) errx(1, "monitor volume must be between 0 and 255"); break; case 'P': decode_int(optarg, &ti.precision); if (ti.precision != 4 && ti.precision != 8 && ti.precision != 16 && ti.precision != 24 && ti.precision != 32) errx(1, "precision must be between 4, 8, 16, 24 or 32"); break; case 'p': len = strlen(optarg); if (strncmp(optarg, "mic", len) == 0) port |= AUDIO_MICROPHONE; else if (strncmp(optarg, "cd", len) == 0 || strncmp(optarg, "internal-cd", len) == 0) port |= AUDIO_CD; else if (strncmp(optarg, "line", len) == 0) port |= AUDIO_LINE_IN; else errx(1, "port must be `cd', `internal-cd', `mic', or `line'"); break; case 'q': ti.qflag++; break; case 's': decode_int(optarg, &ti.sample_rate); if (ti.sample_rate < 0 || ti.sample_rate > 48000 * 2) /* XXX */ errx(1, "sample rate must be between 0 and 96000"); break; case 't': no_time_limit = 0; decode_time(optarg, &record_time); break; case 'V': verbose++; break; case 'v': decode_int(optarg, &gain); if (gain < 0 || gain > 255) errx(1, "volume must be between 0 and 255"); break; /* case 'h': */ default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 1) usage(); /* * convert the encoding string into a value. */ if (encoding_str) { ti.encoding = audio_enc_to_val(encoding_str); if (ti.encoding == -1) errx(1, "unknown encoding, bailing..."); } /* * open the output file */ if (argv[0][0] != '-' || argv[0][1] != '\0') { /* intuit the file type from the name */ if (ti.format == AUDIO_FORMAT_DEFAULT) { size_t flen = strlen(*argv); const char *arg = *argv; if (strcasecmp(arg + flen - 3, ".au") == 0) ti.format = AUDIO_FORMAT_SUN; else if (strcasecmp(arg + flen - 4, ".wav") == 0) ti.format = AUDIO_FORMAT_WAV; } ti.outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666); if (ti.outfd < 0) err(1, "could not open %s", *argv); } else ti.outfd = STDOUT_FILENO; /* * open the audio device */ if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL && (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */ device = defdevice; audiofd = open(device, O_RDONLY); if (audiofd < 0 && device == defdevice) { device = _PATH_SOUND0; audiofd = open(device, O_RDONLY); } if (audiofd < 0) err(1, "failed to open %s", device); /* * work out the buffer size to use, and allocate it. also work out * what the old monitor gain value is, so that we can reset it later. */ if (ioctl(audiofd, AUDIO_GETINFO, &oinfo) < 0) err(1, "failed to get audio info"); if (bufsize == 0) { bufsize = oinfo.record.buffer_size; if (bufsize < 32 * 1024) bufsize = 32 * 1024; } omonitor_gain = oinfo.monitor_gain; buffer = malloc(bufsize); if (buffer == NULL) err(1, "couldn't malloc buffer of %d size", (int)bufsize); /* * set up audio device for recording with the speified parameters */ AUDIO_INITINFO(&info); /* * for these, get the current values for stuffing into the header */ #define SETINFO2(x, y) if (x) \ info.record.y = x; \ else \ info.record.y = x = oinfo.record.y; #define SETINFO(x) SETINFO2(ti.x, x) SETINFO (sample_rate) SETINFO (channels) SETINFO (precision) SETINFO (encoding) SETINFO2 (gain, gain) SETINFO2 (port, port) SETINFO2 (balance, balance) #undef SETINFO #undef SETINFO2 if (monitor_gain) info.monitor_gain = monitor_gain; else monitor_gain = oinfo.monitor_gain; info.mode = AUMODE_RECORD; if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0) err(1, "failed to set audio info"); signal(SIGINT, stop); ti.total_size = 0; write_header(&ti); if (ti.format == AUDIO_FORMAT_NONE) errx(1, "unable to determine audio format"); conv_func = write_get_conv_func(&ti); if (verbose && conv_func) { const char *s = NULL; if (conv_func == swap_bytes) s = "swap bytes (16 bit)"; else if (conv_func == swap_bytes32) s = "swap bytes (32 bit)"; else if (conv_func == change_sign16_be) s = "change sign (big-endian, 16 bit)"; else if (conv_func == change_sign16_le) s = "change sign (little-endian, 16 bit)"; else if (conv_func == change_sign24_be) s = "change sign (big-endian, 24 bit)"; else if (conv_func == change_sign24_le) s = "change sign (little-endian, 24 bit)"; else if (conv_func == change_sign32_be) s = "change sign (big-endian, 32 bit)"; else if (conv_func == change_sign32_le) s = "change sign (little-endian, 32 bit)"; else if (conv_func == change_sign16_swap_bytes_be) s = "change sign & swap bytes (big-endian, 16 bit)"; else if (conv_func == change_sign16_swap_bytes_le) s = "change sign & swap bytes (little-endian, 16 bit)"; else if (conv_func == change_sign24_swap_bytes_be) s = "change sign & swap bytes (big-endian, 24 bit)"; else if (conv_func == change_sign24_swap_bytes_le) s = "change sign & swap bytes (little-endian, 24 bit)"; else if (conv_func == change_sign32_swap_bytes_be) s = "change sign (big-endian, 32 bit)"; else if (conv_func == change_sign32_swap_bytes_le) s = "change sign & swap bytes (little-endian, 32 bit)"; if (s) fprintf(stderr, "%s: converting, using function: %s\n", getprogname(), s); else fprintf(stderr, "%s: using unnamed conversion " "function\n", getprogname()); } if (verbose) fprintf(stderr, "sample_rate=%d channels=%d precision=%d encoding=%s\n", info.record.sample_rate, info.record.channels, info.record.precision, audio_enc_from_val(info.record.encoding)); if (!no_time_limit && verbose) fprintf(stderr, "recording for %lu seconds, %lu microseconds\n", (u_long)record_time.tv_sec, (u_long)record_time.tv_usec); (void)gettimeofday(&start_time, NULL); while (no_time_limit || timeleft(&start_time, &record_time)) { if ((nread = read(audiofd, buffer, bufsize)) == -1) err(1, "read failed"); if (nread == 0) break; if (conv_func) (*conv_func)(buffer, nread); if (write(ti.outfd, buffer, nread) != nread) err(1, "write failed"); ti.total_size += nread; } cleanup(0); } int timeleft(struct timeval *start_tvp, struct timeval *record_tvp) { struct timeval now, diff; (void)gettimeofday(&now, NULL); timersub(&now, start_tvp, &diff); timersub(record_tvp, &diff, &now); return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0)); } void cleanup(int signo) { rewrite_header(); close(ti.outfd); if (omonitor_gain) { AUDIO_INITINFO(&info); info.monitor_gain = omonitor_gain; if (ioctl(audiofd, AUDIO_SETINFO, &info) < 0) err(1, "failed to reset audio info"); } close(audiofd); if (signo != 0) { (void)raise_default_signal(signo); } exit(0); } static void rewrite_header(void) { /* can't do this here! */ if (ti.outfd == STDOUT_FILENO) return; if (lseek(ti.outfd, (off_t)0, SEEK_SET) == (off_t)-1) err(1, "could not seek to start of file for header rewrite"); write_header(&ti); } static void usage(void) { fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n", getprogname()); fprintf(stderr, "Options:\n\t" "-B buffer size\n\t" "-b balance (0-63)\n\t" "-c channels\n\t" "-d audio device\n\t" "-e encoding\n\t" "-F format\n\t" "-i header information\n\t" "-m monitor volume\n\t" "-P precision (4, 8, 16, 24, or 32 bits)\n\t" "-p input port\n\t" "-s sample rate\n\t" "-t recording time\n\t" "-v volume\n"); exit(EXIT_FAILURE); }