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
* 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 <libaudio.h>
#include <audio_device.h>
#include <audio_encode.h>
/* localization stuff */
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#endif
/* Local variables */
static char *prog;
static char *Stdin;
#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 SWAP_SIZE (8192)
/* swap bytes conversion output buf size */
static double Savevol; /* saved volume level */
static unsigned int Savebal; /* saved output balance */
/* output port (spkr, line, hp-jack) */
static unsigned int Saveport = 0;
/* save prev. val so we can restore */
/* don't hang waiting for device */
static int Audio_fd = -1;
/* file descriptor for audio device */
static int Audio_ctlfd = -1;
/* file descriptor for control device */
/* saved audio header for device */
static char *Ifile; /* current filename */
static unsigned Decode = AUDIO_ENCODING_NONE;
/* decode type, if any */
static unsigned bufsiz = 0; /* size of output buffer */
/* for adpcm conversion */
/* 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 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 open_audio(void);
static int scale_balance(int g);
static int reconfig(void);
static void mux(char *);
static void freemux(void);
static void
usage(void)
{
"\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"
"\tfile\tList of files to play\n"
"\t\tIf no files specified, read stdin\n"),
exit(1);
}
static void
{
/* flush output queues before exiting */
if (Audio_fd >= 0) {
(void) audio_flush_play(Audio_fd);
/* restore saved parameters */
if ((Audio_ctlfd >= 0) &&
}
}
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 */
if (Immediate) {
exit(1);
}
if (Verbose) {
}
/* Now hang until it's open */
if (Verbose)
}
if (Audio_fd < 0) {
exit(1);
}
/* Clear the non-blocking flag (in System V it persists after open) */
/* Get the device output encoding configuration */
exit(1);
}
/* If -v flag, set the output volume now */
if (err != AUDIO_SUCCESS) {
MGET("%s: could not set output volume for %s\n"),
exit(1);
}
}
if (err != AUDIO_SUCCESS) {
MGET("%s: could not set output balance for %s\n"),
exit(1);
}
}
/* If -p flag, set the output port now */
if (err != AUDIO_SUCCESS) {
MGET("%s: could not set output port %s\n"),
exit(1);
}
}
}
/* Play a list of audio files. */
int
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;
char *cp;
char ctldev[MAXPATHLEN];
(void) textdomain(TEXT_DOMAIN);
/* Get the program name */
else
prog++;
/* Check AUDIODEV environment for audio device name */
}
/* Parse the command line arguments */
err = 0;
switch (i) {
case 'v':
err++;
"for -v\n"), prog);
err++;
}
break;
case 'b':
"for -b\n"), prog);
err++;
} else {
}
break;
case 'd':
break;
case 'V':
break;
case 'E':
break;
case 'i':
break;
case 'p':
/* a partial match is OK */
} else {
"for -p\n"), prog);
err++;
}
break;
case '?':
usage();
/*NOTREACHED*/
}
}
if (err > 0)
exit(1);
/* Validate and open the audio device */
if (err < 0) {
exit(1);
}
exit(1);
}
/* This should probably use audio_cntl instead of open_audio */
if (Verbose) {
MGET("%s: No files - setting audio device parameters.\n"),
prog);
}
open_audio();
exit(0);
}
/* Check on the -i status now. */
if (Immediate) {
exit(1);
}
}
Audio_fd = -1;
/* Try to open the control device and save the current format */
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 = -1;
!= AUDIO_SUCCESS) {
(void) close(Audio_ctlfd);
Audio_ctlfd = -1;
}
}
/* store AUDIOPATH so we don't keep doing getenv() */
/* Set up SIGINT handler to flush output */
/* Set the endian nature of the machine. */
}
/* If no filenames, read stdin */
if (argc <= 0) {
} else {
argc--;
}
/* Loop through all filenames */
do {
/* Interpret "-" filename to mean stdin */
if (stdinseen) {
MGET("%s: stdin already processed\n"),
prog);
goto nextfile;
}
} else {
< 0) {
errorStatus++;
goto nextfile;
}
}
/* Check to make sure this is an audio file */
(char *)NULL, 0);
if (err != AUDIO_SUCCESS) {
MGET("%s: %s is not a valid audio file\n"),
errorStatus++;
goto closeinput;
}
/* If G.72X adpcm, set flags for conversion */
g721_init_state(&adpcm_state[i]);
}
g723_init_state(&adpcm_state[i]);
}
} else {
}
/* Check the device configuration */
open_audio();
/*
* 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.
*/
/* Flush any remaining audio */
exit(1);
}
/* Flush any remaining audio */
if (!reconfig()) {
errorStatus++;
goto closeinput;
}
}
/* try to do the mmaping - for regular files only ... */
if (err < 0) {
exit(1);
}
/* If regular file, map it. Else, allocate a buffer */
mapaddr = 0;
/*
* This should compare to MAP_FAILED not -1, can't
* find MAP_FAILED
*/
/* Skip the file header and set the proper size */
if (cnt < 0) {
perror("lseek");
exit(1);
}
} else { /* Not a regular file, or map failed */
/* mark is so. */
mapaddr = 0;
/* Allocate buffer to hold 10 seconds of data */
}
MGET("%s: couldn't allocate %dK "
exit(1);
}
}
}
/* Set buffer sizes and pointers for conversion, if any */
switch (Decode) {
default:
case AUDIO_ENCODING_NONE:
break;
case AUDIO_ENCODING_G721:
break;
case AUDIO_ENCODING_G723:
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.
*/
} else {
}
if (swapBytes) {
/* Read in interal number of sample frames. */
/* make the output buffer the swap buffer. */
}
/*
* At this point, we're all ready to copy the data.
*/
if (mapaddr == 0) { /* Not mmapped, do it a buffer at a time. */
rem = 0;
/*
* We need to ensure only an integral number of
* samples is ever written to the audio device.
*/
/*
* 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;
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
if (err != AUDIO_SUCCESS) {
"%s: error decoding g721\n"),
prog);
errorStatus++;
break;
}
}
} else if (Decode == AUDIO_ENCODING_G723) {
outsiz = 0;
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
if (err != AUDIO_SUCCESS) {
"%s: error decoding g723\n"),
prog);
errorStatus++;
break;
}
}
} else if (swapBytes) {
}
/* If input EOF, write an eof marker */
if (err < 0) {
perror("write");
errorStatus++;
break;
perror("");
errorStatus++;
break;
}
if (cnt == 0) {
break;
}
/* Move remainder to the front of the buffer */
if (rem != 0) {
}
}
if (cnt < 0) {
errorStatus++;
}
} else { /* We're mmaped */
/* Transform data if we have to. */
}
if (Decode == AUDIO_ENCODING_G721) {
outsiz = 0;
c++) {
err = g721_decode(
in_ch_data[c],
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
if (err != AUDIO_SUCCESS) {
"%s: error decoding "
"g721\n"), prog);
errorStatus++;
break;
}
}
} else if
(Decode == AUDIO_ENCODING_G723) {
outsiz = 0;
demux(3,
for (c = 0;
c++) {
err = g723_decode(
in_ch_data[c],
cnt /
&File_hdr,
(void*)out_ch_data[c],
&tsize,
&adpcm_state[c]);
if (err != AUDIO_SUCCESS) {
"%s: error "
"decoding g723\n"),
prog);
errorStatus++;
break;
}
}
} else if (swapBytes) {
cnt);
}
/* If input EOF, write an eof marker */
outsiz);
if (err < 0) {
perror("write");
errorStatus++;
} else if (outsiz == 0) {
break;
}
}
} else {
/* write the whole thing at once! */
if (err < 0) {
perror("write");
errorStatus++;
}
perror("");
errorStatus++;
}
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;
}
if (mapaddr != 0)
if (Errdetect) {
cnt = 0;
if (cnt) {
MGET("%s: output underflow in %s\n"),
errorStatus++;
}
}
nextfile:;
/*
* Though drain is implicit on close(), it's performed here
* to ensure that the volume is reset after all output is complete.
*/
/* Flush any remaining audio */
}
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];
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.
*/
double ratio;
(double)File_hdr.sample_rate;
if (ratio <= SAMPLE_RATE_THRESHOLD) {
if (Verbose) {
MGET("%s: WARNING: %s sampled at %d, playing at %d\n"),
}
return (TRUE);
}
MGET("%s: %s sample rate %d not available\n"),
return (FALSE);
}
return (FALSE);
default:
MGET("%s: %s audio encoding type not available\n"),
exit(1);
}
return (TRUE);
}
/* Parse an unsigned integer */
static int
{
char x;
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
{
char *buf; /* malloc off the tmp buff */
char *cp;
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 ../).
*/
}
/*
* Malloc off a buffer to hold the path variable.
* This is NOT limited to MAXPATHLEN characters as
* it may contain multiple paths.
*/
/*
* 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 "./".
*/
} else {
}
}
/* the safest way to create the path string :-) */
if (*path) {
} else {
/* a NULL path element means "./" */
}
/* see if there's a match */
/* got a match! */
if (Verbose) {
MGET("%s: Found %s in path at %s\n"),
}
}
}
/* go on to the next one */
}
/*
* if we fall through with no match, just do a normal file open
*/
}
/* Convert local balance into device parameters */
static int
scale_balance(int g)
{
(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
{
int c; /* Channel */
int in_ch_size; /* Input channel size */
/* Size of each input channel */
/* Size of each output channel */
/* Allocate pointers to input channels */
in_ch_data = (unsigned char **)malloc(sizeof (unsigned char *)
if (in_ch_data == NULL) {
"buf\n"), prog, sizeof (unsigned char *) *
exit(1);
}
/* Allocate input channels */
in_ch_data[c] = (unsigned char *)malloc
(sizeof (unsigned char) * in_ch_size);
if (in_ch_data[c] == NULL) {
exit(1);
}
}
/* Allocate pointers to output channels */
out_ch_data = (unsigned char **)malloc(sizeof (unsigned char *)
if (out_ch_data == NULL) {
"buf\n"), prog, sizeof (unsigned char *) *
exit(1);
}
/* Allocate output channels */
out_ch_data[c] = (unsigned char *)malloc
(sizeof (unsigned char) * out_ch_size);
if (out_ch_data[c] == NULL) {
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
{
int c; /* Channel */
int s; /* Sample */
int b; /* Byte */
int tp; /* Pointer into current data */
int dp; /* Pointer into target data */
/* Split */
for (b = 0; b < unitsz; b++) {
}
}
}
}
/*
* mux()
*
* Description:
* Combine separate channels to produce a multichannel signal.
*
* Arguments:
* char *outbuf Combined signal
*
* Returns:
* void
*/
static void
{
int c; /* Channel */
int s; /* Sample */
/* Combine */
for (s = 0; s < out_ch_size; s++) {
}
}
}
/*
* freemux()
*
* Description:
* Free memory used in multiplexing/demultiplexing.
*
* Arguments:
* void
*
* Returns:
* void
*/
static void
freemux(void)
{
int c; /* Channel */
/* Free */
free(in_ch_data[c]);
free(out_ch_data[c]);
free(&adpcm_state[c]);
}
}