filehdr.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 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;
}