Home | History | Annotate | Line # | Download | only in record
      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 
     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 static int no_time_limit = 1;
     72 
     73 static void (*conv_func) (u_char *, int);
     74 
     75 static void usage (void) __dead;
     76 static int timeleft (struct timeval *, struct timeval *);
     77 static void cleanup (int) __dead;
     78 static void rewrite_header (void);
     79 static void stop (int);
     80 
     81 static void stop (int sig)
     82 {
     83 	no_time_limit = 0;
     84 	timerclear(&record_time);
     85 }
     86 
     87 int
     88 main(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 
    370 int
    371 timeleft(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 
    382 void
    383 cleanup(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 
    401 static void
    402 rewrite_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 
    413 static void
    414 usage(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