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