audiorecord.c revision 9484:fbd5ddc28e96
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/* Command-line audio record utility */
27
28#include <stdio.h>
29#include <libgen.h>
30#include <errno.h>
31#include <ctype.h>
32#include <math.h>
33#include <stdlib.h>
34#include <unistd.h>
35#include <string.h>
36#include <strings.h>
37#include <locale.h>
38#include <fcntl.h>
39#include <signal.h>
40#include <limits.h>	/* All occurances of INT_MAX used to be ~0  (by MCA) */
41#include <sys/types.h>
42#include <sys/file.h>
43#include <sys/stat.h>
44#include <sys/param.h>
45#include <stropts.h>
46#include <poll.h>
47#include <sys/ioctl.h>
48#include <netinet/in.h>
49
50#include <libaudio.h>
51#include <audio_device.h>
52
53#define	irint(d)	((int)d)
54
55/* localization stuff */
56#define	MGET(s)		(char *)gettext(s)
57
58#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
59#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
60#endif
61
62#define	Error		(void) fprintf
63
64/* Local variables */
65static char	*prog;
66static char	prog_opts[] = "aft:v:d:i:e:s:c:T:?"; /* getopt() flags */
67static char	*Stdout;
68
69/* XXX - the input buffer size should depend on sample_rate */
70#define	AUDIO_BUFSIZ (1024 * 64)
71static unsigned char	buf[AUDIO_BUFSIZ];
72static char 		swapBuf[AUDIO_BUFSIZ];	/* for byte swapping */
73
74
75#define	MAX_GAIN		(100)	/* maximum gain */
76
77static char	*Info = NULL;		/* pointer to info data */
78static unsigned	Ilen = 0;		/* length of info data */
79static unsigned	Volume = INT_MAX;	/* record volume */
80static double	Savevol;		/* saved  volume */
81static unsigned	Sample_rate = 0;
82static unsigned	Channels = 0;
83static unsigned	Precision = 0;		/* based on encoding */
84static unsigned	Encoding = 0;
85
86static int	NetEndian = TRUE;	/* endian nature of the machines */
87
88static int	Append = FALSE;		/* append to output file */
89static int	Force = FALSE;		/* ignore rate differences on append */
90static double	Time = -1.;		/* recording time */
91static unsigned	Limit = AUDIO_UNKNOWN_SIZE;	/* recording limit */
92static char	*Audio_dev = "/dev/audio";
93
94static int		Audio_fd = -1;
95			/* file descriptor for audio device */
96static Audio_hdr	Dev_hdr;		/* audio header for device */
97static Audio_hdr	Save_hdr;		/* saved audio device header */
98static char		*Ofile;			/* current filename */
99static int		File_type = FILE_AU;	/* audio file type */
100static int		File_type_set = FALSE;	/* file type specified as arg */
101static Audio_hdr	File_hdr;		/* audio header for file */
102static int		Cleanup = FALSE;	/* SIGINT sets this flag */
103static unsigned		Size = 0;		/* Size of output file */
104static unsigned		Oldsize = 0;
105			/* Size of input file, if append */
106
107/* Global variables */
108extern int getopt();
109extern int optind;
110extern char *optarg;
111
112/* Local Functions */
113static void usage(void);
114static void sigint(int sig);
115static int parse_unsigned(char *str, unsigned *dst, char *flag);
116static int parse_sample_rate(char *s, unsigned *rate);
117
118
119static void
120usage(void)
121{
122	Error(stderr, MGET("Record an audio file -- usage:\n"
123	    "\t%s [-af] [-v vol]\n"
124	    "\t%.*s [-c channels] [-s rate] [-e encoding]\n"
125	    "\t%.*s [-t time] [-i info] [-d dev] [-T au|wav|aif[f]] [file]\n"
126	    "where:\n"
127	    "\t-a\tAppend to output file\n"
128	    "\t-f\tIgnore sample rate differences on append\n"
129	    "\t-v\tSet record volume (0 - %d)\n"
130	    "\t-c\tSpecify number of channels to record\n"
131	    "\t-s\tSpecify rate in samples per second\n"
132	    "\t-e\tSpecify encoding (ulaw | alaw | [u]linear | linear8 )\n"
133	    "\t-t\tSpecify record time (hh:mm:ss.dd)\n"
134	    "\t-i\tSpecify a file header information string\n"
135	    "\t-d\tSpecify audio device (default: /dev/audio)\n"
136	    "\t-T\tSpecify the audio file type (default: au)\n"
137	    "\tfile\tRecord to named file\n"
138	    "\t\tIf no file specified, write to stdout\n"
139	    "\t\tDefault audio encoding is ulaw, 8khz, mono\n"
140	    "\t\tIf -t is not specified, record until ^C\n"),
141	    prog,
142	    strlen(prog), "                    ",
143	    strlen(prog), "                    ",
144	    MAX_GAIN);
145	exit(1);
146}
147
148static void
149sigint(int sig)
150{
151	/* If this is the first ^C, set a flag for the main loop */
152	if (!Cleanup && (Audio_fd >= 0)) {
153		/* flush input queues before exiting */
154		Cleanup = TRUE;
155		if (audio_pause_record(Audio_fd) == AUDIO_SUCCESS)
156			return;
157		Error(stderr, MGET("%s: could not flush input buffer\n"), prog);
158	}
159
160	/* If double ^C, really quit */
161	if (Audio_fd >= 0) {
162		if (Volume != INT_MAX)
163			(void) audio_set_record_gain(Audio_fd, &Savevol);
164		if (audio_cmp_hdr(&Save_hdr, &Dev_hdr) != 0) {
165			(void) audio_set_record_config(Audio_fd, &Save_hdr);
166		}
167	}
168	exit(1);
169}
170
171/*
172 * Record from the audio device to a file.
173 */
174int
175main(int argc, char **argv)
176{
177	int		i;
178	int		cnt;
179	int		err;
180	int		file_type;
181	int		ofd;
182	int 		swapBytes = FALSE;
183	double		vol;
184	struct stat	st;
185	struct pollfd	pfd;
186	char		*cp;
187
188	(void) setlocale(LC_ALL, "");
189	(void) textdomain(TEXT_DOMAIN);
190
191	/* Get the program name */
192	prog = strrchr(argv[0], '/');
193	if (prog == NULL)
194		prog = argv[0];
195	else
196		prog++;
197	Stdout = MGET("(stdout)");
198
199	/* first check AUDIODEV environment for audio device name */
200	if (cp = getenv("AUDIODEV")) {
201		Audio_dev = cp;
202	}
203
204	/* Set the endian nature of the machine */
205	if ((ulong_t)1 != htonl((ulong_t)1)) {
206		NetEndian = FALSE;
207	}
208
209	err = 0;
210	while ((i = getopt(argc, argv, prog_opts)) != EOF) {
211		switch (i) {
212		case 'v':
213			if (parse_unsigned(optarg, &Volume, "-v")) {
214				err++;
215			} else if (Volume > MAX_GAIN) {
216				Error(stderr, MGET("%s: invalid value for "
217				"-v\n"), prog);
218				err++;
219			}
220			break;
221		case 't':
222			Time = audio_str_to_secs(optarg);
223			if ((Time == HUGE_VAL) || (Time < 0.)) {
224				Error(stderr, MGET("%s: invalid value for "
225				"-t\n"), prog);
226				err++;
227			}
228			break;
229		case 'd':
230			Audio_dev = optarg;
231			break;
232		case 'f':
233			Force = TRUE;
234			break;
235		case 'a':
236			Append = TRUE;
237			break;
238		case 'i':
239			Info = optarg;		/* set information string */
240			Ilen = strlen(Info);
241			break;
242		case 's':
243			if (parse_sample_rate(optarg, &Sample_rate)) {
244				err++;
245			}
246			break;
247		case 'c':
248			if (strncmp(optarg, "mono", strlen(optarg)) == 0) {
249				Channels = 1;
250			} else if (strncmp(optarg, "stereo",
251			    strlen(optarg)) == 0) {
252				Channels = 2;
253			} else if (parse_unsigned(optarg, &Channels, "-c")) {
254				err++;
255			} else if ((Channels != 1) && (Channels != 2)) {
256				Error(stderr, "%s: invalid value for -c\n",
257				    prog);
258				err++;
259			}
260			break;
261		case 'e':
262			if (strncmp(optarg, "ulinear", strlen(optarg)) == 0) {
263				Encoding = AUDIO_ENCODING_LINEAR8;
264				Precision = 8;
265			} else if (strncmp(optarg, "linear8",
266			    strlen("linear8")) == 0) {
267				Encoding = AUDIO_ENCODING_LINEAR;
268				Precision = 8;
269			} else if (strncmp(optarg, "ulaw",
270			    strlen(optarg)) == 0) {
271				Encoding = AUDIO_ENCODING_ULAW;
272				Precision = 8;
273			} else if (strncmp(optarg, "alaw",
274			    strlen(optarg)) == 0) {
275				Encoding = AUDIO_ENCODING_ALAW;
276				Precision = 8;
277			} else if ((strncmp(optarg, "linear",
278			    strlen(optarg)) == 0) || (strncmp(optarg, "pcm",
279			    strlen(optarg)) == 0)) {
280				Encoding = AUDIO_ENCODING_LINEAR;
281				Precision = 16;
282			} else {
283				Error(stderr, MGET("%s: invalid value for "
284				    "-e\n"), prog);
285				err++;
286			}
287			break;
288		case 'T':
289			if (strncmp(optarg, "au", strlen(optarg)) == 0) {
290				File_type = FILE_AU;
291			} else if (strncmp(optarg, "wav",
292			    strlen(optarg)) == 0) {
293				File_type = FILE_WAV;
294			} else if (strncmp(optarg, "aif",
295			    strlen(optarg)) == 0) {
296				File_type = FILE_AIFF;
297			} else if (strncmp(optarg, "aiff",
298			    strlen(optarg)) == 0) {
299				File_type = FILE_AIFF;
300			} else {
301				Error(stderr, MGET("%s: invalid value for "
302				    "-T\n"), prog);
303				err++;
304			}
305			File_type_set = TRUE;
306			break;
307		case '?':
308			usage();
309	/*NOTREACHED*/
310		}
311	}
312	if (Append && (Info != NULL)) {
313		Error(stderr, MGET("%s: cannot specify -a and -i\n"), prog);
314		err++;
315	}
316	if (err > 0)
317		exit(1);
318
319	argc -= optind;		/* update arg pointers */
320	argv += optind;
321
322	/* Open the output file */
323	if (argc <= 0) {
324		Ofile = Stdout;
325	} else {
326		Ofile = *argv++;
327		argc--;
328
329		/* Interpret "-" filename to mean stdout */
330		if (strcmp(Ofile, "-") == 0)
331			Ofile = Stdout;
332
333		/* if -T not set then we use the file suffix */
334		if (File_type_set == FALSE) {
335			char	*file_name;
336			char	*start;
337
338			/* get the file name without the path */
339			file_name = basename(Ofile);
340
341			/* get the true suffix */
342			start = strrchr(file_name, '.');
343
344			/* if no '.' then there's no suffix */
345			if (start) {
346				/* is this a .au file? */
347				if (strcasecmp(start, ".au") == 0) {
348					File_type = FILE_AU;
349				} else if (strcasecmp(start, ".wav") == 0) {
350					File_type = FILE_WAV;
351				} else if (strcasecmp(start, ".aif") == 0) {
352					File_type = FILE_AIFF;
353				} else if (strcasecmp(start, ".aiff") == 0) {
354					File_type = FILE_AIFF;
355				} else {
356					/* the default is .au */
357					File_type = FILE_AU;
358				}
359			} else {
360				/* no suffix, so default to .au */
361				File_type = FILE_AU;
362			}
363		}
364	}
365
366	if (Ofile == Stdout) {
367		ofd = fileno(stdout);
368		Append = FALSE;
369	} else {
370		ofd = open(Ofile,
371		    (O_RDWR | O_CREAT | (Append ? 0 : O_TRUNC)), 0666);
372		if (ofd < 0) {
373			Error(stderr, MGET("%s: cannot open "), prog);
374			perror(Ofile);
375			exit(1);
376		}
377		if (Append) {
378			/*
379			 * Check to make sure we're appending to an audio file.
380			 * It must be a regular file (if zero-length, simply
381			 * write it from scratch).  Also, its file header
382			 * must match the input device configuration.
383			 */
384			if ((fstat(ofd, &st) < 0) || (!S_ISREG(st.st_mode))) {
385				Error(stderr,
386				    MGET("%s: %s is not a regular file\n"),
387				    prog, Ofile);
388				exit(1);
389			}
390			if (st.st_size == 0) {
391				Append = FALSE;
392				goto openinput;
393			}
394
395			err = audio_read_filehdr(ofd, &File_hdr, &file_type,
396			    (char *)NULL, 0);
397
398			if (err != AUDIO_SUCCESS) {
399				Error(stderr,
400				    MGET("%s: %s is not a valid audio file\n"),
401				    prog, Ofile);
402				exit(1);
403			}
404
405			/* we need to make sure file types match */
406			if (File_type_set == TRUE) {
407				/* specified by the command line, must match */
408				if (File_type != file_type) {
409					Error(stderr,
410					    MGET("%s: file types must match\n"),
411					    prog);
412					exit(1);
413				}
414			} else {
415				/* not specified, so force */
416				File_type = file_type;
417			}
418
419			/*
420			 * Set the format state to the format
421			 * in the file header.
422			 */
423			Sample_rate = File_hdr.sample_rate;
424			Channels = File_hdr.channels;
425			Encoding = File_hdr.encoding;
426			Precision = File_hdr.bytes_per_unit * 8;
427
428			/* make sure we support the encoding method */
429			switch (Encoding) {
430				case AUDIO_ENCODING_LINEAR8:
431				case AUDIO_ENCODING_ULAW:
432				case AUDIO_ENCODING_ALAW:
433				case AUDIO_ENCODING_LINEAR:
434					break;
435				default: {
436					char	msg[AUDIO_MAX_ENCODE_INFO];
437					(void) audio_enc_to_str(&File_hdr, msg);
438					Error(stderr,
439					    MGET("%s: Append is not supported "
440					    "for "), prog);
441					Error(stderr,
442					    MGET("this file encoding:\n\t"
443					    "[%s]\n"), msg);
444					exit(1);
445					}
446			}
447
448			/* Get the current size, if possible */
449			Oldsize = File_hdr.data_size;
450			if ((Oldsize == AUDIO_UNKNOWN_SIZE) &&
451			    ((err = (int)lseek(ofd, 0L, SEEK_CUR)) >= 0)) {
452				if (err < 0) {
453					Error(stderr,
454					    MGET("%s: %s is not a valid audio "
455					    "file\n"), prog, Ofile);
456					exit(1);
457				}
458				Oldsize = st.st_size - err;
459			}
460			/* Seek to end to start append */
461			if ((int)lseek(ofd, st.st_size, SEEK_SET) < 0) {
462				Error(stderr,
463				    MGET("%s: cannot find end of %s\n"),
464				    prog, Ofile);
465				exit(1);
466			}
467		}
468	}
469openinput:
470	/* Validate and open the audio device */
471	err = stat(Audio_dev, &st);
472	if (err < 0) {
473		Error(stderr, MGET("%s: cannot open "), prog);
474		perror(Audio_dev);
475		exit(1);
476	}
477	if (!S_ISCHR(st.st_mode)) {
478		Error(stderr, MGET("%s: %s is not an audio device\n"), prog,
479		    Audio_dev);
480		exit(1);
481	}
482
483	/*
484	 * For the mixer environment we need to open the audio device before
485	 * the control device. If successful we pause right away to keep
486	 * from queueing up a bunch of useless data.
487	 */
488	Audio_fd = open(Audio_dev, O_RDONLY | O_NONBLOCK);
489	if (Audio_fd < 0) {
490		if (errno == EBUSY) {
491			Error(stderr, MGET("%s: %s is busy\n"),
492			    prog, Audio_dev);
493		} else {
494			Error(stderr, MGET("%s: error opening "), prog);
495			perror(Audio_dev);
496		}
497		exit(1);
498	}
499	if (audio_pause_record(Audio_fd) != AUDIO_SUCCESS) {
500		Error(stderr, MGET("%s: not able to pause recording\n"), prog);
501		exit(1);
502	}
503
504	/* get the current settings */
505	if (audio_get_record_config(Audio_fd, &Save_hdr) != AUDIO_SUCCESS) {
506		(void) close(Audio_fd);
507		Error(stderr, MGET("%s: %s is not an audio device\n"),
508		    prog, Audio_dev);
509		exit(1);
510	}
511	/* make a copy into the working data structure */
512	bcopy(&Save_hdr, &Dev_hdr, sizeof (Save_hdr));
513
514	/* flush any queued audio data */
515	if (audio_flush_record(Audio_fd) != AUDIO_SUCCESS) {
516		Error(stderr, MGET("%s: not able to flush recording\n"), prog);
517		exit(1);
518	}
519
520	if (Sample_rate != 0) {
521		Dev_hdr.sample_rate = Sample_rate;
522	}
523	if (Channels != 0) {
524		Dev_hdr.channels = Channels;
525	}
526	if (Precision != 0) {
527		Dev_hdr.bytes_per_unit = Precision / 8;
528	}
529	if (Encoding != 0) {
530		Dev_hdr.encoding = Encoding;
531	}
532
533	/*
534	 * For .wav we always record 8-bit linear as unsigned. Thus we
535	 * force unsigned linear to make life a lot easier on the user.
536	 *
537	 * For .aiff we set the default to 8-bit signed linear, not
538	 * u-law, if Encoding isn't already set.
539	 */
540	if (File_type == FILE_WAV &&
541	    Dev_hdr.encoding == AUDIO_ENCODING_LINEAR &&
542	    Dev_hdr.bytes_per_unit == 1) {
543		/* force to unsigned */
544		Dev_hdr.encoding = AUDIO_ENCODING_LINEAR8;
545	} else if (File_type == FILE_AIFF && Encoding == 0) {
546		Dev_hdr.encoding = AUDIO_ENCODING_LINEAR;
547		if (Precision == 0) {
548			Dev_hdr.bytes_per_unit = AUDIO_PRECISION_8 / 8;
549		}
550	}
551
552	if (audio_set_record_config(Audio_fd, &Dev_hdr) != AUDIO_SUCCESS) {
553		Error(stderr, MGET(
554		    "%s: Audio format not supported by the audio device\n"),
555		    prog);
556		exit(1);
557	}
558
559	if (audio_resume_record(Audio_fd) != AUDIO_SUCCESS) {
560		Error(stderr, MGET("%s: not able to resume recording\n"), prog);
561		exit(1);
562	}
563
564	/* If appending to an existing file, check the configuration */
565	if (Append) {
566		char	msg[AUDIO_MAX_ENCODE_INFO];
567
568		switch (audio_cmp_hdr(&Dev_hdr, &File_hdr)) {
569		case 0:			/* configuration matches */
570			break;
571		case 1:			/* all but sample rate matches */
572			if (Force) {
573				Error(stderr, MGET("%s: WARNING: appending "
574				    "%.3fkHz data to %s (%.3fkHz)\n"), prog,
575				    ((double)Dev_hdr.sample_rate / 1000.),
576				    Ofile,
577				    ((double)File_hdr.sample_rate / 1000.));
578				break;
579			}		/* if not -f, fall through */
580
581		default:		/* encoding mismatch */
582			(void) audio_enc_to_str(&Dev_hdr, msg);
583			Error(stderr,
584			    MGET("%s: device encoding [%s]\n"), prog, msg);
585			(void) audio_enc_to_str(&File_hdr, msg);
586			Error(stderr,
587			    MGET("\tdoes not match file encoding [%s]\n"), msg);
588			exit(1);
589		}
590	} else if (!isatty(ofd)) {
591		if (audio_write_filehdr(ofd, &Dev_hdr, File_type, Info,
592		    Ilen) != AUDIO_SUCCESS) {
593			Error(stderr,
594			    MGET("%s: error writing header for %s\n"), prog,
595			    Ofile);
596			exit(1);
597		}
598	}
599
600	/*
601	 * 8-bit audio isn't a problem, however 16-bit audio is. If the file
602	 * is an endian that is different from the machine then the bytes
603	 * will need to be swapped.
604	 *
605	 * Note: The following if() could be simplified, but then it gets
606	 * to be very hard to read. So it's left as is.
607	 */
608	if (Dev_hdr.bytes_per_unit == 2 &&
609	    ((!NetEndian && File_type == FILE_AIFF) ||
610	    (!NetEndian && File_type == FILE_AU) ||
611	    (NetEndian && File_type == FILE_WAV))) {
612		swapBytes = TRUE;
613	}
614
615	/* If -v flag, set the record volume now */
616	if (Volume != INT_MAX) {
617		vol = (double)Volume / (double)MAX_GAIN;
618		(void) audio_get_record_gain(Audio_fd, &Savevol);
619		err = audio_set_record_gain(Audio_fd, &vol);
620		if (err != AUDIO_SUCCESS) {
621			Error(stderr,
622			    MGET("%s: could not set record volume for %s\n"),
623			    prog, Audio_dev);
624			exit(1);
625		}
626	}
627
628	if (isatty(ofd)) {
629		Error(stderr, MGET("%s: No files and stdout is a tty\n"),
630		    prog);
631		exit(1);
632	}
633
634	/* Set up SIGINT handler so that final buffers may be flushed */
635	(void) signal(SIGINT, sigint);
636
637	/*
638	 * At this point, we're (finally) ready to copy the data.
639	 * Init a poll() structure, to use when there's nothing to read.
640	 */
641	if (Time > 0)
642		Limit = audio_secs_to_bytes(&Dev_hdr, Time);
643	pfd.fd = Audio_fd;
644	pfd.events = POLLIN;
645	while ((Limit == AUDIO_UNKNOWN_SIZE) || (Limit != 0)) {
646		/* Fill the buffer or read to the time limit */
647		cnt = read(Audio_fd, (char *)buf,
648		    ((Limit != AUDIO_UNKNOWN_SIZE) && (Limit < sizeof (buf)) ?
649		    (int)Limit : sizeof (buf)));
650
651		if (cnt == 0)		/* normally, eof can't happen */
652			break;
653
654		/* If error, probably have to wait for input */
655		if (cnt < 0) {
656			if (Cleanup)
657				break;		/* done if ^C seen */
658			switch (errno) {
659			case EAGAIN:
660				(void) poll(&pfd, 1L, -1);
661				break;
662			case EOVERFLOW:  /* Possibly a Large File */
663				Error(stderr, MGET("%s: error reading"), prog);
664				perror("Large File");
665				exit(1);
666			default:
667				Error(stderr, MGET("%s: error reading"), prog);
668				perror(Audio_dev);
669				exit(1);
670			}
671			continue;
672		}
673
674		/* Swab the output if required. */
675		if (swapBytes) {
676			swab((char *)buf, swapBuf, cnt);
677			err = write(ofd, swapBuf, cnt);
678		} else {
679			err = write(ofd, (char *)buf, cnt);
680		}
681		if (err < 0) {
682			Error(stderr, MGET("%s: error writing "), prog);
683			perror(Ofile);
684			exit(1);
685		}
686		if (err != cnt) {
687			Error(stderr, MGET("%s: error writing "), prog);
688			perror(Ofile);
689			break;
690		}
691		Size += cnt;
692		if (Limit != AUDIO_UNKNOWN_SIZE)
693			Limit -= cnt;
694	}
695
696	/* Attempt to rewrite the data_size field of the file header */
697	if (!Append || (Oldsize != AUDIO_UNKNOWN_SIZE)) {
698		if (Append)
699			Size += Oldsize;
700		(void) audio_rewrite_filesize(ofd, File_type, Size,
701		    Dev_hdr.channels, Dev_hdr.bytes_per_unit);
702	}
703
704	(void) close(ofd);			/* close input file */
705
706
707	/* Check for error during record */
708	if (audio_get_record_error(Audio_fd, (unsigned *)&err) != AUDIO_SUCCESS)
709		Error(stderr, MGET("%s: error reading device status\n"), prog);
710	else if (err)
711		Error(stderr, MGET("%s: WARNING: Data overflow occurred\n"),
712		    prog);
713
714	/* Reset record volume, encoding */
715	if (Volume != INT_MAX)
716		(void) audio_set_record_gain(Audio_fd, &Savevol);
717	if (audio_cmp_hdr(&Save_hdr, &Dev_hdr) != 0) {
718		(void) audio_set_record_config(Audio_fd, &Save_hdr);
719	}
720	(void) close(Audio_fd);
721	return (0);
722}
723
724/* Parse an unsigned integer */
725static int
726parse_unsigned(char *str, unsigned *dst, char *flag)
727{
728	char		x;
729
730	if (sscanf(str, "%u%c", dst, &x) != 1) {
731		Error(stderr, MGET("%s: invalid value for %s\n"), prog, flag);
732		return (1);
733	}
734	return (0);
735}
736
737/*
738 * set the sample rate. assume anything is ok. check later on to make sure
739 * the sample rate is valid.
740 */
741static int
742parse_sample_rate(char *s, unsigned *rate)
743{
744	char		*cp;
745	double		drate;
746
747	/*
748	 * check if it's "cd" or "dat" or "voice". these also set
749	 * the precision and encoding, etc.
750	 */
751	if (strcasecmp(s, "dat") == 0) {
752		drate = 48000.0;
753	} else if (strcasecmp(s, "cd") == 0) {
754		drate = 44100.0;
755	} else if (strcasecmp(s, "voice") == 0) {
756		drate = 8000.0;
757	} else {
758		/* just do an atof */
759		drate = atof(s);
760
761		/*
762		 * if the first non-digit is a "k" multiply by 1000,
763		 * if it's an "h", leave it alone. anything else,
764		 * return an error.
765		 */
766
767		/*
768		 * XXX bug alert: could have multiple "." in string
769		 * and mess things up.
770		 */
771		for (cp = s; *cp && (isdigit(*cp) || (*cp == '.')); cp++)
772			/* NOP */;
773		if (*cp != NULL) {
774			if ((*cp == 'k') || (*cp == 'K')) {
775				drate *= 1000.0;
776			} else if ((*cp != 'h') || (*cp != 'H')) {
777				/* bogus! */
778				Error(stderr,
779				    MGET("invalid sample rate: %s\n"), s);
780				return (1);
781			}
782		}
783
784	}
785
786	*rate = irint(drate);
787	return (0);
788}
789