Logo Search packages:      
Sourcecode: ia32-libs-sdl version File versions  Download package

wavestream.c

/*
    SDL_mixer:  An audio mixer library based on the SDL library
    Copyright (C) 1997-2004 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@libsdl.org
*/

/* $Id: wavestream.c,v 1.19 2004/01/04 17:37:04 slouken Exp $ */

/* This file supports streaming WAV files, without volume adjustment */

#include <stdlib.h>
#include <string.h>

#include "SDL_audio.h"
#include "SDL_mutex.h"
#include "SDL_rwops.h"
#include "SDL_endian.h"

#include "SDL_mixer.h"
#include "wavestream.h"

/*
    Taken with permission from SDL_wave.h, part of the SDL library,
    available at: http://www.libsdl.org/
    and placed under the same license as this mixer library.
*/

/* WAVE files are little-endian */

/*******************************************/
/* Define values for Microsoft WAVE format */
/*******************************************/
#define RIFF            0x46464952        /* "RIFF" */
#define WAVE            0x45564157        /* "WAVE" */
#define FACT            0x74636166        /* "fact" */
#define LIST            0x5453494c        /* "LIST" */
#define FMT       0x20746D66        /* "fmt " */
#define DATA            0x61746164        /* "data" */
#define PCM_CODE  1
#define ADPCM_CODE      2
#define WAVE_MONO 1
#define WAVE_STEREO     2

/* Normally, these three chunks come consecutively in a WAVE file */
typedef struct WaveFMT {
/* Not saved in the chunk we read:
      Uint32      FMTchunk;
      Uint32      fmtlen;
*/
      Uint16      encoding;   
      Uint16      channels;         /* 1 = mono, 2 = stereo */
      Uint32      frequency;        /* One of 11025, 22050, or 44100 Hz */
      Uint32      byterate;         /* Average bytes per second */
      Uint16      blockalign;       /* Bytes per sample block */
      Uint16      bitspersample;          /* One of 8, 12, 16, or 4 for ADPCM */
} WaveFMT;

/* The general chunk found in the WAVE file */
typedef struct Chunk {
      Uint32 magic;
      Uint32 length;
      Uint8 *data;                  /* Data includes magic and length */
} Chunk;

/*********************************************/
/* Define values for AIFF (IFF audio) format */
/*********************************************/
#define FORM            0x4d524f46        /* "FORM" */
#define AIFF            0x46464941        /* "AIFF" */
#define SSND            0x444e5353        /* "SSND" */
#define COMM            0x4d4d4f43        /* "COMM" */


/* Currently we only support a single stream at a time */
static WAVStream *music = NULL;

/* This is the format of the audio mixer data */
static SDL_AudioSpec mixer;
static int wavestream_volume = MIX_MAX_VOLUME;

/* Function to load the WAV/AIFF stream */
static FILE *LoadWAVStream (const char *file, SDL_AudioSpec *spec,
                              long *start, long *stop);
static FILE *LoadAIFFStream (const char *file, SDL_AudioSpec *spec,
                              long *start, long *stop);

/* Initialize the WAVStream player, with the given mixer settings
   This function returns 0, or -1 if there was an error.
 */
int WAVStream_Init(SDL_AudioSpec *mixerfmt)
{
      mixer = *mixerfmt;
      return(0);
}

void WAVStream_SetVolume(int volume)
{
      wavestream_volume = volume;
}

/* Load a WAV stream from the given file */
WAVStream *WAVStream_LoadSong(const char *file, const char *magic)
{
      WAVStream *wave;
      SDL_AudioSpec wavespec;

      if ( ! mixer.format ) {
            Mix_SetError("WAV music output not started");
            return(NULL);
      }
      wave = (WAVStream *)malloc(sizeof *wave);
      if ( wave ) {
            memset(wave, 0, (sizeof *wave));
            if ( strcmp(magic, "RIFF") == 0 ) {
                  wave->wavefp = LoadWAVStream(file, &wavespec,
                              &wave->start, &wave->stop);
            } else
            if ( strcmp(magic, "FORM") == 0 ) {
                  wave->wavefp = LoadAIFFStream(file, &wavespec,
                              &wave->start, &wave->stop);
            } else {
                  Mix_SetError("Unknown WAVE format");
            }
            if ( wave->wavefp == NULL ) {
                  free(wave);
                  return(NULL);
            }
            SDL_BuildAudioCVT(&wave->cvt,
                  wavespec.format, wavespec.channels, wavespec.freq,
                  mixer.format, mixer.channels, mixer.freq);
      }
      return(wave);
}

/* Start playback of a given WAV stream */
void WAVStream_Start(WAVStream *wave)
{
      clearerr(wave->wavefp);
      fseek(wave->wavefp, wave->start, SEEK_SET);
      music = wave;
}

/* Play some of a stream previously started with WAVStream_Start() */
void WAVStream_PlaySome(Uint8 *stream, int len)
{
      long pos;

      if ( music && ((pos=ftell(music->wavefp)) < music->stop) ) {
            if ( music->cvt.needed ) {
                  int original_len;

                  original_len=(int)((double)len/music->cvt.len_ratio);
                  if ( music->cvt.len != original_len ) {
                        int worksize;
                        if ( music->cvt.buf != NULL ) {
                              free(music->cvt.buf);
                        }
                        worksize = original_len*music->cvt.len_mult;
                        music->cvt.buf=(Uint8 *)malloc(worksize);
                        if ( music->cvt.buf == NULL ) {
                              return;
                        }
                        music->cvt.len = original_len;
                  }
                  if ( (music->stop - pos) < original_len ) {
                        original_len = (music->stop - pos);
                  }
                  original_len = fread(music->cvt.buf,1,original_len,music->wavefp);
                  /* At least at the time of writing, SDL_ConvertAudio()
                     does byte-order swapping starting at the end of the
                     buffer. Thus, if we are reading 16-bit samples, we
                     had better make damn sure that we get an even
                     number of bytes, or we'll get garbage.
                   */
                  if ( (music->cvt.src_format & 0x0010) && (original_len & 1) ) {
                        original_len--;
                  }
                  music->cvt.len = original_len;
                  SDL_ConvertAudio(&music->cvt);
                  SDL_MixAudio(stream, music->cvt.buf, music->cvt.len_cvt, wavestream_volume);
            } else {
                  if ( (music->stop - pos) < len ) {
                        len = (music->stop - pos);
                  }
                  fread(stream, len, 1, music->wavefp);
            }
      }
}

/* Stop playback of a stream previously started with WAVStream_Start() */
void WAVStream_Stop(void)
{
      music = NULL;
}

/* Close the given WAV stream */
void WAVStream_FreeSong(WAVStream *wave)
{
      if ( wave ) {
            /* Clean up associated data */
            if ( wave->wavefp ) {
                  fclose(wave->wavefp);
            }
            if ( wave->cvt.buf ) {
                  free(wave->cvt.buf);
            }
            free(wave);
      }
}

/* Return non-zero if a stream is currently playing */
int WAVStream_Active(void)
{
      int active;

      active = 0;
      if ( music && (ftell(music->wavefp) < music->stop) ) {
            active = 1;
      }
      return(active);
}

static int ReadChunk(SDL_RWops *src, Chunk *chunk, int read_data)
{
      chunk->magic      = SDL_ReadLE32(src);
      chunk->length     = SDL_ReadLE32(src);
      if ( read_data ) {
            chunk->data = (Uint8 *)malloc(chunk->length);
            if ( chunk->data == NULL ) {
                  Mix_SetError("Out of memory");
                  return(-1);
            }
            if ( SDL_RWread(src, chunk->data, chunk->length, 1) != 1 ) {
                  Mix_SetError("Couldn't read chunk");
                  free(chunk->data);
                  return(-1);
            }
      } else {
            SDL_RWseek(src, chunk->length, SEEK_CUR);
      }
      return(chunk->length);
}

static FILE *LoadWAVStream (const char *file, SDL_AudioSpec *spec,
                              long *start, long *stop)
{
      int was_error;
      FILE *wavefp;
      SDL_RWops *src;
      Chunk chunk;
      int lenread;

      /* WAV magic header */
      Uint32 RIFFchunk;
      Uint32 wavelen;
      Uint32 WAVEmagic;

      /* FMT chunk */
      WaveFMT *format = NULL;

      /* Make sure we are passed a valid data source */
      was_error = 0;
      wavefp = fopen(file, "rb");
      src = NULL;
      if ( wavefp ) {
            src = SDL_RWFromFP(wavefp, 0);
      }
      if ( src == NULL ) {
            was_error = 1;
            goto done;
      }

      /* Check the magic header */
      RIFFchunk   = SDL_ReadLE32(src);
      wavelen           = SDL_ReadLE32(src);
      WAVEmagic   = SDL_ReadLE32(src);
      if ( (RIFFchunk != RIFF) || (WAVEmagic != WAVE) ) {
            Mix_SetError("Unrecognized file type (not WAVE)");
            was_error = 1;
            goto done;
      }

      /* Read the audio data format chunk */
      chunk.data = NULL;
      do {
            /* FIXME! Add this logic to SDL_LoadWAV_RW() */
            if ( chunk.data ) {
                  free(chunk.data);
            }
            lenread = ReadChunk(src, &chunk, 1);
            if ( lenread < 0 ) {
                  was_error = 1;
                  goto done;
            }
      } while ( (chunk.magic == FACT) || (chunk.magic == LIST) );

      /* Decode the audio data format */
      format = (WaveFMT *)chunk.data;
      if ( chunk.magic != FMT ) {
            free(chunk.data);
            Mix_SetError("Complex WAVE files not supported");
            was_error = 1;
            goto done;
      }
      switch (SDL_SwapLE16(format->encoding)) {
            case PCM_CODE:
                  /* We can understand this */
                  break;
            default:
                  Mix_SetError("Unknown WAVE data format");
                  was_error = 1;
                  goto done;
      }
      memset(spec, 0, (sizeof *spec));
      spec->freq = SDL_SwapLE32(format->frequency);
      switch (SDL_SwapLE16(format->bitspersample)) {
            case 8:
                  spec->format = AUDIO_U8;
                  break;
            case 16:
                  spec->format = AUDIO_S16;
                  break;
            default:
                  Mix_SetError("Unknown PCM data format");
                  was_error = 1;
                  goto done;
      }
      spec->channels = (Uint8) SDL_SwapLE16(format->channels);
      spec->samples = 4096;         /* Good default buffer size */

      /* Set the file offset to the DATA chunk data */
      chunk.data = NULL;
      do {
            *start = SDL_RWtell(src) + 2*sizeof(Uint32);
            lenread = ReadChunk(src, &chunk, 0);
            if ( lenread < 0 ) {
                  was_error = 1;
                  goto done;
            }
      } while ( chunk.magic != DATA );
      *stop = SDL_RWtell(src);

done:
      if ( format != NULL ) {
            free(format);
      }
      if ( src ) {
            SDL_RWclose(src);
      }
      if ( was_error ) {
            if ( wavefp ) {
                  fclose(wavefp);
                  wavefp = NULL;
            }
      }
      return(wavefp);
}

/* I couldn't get SANE_to_double() to work, so I stole this from libsndfile.
 * I don't pretend to fully understand it.
 */

static Uint32 SANE_to_Uint32 (Uint8 *sanebuf)
{
      /* Negative number? */
      if (sanebuf[0] & 0x80)
            return 0;

      /* Less than 1? */
      if (sanebuf[0] <= 0x3F)
            return 1;

      /* Way too big? */
      if (sanebuf[0] > 0x40)
            return 0x4000000;

      /* Still too big? */
      if (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C)
            return 800000000;

      return ((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7)
            | (sanebuf[5] >> 1)) >> (29 - sanebuf[1]);
}

static FILE *LoadAIFFStream (const char *file, SDL_AudioSpec *spec,
                              long *start, long *stop)
{
      int was_error;
      int found_SSND;
      int found_COMM;
      FILE *wavefp;
      SDL_RWops *src;

      Uint32 chunk_type;
      Uint32 chunk_length;
      long next_chunk;

      /* AIFF magic header */
      Uint32 FORMchunk;
      Uint32 AIFFmagic;
      /* SSND chunk        */
      Uint32 offset;
      Uint32 blocksize;
      /* COMM format chunk */
      Uint16 channels = 0;
      Uint32 numsamples = 0;
      Uint16 samplesize = 0;
      Uint8 sane_freq[10];
      Uint32 frequency = 0;


      /* Make sure we are passed a valid data source */
      was_error = 0;
      wavefp = fopen(file, "rb");
      src = NULL;
      if ( wavefp ) {
            src = SDL_RWFromFP(wavefp, 0);
      }
      if ( src == NULL ) {
            was_error = 1;
            goto done;
      }

      /* Check the magic header */
      FORMchunk   = SDL_ReadLE32(src);
      chunk_length      = SDL_ReadBE32(src);
      AIFFmagic   = SDL_ReadLE32(src);
      if ( (FORMchunk != FORM) || (AIFFmagic != AIFF) ) {
            Mix_SetError("Unrecognized file type (not AIFF)");
            was_error = 1;
            goto done;
      }

      /* From what I understand of the specification, chunks may appear in
         * any order, and we should just ignore unknown ones.
       *
       * TODO: Better sanity-checking. E.g. what happens if the AIFF file
       *       contains compressed sound data?
         */

      found_SSND = 0;
      found_COMM = 0;

      do {
          chunk_type          = SDL_ReadLE32(src);
          chunk_length  = SDL_ReadBE32(src);
          next_chunk          = SDL_RWtell(src) + chunk_length;

          /* Paranoia to avoid infinite loops */
          if (chunk_length == 0)
            break;

            switch (chunk_type) {
            case SSND:
                found_SSND          = 1;
                offset        = SDL_ReadBE32(src);
                blocksize           = SDL_ReadBE32(src);
                *start        = SDL_RWtell(src) + offset;
                break;

            case COMM:
                found_COMM          = 1;

                /* Read the audio data format chunk */
                channels            = SDL_ReadBE16(src);
                numsamples          = SDL_ReadBE32(src);
                samplesize          = SDL_ReadBE16(src);
                SDL_RWread(src, sane_freq, sizeof(sane_freq), 1);
                frequency           = SANE_to_Uint32(sane_freq);
                break;

            default:
                break;
          }
      } while ((!found_SSND || !found_COMM)
             && SDL_RWseek(src, next_chunk, SEEK_SET) != -1);

      if (!found_SSND) {
          Mix_SetError("Bad AIFF file (no SSND chunk)");
          was_error = 1;
          goto done;
      }
                
      if (!found_COMM) {
          Mix_SetError("Bad AIFF file (no COMM chunk)");
          was_error = 1;
          goto done;
      }

      *stop = *start + channels * numsamples * (samplesize / 8);

      /* Decode the audio data format */
      memset(spec, 0, (sizeof *spec));
      spec->freq = frequency;
      switch (samplesize) {
            case 8:
                  spec->format = AUDIO_S8;
                  break;
            case 16:
                  spec->format = AUDIO_S16MSB;
                  break;
            default:
                  Mix_SetError("Unknown samplesize in data format");
                  was_error = 1;
                  goto done;
      }
      spec->channels = (Uint8) channels;
      spec->samples = 4096;         /* Good default buffer size */

done:
      if ( src ) {
            SDL_RWclose(src);
      }
      if ( was_error ) {
            if ( wavefp ) {
                  fclose(wavefp);
                  wavefp = NULL;
            }
      }
      return(wavefp);
}

Generated by  Doxygen 1.6.0   Back to index