#include <stdio.h>
#include <libgen.h>
#include <errno.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <locale.h>
#include <fcntl.h>
#include <signal.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stropts.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <libaudio.h>
#include <audio_device.h>
#define irint(d) ((int)d)
#define MGET(s) (char *)gettext(s)
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
#define Error (void) fprintf
static char *prog;
static char prog_opts[] = "aft:v:d:i:e:s:c:T:?";
static char *Stdout;
#define AUDIO_BUFSIZ (1024 * 64)
static unsigned char buf[AUDIO_BUFSIZ];
static char swapBuf[AUDIO_BUFSIZ];
#define MAX_GAIN (100)
static char *Info = NULL;
static unsigned Ilen = 0;
static unsigned Volume = INT_MAX;
static double Savevol;
static unsigned Sample_rate = 0;
static unsigned Channels = 0;
static unsigned Precision = 0;
static unsigned Encoding = 0;
static int NetEndian = TRUE;
static int Append = FALSE;
static int Force = FALSE;
static double Time = -1.;
static unsigned Limit = AUDIO_UNKNOWN_SIZE;
static char *Audio_dev = "/dev/audio";
static int Audio_fd = -1;
static Audio_hdr Dev_hdr;
static Audio_hdr Save_hdr;
static char *Ofile;
static int File_type = FILE_AU;
static int File_type_set = FALSE;
static Audio_hdr File_hdr;
static int Cleanup = FALSE;
static unsigned Size = 0;
static unsigned Oldsize = 0;
extern int optind;
extern char *optarg;
static void usage(void);
static void sigint(int sig);
static int parse_unsigned(char *str, unsigned *dst, char *flag);
static int parse_sample_rate(char *s, unsigned *rate);
static void
usage(void)
{
Error(stderr, MGET("Record an audio file -- usage:\n"
"\t%s [-af] [-v vol]\n"
"\t%.*s [-c channels] [-s rate] [-e encoding]\n"
"\t%.*s [-t time] [-i info] [-d dev] [-T au|wav|aif[f]] [file]\n"
"where:\n"
"\t-a\tAppend to output file\n"
"\t-f\tIgnore sample rate differences on append\n"
"\t-v\tSet record volume (0 - %d)\n"
"\t-c\tSpecify number of channels to record\n"
"\t-s\tSpecify rate in samples per second\n"
"\t-e\tSpecify encoding (ulaw | alaw | [u]linear | linear8 )\n"
"\t-t\tSpecify record time (hh:mm:ss.dd)\n"
"\t-i\tSpecify a file header information string\n"
"\t-d\tSpecify audio device (default: /dev/audio)\n"
"\t-T\tSpecify the audio file type (default: au)\n"
"\tfile\tRecord to named file\n"
"\t\tIf no file specified, write to stdout\n"
"\t\tDefault audio encoding is ulaw, 8khz, mono\n"
"\t\tIf -t is not specified, record until ^C\n"),
prog,
strlen(prog), " ",
strlen(prog), " ",
MAX_GAIN);
exit(1);
}
static void
sigint(int sig)
{
if (!Cleanup && (Audio_fd >= 0)) {
Cleanup = TRUE;
if (audio_pause_record(Audio_fd) == AUDIO_SUCCESS)
return;
Error(stderr, MGET("%s: could not flush input buffer\n"), prog);
}
if (Audio_fd >= 0) {
if (Volume != INT_MAX)
(void) audio_set_record_gain(Audio_fd, &Savevol);
if (audio_cmp_hdr(&Save_hdr, &Dev_hdr) != 0) {
(void) audio_set_record_config(Audio_fd, &Save_hdr);
}
}
exit(1);
}
int
main(int argc, char **argv)
{
int i;
int cnt;
int err;
int file_type;
int ofd;
int swapBytes = FALSE;
double vol;
struct stat st;
struct pollfd pfd;
char *cp;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
prog = strrchr(argv[0], '/');
if (prog == NULL)
prog = argv[0];
else
prog++;
Stdout = MGET("(stdout)");
if (cp = getenv("AUDIODEV")) {
Audio_dev = cp;
}
if ((ulong_t)1 != htonl((ulong_t)1)) {
NetEndian = FALSE;
}
err = 0;
while ((i = getopt(argc, argv, prog_opts)) != EOF) {
switch (i) {
case 'v':
if (parse_unsigned(optarg, &Volume, "-v")) {
err++;
} else if (Volume > MAX_GAIN) {
Error(stderr, MGET("%s: invalid value for "
"-v\n"), prog);
err++;
}
break;
case 't':
Time = audio_str_to_secs(optarg);
if ((Time == HUGE_VAL) || (Time < 0.)) {
Error(stderr, MGET("%s: invalid value for "
"-t\n"), prog);
err++;
}
break;
case 'd':
Audio_dev = optarg;
break;
case 'f':
Force = TRUE;
break;
case 'a':
Append = TRUE;
break;
case 'i':
Info = optarg;
Ilen = strlen(Info);
break;
case 's':
if (parse_sample_rate(optarg, &Sample_rate)) {
err++;
}
break;
case 'c':
if (strncmp(optarg, "mono", strlen(optarg)) == 0) {
Channels = 1;
} else if (strncmp(optarg, "stereo",
strlen(optarg)) == 0) {
Channels = 2;
} else if (parse_unsigned(optarg, &Channels, "-c")) {
err++;
} else if ((Channels != 1) && (Channels != 2)) {
Error(stderr, "%s: invalid value for -c\n",
prog);
err++;
}
break;
case 'e':
if (strncmp(optarg, "ulinear", strlen(optarg)) == 0) {
Encoding = AUDIO_ENCODING_LINEAR8;
Precision = 8;
} else if (strncmp(optarg, "linear8",
strlen("linear8")) == 0) {
Encoding = AUDIO_ENCODING_LINEAR;
Precision = 8;
} else if (strncmp(optarg, "ulaw",
strlen(optarg)) == 0) {
Encoding = AUDIO_ENCODING_ULAW;
Precision = 8;
} else if (strncmp(optarg, "alaw",
strlen(optarg)) == 0) {
Encoding = AUDIO_ENCODING_ALAW;
Precision = 8;
} else if ((strncmp(optarg, "linear",
strlen(optarg)) == 0) || (strncmp(optarg, "pcm",
strlen(optarg)) == 0)) {
Encoding = AUDIO_ENCODING_LINEAR;
Precision = 16;
} else {
Error(stderr, MGET("%s: invalid value for "
"-e\n"), prog);
err++;
}
break;
case 'T':
if (strncmp(optarg, "au", strlen(optarg)) == 0) {
File_type = FILE_AU;
} else if (strncmp(optarg, "wav",
strlen(optarg)) == 0) {
File_type = FILE_WAV;
} else if (strncmp(optarg, "aif",
strlen(optarg)) == 0) {
File_type = FILE_AIFF;
} else if (strncmp(optarg, "aiff",
strlen(optarg)) == 0) {
File_type = FILE_AIFF;
} else {
Error(stderr, MGET("%s: invalid value for "
"-T\n"), prog);
err++;
}
File_type_set = TRUE;
break;
case '?':
usage();
}
}
if (Append && (Info != NULL)) {
Error(stderr, MGET("%s: cannot specify -a and -i\n"), prog);
err++;
}
if (err > 0)
exit(1);
argc -= optind;
argv += optind;
if (argc <= 0) {
Ofile = Stdout;
} else {
Ofile = *argv++;
argc--;
if (strcmp(Ofile, "-") == 0)
Ofile = Stdout;
if (File_type_set == FALSE) {
char *file_name;
char *start;
file_name = basename(Ofile);
start = strrchr(file_name, '.');
if (start) {
if (strcasecmp(start, ".au") == 0) {
File_type = FILE_AU;
} else if (strcasecmp(start, ".wav") == 0) {
File_type = FILE_WAV;
} else if (strcasecmp(start, ".aif") == 0) {
File_type = FILE_AIFF;
} else if (strcasecmp(start, ".aiff") == 0) {
File_type = FILE_AIFF;
} else {
File_type = FILE_AU;
}
} else {
File_type = FILE_AU;
}
}
}
if (Ofile == Stdout) {
ofd = fileno(stdout);
Append = FALSE;
} else {
ofd = open(Ofile,
(O_RDWR | O_CREAT | (Append ? 0 : O_TRUNC)), 0666);
if (ofd < 0) {
Error(stderr, MGET("%s: cannot open "), prog);
perror(Ofile);
exit(1);
}
if (Append) {
if ((fstat(ofd, &st) < 0) || (!S_ISREG(st.st_mode))) {
Error(stderr,
MGET("%s: %s is not a regular file\n"),
prog, Ofile);
exit(1);
}
if (st.st_size == 0) {
Append = FALSE;
goto openinput;
}
err = audio_read_filehdr(ofd, &File_hdr, &file_type,
(char *)NULL, 0);
if (err != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: %s is not a valid audio file\n"),
prog, Ofile);
exit(1);
}
if (File_type_set == TRUE) {
if (File_type != file_type) {
Error(stderr,
MGET("%s: file types must match\n"),
prog);
exit(1);
}
} else {
File_type = file_type;
}
Sample_rate = File_hdr.sample_rate;
Channels = File_hdr.channels;
Encoding = File_hdr.encoding;
Precision = File_hdr.bytes_per_unit * 8;
switch (Encoding) {
case AUDIO_ENCODING_LINEAR8:
case AUDIO_ENCODING_ULAW:
case AUDIO_ENCODING_ALAW:
case AUDIO_ENCODING_LINEAR:
break;
default: {
char msg[AUDIO_MAX_ENCODE_INFO];
(void) audio_enc_to_str(&File_hdr, msg);
Error(stderr,
MGET("%s: Append is not supported "
"for "), prog);
Error(stderr,
MGET("this file encoding:\n\t"
"[%s]\n"), msg);
exit(1);
}
}
Oldsize = File_hdr.data_size;
if ((Oldsize == AUDIO_UNKNOWN_SIZE) &&
((err = (int)lseek(ofd, 0L, SEEK_CUR)) >= 0)) {
if (err < 0) {
Error(stderr,
MGET("%s: %s is not a valid audio "
"file\n"), prog, Ofile);
exit(1);
}
Oldsize = st.st_size - err;
}
if ((int)lseek(ofd, st.st_size, SEEK_SET) < 0) {
Error(stderr,
MGET("%s: cannot find end of %s\n"),
prog, Ofile);
exit(1);
}
}
}
openinput:
err = stat(Audio_dev, &st);
if (err < 0) {
Error(stderr, MGET("%s: cannot open "), prog);
perror(Audio_dev);
exit(1);
}
if (!S_ISCHR(st.st_mode)) {
Error(stderr, MGET("%s: %s is not an audio device\n"), prog,
Audio_dev);
exit(1);
}
Audio_fd = open(Audio_dev, O_RDONLY | O_NONBLOCK);
if (Audio_fd < 0) {
if (errno == EBUSY) {
Error(stderr, MGET("%s: %s is busy\n"),
prog, Audio_dev);
} else {
Error(stderr, MGET("%s: error opening "), prog);
perror(Audio_dev);
}
exit(1);
}
if (audio_pause_record(Audio_fd) != AUDIO_SUCCESS) {
Error(stderr, MGET("%s: not able to pause recording\n"), prog);
exit(1);
}
if (audio_get_record_config(Audio_fd, &Save_hdr) != AUDIO_SUCCESS) {
(void) close(Audio_fd);
Error(stderr, MGET("%s: %s is not an audio device\n"),
prog, Audio_dev);
exit(1);
}
bcopy(&Save_hdr, &Dev_hdr, sizeof (Save_hdr));
if (audio_flush_record(Audio_fd) != AUDIO_SUCCESS) {
Error(stderr, MGET("%s: not able to flush recording\n"), prog);
exit(1);
}
if (Sample_rate != 0) {
Dev_hdr.sample_rate = Sample_rate;
}
if (Channels != 0) {
Dev_hdr.channels = Channels;
}
if (Precision != 0) {
Dev_hdr.bytes_per_unit = Precision / 8;
}
if (Encoding != 0) {
Dev_hdr.encoding = Encoding;
}
if (File_type == FILE_WAV &&
Dev_hdr.encoding == AUDIO_ENCODING_LINEAR &&
Dev_hdr.bytes_per_unit == 1) {
Dev_hdr.encoding = AUDIO_ENCODING_LINEAR8;
} else if (File_type == FILE_AIFF && Encoding == 0) {
Dev_hdr.encoding = AUDIO_ENCODING_LINEAR;
if (Precision == 0) {
Dev_hdr.bytes_per_unit = AUDIO_PRECISION_8 / 8;
}
}
if (audio_set_record_config(Audio_fd, &Dev_hdr) != AUDIO_SUCCESS) {
Error(stderr, MGET(
"%s: Audio format not supported by the audio device\n"),
prog);
exit(1);
}
if (audio_resume_record(Audio_fd) != AUDIO_SUCCESS) {
Error(stderr, MGET("%s: not able to resume recording\n"), prog);
exit(1);
}
if (Append) {
char msg[AUDIO_MAX_ENCODE_INFO];
switch (audio_cmp_hdr(&Dev_hdr, &File_hdr)) {
case 0:
break;
case 1:
if (Force) {
Error(stderr, MGET("%s: WARNING: appending "
"%.3fkHz data to %s (%.3fkHz)\n"), prog,
((double)Dev_hdr.sample_rate / 1000.),
Ofile,
((double)File_hdr.sample_rate / 1000.));
break;
}
default:
(void) audio_enc_to_str(&Dev_hdr, msg);
Error(stderr,
MGET("%s: device encoding [%s]\n"), prog, msg);
(void) audio_enc_to_str(&File_hdr, msg);
Error(stderr,
MGET("\tdoes not match file encoding [%s]\n"), msg);
exit(1);
}
} else if (!isatty(ofd)) {
if (audio_write_filehdr(ofd, &Dev_hdr, File_type, Info,
Ilen) != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: error writing header for %s\n"), prog,
Ofile);
exit(1);
}
}
if (Dev_hdr.bytes_per_unit == 2 &&
((!NetEndian && File_type == FILE_AIFF) ||
(!NetEndian && File_type == FILE_AU) ||
(NetEndian && File_type == FILE_WAV))) {
swapBytes = TRUE;
}
if (Volume != INT_MAX) {
vol = (double)Volume / (double)MAX_GAIN;
(void) audio_get_record_gain(Audio_fd, &Savevol);
err = audio_set_record_gain(Audio_fd, &vol);
if (err != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: could not set record volume for %s\n"),
prog, Audio_dev);
exit(1);
}
}
if (isatty(ofd)) {
Error(stderr, MGET("%s: No files and stdout is a tty\n"),
prog);
exit(1);
}
(void) signal(SIGINT, sigint);
if (Time > 0)
Limit = audio_secs_to_bytes(&Dev_hdr, Time);
pfd.fd = Audio_fd;
pfd.events = POLLIN;
while ((Limit == AUDIO_UNKNOWN_SIZE) || (Limit != 0)) {
cnt = read(Audio_fd, (char *)buf,
((Limit != AUDIO_UNKNOWN_SIZE) && (Limit < sizeof (buf)) ?
(int)Limit : sizeof (buf)));
if (cnt == 0)
break;
if (cnt < 0) {
if (Cleanup)
break;
switch (errno) {
case EAGAIN:
(void) poll(&pfd, 1L, -1);
break;
case EOVERFLOW:
Error(stderr, MGET("%s: error reading"), prog);
perror("Large File");
exit(1);
default:
Error(stderr, MGET("%s: error reading"), prog);
perror(Audio_dev);
exit(1);
}
continue;
}
if (swapBytes) {
swab((char *)buf, swapBuf, cnt);
err = write(ofd, swapBuf, cnt);
} else {
err = write(ofd, (char *)buf, cnt);
}
if (err < 0) {
Error(stderr, MGET("%s: error writing "), prog);
perror(Ofile);
exit(1);
}
if (err != cnt) {
Error(stderr, MGET("%s: error writing "), prog);
perror(Ofile);
break;
}
Size += cnt;
if (Limit != AUDIO_UNKNOWN_SIZE)
Limit -= cnt;
}
if (!Append || (Oldsize != AUDIO_UNKNOWN_SIZE)) {
if (Append)
Size += Oldsize;
(void) audio_rewrite_filesize(ofd, File_type, Size,
Dev_hdr.channels, Dev_hdr.bytes_per_unit);
}
(void) close(ofd);
if (audio_get_record_error(Audio_fd, (unsigned *)&err) != AUDIO_SUCCESS)
Error(stderr, MGET("%s: error reading device status\n"), prog);
else if (err)
Error(stderr, MGET("%s: WARNING: Data overflow occurred\n"),
prog);
if (Volume != INT_MAX)
(void) audio_set_record_gain(Audio_fd, &Savevol);
if (audio_cmp_hdr(&Save_hdr, &Dev_hdr) != 0) {
(void) audio_set_record_config(Audio_fd, &Save_hdr);
}
(void) close(Audio_fd);
return (0);
}
static int
parse_unsigned(char *str, unsigned *dst, char *flag)
{
char x;
if (sscanf(str, "%u%c", dst, &x) != 1) {
Error(stderr, MGET("%s: invalid value for %s\n"), prog, flag);
return (1);
}
return (0);
}
static int
parse_sample_rate(char *s, unsigned *rate)
{
char *cp;
double drate;
if (strcasecmp(s, "dat") == 0) {
drate = 48000.0;
} else if (strcasecmp(s, "cd") == 0) {
drate = 44100.0;
} else if (strcasecmp(s, "voice") == 0) {
drate = 8000.0;
} else {
drate = atof(s);
for (cp = s; *cp && (isdigit(*cp) || (*cp == '.')); cp++)
;
if (*cp != '\0') {
if ((*cp == 'k') || (*cp == 'K')) {
drate *= 1000.0;
} else if ((*cp != 'h') && (*cp != 'H')) {
Error(stderr,
MGET("invalid sample rate: %s\n"), s);
return (1);
}
}
}
*rate = irint(drate);
return (0);
}