Home | History | Annotate | Line # | Download | only in record
record.c revision 1.53
      1 /*	$NetBSD: record.c,v 1.53 2013/08/30 20:57:26 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.53 2013/08/30 20:57:26 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 write_info wi;
     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 write_info.
     89 	 */
     90 	wi.format = AUDIO_FORMAT_DEFAULT;
     91 	wi.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 			wi.format = audio_format_from_str(optarg);
    112 			if (wi.format < 0)
    113 				errx(1, "Unknown audio format; supported "
    114 				    "formats: \"sun\", \"wav\", and \"none\"");
    115 			break;
    116 		case 'c':
    117 			decode_int(optarg, &wi.channels);
    118 			if (wi.channels < 0 || wi.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 			wi.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, &wi.precision);
    140 			if (wi.precision != 4 && wi.precision != 8 &&
    141 			    wi.precision != 16 && wi.precision != 24 &&
    142 			    wi.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 			wi.qflag++;
    161 			break;
    162 		case 's':
    163 			decode_int(optarg, &wi.sample_rate);
    164 			if (wi.sample_rate < 0 || wi.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 		wi.encoding = audio_enc_to_val(encoding_str);
    196 		if (wi.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 (wi.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 				wi.format = AUDIO_FORMAT_SUN;
    212 			else if (strcasecmp(arg + flen - 4, ".wav") == 0)
    213 				wi.format = AUDIO_FORMAT_WAV;
    214 		}
    215 		wi.outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666);
    216 		if (wi.outfd < 0)
    217 			err(1, "could not open %s", *argv);
    218 	} else
    219 		wi.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(wi.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 	wi.total_size = 0;
    289 
    290 	write_header(&wi);
    291 	if (wi.format == AUDIO_FORMAT_NONE)
    292 		errx(1, "unable to determine audio format");
    293 	conv_func = write_get_conv_func(&wi);
    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(wi.outfd, buffer, bufsize) != bufsize)
    345 			err(1, "write failed");
    346 		wi.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(wi.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 (wi.outfd == STDOUT_FILENO)
    388 		return;
    389 
    390 	if (lseek(wi.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(&wi);
    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