diff options
Diffstat (limited to 'src/libs/sound/decoders/aiffaud.c')
-rw-r--r-- | src/libs/sound/decoders/aiffaud.c | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/src/libs/sound/decoders/aiffaud.c b/src/libs/sound/decoders/aiffaud.c new file mode 100644 index 0000000..102a78e --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.c @@ -0,0 +1,650 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Portions (C) Serge van den Boom (svdb at stack.nl) */ +/* Portions (C) Alex Volkov (codepro at usa.net) */ + +/* AIFF decoder (.aif) + * + * Doesn't work on *all* aiff files in general, only 8/16 PCM and + * 16-bit AIFF-C SDX2-compressed. + */ + +#include <stdio.h> +#include <stdlib.h> + // for abs() +#include <errno.h> +#ifndef _WIN32_WCE +# include <memory.h> +#endif +#include <string.h> +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "aiffaud.h" + +typedef uint32 aiff_ID; + +#define aiff_MAKE_ID(x1, x2, x3, x4) \ + (((x1) << 24) | ((x2) << 16) | ((x3) << 8) | (x4)) + +#define aiff_FormID aiff_MAKE_ID('F', 'O', 'R', 'M') +#define aiff_FormVersionID aiff_MAKE_ID('F', 'V', 'E', 'R') +#define aiff_CommonID aiff_MAKE_ID('C', 'O', 'M', 'M') +#define aiff_SoundDataID aiff_MAKE_ID('S', 'S', 'N', 'D') + +#define aiff_FormTypeAIFF aiff_MAKE_ID('A', 'I', 'F', 'F') +#define aiff_FormTypeAIFC aiff_MAKE_ID('A', 'I', 'F', 'C') + +#define aiff_CompressionTypeSDX2 aiff_MAKE_ID('S', 'D', 'X', '2') + + +typedef struct +{ + aiff_ID id; + uint32 size; +} aiff_ChunkHeader; + +#define AIFF_CHUNK_HDR_SIZE (4+4) + +typedef struct +{ + aiff_ChunkHeader chunk; + aiff_ID type; +} aiff_FileHeader; + +typedef struct +{ + uint32 version; /* format version, in Mac format */ +} aiff_FormatVersionChunk; + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + /* this is actually stored as IEEE-754 80bit in files */ +} aiff_CommonChunk; + +#define AIFF_COMM_SIZE (2+4+2+10) + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + aiff_ID extTypeID; /* compression type ID */ + char extName[32]; /* compression type name */ +} aiff_ExtCommonChunk; + +#define AIFF_EXT_COMM_SIZE (AIFF_COMM_SIZE+4) + +typedef struct +{ + uint32 offset; /* offset to sound data */ + uint32 blockSize; /* size of alignment blocks */ +} aiff_SoundDataChunk; + +#define AIFF_SSND_SIZE (4+4) + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* aifa_GetName (void); +static bool aifa_InitModule (int flags, const TFB_DecoderFormats*); +static void aifa_TermModule (void); +static uint32 aifa_GetStructSize (void); +static int aifa_GetError (THIS_PTR); +static bool aifa_Init (THIS_PTR); +static void aifa_Term (THIS_PTR); +static bool aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void aifa_Close (THIS_PTR); +static int aifa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 aifa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 aifa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs aifa_DecoderVtbl = +{ + aifa_GetName, + aifa_InitModule, + aifa_TermModule, + aifa_GetStructSize, + aifa_GetError, + aifa_Init, + aifa_Term, + aifa_Open, + aifa_Close, + aifa_Decode, + aifa_Seek, + aifa_GetFrame, +}; + + +typedef enum +{ + aifc_None, + aifc_Sdx2, +} aiff_CompressionType; + +#define MAX_CHANNELS 4 + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + aiff_ExtCommonChunk fmtHdr; + aiff_CompressionType comp_type; + unsigned bits_per_sample; + unsigned block_align; + unsigned file_block; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + sint32 prev_val[MAX_CHANNELS]; + +} TFB_AiffSoundDecoder; + +static const TFB_DecoderFormats* aifa_formats = NULL; + +static int aifa_DecodePCM (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); +static int aifa_DecodeSDX2 (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); + + +static const char* +aifa_GetName (void) +{ + return "AIFF"; +} + +static bool +aifa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + aifa_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +aifa_TermModule (void) +{ + // no specific module term +} + +static uint32 +aifa_GetStructSize (void) +{ + return sizeof (TFB_AiffSoundDecoder); +} + +static int +aifa_GetError (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + int ret = aifa->last_error; + aifa->last_error = 0; + return ret; +} + +static bool +aifa_Init (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + This->need_swap = !aifa_formats->want_big_endian; + return true; +} + +static void +aifa_Term (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aifa_Close (This); // ensure cleanup +} + +static bool +read_be_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE16 (*v); + return true; +} + +static bool +read_be_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE32 (*v); + return true; +} + +// Read 80-bit IEEE 754 floating point number. +// We are only interested in values that we can work with, +// so using an sint32 here is fine. +static bool +read_be_f80 (uio_Stream *fp, sint32 *v) +{ + int sign, exp; + int shift; + uint16 se; + uint32 mant, mant_low; + if (!read_be_16 (fp, &se) || + !read_be_32 (fp, &mant) || !read_be_32 (fp, &mant_low)) + return false; + + sign = (se >> 15) & 1; // sign is the highest bit + exp = (se & ((1 << 15) - 1)); // exponent is next highest 15 bits +#if 0 // XXX: 80bit IEEE 754 used in AIFF uses explicit mantissa MS bit + // mantissa has an implied leading bit which is typically 1 + mant >>= 1; + if (exp != 0) + mant |= 0x80000000; +#endif + mant >>= 1; // we also need space for sign + exp -= (1 << 14) - 1; // exponent is biased by (2^(e-1) - 1) + shift = exp - 31 + 1; // mantissa is already 31 bits before decimal pt. + if (shift > 0) + mant = 0x7fffffff; // already too big + else if (shift < 0) + mant >>= -shift; + + *v = sign ? -(sint32)mant : (sint32)mant; + + return true; +} + +static bool +aifa_readFileHeader (TFB_AiffSoundDecoder* aifa, aiff_FileHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->chunk.id) || + !read_be_32 (aifa->fp, &hdr->chunk.size) || + !read_be_32 (aifa->fp, &hdr->type)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_readChunkHeader (TFB_AiffSoundDecoder* aifa, aiff_ChunkHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->id) || + !read_be_32 (aifa->fp, &hdr->size)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static int +aifa_readCommonChunk (TFB_AiffSoundDecoder* aifa, uint32 size, + aiff_ExtCommonChunk* fmt) +{ + int bytes; + + memset(fmt, 0, sizeof(*fmt)); + if (size < AIFF_COMM_SIZE) + { + aifa->last_error = aifae_BadFile; + return 0; + } + + if (!read_be_16 (aifa->fp, &fmt->channels) || + !read_be_32 (aifa->fp, &fmt->sampleFrames) || + !read_be_16 (aifa->fp, &fmt->sampleSize) || + !read_be_f80 (aifa->fp, &fmt->sampleRate)) + { + aifa->last_error = errno; + return 0; + } + bytes = AIFF_COMM_SIZE; + + if (size >= AIFF_EXT_COMM_SIZE) + { + if (!read_be_32 (aifa->fp, &fmt->extTypeID)) + { + aifa->last_error = errno; + return 0; + } + bytes += sizeof(fmt->extTypeID); + } + + return bytes; +} + +static bool +aifa_readSoundDataChunk (TFB_AiffSoundDecoder* aifa, + aiff_SoundDataChunk* data) +{ + if (!read_be_32 (aifa->fp, &data->offset) || + !read_be_32 (aifa->fp, &data->blockSize)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aiff_FileHeader fileHdr; + aiff_ChunkHeader chunkHdr; + sint32 remSize; + + aifa->fp = uio_fopen (dir, filename, "rb"); + if (!aifa->fp) + { + aifa->last_error = errno; + return false; + } + + aifa->data_size = 0; + aifa->max_pcm = 0; + aifa->data_ofs = 0; + memset(&aifa->fmtHdr, 0, sizeof(aifa->fmtHdr)); + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + // read wave header + if (!aifa_readFileHeader (aifa, &fileHdr)) + { + aifa->last_error = errno; + aifa_Close (This); + return false; + } + if (fileHdr.chunk.id != aiff_FormID) + { + log_add (log_Warning, "aifa_Open(): not an aiff file, ID 0x%08x", + fileHdr.chunk.id); + aifa_Close (This); + return false; + } + if (fileHdr.type != aiff_FormTypeAIFF && fileHdr.type != aiff_FormTypeAIFC) + { + log_add (log_Warning, "aifa_Open(): unsupported aiff file" + ", Type 0x%08x", fileHdr.type); + aifa_Close (This); + return false; + } + + for (remSize = fileHdr.chunk.size - sizeof(aiff_ID); remSize > 0; + remSize -= ((chunkHdr.size + 1) & ~1) + AIFF_CHUNK_HDR_SIZE) + { + if (!aifa_readChunkHeader (aifa, &chunkHdr)) + { + aifa_Close (This); + return false; + } + + if (chunkHdr.id == aiff_CommonID) + { + int read = aifa_readCommonChunk (aifa, chunkHdr.size, &aifa->fmtHdr); + if (!read) + { + aifa_Close (This); + return false; + } + uio_fseek (aifa->fp, chunkHdr.size - read, SEEK_CUR); + } + else if (chunkHdr.id == aiff_SoundDataID) + { + aiff_SoundDataChunk data; + if (!aifa_readSoundDataChunk (aifa, &data)) + { + aifa_Close (This); + return false; + } + aifa->data_ofs = uio_ftell (aifa->fp) + data.offset; + uio_fseek (aifa->fp, chunkHdr.size - AIFF_SSND_SIZE, SEEK_CUR); + } + else + { // skip uninteresting chunk + uio_fseek (aifa->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + uio_fseek (aifa->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (aifa->fmtHdr.sampleFrames == 0) + { + log_add (log_Warning, "aifa_Open(): aiff file has no sound data"); + aifa_Close (This); + return false; + } + + // make bits-per-sample a multiple of 8 + aifa->bits_per_sample = (aifa->fmtHdr.sampleSize + 7) & ~7; + if (aifa->bits_per_sample == 0 || aifa->bits_per_sample > 16) + { // XXX: for now we do not support 24 and 32 bps + log_add (log_Warning, "aifa_Open(): unsupported sample size %u", + aifa->bits_per_sample); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.channels != 1 && aifa->fmtHdr.channels != 2) + { + log_add (log_Warning, "aifa_Open(): unsupported number of channels %u", + (unsigned)aifa->fmtHdr.channels); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.sampleRate < 300 || aifa->fmtHdr.sampleRate > 128000) + { + log_add (log_Warning, "aifa_Open(): unsupported sampling rate %ld", + (long)aifa->fmtHdr.sampleRate); + aifa_Close (This); + return false; + } + + aifa->block_align = aifa->bits_per_sample / 8 * aifa->fmtHdr.channels; + aifa->file_block = aifa->block_align; + if (!aifa->data_ofs) + { + log_add (log_Warning, "aifa_Open(): bad aiff file," + " no SSND chunk found"); + aifa_Close (This); + return false; + } + + if (fileHdr.type == aiff_FormTypeAIFF) + { + if (aifa->fmtHdr.extTypeID != 0) + { + log_add (log_Warning, "aifa_Open(): unsupported extension 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_None; + } + else if (fileHdr.type == aiff_FormTypeAIFC) + { + if (aifa->fmtHdr.extTypeID != aiff_CompressionTypeSDX2) + { + log_add (log_Warning, "aifa_Open(): unsupported compression 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_Sdx2; + aifa->file_block /= 2; + assert(aifa->fmtHdr.channels <= MAX_CHANNELS); + // after decompression, we will get samples in machine byte order + This->need_swap = (aifa_formats->big_endian + != aifa_formats->want_big_endian); + } + + aifa->data_size = aifa->fmtHdr.sampleFrames * aifa->file_block; + + if (aifa->comp_type == aifc_Sdx2 && aifa->bits_per_sample != 16) + { + log_add (log_Warning, "aifa_Open(): unsupported sample size %u for SDX2", + (unsigned)aifa->fmtHdr.sampleSize); + aifa_Close (This); + return false; + } + + This->format = (aifa->fmtHdr.channels == 1 ? + (aifa->bits_per_sample == 8 ? + aifa_formats->mono8 : aifa_formats->mono16) + : + (aifa->bits_per_sample == 8 ? + aifa_formats->stereo8 : aifa_formats->stereo16) + ); + This->frequency = aifa->fmtHdr.sampleRate; + + uio_fseek (aifa->fp, aifa->data_ofs, SEEK_SET); + aifa->max_pcm = aifa->fmtHdr.sampleFrames; + aifa->cur_pcm = 0; + This->length = (float) aifa->max_pcm / aifa->fmtHdr.sampleRate; + aifa->last_error = 0; + + return true; +} + +static void +aifa_Close (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (aifa->fp) + { + uio_fclose (aifa->fp); + aifa->fp = NULL; + } +} + +static int +aifa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + switch (aifa->comp_type) + { + case aifc_None: + return aifa_DecodePCM (aifa, buf, bufsize); + case aifc_Sdx2: + return aifa_DecodeSDX2 (aifa, buf, bufsize); + default: + assert(false && "Unknown comp_type"); + return 0; + } +} + +static int +aifa_DecodePCM (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + uint32 size; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + dec_pcm = uio_fread (buf, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + size = dec_pcm * aifa->block_align; + + if (aifa->bits_per_sample == 8) + { // AIFF files store 8-bit data as signed + // and we need it unsigned + uint8* ptr = (uint8*)buf; + uint32 left; + for (left = size; left > 0; --left, ++ptr) + *ptr += 128; + } + + return size; +} + +static int +aifa_DecodeSDX2 (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + sint8 *src; + sint16 *dst = buf; + uint32 left; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + src = (sint8*)buf + bufsize - (dec_pcm * aifa->file_block); + dec_pcm = uio_fread (src, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + + for (left = dec_pcm; left > 0; --left) + { + int i; + sint32 *prev = aifa->prev_val; + for (i = aifa->fmtHdr.channels; i > 0; --i, ++prev, ++src, ++dst) + { + sint32 v = (*src * abs(*src)) << 1; + if (*src & 1) + v += *prev; + // saturate the value + if (v > 32767) + v = 32767; + else if (v < -32768) + v = -32768; + *prev = v; + *dst = v; + } + } + + return dec_pcm * aifa->block_align; +} + +static uint32 +aifa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (pcm_pos > aifa->max_pcm) + pcm_pos = aifa->max_pcm; + aifa->cur_pcm = pcm_pos; + uio_fseek (aifa->fp, + aifa->data_ofs + pcm_pos * aifa->file_block, + SEEK_SET); + + // reset previous values for SDX2 on seek ops + // the delta will recover faster with reset + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + return pcm_pos; +} + +static uint32 +aifa_GetFrame (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} |