OpenSolaris_b135/cmd/audio/utilities/filehdr.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This file contains a set of Very Paranoid routines to convert
 * audio file headers to in-core audio headers and vice versa.
 *
 * They are robust enough to handle any random file input without
 * crashing miserably.  Of course, bad audio headers coming from
 * the calling program can cause significant problems.
 */

#include <stdlib.h>
#include <memory.h>
#include <fcntl.h>
#include <errno.h>	/* needed for large file error checking */
#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <libintl.h>
#include <math.h>

#include <libaudio_impl.h>	/* include other audio hdr's */

/* Round up to a double boundary */
#define	ROUND_DBL(x)	(((x) + 7) & ~7)

#define	HEADER_BUFFER		100

#define	_MGET_(str)	(char *)dgettext(TEXT_DOMAIN, str)

static int audio_encode_aiff(Audio_hdr *, unsigned char *, unsigned int *);
static int audio_encode_au(Audio_hdr *, char *, unsigned int,
	unsigned char *, unsigned int *);
static int audio_encode_wav(Audio_hdr *, unsigned char *, unsigned int *);
static double convert_from_ieee_extended(unsigned char *);
static void convert_to_ieee_extended(double, unsigned char *);

/*
 * Write an audio file header to an output stream.
 *
 * The file header is encoded from the supplied Audio_hdr structure.
 * If 'infop' is not NULL, it is the address of a buffer containing 'info'
 * data.  'ilen' specifies the size of this buffer.
 * The entire file header will be zero-padded to a double-word boundary.
 *
 * Note that the file header is stored on-disk in big-endian format,
 * regardless of the machine type.
 *
 * Note also that the output file descriptor must not have been set up
 * non-blocking i/o.  If non-blocking behavior is desired, set this
 * flag after writing the file header.
 */
int
audio_write_filehdr(int fd, Audio_hdr *hdrp, int file_type, char *infop,
	unsigned int ilen)
					/* file descriptor */
					/* audio header */
					/* audio header type */
					/* info buffer pointer */
					/* buffer size */
{
	int		err;
	unsigned	blen;
	unsigned char	*buf;		/* temporary buffer */

	/* create tmp buf for the encoding routines to work with */
	blen = HEADER_BUFFER + (infop ? ilen : 0) + 4;
	blen = ROUND_DBL(blen);

	if (!(buf = (unsigned char *)calloc(1, blen))) {
		return (AUDIO_UNIXERROR);
	}

	switch (file_type) {
	case FILE_AU:
		err = audio_encode_au(hdrp, infop, ilen, buf, &blen);
		break;
	case FILE_WAV:
		err = audio_encode_wav(hdrp, buf, &blen);
		break;
	case FILE_AIFF:
		err = audio_encode_aiff(hdrp, buf, &blen);
		break;
	default:
		return (AUDIO_ERR_BADFILETYPE);
	}

	if (err != AUDIO_SUCCESS) {
		return (err);
	}

	/* Write and free the holding buffer */
	err = write(fd, (char *)buf, (int)blen);
	(void) free((char *)buf);

	if (err != blen)
		return ((err < 0) ? AUDIO_UNIXERROR : AUDIO_ERR_BADFILEHDR);

	return (AUDIO_SUCCESS);

}

/*
 * Rewrite the aiff header chunk length and the data chunk length fields.
 */
static int
audio_rewrite_aiff_filesize(int fd, unsigned int size, unsigned int channels,
	unsigned int bytes_per_sample)
{
	unsigned int	offset;
	unsigned int	tmp_uint;
	unsigned int	tmp_uint2;
	unsigned int	total_size;

	/* first fix aiff_hdr_size */
	total_size = size + sizeof (aiff_hdr_chunk_t) +
	    AUDIO_AIFF_COMM_CHUNK_SIZE + sizeof (aiff_ssnd_chunk_t);
	tmp_uint = total_size - (2 * sizeof (int));
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &tmp_uint2);
	offset = sizeof (int);
	if (lseek(fd, offset, SEEK_SET) < 0) {
		return (AUDIO_ERR_NOEFFECT);
	}
	if (write(fd, &tmp_uint2, sizeof (tmp_uint2)) != sizeof (tmp_uint2)) {
		return (AUDIO_ERR_NOEFFECT);
	}

	/* fix the frame count */
	tmp_uint = size / channels / bytes_per_sample;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &tmp_uint2);
	offset = sizeof (aiff_hdr_chunk_t) + (2 * sizeof (int)) +
	    sizeof (short);
	if (lseek(fd, offset, SEEK_SET) < 0) {
		return (AUDIO_ERR_NOEFFECT);
	}
	if (write(fd, &tmp_uint2, sizeof (tmp_uint2)) != sizeof (tmp_uint2)) {
		return (AUDIO_ERR_NOEFFECT);
	}

	/* fix the data size */
	tmp_uint = size + sizeof (aiff_ssnd_chunk_t) - (2 * sizeof (int));
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &tmp_uint2);
	offset = sizeof (aiff_hdr_chunk_t) + AUDIO_AIFF_COMM_CHUNK_SIZE +
	    sizeof (int);
	if (lseek(fd, offset, SEEK_SET) < 0) {
		return (AUDIO_ERR_NOEFFECT);
	}
	if (write(fd, &tmp_uint2, sizeof (tmp_uint2)) != sizeof (tmp_uint2)) {
		return (AUDIO_ERR_NOEFFECT);
	}

	return (AUDIO_SUCCESS);

}

/*
 * Rewrite the data size field for the .au file format. Rewrite the audio
 * file header au_data_size field with the supplied value. Otherwise,
 * return AUDIO_ERR_NOEFFECT.
 */
static int
audio_rewrite_au_filesize(int fd, unsigned int size)
{
	au_filehdr_t	fhdr;
	int		err;
	int		data;
	int		offset;

	/* seek to the position of the au_data_size member */
	offset = (char *)&fhdr.au_data_size - (char *)&fhdr;
	if (lseek(fd, offset, SEEK_SET) < 0) {
		return (AUDIO_ERR_NOEFFECT);
	}

	/* Encode the 32-bit integer header field */
	AUDIO_AU_HOST2FILE(&size, &data);

	/* Write the data */
	err = write(fd, (char *)&data, sizeof (fhdr.au_data_size));
	if (err != sizeof (fhdr.au_data_size))
		return ((err < 0) ? AUDIO_UNIXERROR : AUDIO_ERR_BADFILEHDR);

	return (AUDIO_SUCCESS);

}

/*
 * Rewrite the riff header chunk length and the data chunk length fields.
 */
static int
audio_rewrite_wav_filesize(int fd, unsigned int size)
{
	wav_filehdr_t	fhdr;
	int		calc_size;
	int		err;
	int		data;
	int		offset;

	/* seek to the position of the riff header chunk length */
	calc_size = size + sizeof (fhdr) - sizeof (fhdr.wav_riff_ID) -
	    sizeof (fhdr.wav_riff_size);
	AUDIO_WAV_HOST2FILE_INT(&calc_size, &data);
	offset = (char *)&fhdr.wav_riff_size - (char *)&fhdr;
	if (lseek(fd, offset, SEEK_SET) < 0) {
		return (AUDIO_ERR_NOEFFECT);
	}

	/* Write the data */
	err = write(fd, (char *)&data, sizeof (fhdr.wav_riff_size));
	if (err != sizeof (fhdr.wav_riff_size))
		return ((err < 0) ? AUDIO_UNIXERROR : AUDIO_ERR_BADFILEHDR);

	/* now seek to the position of the data chunk length */
	AUDIO_WAV_HOST2FILE_INT(&size, &data);
	offset = (char *)&fhdr.wav_data_size - (char *)&fhdr;
	if (lseek(fd, offset, SEEK_SET) < 0) {
		return (AUDIO_ERR_NOEFFECT);
	}

	/* Write the data */
	err = write(fd, (char *)&data, sizeof (fhdr.wav_data_size));
	if (err != sizeof (fhdr.wav_data_size))
		return ((err < 0) ? AUDIO_UNIXERROR : AUDIO_ERR_BADFILEHDR);

	return (AUDIO_SUCCESS);

}

/*
 * Rewrite the data size field of an audio header to the output stream if
 * the output file is capable of seeking.
 */
int
audio_rewrite_filesize(int fd, int file_type, unsigned int size,
	unsigned int channels, unsigned int bytes_per_sample)
					/* file descriptor */
					/* audio file type */
					/* new data size */
					/* number of channels */
					/* number of bytes per sample */
{
	int		fcntl_err;

	/* Can we seek back in this file and write without appending? */
	fcntl_err = fcntl(fd, F_GETFL, 0);
	if ((fcntl_err < 0) && ((errno == EOVERFLOW) || (errno == EINVAL))) {
		/* Large file encountered (probably) */
		perror("fcntl");
		exit(1);
	} else if ((lseek(fd, (off_t)0, SEEK_SET) < 0) ||
		    (fcntl_err & FAPPEND)) {
		return (AUDIO_ERR_NOEFFECT);
	}

	switch (file_type) {
	case FILE_AU:
		return (audio_rewrite_au_filesize(fd, size));
	case FILE_WAV:
		return (audio_rewrite_wav_filesize(fd, size));
	case FILE_AIFF:
		return (audio_rewrite_aiff_filesize(fd, size, channels,
		    bytes_per_sample));
	default:
		return (AUDIO_ERR_BADFILETYPE);
	}
}


/*
 * Decode an audio file header from an input stream.
 *
 * The file header is decoded into the supplied Audio_hdr structure, regardless
 * of the file format. Thus .wav and .aiff files look like .au files once the
 * header is decoded.
 *
 * If 'infop' is not NULL, it is the address of a buffer to which the
 * 'info' portion of the file header will be copied.  'ilen' specifies
 * the maximum number of bytes to copy.  The buffer will be NULL-terminated,
 * even if it means over-writing the last byte.
 *
 * Note that the .au file header is stored on-disk in big-endian format,
 * regardless of the machine type.  This may not have been true if
 * the file was written on a non-Sun machine.  For now, such
 * files will appear invalid.
 *
 * Note also that the input file descriptor must not have been set up
 * non-blocking i/o.  If non-blocking behavior is desired, set this
 * flag after reading the file header.
 */
int
audio_read_filehdr(int fd, Audio_hdr *hdrp, int *file_type, char *infop,
	unsigned int ilen)
					/* input file descriptor */
					/* output audio header */
					/* audio file type */
					/* info buffer pointer */
					/* buffer size */
{
	int		err;
	int		dsize;
	int		isize;
	unsigned	resid;
	unsigned char	buf[HEADER_BUFFER];
	struct stat	st;

	/* decode the file header and fill in the hdrp structure */
	if ((err = audio_decode_filehdr(fd, buf, file_type, hdrp, &isize)) !=
	    AUDIO_SUCCESS) {
		goto checkerror;
	}

	/* Stat the file, to determine if it is a regular file. */
	err = fstat(fd, &st);
	if (err < 0) {
		return (AUDIO_UNIXERROR);
	}

	/*
	 * If au_data_size is not indeterminate (i.e., this isn't a pipe),
	 * try to validate the au_offset and au_data_size.
	 */
	if (*file_type == FILE_AU && hdrp->data_size != AUDIO_UNKNOWN_SIZE) {
		/* Only trust the size for regular files */
		if (S_ISREG(st.st_mode)) {
			dsize = isize + hdrp->data_size + sizeof (au_filehdr_t);
			if (st.st_size < dsize) {
				(void) fprintf(stderr,
				    _MGET_("Warning: More audio data "
				    "than the file header specifies\n"));
			} else if (st.st_size > dsize) {
				(void) fprintf(stderr,
				    _MGET_("Warning: Less audio data "
				    "than the file header specifies\n"));
			}
		}
	}

	resid = isize;
	/*
	 * Deal with extra header data.
	 */
	if ((infop != NULL) && (ilen != 0)) {
		/*
		 * If infop is non-NULL, try to read in the info data
		 */
		if (isize > ilen)
			isize = ilen;
		err = read(fd, infop, (int)isize);
		if (err != isize)
			goto checkerror;

		/* Zero any residual bytes in the text buffer */
		if (isize < ilen)
			(void) memset(&infop[isize], '\0',
				    (int)(ilen - isize));
		else
			infop[ilen - 1] = '\0';	/* zero-terminate */

		resid -= err;		/* subtract the amount read */
	}

	/*
	 * If we truncated the info, seek or read data until info size
	 * is satisfied.  If regular file, seek nearly to end and check
	 * for eof.
	 */
	if (resid != 0) {
		if (S_ISREG(st.st_mode)) {
			err = lseek(fd, (off_t)(resid - 1), SEEK_CUR);
			if ((err < 0) ||
			    ((err = read(fd, (char *)buf, 1)) != 1))
				goto checkerror;
		} else while (resid != 0) {
			char	junk[8192];	/* temporary buffer */

			isize = (resid > sizeof (junk)) ?
			    sizeof (junk) : resid;
			err = read(fd, junk, isize);
			if (err != isize)
				goto checkerror;
			resid -= err;
		}
	}

	return (AUDIO_SUCCESS);

checkerror:
	if ((err < 0) && (errno == EOVERFLOW)) {
		perror("read");
		exit(1);
	} else {
		return ((err < 0) ? AUDIO_UNIXERROR : AUDIO_ERR_BADFILEHDR);
	}
	return (AUDIO_SUCCESS);
}

/*
 * Return TRUE if the named file is an audio file.  Else, return FALSE.
 */
int
audio_isaudiofile(char *name)
{
	int		fd;
	int		err;
	int		file_type;	/* ignored */
	int		isize;
	Audio_hdr	hdr;
	unsigned char	buf[sizeof (au_filehdr_t)];

	/* Open the file (set O_NONBLOCK in case the name refers to a device) */
	fd = open(name, O_RDONLY | O_NONBLOCK);
	if (fd < 0) {
		if (errno == EOVERFLOW) {
			perror("open");
			exit(1);
		} else {
			return (FALSE);
		}
	}

	/* Read the header (but not the text info). */
	err = read(fd, (char *)buf, sizeof (buf));
	if (err < 0) {
		if (errno == EOVERFLOW) {
			perror("open");
			exit(1);
		} else {
			return (FALSE);
		}
	}
	(void) close(fd);

	if ((err == sizeof (buf)) &&
	    (audio_decode_filehdr(fd, buf, &file_type, &hdr, &isize) ==
	    AUDIO_SUCCESS)) {
		return (hdr.encoding);
	} else {
		return (FALSE);
	}
}

/*
 * audio_endian()
 *
 * This routine tests the magic number at the head of a buffer
 * containing the file header.  The first thing in the header
 * should be the magic number.
 */
static int
audio_endian(unsigned char *buf, int *file_type)
{
	unsigned int	magic1;
	unsigned int	magic2;

	/* put the buffer into an int that is aligned properly */
	(void) memcpy(&magic1, buf, sizeof (magic1));

	magic2 = magic1;
	SWABI(magic2);

	if (magic1 == AUDIO_AU_FILE_MAGIC || magic2 == AUDIO_AU_FILE_MAGIC) {
		*file_type = FILE_AU;
		return (AUDIO_ENDIAN_BIG);
	} else if (magic1 == AUDIO_WAV_RIFF_ID || magic2 == AUDIO_WAV_RIFF_ID) {
		*file_type = FILE_WAV;
		return (AUDIO_ENDIAN_SMALL);
	} else if (magic1 == AUDIO_AIFF_HDR_CHUNK_ID ||
	    magic2 == AUDIO_AIFF_HDR_CHUNK_ID) {
		*file_type = FILE_AIFF;
		return (AUDIO_ENDIAN_BIG);
	}

	return (AUDIO_ENDIAN_UNKNOWN);
}

/*
 * Decode an aiff file header. Unlike .au and .wav, we have to process
 * by chunk.
 */
static int
decode_aiff(int fd, unsigned char *buf, Audio_hdr *hdrp, int *isize)
{
	aiff_hdr_chunk_t	hdr_chunk;
	aiff_comm_chunk_t	comm_chunk;
	aiff_ssnd_chunk_t	ssnd_chunk;
	uint32_t		ID;
	uint32_t		size;
	uint32_t		tmp;
	int			data_type;
	int			hdr_sizes;
	int			sr;
	short			bits_per_sample;
	short			channels;

	/* we've read in 4 bytes, read in the rest of the wav header */
	size = sizeof (hdr_chunk) - sizeof (hdr_chunk.aiff_hdr_ID);

	/* read in the rest of the header */
	if (read(fd, &hdr_chunk.aiff_hdr_size, size) != size) {
		return (AUDIO_UNIXERROR);
	}

	/* see which kind of audio file we have */
	AUDIO_AIFF_FILE2HOST_INT(&hdr_chunk.aiff_hdr_data_type, &data_type);
	if (data_type != AUDIO_AIFF_HDR_FORM_AIFF) {
		/* we can't play this version of a .aiff file */
		return (AUDIO_ERR_BADFILEHDR);
	}

	hdr_sizes = sizeof (hdr_chunk);

	/*
	 * We don't know what the chunk order will be, so read each, getting
	 * the data we need from each. Eventually we'll get to the end of
	 * the file, in which case we should have all of the info on the
	 * file that we need. We then lseek() back to the data to play.
	 *
	 * We start each loop by reading the chunk ID.
	 */
	while (read(fd, &tmp, sizeof (tmp)) == sizeof (tmp)) {
		AUDIO_AIFF_FILE2HOST_INT(&tmp, &ID);
		switch (ID) {
		case AUDIO_AIFF_COMM_ID:
			/* read in the rest of the COMM chunk */
			size = AUDIO_AIFF_COMM_CHUNK_SIZE -
			    sizeof (comm_chunk.aiff_comm_ID);
			if (read(fd, &comm_chunk.aiff_comm_size, size) !=
			    size) {
				return (AUDIO_UNIXERROR);
			}

			sr = convert_from_ieee_extended(
			    comm_chunk.aiff_comm_sample_rate);

			hdr_sizes += AUDIO_AIFF_COMM_CHUNK_SIZE;

			break;
		case AUDIO_AIFF_SSND_ID:
			/* read in the rest of the INST chunk */
			size = sizeof (ssnd_chunk) -
			    sizeof (ssnd_chunk.aiff_ssnd_ID);
			if (read(fd, &ssnd_chunk.aiff_ssnd_size, size) !=
			    size) {
				return (AUDIO_UNIXERROR);
			}

			/*
			 * This has to be the last chunk because the audio data
			 * follows. So we should have all we need to tell the
			 * app the format information.
			 */
			hdrp->sample_rate = sr;

			AUDIO_AIFF_FILE2HOST_SHORT(
			    &comm_chunk.aiff_comm_channels,
			    &channels);
			/* use channels to convert from short to int */
			hdrp->channels = channels;

			AUDIO_AIFF_FILE2HOST_SHORT(
			    &comm_chunk.aiff_comm_sample_size,
			    &bits_per_sample);
			switch (bits_per_sample) {
			case AUDIO_AIFF_COMM_8_BIT_SAMPLE_SIZE:
				hdrp->encoding = AUDIO_AU_ENCODING_LINEAR_8;
				break;
			case AUDIO_AIFF_COMM_16_BIT_SAMPLE_SIZE:
				hdrp->encoding = AUDIO_AU_ENCODING_LINEAR_16;
				break;
			default:
				return (AUDIO_ERR_BADFILEHDR);
			}

			AUDIO_AIFF_FILE2HOST_INT(&ssnd_chunk.aiff_ssnd_size,
			    &size);
			size -= sizeof (ssnd_chunk.aiff_ssnd_offset) +
			    sizeof (ssnd_chunk.aiff_ssnd_block_size);
			hdrp->data_size = size;

			hdr_sizes += sizeof (ssnd_chunk);

			*isize = hdr_sizes - sizeof (au_filehdr_t);

			return (AUDIO_SUCCESS);
		default:
			/*
			 * Unknown chunk. Read the size, which is right after
			 * the ID. Then seek past it to get to the next chunk.
			 */
			if (read(fd, &size, sizeof (size)) != sizeof (size)) {
				return (AUDIO_UNIXERROR);
			}

			if (lseek(fd, size, SEEK_CUR) < 0) {
				return (AUDIO_UNIXERROR);
			}
			break;
		}
	}

	return (AUDIO_SUCCESS);

}	/* decode_aiff() */

/*
 * Decode an au file header.
 */
static int
decode_au(int fd, unsigned char *buf, Audio_hdr *hdrp, int *isize,
    boolean_t read_info)
{
	au_filehdr_t	fhdr;
	int		offset;
	int		size;

	if (read_info) {
		/* read in the rest of the au header */
		size = sizeof (fhdr) - sizeof (int);
		(void) lseek(fd, (off_t)4, SEEK_SET);
		if (read(fd, &buf[sizeof (int)], size) != size) {

			return (AUDIO_UNIXERROR);
		}
	}

	/* put the buffer into a structure that is aligned properly */
	(void) memcpy(&fhdr, buf, sizeof (fhdr));

	/* Decode the 32-bit integer header fields. */
	AUDIO_AU_FILE2HOST(&fhdr.au_offset, &offset);
	AUDIO_AU_FILE2HOST(&fhdr.au_data_size, &hdrp->data_size);
	AUDIO_AU_FILE2HOST(&fhdr.au_encoding, &hdrp->encoding);
	AUDIO_AU_FILE2HOST(&fhdr.au_sample_rate, &hdrp->sample_rate);
	AUDIO_AU_FILE2HOST(&fhdr.au_channels, &hdrp->channels);

	/* Set the info field size (ie, number of bytes left before data). */
	*isize = offset - sizeof (au_filehdr_t);

	return (AUDIO_SUCCESS);

}	/* decode_au() */

/*
 * Decode a wav file header.
 *
 * .wav files are stored on-disk in little-endian format.
 */
static int
decode_wav(int fd, unsigned char *buf, Audio_hdr *hdrp, int *isize)
{
	wav_filehdr_t	fhdr;
	uint32_t	ID;
	uint32_t	size;
	short		bits_per_sample;
	short		encoding;

	/* we've read in 4 bytes, read in the rest of the wav header */
	size = sizeof (fhdr) - sizeof (int);

	/* read in the rest of the header */
	if (read(fd, &buf[sizeof (int)], size) != size) {
		return (AUDIO_UNIXERROR);
	}

	/* put the buffer into a structure that is aligned properly */
	(void) memcpy(&fhdr, buf, sizeof (fhdr));

	/* make sure we have the correct RIFF type */
	AUDIO_WAV_FILE2HOST_INT(&fhdr.wav_type_ID, &ID);
	if (ID != AUDIO_WAV_TYPE_ID) {
		/* not a wave file */
		return (AUDIO_ERR_BADFILEHDR);
	}

	/* decode the fields */
	AUDIO_WAV_FILE2HOST_INT(&fhdr.wav_fmt_ID, &ID);
	if (ID != AUDIO_WAV_FORMAT_ID) {
		/* mangled format */
		return (AUDIO_ERR_BADFILEHDR);
	}

	AUDIO_WAV_FILE2HOST_SHORT(&fhdr.wav_fmt_encoding, &encoding);
	AUDIO_WAV_FILE2HOST_SHORT(&fhdr.wav_fmt_channels, &hdrp->channels);
	AUDIO_WAV_FILE2HOST_INT(&fhdr.wav_fmt_sample_rate, &hdrp->sample_rate);
	AUDIO_WAV_FILE2HOST_SHORT(&fhdr.wav_fmt_bits_per_sample,
	    &bits_per_sample);

	/* convert .wav encodings to .au encodings */
	switch (encoding) {
	case AUDIO_WAV_FMT_ENCODING_PCM:
		switch (bits_per_sample) {
		case AUDIO_WAV_FMT_BITS_PER_SAMPLE_8_BITS:
			hdrp->encoding = AUDIO_AU_ENCODING_LINEAR_8;
			break;
		case AUDIO_WAV_FMT_BITS_PER_SAMPLE_16_BITS:
			hdrp->encoding = AUDIO_AU_ENCODING_LINEAR_16;
			break;
		default:
			return (AUDIO_ERR_BADFILEHDR);
		}
		break;
	case AUDIO_WAV_FMT_ENCODING_ALAW:
		hdrp->encoding = AUDIO_AU_ENCODING_ALAW;
		break;
	case AUDIO_WAV_FMT_ENCODING_MULAW:
		hdrp->encoding = AUDIO_AU_ENCODING_ULAW;
		break;
	default:
		return (AUDIO_ERR_BADFILEHDR);
	}

	AUDIO_WAV_FILE2HOST_INT(&fhdr.wav_data_size, &hdrp->data_size);

	*isize = sizeof (wav_filehdr_t) - sizeof (au_filehdr_t);

	return (AUDIO_SUCCESS);

}	/* decode_wav() */

/*
 * Try to decode buffer containing an audio file header into an audio header.
 */
int
audio_decode_filehdr(int fd, unsigned char *buf, int *file_type,
	Audio_hdr *hdrp, int *isize)
					/* file descriptor */
					/* buffer address */
					/* audio file type */
					/* output audio header */
					/* output size of info */
{
	int		err;
	struct stat	fd_stat;
	boolean_t	read_info;

	/* Test for .au first */
	hdrp->endian = audio_endian(buf, file_type);

	/*
	 * When cat'ing a file, audioconvert will read the whole header
	 * trying to figure out the file. audioplay however, does not.
	 * Hence we check if this is a pipe and do not attempt to read
	 * any more header info if the file type is already known.
	 * Otherwise we overwrite the header data already in the buffer.
	 */
	if (fstat(fd, &fd_stat) < 0) {
		return (AUDIO_ERR_BADFILEHDR);
	}
	if (S_ISFIFO(fd_stat.st_mode) && (*file_type == FILE_AU)) {
		read_info = B_FALSE;
	} else {
		/*
		 * Not an au file, or file type unknown. Reread the header's
		 * magic number. Fortunately this is always an int.
		 */
		(void) lseek(fd, (off_t)0, SEEK_SET);
		err = read(fd, (char *)buf, sizeof (int));
		read_info = B_TRUE;

		/* test the magic number to determine the endian */
		if ((hdrp->endian = audio_endian(buf, file_type)) ==
		    AUDIO_ENDIAN_UNKNOWN) {

			return (AUDIO_ERR_BADFILEHDR);
		}
	}

	/* decode the different file types, putting the data into hdrp */
	switch (*file_type) {
	case FILE_AU:
		if ((err = decode_au(fd, buf, hdrp, isize, read_info)) !=
		    AUDIO_SUCCESS) {
			return (err);
		}
		break;
	case FILE_WAV:
		if ((err = decode_wav(fd, buf, hdrp, isize)) != AUDIO_SUCCESS) {
			return (err);
		}
		break;
	case FILE_AIFF:
		if ((err = decode_aiff(fd, buf, hdrp, isize)) !=
		    AUDIO_SUCCESS) {
			return (err);
		}
		break;
	default:
		return (AUDIO_ERR_BADFILEHDR);
	}

	/* Convert from file format info to audio format info */
	switch (hdrp->encoding) {
	case AUDIO_AU_ENCODING_ULAW:
		hdrp->encoding = AUDIO_ENCODING_ULAW;
		hdrp->bytes_per_unit = 1;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_ALAW:
		hdrp->encoding = AUDIO_ENCODING_ALAW;
		hdrp->bytes_per_unit = 1;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_LINEAR_8:
		if (*file_type == FILE_WAV) {
			hdrp->encoding = AUDIO_ENCODING_LINEAR8;
		} else {
			hdrp->encoding = AUDIO_ENCODING_LINEAR;
		}
		hdrp->bytes_per_unit = 1;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_LINEAR_16:
		hdrp->encoding = AUDIO_ENCODING_LINEAR;
		hdrp->bytes_per_unit = 2;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_LINEAR_24:
		hdrp->encoding = AUDIO_ENCODING_LINEAR;
		hdrp->bytes_per_unit = 3;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_LINEAR_32:
		hdrp->encoding = AUDIO_ENCODING_LINEAR;
		hdrp->bytes_per_unit = 4;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_FLOAT:
		hdrp->encoding = AUDIO_ENCODING_FLOAT;
		hdrp->bytes_per_unit = 4;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_DOUBLE:
		hdrp->encoding = AUDIO_ENCODING_FLOAT;
		hdrp->bytes_per_unit = 8;
		hdrp->samples_per_unit = 1;
		break;
	case AUDIO_AU_ENCODING_ADPCM_G721:
		hdrp->encoding = AUDIO_ENCODING_G721;
		hdrp->bytes_per_unit = 1;
		hdrp->samples_per_unit = 2;
		break;
	case AUDIO_AU_ENCODING_ADPCM_G723_3:
		hdrp->encoding = AUDIO_ENCODING_G723;
		hdrp->bytes_per_unit = 3;
		hdrp->samples_per_unit = 8;
		break;
	case AUDIO_AU_ENCODING_ADPCM_G723_5:
		hdrp->encoding = AUDIO_ENCODING_G723;
		hdrp->bytes_per_unit = 5;
		hdrp->samples_per_unit = 8;
		break;

	default:
		return (AUDIO_ERR_BADFILEHDR);
	}
	return (AUDIO_SUCCESS);
}

/*
 * Encode a .aiff file header from the supplied Audio_hdr structure and
 * store in the supplied char* buffer. blen is the size of the buffer to
 * store the header in. Unlike .au and .wav we can't cast to a data structure.
 * We have to build it one chunk at a time.
 *
 * NOTE: .aiff doesn't support unsigned 8-bit linear PCM.
 */
static int
audio_encode_aiff(Audio_hdr *hdrp, unsigned char *buf, unsigned int *blen)
					/* audio header */
					/* output buffer */
					/* output buffer size */
{
	aiff_comm_chunk_t	comm_chunk;
	aiff_hdr_chunk_t	hdr_chunk;
	aiff_ssnd_chunk_t	ssnd_chunk;
	uint32_t		tmp_uint;
	uint32_t		tmp_uint2;
	int			buf_size = 0;
	int			encoding;
	uint16_t		tmp_ushort;

	/* the only encoding we support for .aiff is signed linear PCM */
	if (hdrp->encoding != AUDIO_ENCODING_LINEAR) {
		return (AUDIO_ERR_ENCODING);
	}

	/* build the header chunk */
	tmp_uint = AUDIO_AIFF_HDR_CHUNK_ID;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &hdr_chunk.aiff_hdr_ID);
	/* needs to be fixed when closed */
	tmp_uint = AUDIO_AIFF_UNKNOWN_SIZE;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &hdr_chunk.aiff_hdr_size);
	tmp_uint = AUDIO_AIFF_HDR_FORM_AIFF;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &hdr_chunk.aiff_hdr_data_type);
	(void) memcpy(&buf[buf_size], &hdr_chunk, sizeof (hdr_chunk));
	buf_size += sizeof (hdr_chunk);

	/* build the COMM chunk */
	tmp_uint = AUDIO_AIFF_COMM_ID;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &comm_chunk.aiff_comm_ID);
	tmp_uint = AUDIO_AIFF_COMM_SIZE;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &comm_chunk.aiff_comm_size);
	tmp_ushort = hdrp->channels;
	AUDIO_AIFF_HOST2FILE_SHORT(&tmp_ushort, &comm_chunk.aiff_comm_channels);
	/* needs to be fixed when closed */
	tmp_uint = AUDIO_AIFF_UNKNOWN_SIZE;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &tmp_uint2);
	AUDIO_AIFF_COMM_INT2FRAMES(comm_chunk.aiff_comm_frames, tmp_uint2);
	tmp_ushort = hdrp->bytes_per_unit * 8;
	AUDIO_AIFF_HOST2FILE_SHORT(&tmp_ushort,
	    &comm_chunk.aiff_comm_sample_size);
	convert_to_ieee_extended((double)hdrp->sample_rate,
	    comm_chunk.aiff_comm_sample_rate);
	(void) memcpy(&buf[buf_size], &comm_chunk, AUDIO_AIFF_COMM_CHUNK_SIZE);
	buf_size += AUDIO_AIFF_COMM_CHUNK_SIZE;

	/* build the SSND chunk */
	tmp_uint = AUDIO_AIFF_SSND_ID;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &ssnd_chunk.aiff_ssnd_ID);
	/* needs to be fixed when closed */
	tmp_uint = AUDIO_AIFF_UNKNOWN_SIZE;
	AUDIO_AIFF_HOST2FILE_INT(&tmp_uint, &ssnd_chunk.aiff_ssnd_size);
	ssnd_chunk.aiff_ssnd_offset = 0;
	ssnd_chunk.aiff_ssnd_block_size = 0;
	(void) memcpy(&buf[buf_size], &ssnd_chunk, sizeof (ssnd_chunk));
	buf_size += sizeof (ssnd_chunk);

	*blen = buf_size;

	return (AUDIO_SUCCESS);

}	/* audio_encode_aiff() */

/*
 * Encode a .au file header from the supplied Audio_hdr structure and
 * store in the supplied char* buffer. blen is the size of the buffer to
 * store the header in. If 'infop' is not NULL, it is the address of a
 * buffer containing 'info' data. 'ilen' specifies the size of this buffer.
 * The entire file header will be zero-padded to a double-word boundary.
 *
 * NOTE: .au doesn't support unsigned 8-bit linear PCM.
 */
static int
audio_encode_au(Audio_hdr *hdrp, char *infop, unsigned int ilen,
	unsigned char *buf, unsigned int *blen)
					/* audio header */
					/* info buffer pointer */
					/* info buffer size */
					/* output buffer */
					/* output buffer size */
{
	au_filehdr_t	fhdr;
	int		encoding;
	int		hdrsize;
	int		magic;
	int		offset;

	/*
	 * Set the size of the real header (hdr size + info size).
	 * If no supplied info, make sure a minimum size is accounted for.
	 * Also, round the whole thing up to double-word alignment.
	 */
	if ((infop == NULL) || (ilen == 0)) {
		infop = NULL;
		ilen = 4;
	}
	hdrsize = sizeof (fhdr) + ilen;
	offset = ROUND_DBL(hdrsize);

	/* Check the data encoding. */
	switch (hdrp->encoding) {
	case AUDIO_ENCODING_LINEAR8:
		return (AUDIO_ERR_ENCODING);	/* we don't support ulinear */
	case AUDIO_ENCODING_ULAW:
		if (hdrp->samples_per_unit != 1)
			return (AUDIO_ERR_BADHDR);

		switch (hdrp->bytes_per_unit) {
		case 1:
			encoding = AUDIO_AU_ENCODING_ULAW;
			break;
		default:
			return (AUDIO_ERR_BADHDR);
		}
		break;
	case AUDIO_ENCODING_ALAW:
		if (hdrp->samples_per_unit != 1)
			return (AUDIO_ERR_BADHDR);

		switch (hdrp->bytes_per_unit) {
		case 1:
			encoding = AUDIO_AU_ENCODING_ALAW;
			break;
		default:
			return (AUDIO_ERR_BADHDR);
		}
		break;
	case AUDIO_ENCODING_LINEAR:
		if (hdrp->samples_per_unit != 1)
			return (AUDIO_ERR_BADHDR);

		switch (hdrp->bytes_per_unit) {
		case 1:
			encoding = AUDIO_AU_ENCODING_LINEAR_8;
			break;
		case 2:
			encoding = AUDIO_AU_ENCODING_LINEAR_16;
			break;
		case 3:
			encoding = AUDIO_AU_ENCODING_LINEAR_24;
			break;
		case 4:
			encoding = AUDIO_AU_ENCODING_LINEAR_32;
			break;
		default:
			return (AUDIO_ERR_BADHDR);
		}
		break;
	case AUDIO_ENCODING_FLOAT:
		if (hdrp->samples_per_unit != 1)
			return (AUDIO_ERR_BADHDR);

		switch (hdrp->bytes_per_unit) {
		case 4:
			encoding = AUDIO_AU_ENCODING_FLOAT;
			break;
		case 8:
			encoding = AUDIO_AU_ENCODING_DOUBLE;
			break;
		default:
			return (AUDIO_ERR_BADHDR);
		}
		break;
	case AUDIO_ENCODING_G721:
		if (hdrp->bytes_per_unit != 1)
			return (AUDIO_ERR_BADHDR);
		else if (hdrp->samples_per_unit != 2)
			return (AUDIO_ERR_BADHDR);
		else
			encoding = AUDIO_AU_ENCODING_ADPCM_G721;
		break;
	case AUDIO_ENCODING_G723:
		if (hdrp->samples_per_unit != 8)
			return (AUDIO_ERR_BADHDR);
		else if (hdrp->bytes_per_unit == 3)
			encoding = AUDIO_AU_ENCODING_ADPCM_G723_3;
		else if (hdrp->bytes_per_unit == 5)
			encoding = AUDIO_AU_ENCODING_ADPCM_G723_5;
		else
			return (AUDIO_ERR_BADHDR);
		break;
	default:
		return (AUDIO_ERR_BADHDR);
	}

	/* copy the fhdr into the supplied buffer - make sure it'll fit */
	if (*blen < offset) {
		/* XXX - is this apropriate? */
		return (AUDIO_EOF);
	}

	/* reset blen to actual size of hdr data */
	*blen = (unsigned)offset;

	magic = AUDIO_AU_FILE_MAGIC;	/* set the magic number */

	/* Encode the audio header structure. */
	AUDIO_AU_HOST2FILE(&magic, &fhdr.au_magic);
	AUDIO_AU_HOST2FILE(&offset, &fhdr.au_offset);
	AUDIO_AU_HOST2FILE(&hdrp->data_size, &fhdr.au_data_size);
	AUDIO_AU_HOST2FILE(&encoding, &fhdr.au_encoding);
	AUDIO_AU_HOST2FILE(&hdrp->sample_rate, &fhdr.au_sample_rate);
	AUDIO_AU_HOST2FILE(&hdrp->channels, &fhdr.au_channels);

	/* Copy to the buffer */
	(void) memcpy(buf, &fhdr, sizeof (fhdr));

	/* Copy the info data, if present */
	if (infop != NULL) {
		(void) memcpy(&buf[sizeof (fhdr)], infop, (int)ilen);
		buf += ilen;
	}

	if (offset > hdrsize) {
		(void) memset(&buf[hdrsize], '\0', (size_t)(offset - hdrsize));
	}

	/* buf now has the data, just return ... */

	return (AUDIO_SUCCESS);

}	/* audio_encode_au() */

/*
 * Encode a .wav file header from the supplied Audio_hdr structure and
 * store in the supplied char* buffer. blen is the size of the buffer to
 * store the header in. .wav doesn't support an information string like
 * .au does.
 *
 * NOTE: .wav only supports a few encoding methods.
 */
static int
audio_encode_wav(Audio_hdr *hdrp, unsigned char *buf, unsigned int *blen)
					/* audio header */
					/* output buffer */
					/* output buffer size */
{
	wav_filehdr_t	fhdr;
	int		bytes_per_second;
	int		bytes_per_sample;
	int		bits_per_sample;
	int		id;
	int		length;
	int		type;
	short		encoding;

	/* make sure we've got valid encoding and precision settings for .wav */
	switch (hdrp->encoding) {
	case AUDIO_ENCODING_LINEAR8:
		if (hdrp->bytes_per_unit != 1) {
			return (AUDIO_ERR_ENCODING);
		}
		encoding = AUDIO_WAV_FMT_ENCODING_PCM;
		break;
	case AUDIO_ENCODING_ULAW:
		if (hdrp->bytes_per_unit != 1) {
			return (AUDIO_ERR_ENCODING);
		}
		encoding = AUDIO_WAV_FMT_ENCODING_MULAW;
		break;
	case AUDIO_ENCODING_ALAW:
		if (hdrp->bytes_per_unit != 1) {
			return (AUDIO_ERR_ENCODING);
		}
		encoding = AUDIO_WAV_FMT_ENCODING_ALAW;
		break;
	case AUDIO_ENCODING_LINEAR:
		if (hdrp->bytes_per_unit != 2) {
			return (AUDIO_ERR_ENCODING);
		}
		encoding = AUDIO_WAV_FMT_ENCODING_PCM;
		break;
	default:
		return (AUDIO_ERR_ENCODING);
	}

	/* fill in the riff chunk */
	id = AUDIO_WAV_RIFF_ID;
	length = AUDIO_WAV_UNKNOWN_SIZE;
	AUDIO_WAV_HOST2FILE_INT(&id, &fhdr.wav_riff_ID);
	AUDIO_WAV_HOST2FILE_INT(&length, &fhdr.wav_riff_size);

	/* fill in the type chunk */
	type = AUDIO_WAV_TYPE_ID;
	AUDIO_WAV_HOST2FILE_INT(&type, &fhdr.wav_type_ID);


	/* fill in the format chunk */
	id = AUDIO_WAV_FORMAT_ID;
	length = AUDIO_WAV_FORMAT_SIZE;
	bytes_per_second = hdrp->sample_rate * hdrp->channels *
	    hdrp->bytes_per_unit;
	bytes_per_sample = hdrp->channels * hdrp->bytes_per_unit;
	bits_per_sample = hdrp->bytes_per_unit * 8;

	AUDIO_WAV_HOST2FILE_INT(&id, &fhdr.wav_fmt_ID);
	AUDIO_WAV_HOST2FILE_INT(&length, &fhdr.wav_fmt_size);
	AUDIO_WAV_HOST2FILE_SHORT(&encoding, &fhdr.wav_fmt_encoding);
	AUDIO_WAV_HOST2FILE_SHORT(&hdrp->channels, &fhdr.wav_fmt_channels);
	AUDIO_WAV_HOST2FILE_INT(&hdrp->sample_rate, &fhdr.wav_fmt_sample_rate);
	AUDIO_WAV_HOST2FILE_INT(&bytes_per_second,
	    &fhdr.wav_fmt_bytes_per_second);
	AUDIO_WAV_HOST2FILE_SHORT(&bytes_per_sample,
	    &fhdr.wav_fmt_bytes_per_sample);
	AUDIO_WAV_HOST2FILE_SHORT(&bits_per_sample,
	    &fhdr.wav_fmt_bits_per_sample);

	/* fill in the data chunk */
	id = AUDIO_WAV_DATA_ID_LC;
	length = AUDIO_WAV_UNKNOWN_SIZE;
	AUDIO_WAV_HOST2FILE_INT(&id, &fhdr.wav_data_ID);
	AUDIO_WAV_HOST2FILE_INT(&length, &fhdr.wav_data_size);

	*blen = sizeof (fhdr);

	/* copy to the buffer */
	(void) memcpy(buf, &fhdr, sizeof (fhdr));

	return (AUDIO_SUCCESS);

}	/* audio_encode_wav() */

/*
 * Utility routine used to convert 10 byte IEEE extended float into
 * a regular double. Raw data arrives in an unsigned char array. Because
 * this is for sample rate, which is always positive, we don't worry
 * about the sign.
 */
static double
convert_from_ieee_extended(unsigned char *data)
{
	double		value = 0.0;
	unsigned long	high_mantissa;
	unsigned long	low_mantissa;
	int		exponent;

	/* first 2 bytes are the exponent */
	exponent = ((data[0] & 0x7f) << 8) | data[1];

	high_mantissa = ((unsigned long)data[2] << 24) |
	    ((unsigned long)data[3] << 16) |
	    ((unsigned long)data[4] << 8) |
	    (unsigned long)data[5];
	low_mantissa = ((unsigned long)data[6] << 24) |
	    ((unsigned long)data[7] << 16) |
	    ((unsigned long)data[8] << 8) |
	    (unsigned long)data[9];

	/* convert exponent and mantissas into a real double */
	if (exponent == 0 && high_mantissa == 0 && low_mantissa == 0) {
		/* everything is 0, so we're done */
		value = 0.0;
	} else {
		if (exponent == 0x7fff) {	/* infinity */
			value = MAXFLOAT;
		} else {
			/* convert exponent from being unsigned to signed */
			exponent -= 0x3fff;

			exponent -= 31;
			value = ldexp((double)high_mantissa, exponent);

			exponent -= 32;
			value += ldexp((double)low_mantissa, exponent);
		}
	}

	return (value);

}

/*
 * Utility routine to convert a double into 10 byte IEEE extended floating
 * point. The new number is placed into the unsigned char array. This is a
 * very brain dead convesion routine. It only supports integers, but then
 * that should be all we need for sample rate.
 */
static void
convert_to_ieee_extended(double value, unsigned char *data)
{
	double		fmantissa;
	int		exponent;
	int		mantissa;

	exponent = 16398;
	fmantissa = value;

	while (fmantissa < 44000) {
		fmantissa *= 2;
		exponent--;
	}

	mantissa = (int)fmantissa << 16;

	data[0] = exponent >> 8;
	data[1] = exponent;
	data[2] = mantissa >> 24;
	data[3] = mantissa >> 16;
	data[4] = mantissa >> 8;
	data[5] = mantissa;
	data[6] = 0;
	data[7] = 0;
	data[8] = 0;
	data[9] = 0;

}