audioplay.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 1993-2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* Command-line audio play utility */
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <locale.h>
#include <limits.h> /* All occurances of INT_MAX used to be ~0 (by MCA) */
#include <unistd.h>
#include <stropts.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <libaudio.h>
#include <audio_device.h>
#include <audio_encode.h>
/* localization stuff */
#define MGET(s) (char *)gettext(s)
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
#endif
#define Error (void) fprintf
/* Local variables */
static char *prog;
static char prog_opts[] = "VEiv:b:d:p:?"; /* getopt() flags */
static char *Stdin;
#define MAX_GAIN (100) /* maximum gain */
#define LEFT_BAL (-100) /* min/max balance */
#define MID_BAL (0)
#define RIGHT_BAL (100)
/*
* This defines the tolerable sample rate error as a ratio between the
* sample rates of the audio data and the audio device.
*/
#define SAMPLE_RATE_THRESHOLD (.01)
#define BUFFER_LEN 10 /* seconds - for file i/o */
#define ADPCM_SIZE (1000*8) /* adpcm conversion output buf size */
#define SWAP_SIZE (8192)
/* swap bytes conversion output buf size */
static unsigned Volume = INT_MAX; /* output volume */
static double Savevol; /* saved volume level */
static unsigned int Balance = INT_MAX; /* output balance */
static unsigned int Savebal; /* saved output balance */
static unsigned int Port = INT_MAX;
/* output port (spkr, line, hp-jack) */
static unsigned int Saveport = 0;
/* save prev. val so we can restore */
static int Verbose = FALSE; /* verbose messages */
static int Immediate = FALSE;
/* don't hang waiting for device */
static int Errdetect = FALSE; /* don't worry about underrun */
static char *Audio_dev = "/dev/audio";
static int NetEndian = TRUE; /* endian nature of the machine */
static int Audio_fd = -1;
/* file descriptor for audio device */
static int Audio_ctlfd = -1;
/* file descriptor for control device */
static Audio_hdr Save_hdr;
/* saved audio header for device */
static Audio_hdr Dev_hdr; /* audio header for device */
static char *Ifile; /* current filename */
static Audio_hdr File_hdr; /* audio header for file */
static unsigned Decode = AUDIO_ENCODING_NONE;
/* decode type, if any */
static unsigned char *buf = NULL; /* dynamically alloc'd */
static unsigned bufsiz = 0; /* size of output buffer */
static unsigned char adpcm_buf[ADPCM_SIZE + 32];
/* for adpcm conversion */
static unsigned char swap_buf[SWAP_SIZE + 32];
/* for byte swap conversion */
static unsigned char *inbuf;
/* current input buffer pointer */
static unsigned insiz; /* current input buffer size */
/*
* The decode_g72x() function is capable of decoding only one channel
* at a time and so multichannel data must be decomposed (using demux()
* function below ) into its constituent channels and each passed
* separately to the decode_g72x() function. Encoded input channels are
* stored in **in_ch_data and decoded output channels in **out_ch_data.
* Once each channel has been decoded they are recombined (see mux()
* function below) before being written to the audio device. For each
* channel and adpcm state structure is created.
*/
/* adpcm state structures */
static struct audio_g72x_state *adpcm_state = NULL;
static unsigned char **in_ch_data = NULL; /* input channels */
static unsigned char **out_ch_data = NULL; /* output channels */
static int out_ch_size; /* output channel size */
static char *Audio_path = NULL;
/* path to search for audio files */
/* Global variables */
extern int getopt(int, char *const *, const char *);
extern int optind;
extern char *optarg;
/* Local functions */
static void usage(void);
static void sigint(int sig);
static void open_audio(void);
static int path_open(char *fname, int flags, mode_t mode, char *path);
static int parse_unsigned(char *str, unsigned *dst, char *flag);
static int scale_balance(int g);
static int reconfig(void);
static void initmux(int unitsz, int unitsp);
static void demux(int unitsz, int cnt);
static void mux(char *);
static void freemux(void);
static void
usage(void)
{
Error(stderr, MGET("Play an audio file -- usage:\n"
"\t%s [-iV] [-v vol] [-b bal]\n"
"\t%.*s [-p speaker|headphone|line|aux1|aux2|spdif]\n"
"\t%.*s [-d dev] [file ...]\n"
"where:\n"
"\t-i\tDon't hang if audio device is busy\n"
"\t-V\tPrint verbose warning messages\n"
"\t-v\tSet output volume (0 - %d)\n"
"\t-b\tSet output balance (%d=left, %d=center, %d=right)\n"
"\t-p\tSpecify output port\n"
"\t-d\tSpecify audio device (default: /dev/audio)\n"
"\tfile\tList of files to play\n"
"\t\tIf no files specified, read stdin\n"),
prog, strlen(prog), " ",
strlen(prog), " ",
MAX_GAIN, LEFT_BAL, MID_BAL, RIGHT_BAL);
exit(1);
}
static void
sigint(int sig)
{
/* flush output queues before exiting */
if (Audio_fd >= 0) {
(void) audio_flush_play(Audio_fd);
/* restore saved parameters */
if (Volume != INT_MAX)
(void) audio_set_play_gain(Audio_fd, &Savevol);
if (Balance != INT_MAX)
(void) audio_set_play_balance(Audio_fd, &Savebal);
if (Port != INT_MAX)
(void) audio_set_play_port(Audio_fd, &Saveport);
if ((Audio_ctlfd >= 0) &&
(audio_cmp_hdr(&Save_hdr, &Dev_hdr) != 0)) {
(void) audio_set_play_config(Audio_fd, &Save_hdr);
}
}
exit(1);
}
/* Open the audio device and initalize it. */
static void
open_audio(void)
{
int err;
double vol;
/* Return if already open */
if (Audio_fd >= 0)
return;
/* Try opening without waiting, first */
Audio_fd = open(Audio_dev, O_WRONLY | O_NONBLOCK);
if ((Audio_fd < 0) && (errno == EBUSY)) {
if (Immediate) {
Error(stderr, MGET("%s: %s is busy\n"),
prog, Audio_dev);
exit(1);
}
if (Verbose) {
Error(stderr, MGET("%s: waiting for %s..."),
prog, Audio_dev);
(void) fflush(stderr);
}
/* Now hang until it's open */
Audio_fd = open(Audio_dev, O_WRONLY);
if (Verbose)
Error(stderr, (Audio_fd < 0) ? "\n" : MGET("open\n"));
}
if (Audio_fd < 0) {
Error(stderr, MGET("%s: error opening "), prog);
perror(Audio_dev);
exit(1);
}
/* Clear the non-blocking flag (in System V it persists after open) */
(void) fcntl(Audio_fd, F_SETFL,
(fcntl(Audio_fd, F_GETFL, 0) & ~(O_NDELAY | O_NONBLOCK)));
/* Get the device output encoding configuration */
if (audio_get_play_config(Audio_fd, &Dev_hdr) != AUDIO_SUCCESS) {
Error(stderr, MGET("%s: %s is not an audio device\n"),
prog, Audio_dev);
exit(1);
}
/* If -v flag, set the output volume now */
if (Volume != INT_MAX) {
vol = (double)Volume / (double)MAX_GAIN;
(void) audio_get_play_gain(Audio_fd, &Savevol);
err = audio_set_play_gain(Audio_fd, &vol);
if (err != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: could not set output volume for %s\n"),
prog, Audio_dev);
exit(1);
}
}
if (Balance != INT_MAX) {
(void) audio_get_play_balance(Audio_fd, &Savebal);
err = audio_set_play_balance(Audio_fd, &Balance);
if (err != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: could not set output balance for %s\n"),
prog, Audio_dev);
exit(1);
}
}
/* If -p flag, set the output port now */
if (Port != INT_MAX) {
(void) audio_get_play_port(Audio_fd, &Saveport);
err = audio_set_play_port(Audio_fd, &Port);
if (err != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: could not set output port %s\n"),
prog, Audio_dev);
exit(1);
}
}
}
/* Play a list of audio files. */
int
main(int argc, char **argv) {
int errorStatus = 0;
int i;
int c;
int cnt;
int file_type;
int rem;
int outsiz;
int tsize;
int len;
int err;
int ifd;
int stdinseen;
int regular;
int bal;
int swapBytes;
int frame;
char *outbuf;
caddr_t mapaddr;
struct stat st;
char *cp;
char ctldev[MAXPATHLEN];
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
/* Get the program name */
prog = strrchr(argv[0], '/');
if (prog == NULL)
prog = argv[0];
else
prog++;
Stdin = MGET("(stdin)");
/* Check AUDIODEV environment for audio device name */
if (cp = getenv("AUDIODEV")) {
Audio_dev = cp;
}
/* Parse the command line arguments */
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 'b':
bal = atoi(optarg);
if ((bal > RIGHT_BAL) || (bal < LEFT_BAL)) {
Error(stderr, MGET("%s: invalid value "
"for -b\n"), prog);
err++;
} else {
Balance = (unsigned)scale_balance(bal);
}
break;
case 'd':
Audio_dev = optarg;
break;
case 'V':
Verbose = TRUE;
break;
case 'E':
Errdetect = TRUE;
break;
case 'i':
Immediate = TRUE;
break;
case 'p':
/* a partial match is OK */
if (strncmp(optarg, "speaker",
strlen(optarg)) == 0) {
Port = AUDIO_SPEAKER;
} else if (strncmp(optarg, "headphone",
strlen(optarg)) == 0) {
Port = AUDIO_HEADPHONE;
} else if (strncmp(optarg, "line",
strlen(optarg)) == 0) {
Port = AUDIO_LINE_OUT;
} else if (strncmp(optarg, "aux1",
strlen(optarg)) == 0) {
Port = AUDIO_AUX1_OUT;
} else if (strncmp(optarg, "aux2",
strlen(optarg)) == 0) {
Port = AUDIO_AUX2_OUT;
} else if (strncmp(optarg, "spdif",
strlen(optarg)) == 0) {
Port = AUDIO_SPDIF_OUT;
} else {
Error(stderr, MGET("%s: invalid value "
"for -p\n"), prog);
err++;
}
break;
case '?':
usage();
/*NOTREACHED*/
}
}
if (err > 0)
exit(1);
argc -= optind; /* update arg pointers */
argv += optind;
/* Validate and open the audio device */
err = stat(Audio_dev, &st);
if (err < 0) {
Error(stderr, MGET("%s: cannot stat "), 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);
}
/* This should probably use audio_cntl instead of open_audio */
if ((argc <= 0) && isatty(fileno(stdin))) {
if (Verbose) {
Error(stderr,
MGET("%s: No files - setting audio device parameters.\n"),
prog);
}
open_audio();
exit(0);
}
/* Check on the -i status now. */
Audio_fd = open(Audio_dev, O_WRONLY | O_NONBLOCK);
if ((Audio_fd < 0) && (errno == EBUSY)) {
if (Immediate) {
Error(stderr, MGET("%s: %s is busy\n"), prog,
Audio_dev);
exit(1);
}
}
(void) close(Audio_fd);
Audio_fd = -1;
/* Try to open the control device and save the current format */
(void) sprintf(ctldev, "%sctl", Audio_dev);
Audio_ctlfd = open(ctldev, O_RDWR);
if (Audio_ctlfd >= 0) {
/*
* wait for the device to become available then get the
* controls. We want to save the format that is left when the
* device is in a quiescent state. So wait until then.
*/
Audio_fd = open(Audio_dev, O_WRONLY);
(void) close(Audio_fd);
Audio_fd = -1;
if (audio_get_play_config(Audio_ctlfd, &Save_hdr)
!= AUDIO_SUCCESS) {
(void) close(Audio_ctlfd);
Audio_ctlfd = -1;
}
}
/* store AUDIOPATH so we don't keep doing getenv() */
Audio_path = getenv("AUDIOPATH");
/* Set up SIGINT handler to flush output */
(void) signal(SIGINT, sigint);
/* Set the endian nature of the machine. */
if ((ulong_t)1 != htonl((ulong_t)1)) {
NetEndian = FALSE;
}
/* If no filenames, read stdin */
stdinseen = FALSE;
if (argc <= 0) {
Ifile = Stdin;
} else {
Ifile = *argv++;
argc--;
}
/* Loop through all filenames */
do {
/* Interpret "-" filename to mean stdin */
if (strcmp(Ifile, "-") == 0)
Ifile = Stdin;
if (Ifile == Stdin) {
if (stdinseen) {
Error(stderr,
MGET("%s: stdin already processed\n"),
prog);
goto nextfile;
}
stdinseen = TRUE;
ifd = fileno(stdin);
} else {
if ((ifd = path_open(Ifile, O_RDONLY, 0, Audio_path))
< 0) {
Error(stderr, MGET("%s: cannot open "), prog);
perror(Ifile);
errorStatus++;
goto nextfile;
}
}
/* Check to make sure this is an audio file */
err = audio_read_filehdr(ifd, &File_hdr, &file_type,
(char *)NULL, 0);
if (err != AUDIO_SUCCESS) {
Error(stderr,
MGET("%s: %s is not a valid audio file\n"),
prog, Ifile);
errorStatus++;
goto closeinput;
}
/* If G.72X adpcm, set flags for conversion */
if ((File_hdr.encoding == AUDIO_ENCODING_G721) &&
(File_hdr.samples_per_unit == 2) &&
(File_hdr.bytes_per_unit == 1)) {
Decode = AUDIO_ENCODING_G721;
File_hdr.encoding = AUDIO_ENCODING_ULAW;
File_hdr.samples_per_unit = 1;
File_hdr.bytes_per_unit = 1;
adpcm_state = (struct audio_g72x_state *)malloc
(sizeof (*adpcm_state) * File_hdr.channels);
for (i = 0; i < File_hdr.channels; i++) {
g721_init_state(&adpcm_state[i]);
}
} else if ((File_hdr.encoding == AUDIO_ENCODING_G723) &&
(File_hdr.samples_per_unit == 8) &&
(File_hdr.bytes_per_unit == 3)) {
Decode = AUDIO_ENCODING_G723;
File_hdr.encoding = AUDIO_ENCODING_ULAW;
File_hdr.samples_per_unit = 1;
File_hdr.bytes_per_unit = 1;
adpcm_state = (struct audio_g72x_state *)malloc
(sizeof (*adpcm_state) * File_hdr.channels);
for (i = 0; i < File_hdr.channels; i++) {
g723_init_state(&adpcm_state[i]);
}
} else {
Decode = AUDIO_ENCODING_NONE;
}
/* Check the device configuration */
open_audio();
if (audio_cmp_hdr(&Dev_hdr, &File_hdr) != 0) {
/*
* The device does not match the input file.
* Wait for any old output to drain, then attempt
* to reconfigure the audio device to match the
* input data.
*/
if (audio_drain(Audio_fd, FALSE) != AUDIO_SUCCESS) {
/* Flush any remaining audio */
(void) ioctl(Audio_fd, I_FLUSH, FLUSHW);
Error(stderr, MGET("%s: "), prog);
perror(MGET("AUDIO_DRAIN error"));
exit(1);
}
/* Flush any remaining audio */
(void) ioctl(Audio_fd, I_FLUSH, FLUSHW);
if (!reconfig()) {
errorStatus++;
goto closeinput;
}
}
/* try to do the mmaping - for regular files only ... */
err = fstat(ifd, &st);
if (err < 0) {
Error(stderr, MGET("%s: cannot stat "), prog);
perror(Ifile);
exit(1);
}
regular = (S_ISREG(st.st_mode));
/* If regular file, map it. Else, allocate a buffer */
mapaddr = 0;
/*
* This should compare to MAP_FAILED not -1, can't
* find MAP_FAILED
*/
if (regular && ((mapaddr = mmap(0, st.st_size, PROT_READ,
MAP_SHARED, ifd, 0)) != MAP_FAILED)) {
(void) madvise(mapaddr, st.st_size, MADV_SEQUENTIAL);
/* Skip the file header and set the proper size */
cnt = lseek(ifd, 0, SEEK_CUR);
if (cnt < 0) {
perror("lseek");
exit(1);
}
inbuf = (unsigned char *) mapaddr + cnt;
len = cnt = st.st_size - cnt;
} else { /* Not a regular file, or map failed */
/* mark is so. */
mapaddr = 0;
/* Allocate buffer to hold 10 seconds of data */
cnt = BUFFER_LEN * File_hdr.sample_rate *
File_hdr.bytes_per_unit * File_hdr.channels;
if (bufsiz != cnt) {
if (buf != NULL) {
(void) free(buf);
}
buf = (unsigned char *) malloc(cnt);
if (buf == NULL) {
Error(stderr,
MGET("%s: couldn't allocate %dK "
"buf\n"), prog, bufsiz / 1000);
exit(1);
}
inbuf = buf;
bufsiz = cnt;
}
}
/* Set buffer sizes and pointers for conversion, if any */
switch (Decode) {
default:
case AUDIO_ENCODING_NONE:
insiz = bufsiz;
outbuf = (char *)buf;
break;
case AUDIO_ENCODING_G721:
insiz = ADPCM_SIZE / 2;
outbuf = (char *)adpcm_buf;
initmux(1, 2);
break;
case AUDIO_ENCODING_G723:
insiz = (ADPCM_SIZE * 3) / 8;
outbuf = (char *)adpcm_buf;
initmux(3, 8);
break;
}
/*
* 8-bit audio isn't a problem, however 16-bit audio is.
* If the file is an endian that is different from the machine
* then the bytes will need to be swapped.
*
* Note: Because the G.72X conversions produce 8bit output,
* they don't require a byte swap before display and so
* this scheme works just fine. If a conversion is added
* that produces a 16 bit result and therefore requires
* byte swapping before output, then a mechanism
* for chaining the two conversions will have to be built.
*
* Note: The following if() could be simplified, but then
* it gets to be very hard to read. So it's left as is.
*/
if (File_hdr.bytes_per_unit == 2 &&
((!NetEndian && file_type == FILE_AIFF) ||
(!NetEndian && file_type == FILE_AU) ||
(NetEndian && file_type == FILE_WAV))) {
swapBytes = TRUE;
} else {
swapBytes = FALSE;
}
if (swapBytes) {
/* Read in interal number of sample frames. */
frame = File_hdr.bytes_per_unit * File_hdr.channels;
insiz = (SWAP_SIZE / frame) * frame;
/* make the output buffer the swap buffer. */
outbuf = (char *)swap_buf;
}
/*
* At this point, we're all ready to copy the data.
*/
if (mapaddr == 0) { /* Not mmapped, do it a buffer at a time. */
inbuf = buf;
frame = File_hdr.bytes_per_unit * File_hdr.channels;
rem = 0;
while ((cnt = read(ifd, inbuf+rem, insiz-rem)) >= 0) {
/*
* We need to ensure only an integral number of
* samples is ever written to the audio device.
*/
cnt = cnt + rem;
rem = cnt % frame;
cnt = cnt - rem;
/*
* If decoding adpcm, or swapping bytes do it
* now.
*
* We treat the swapping like a separate
* encoding here because the G.72X encodings
* decode to single byte output samples. If
* another encoding is added and it produces
* multi-byte output samples this will have to
* be changed.
*/
if (Decode == AUDIO_ENCODING_G721) {
outsiz = 0;
demux(1, cnt / File_hdr.channels);
for (c = 0; c < File_hdr.channels; c++) {
err = g721_decode(in_ch_data[c],
cnt / File_hdr.channels,
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
outsiz = outsiz + tsize;
if (err != AUDIO_SUCCESS) {
Error(stderr, MGET(
"%s: error decoding g721\n"),
prog);
errorStatus++;
break;
}
}
mux(outbuf);
cnt = outsiz;
} else if (Decode == AUDIO_ENCODING_G723) {
outsiz = 0;
demux(3, cnt / File_hdr.channels);
for (c = 0; c < File_hdr.channels; c++) {
err = g723_decode(in_ch_data[c],
cnt / File_hdr.channels,
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
outsiz = outsiz + tsize;
if (err != AUDIO_SUCCESS) {
Error(stderr, MGET(
"%s: error decoding g723\n"),
prog);
errorStatus++;
break;
}
}
mux(outbuf);
cnt = outsiz;
} else if (swapBytes) {
swab((char *)inbuf, outbuf, cnt);
}
/* If input EOF, write an eof marker */
err = write(Audio_fd, outbuf, cnt);
if (err < 0) {
perror("write");
errorStatus++;
break;
} else if (err != cnt) {
Error(stderr,
MGET("%s: output error: "), prog);
perror("");
errorStatus++;
break;
}
if (cnt == 0) {
break;
}
/* Move remainder to the front of the buffer */
if (rem != 0) {
(void *)memcpy(inbuf, inbuf + cnt, rem);
}
}
if (cnt < 0) {
Error(stderr, MGET("%s: error reading "), prog);
perror(Ifile);
errorStatus++;
}
} else { /* We're mmaped */
if ((Decode != AUDIO_ENCODING_NONE) || swapBytes) {
/* Transform data if we have to. */
for (i = 0; i <= len; i += cnt) {
cnt = insiz;
if ((i + cnt) > len) {
cnt = len - i;
}
if (Decode == AUDIO_ENCODING_G721) {
outsiz = 0;
demux(1, cnt / File_hdr.channels);
for (c = 0; c < File_hdr.channels;
c++) {
err = g721_decode(
in_ch_data[c],
cnt / File_hdr.channels,
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
outsiz = outsiz + tsize;
if (err != AUDIO_SUCCESS) {
Error(stderr, MGET(
"%s: error decoding "
"g721\n"), prog);
errorStatus++;
break;
}
}
mux(outbuf);
} else if
(Decode == AUDIO_ENCODING_G723) {
outsiz = 0;
demux(3,
cnt / File_hdr.channels);
for (c = 0;
c < File_hdr.channels;
c++) {
err = g723_decode(
in_ch_data[c],
cnt /
File_hdr.channels,
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
outsiz = outsiz + tsize;
if (err != AUDIO_SUCCESS) {
Error(stderr, MGET(
"%s: error "
"decoding g723\n"),
prog);
errorStatus++;
break;
}
}
mux(outbuf);
} else if (swapBytes) {
swab((char *)inbuf, outbuf,
cnt);
outsiz = cnt;
}
inbuf += cnt;
/* If input EOF, write an eof marker */
err = write(Audio_fd, (char *)outbuf,
outsiz);
if (err < 0) {
perror("write");
errorStatus++;
} else if (outsiz == 0) {
break;
}
}
} else {
/* write the whole thing at once! */
err = write(Audio_fd, inbuf, len);
if (err < 0) {
perror("write");
errorStatus++;
}
if (err != len) {
Error(stderr,
MGET("%s: output error: "), prog);
perror("");
errorStatus++;
}
err = write(Audio_fd, inbuf, 0);
if (err < 0) {
perror("write");
errorStatus++;
}
}
}
/* Free memory if decoding ADPCM */
switch (Decode) {
case AUDIO_ENCODING_G721:
case AUDIO_ENCODING_G723:
freemux();
break;
default:
break;
}
closeinput:;
if (mapaddr != 0)
(void) munmap(mapaddr, st.st_size);
(void) close(ifd); /* close input file */
if (Errdetect) {
cnt = 0;
audio_set_play_error(Audio_fd, (unsigned int *)&cnt);
if (cnt) {
Error(stderr,
MGET("%s: output underflow in %s\n"),
Ifile, prog);
errorStatus++;
}
}
nextfile:;
} while ((argc > 0) && (argc--, (Ifile = *argv++) != NULL));
/*
* Though drain is implicit on close(), it's performed here
* to ensure that the volume is reset after all output is complete.
*/
(void) audio_drain(Audio_fd, FALSE);
/* Flush any remaining audio */
(void) ioctl(Audio_fd, I_FLUSH, FLUSHW);
if (Volume != INT_MAX)
(void) audio_set_play_gain(Audio_fd, &Savevol);
if (Balance != INT_MAX)
(void) audio_set_play_balance(Audio_fd, &Savebal);
if (Port != INT_MAX)
(void) audio_set_play_port(Audio_fd, &Saveport);
if ((Audio_ctlfd >= 0) && (audio_cmp_hdr(&Save_hdr, &Dev_hdr) != 0)) {
(void) audio_set_play_config(Audio_fd, &Save_hdr);
}
(void) close(Audio_fd); /* close output */
return (errorStatus);
}
/*
* Try to reconfigure the audio device to match the file encoding.
* If this fails, we should attempt to make the input data match the
* device encoding. For now, we give up on this file.
*
* Returns TRUE if successful. Returns FALSE if not.
*/
static int
reconfig(void)
{
int err;
char msg[AUDIO_MAX_ENCODE_INFO];
Dev_hdr = File_hdr;
err = audio_set_play_config(Audio_fd, &Dev_hdr);
switch (err) {
case AUDIO_SUCCESS:
return (TRUE);
case AUDIO_ERR_NOEFFECT:
/*
* Couldn't change the device.
* Check to see if we're nearly compatible.
* audio_cmp_hdr() returns >0 if only sample rate difference.
*/
if (audio_cmp_hdr(&Dev_hdr, &File_hdr) > 0) {
double ratio;
ratio = (double)abs((int)
(Dev_hdr.sample_rate - File_hdr.sample_rate)) /
(double)File_hdr.sample_rate;
if (ratio <= SAMPLE_RATE_THRESHOLD) {
if (Verbose) {
Error(stderr,
MGET("%s: WARNING: %s sampled at %d, playing at %d\n"),
prog, Ifile, File_hdr.sample_rate,
Dev_hdr.sample_rate);
}
return (TRUE);
}
Error(stderr,
MGET("%s: %s sample rate %d not available\n"),
prog, Ifile, File_hdr.sample_rate);
return (FALSE);
}
(void) audio_enc_to_str(&File_hdr, msg);
Error(stderr, MGET("%s: %s encoding not available: %s\n"),
prog, Ifile, msg);
return (FALSE);
default:
Error(stderr,
MGET("%s: %s audio encoding type not available\n"),
prog, Ifile);
exit(1);
}
return (TRUE);
}
/* Parse an unsigned integer */
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);
}
/*
* Search for fname in path and open. Ignore path not opened O_RDONLY.
* Note: in general path can be a list of ':' separated paths to search
* through.
*/
static int
path_open(char *fname, int flags, mode_t mode, char *path)
{
char fullpath[MAXPATHLEN]; /* full path of file */
char *buf; /* malloc off the tmp buff */
char *cp;
struct stat st;
if (!fname) { /* bogus */
return (-1);
}
/*
* cases where we don't bother checking path:
* - no path
* - file not opened O_RDONLY
* - not a relative path (i.e. starts with /, ./, or ../).
*/
if ((!path) || (flags != O_RDONLY) || (*fname == '/') ||
(strncmp(fname, "./", strlen("./")) == 0) ||
(strncmp(fname, "../", strlen("../")) == 0)) {
return (open(fname, flags, mode));
}
/*
* Malloc off a buffer to hold the path variable.
* This is NOT limited to MAXPATHLEN characters as
* it may contain multiple paths.
*/
buf = malloc(strlen(path) + 1);
/*
* if first character is ':', but not the one following it,
* skip over it - or it'll be interpreted as "./". it's OK
* to have "::" since that does mean "./".
*/
if ((path[0] == ':') && (path[1] != ':')) {
(void) strncpy(buf, path+1, strlen(path));
} else {
(void) strncpy(buf, path, strlen(path));
}
for (path = buf; path && *path; ) {
if (cp = strchr(path, ':')) {
*cp++ = NULL; /* now pts to next path element */
}
/* the safest way to create the path string :-) */
if (*path) {
(void) strncpy(fullpath, path, MAXPATHLEN);
(void) strncat(fullpath, "/", MAXPATHLEN);
} else {
/* a NULL path element means "./" */
(void) strncpy(fullpath, "./", MAXPATHLEN);
}
(void) strncat(fullpath, fname, MAXPATHLEN);
/* see if there's a match */
if (stat(fullpath, &st) >= 0) {
if (S_ISREG(st.st_mode)) {
/* got a match! */
if (Verbose) {
Error(stderr,
MGET("%s: Found %s in path at %s\n"),
prog, fname, fullpath);
}
return (open(fullpath, flags, mode));
}
}
/* go on to the next one */
path = cp;
}
/*
* if we fall through with no match, just do a normal file open
*/
return (open(fname, flags, mode));
}
/* Convert local balance into device parameters */
static int
scale_balance(int g)
{
return (int)(((g + RIGHT_BAL) / (double)(RIGHT_BAL - LEFT_BAL)) *
(double)AUDIO_RIGHT_BALANCE);
}
/*
* initmux()
*
* Description:
* Allocates memory for carrying out demultiplexing/multiplexing.
*
* Arguments:
* int unitsz Bytes per unit
* int unitsp Samples per unit
*
* Returns:
* void
*/
static void
initmux(int unitsz, int unitsp)
{
int c; /* Channel */
int in_ch_size; /* Input channel size */
/* Size of each input channel */
in_ch_size = insiz / File_hdr.channels;
/* Size of each output channel */
out_ch_size = in_ch_size * unitsp / unitsz;
/* Allocate pointers to input channels */
in_ch_data = (unsigned char **)malloc(sizeof (unsigned char *)
* File_hdr.channels);
if (in_ch_data == NULL) {
Error(stderr, MGET("%s: couldn't allocate %dK "
"buf\n"), prog, sizeof (unsigned char *) *
File_hdr.channels / 1000);
exit(1);
}
/* Allocate input channels */
for (c = 0; c < File_hdr.channels; c++) {
in_ch_data[c] = (unsigned char *)malloc
(sizeof (unsigned char) * in_ch_size);
if (in_ch_data[c] == NULL) {
Error(stderr, MGET("%s: couldn't allocate %dK "
"buf\n"), prog, in_ch_size / 1000);
exit(1);
}
}
/* Allocate pointers to output channels */
out_ch_data = (unsigned char **)malloc(sizeof (unsigned char *)
* File_hdr.channels);
if (out_ch_data == NULL) {
Error(stderr, MGET("%s: couldn't allocate %dK "
"buf\n"), prog, sizeof (unsigned char *) *
File_hdr.channels / 1000);
exit(1);
}
/* Allocate output channels */
for (c = 0; c < File_hdr.channels; c++) {
out_ch_data[c] = (unsigned char *)malloc
(sizeof (unsigned char) * out_ch_size);
if (out_ch_data[c] == NULL) {
Error(stderr, MGET("%s: couldn't allocate %dK "
"buf\n"), prog, out_ch_size / 1000);
exit(1);
}
}
}
/*
* demux()
*
* Description:
* Split a multichannel signal into separate channels.
*
* Arguments:
* int unitsz Bytes per unit
* int cnt Bytes to process
*
* Returns:
* void
*/
static void
demux(int unitsz, int cnt)
{
int c; /* Channel */
int s; /* Sample */
int b; /* Byte */
int tp; /* Pointer into current data */
int dp; /* Pointer into target data */
/* Split */
for (c = 0; c < File_hdr.channels; c++) {
for (s = 0; s < cnt / unitsz; s++) {
tp = s * unitsz;
dp = (s * File_hdr.channels + c) * unitsz;
for (b = 0; b < unitsz; b++) {
in_ch_data[c][tp + b] = inbuf[dp + b];
}
}
}
}
/*
* mux()
*
* Description:
* Combine separate channels to produce a multichannel signal.
*
* Arguments:
* char *outbuf Combined signal
*
* Returns:
* void
*/
static void
mux(char *outbuf)
{
int c; /* Channel */
int s; /* Sample */
/* Combine */
for (c = 0; c < File_hdr.channels; c++) {
for (s = 0; s < out_ch_size; s++) {
outbuf[File_hdr.channels * s + c] = out_ch_data[c][s];
}
}
}
/*
* freemux()
*
* Description:
* Free memory used in multiplexing/demultiplexing.
*
* Arguments:
* void
*
* Returns:
* void
*/
static void
freemux(void)
{
int c; /* Channel */
/* Free */
for (c = 0; c < File_hdr.channels; c++) {
free(in_ch_data[c]);
free(out_ch_data[c]);
free(&adpcm_state[c]);
}
free(in_ch_data);
free(out_ch_data);
}