wavestream.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 26 Feb 2008 11:46:22 +0000
changeset 381 2064088ea781
parent 308 02f1dc41d4e7
child 386 695494546b3c
permissions -rw-r--r--
Date: Sat, 16 Feb 2008 20:59:24 +0100
From: Tilman Sauerbeck
Subject: [PATCH] Allow SDL_mixer to open wave streams via SDL_rwops

the current version of SDL_mixer can only load WAVE streams directly
from files. In my application I have the need to load them from an
SDL_RWops structure. The attached patch adds that feature to SDL_mixer.
     1 /*
     2     SDL_mixer:  An audio mixer library based on the SDL library
     3     Copyright (C) 1997-2004 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 
    23 /* $Id$ */
    24 
    25 /* This file supports streaming WAV files, without volume adjustment */
    26 
    27 #include <stdlib.h>
    28 #include <string.h>
    29 
    30 #include "SDL_audio.h"
    31 #include "SDL_mutex.h"
    32 #include "SDL_rwops.h"
    33 #include "SDL_endian.h"
    34 
    35 #include "SDL_mixer.h"
    36 #include "wavestream.h"
    37 
    38 /*
    39     Taken with permission from SDL_wave.h, part of the SDL library,
    40     available at: http://www.libsdl.org/
    41     and placed under the same license as this mixer library.
    42 */
    43 
    44 /* WAVE files are little-endian */
    45 
    46 /*******************************************/
    47 /* Define values for Microsoft WAVE format */
    48 /*******************************************/
    49 #define RIFF		0x46464952		/* "RIFF" */
    50 #define WAVE		0x45564157		/* "WAVE" */
    51 #define FACT		0x74636166		/* "fact" */
    52 #define LIST		0x5453494c		/* "LIST" */
    53 #define FMT		0x20746D66		/* "fmt " */
    54 #define DATA		0x61746164		/* "data" */
    55 #define PCM_CODE	1
    56 #define ADPCM_CODE	2
    57 #define WAVE_MONO	1
    58 #define WAVE_STEREO	2
    59 
    60 /* Normally, these three chunks come consecutively in a WAVE file */
    61 typedef struct WaveFMT {
    62 /* Not saved in the chunk we read:
    63 	Uint32	FMTchunk;
    64 	Uint32	fmtlen;
    65 */
    66 	Uint16	encoding;	
    67 	Uint16	channels;		/* 1 = mono, 2 = stereo */
    68 	Uint32	frequency;		/* One of 11025, 22050, or 44100 Hz */
    69 	Uint32	byterate;		/* Average bytes per second */
    70 	Uint16	blockalign;		/* Bytes per sample block */
    71 	Uint16	bitspersample;		/* One of 8, 12, 16, or 4 for ADPCM */
    72 } WaveFMT;
    73 
    74 /* The general chunk found in the WAVE file */
    75 typedef struct Chunk {
    76 	Uint32 magic;
    77 	Uint32 length;
    78 	Uint8 *data;			/* Data includes magic and length */
    79 } Chunk;
    80 
    81 /*********************************************/
    82 /* Define values for AIFF (IFF audio) format */
    83 /*********************************************/
    84 #define FORM		0x4d524f46		/* "FORM" */
    85 #define AIFF		0x46464941		/* "AIFF" */
    86 #define SSND		0x444e5353		/* "SSND" */
    87 #define COMM		0x4d4d4f43		/* "COMM" */
    88 
    89 
    90 /* Currently we only support a single stream at a time */
    91 static WAVStream *music = NULL;
    92 
    93 /* This is the format of the audio mixer data */
    94 static SDL_AudioSpec mixer;
    95 static int wavestream_volume = MIX_MAX_VOLUME;
    96 
    97 /* Function to load the WAV/AIFF stream */
    98 static SDL_RWops *LoadWAVStream (SDL_RWops *rw, SDL_AudioSpec *spec,
    99 					long *start, long *stop);
   100 static SDL_RWops *LoadAIFFStream (SDL_RWops *rw, SDL_AudioSpec *spec,
   101 					long *start, long *stop);
   102 
   103 /* Initialize the WAVStream player, with the given mixer settings
   104    This function returns 0, or -1 if there was an error.
   105  */
   106 int WAVStream_Init(SDL_AudioSpec *mixerfmt)
   107 {
   108 	mixer = *mixerfmt;
   109 	return(0);
   110 }
   111 
   112 void WAVStream_SetVolume(int volume)
   113 {
   114 	wavestream_volume = volume;
   115 }
   116 
   117 WAVStream *WAVStream_LoadSong(const char *file, const char *magic)
   118 {
   119 	SDL_RWops *rw;
   120 	WAVStream *wave;
   121 
   122 	rw = SDL_RWFromFile(file, "rb");
   123 	if ( rw == NULL ) {
   124 		SDL_SetError("Couldn't open %s", file);
   125 		return NULL;
   126 	}
   127 	wave = WAVStream_LoadSong_RW(rw, magic);
   128 	if ( wave == NULL ) {
   129 		SDL_FreeRW(rw);
   130 		return NULL;
   131 	}
   132 	return wave;
   133 }
   134 
   135 /* Load a WAV stream from the given file */
   136 WAVStream *WAVStream_LoadSong_RW(SDL_RWops *rw, const char *magic)
   137 {
   138 	WAVStream *wave;
   139 	SDL_AudioSpec wavespec;
   140 
   141 	if ( ! mixer.format ) {
   142 		Mix_SetError("WAV music output not started");
   143 		return(NULL);
   144 	}
   145 	wave = (WAVStream *)malloc(sizeof *wave);
   146 	if ( wave ) {
   147 		memset(wave, 0, (sizeof *wave));
   148 		if ( strcmp(magic, "RIFF") == 0 ) {
   149 			wave->rw = LoadWAVStream(rw, &wavespec,
   150 					&wave->start, &wave->stop);
   151 		} else
   152 		if ( strcmp(magic, "FORM") == 0 ) {
   153 			wave->rw = LoadAIFFStream(rw, &wavespec,
   154 					&wave->start, &wave->stop);
   155 		} else {
   156 			Mix_SetError("Unknown WAVE format");
   157 		}
   158 		if ( wave->rw == NULL ) {
   159 			free(wave);
   160 			return(NULL);
   161 		}
   162 		SDL_BuildAudioCVT(&wave->cvt,
   163 			wavespec.format, wavespec.channels, wavespec.freq,
   164 			mixer.format, mixer.channels, mixer.freq);
   165 	}
   166 	return(wave);
   167 }
   168 
   169 /* Start playback of a given WAV stream */
   170 void WAVStream_Start(WAVStream *wave)
   171 {
   172 	SDL_RWseek (wave->rw, wave->start, SEEK_SET);
   173 	music = wave;
   174 }
   175 
   176 /* Play some of a stream previously started with WAVStream_Start() */
   177 void WAVStream_PlaySome(Uint8 *stream, int len)
   178 {
   179 	long pos;
   180 
   181 	if ( music && ((pos=SDL_RWtell(music->rw)) < music->stop) ) {
   182 		if ( music->cvt.needed ) {
   183 			int original_len;
   184 
   185 			original_len=(int)((double)len/music->cvt.len_ratio);
   186 			if ( music->cvt.len != original_len ) {
   187 				int worksize;
   188 				if ( music->cvt.buf != NULL ) {
   189 					free(music->cvt.buf);
   190 				}
   191 				worksize = original_len*music->cvt.len_mult;
   192 				music->cvt.buf=(Uint8 *)malloc(worksize);
   193 				if ( music->cvt.buf == NULL ) {
   194 					return;
   195 				}
   196 				music->cvt.len = original_len;
   197 			}
   198 			if ( (music->stop - pos) < original_len ) {
   199 				original_len = (music->stop - pos);
   200 			}
   201 			original_len = SDL_RWread(music->rw, music->cvt.buf,1,original_len);
   202 			/* At least at the time of writing, SDL_ConvertAudio()
   203 			   does byte-order swapping starting at the end of the
   204 			   buffer. Thus, if we are reading 16-bit samples, we
   205 			   had better make damn sure that we get an even
   206 			   number of bytes, or we'll get garbage.
   207 			 */
   208 			if ( (music->cvt.src_format & 0x0010) && (original_len & 1) ) {
   209 				original_len--;
   210 			}
   211 			music->cvt.len = original_len;
   212 			SDL_ConvertAudio(&music->cvt);
   213 			SDL_MixAudio(stream, music->cvt.buf, music->cvt.len_cvt, wavestream_volume);
   214 		} else {
   215 			Uint8 *data;
   216 			if ( (music->stop - pos) < len ) {
   217 				len = (music->stop - pos);
   218 			}
   219 			data = SDL_stack_alloc(Uint8, len);
   220 			if (data)
   221 			{		
   222 				SDL_RWread(music->rw, data, len, 1);
   223 				SDL_MixAudio(stream, data, len, wavestream_volume);
   224 				SDL_stack_free(data);
   225 			}	
   226 		}
   227 	}
   228 }
   229 
   230 /* Stop playback of a stream previously started with WAVStream_Start() */
   231 void WAVStream_Stop(void)
   232 {
   233 	music = NULL;
   234 }
   235 
   236 /* Close the given WAV stream */
   237 void WAVStream_FreeSong(WAVStream *wave)
   238 {
   239 	if ( wave ) {
   240 		/* Clean up associated data */
   241 		if ( wave->freerw ) {
   242 			SDL_FreeRW(wave->rw);
   243 		}
   244 		if ( wave->cvt.buf ) {
   245 			free(wave->cvt.buf);
   246 		}
   247 		free(wave);
   248 	}
   249 }
   250 
   251 /* Return non-zero if a stream is currently playing */
   252 int WAVStream_Active(void)
   253 {
   254 	int active;
   255 
   256 	active = 0;
   257 	if ( music && (SDL_RWtell(music->rw) < music->stop) ) {
   258 		active = 1;
   259 	}
   260 	return(active);
   261 }
   262 
   263 static int ReadChunk(SDL_RWops *src, Chunk *chunk, int read_data)
   264 {
   265 	chunk->magic	= SDL_ReadLE32(src);
   266 	chunk->length	= SDL_ReadLE32(src);
   267 	if ( read_data ) {
   268 		chunk->data = (Uint8 *)malloc(chunk->length);
   269 		if ( chunk->data == NULL ) {
   270 			Mix_SetError("Out of memory");
   271 			return(-1);
   272 		}
   273 		if ( SDL_RWread(src, chunk->data, chunk->length, 1) != 1 ) {
   274 			Mix_SetError("Couldn't read chunk");
   275 			free(chunk->data);
   276 			return(-1);
   277 		}
   278 	} else {
   279 		SDL_RWseek(src, chunk->length, SEEK_CUR);
   280 	}
   281 	return(chunk->length);
   282 }
   283 
   284 static SDL_RWops *LoadWAVStream (SDL_RWops *src, SDL_AudioSpec *spec,
   285 					long *start, long *stop)
   286 {
   287 	int was_error;
   288 	Chunk chunk;
   289 	int lenread;
   290 
   291 	/* WAV magic header */
   292 	Uint32 RIFFchunk;
   293 	Uint32 wavelen;
   294 	Uint32 WAVEmagic;
   295 
   296 	/* FMT chunk */
   297 	WaveFMT *format = NULL;
   298 
   299 	was_error = 0;
   300 
   301 	/* Check the magic header */
   302 	RIFFchunk	= SDL_ReadLE32(src);
   303 	wavelen		= SDL_ReadLE32(src);
   304 	WAVEmagic	= SDL_ReadLE32(src);
   305 	if ( (RIFFchunk != RIFF) || (WAVEmagic != WAVE) ) {
   306 		Mix_SetError("Unrecognized file type (not WAVE)");
   307 		was_error = 1;
   308 		goto done;
   309 	}
   310 
   311 	/* Read the audio data format chunk */
   312 	chunk.data = NULL;
   313 	do {
   314 		/* FIXME! Add this logic to SDL_LoadWAV_RW() */
   315 		if ( chunk.data ) {
   316 			free(chunk.data);
   317 		}
   318 		lenread = ReadChunk(src, &chunk, 1);
   319 		if ( lenread < 0 ) {
   320 			was_error = 1;
   321 			goto done;
   322 		}
   323 	} while ( (chunk.magic == FACT) || (chunk.magic == LIST) );
   324 
   325 	/* Decode the audio data format */
   326 	format = (WaveFMT *)chunk.data;
   327 	if ( chunk.magic != FMT ) {
   328 		free(chunk.data);
   329 		Mix_SetError("Complex WAVE files not supported");
   330 		was_error = 1;
   331 		goto done;
   332 	}
   333 	switch (SDL_SwapLE16(format->encoding)) {
   334 		case PCM_CODE:
   335 			/* We can understand this */
   336 			break;
   337 		default:
   338 			Mix_SetError("Unknown WAVE data format");
   339 			was_error = 1;
   340 			goto done;
   341 	}
   342 	memset(spec, 0, (sizeof *spec));
   343 	spec->freq = SDL_SwapLE32(format->frequency);
   344 	switch (SDL_SwapLE16(format->bitspersample)) {
   345 		case 8:
   346 			spec->format = AUDIO_U8;
   347 			break;
   348 		case 16:
   349 			spec->format = AUDIO_S16;
   350 			break;
   351 		default:
   352 			Mix_SetError("Unknown PCM data format");
   353 			was_error = 1;
   354 			goto done;
   355 	}
   356 	spec->channels = (Uint8) SDL_SwapLE16(format->channels);
   357 	spec->samples = 4096;		/* Good default buffer size */
   358 
   359 	/* Set the file offset to the DATA chunk data */
   360 	chunk.data = NULL;
   361 	do {
   362 		*start = SDL_RWtell(src) + 2*sizeof(Uint32);
   363 		lenread = ReadChunk(src, &chunk, 0);
   364 		if ( lenread < 0 ) {
   365 			was_error = 1;
   366 			goto done;
   367 		}
   368 	} while ( chunk.magic != DATA );
   369 	*stop = SDL_RWtell(src);
   370 
   371 done:
   372 	if ( format != NULL ) {
   373 		free(format);
   374 	}
   375 	if ( was_error ) {
   376 		return NULL;
   377 	}
   378 	return(src);
   379 }
   380 
   381 /* I couldn't get SANE_to_double() to work, so I stole this from libsndfile.
   382  * I don't pretend to fully understand it.
   383  */
   384 
   385 static Uint32 SANE_to_Uint32 (Uint8 *sanebuf)
   386 {
   387 	/* Negative number? */
   388 	if (sanebuf[0] & 0x80)
   389 		return 0;
   390 
   391 	/* Less than 1? */
   392 	if (sanebuf[0] <= 0x3F)
   393 		return 1;
   394 
   395 	/* Way too big? */
   396 	if (sanebuf[0] > 0x40)
   397 		return 0x4000000;
   398 
   399 	/* Still too big? */
   400 	if (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C)
   401 		return 800000000;
   402 
   403 	return ((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7)
   404 		| (sanebuf[5] >> 1)) >> (29 - sanebuf[1]);
   405 }
   406 
   407 static SDL_RWops *LoadAIFFStream (SDL_RWops *src, SDL_AudioSpec *spec,
   408 					long *start, long *stop)
   409 {
   410 	int was_error;
   411 	int found_SSND;
   412 	int found_COMM;
   413 
   414 	Uint32 chunk_type;
   415 	Uint32 chunk_length;
   416 	long next_chunk;
   417 
   418 	/* AIFF magic header */
   419 	Uint32 FORMchunk;
   420 	Uint32 AIFFmagic;
   421 	/* SSND chunk        */
   422 	Uint32 offset;
   423 	Uint32 blocksize;
   424 	/* COMM format chunk */
   425 	Uint16 channels = 0;
   426 	Uint32 numsamples = 0;
   427 	Uint16 samplesize = 0;
   428 	Uint8 sane_freq[10];
   429 	Uint32 frequency = 0;
   430 
   431 	was_error = 0;
   432 
   433 	/* Check the magic header */
   434 	FORMchunk	= SDL_ReadLE32(src);
   435 	chunk_length	= SDL_ReadBE32(src);
   436 	AIFFmagic	= SDL_ReadLE32(src);
   437 	if ( (FORMchunk != FORM) || (AIFFmagic != AIFF) ) {
   438 		Mix_SetError("Unrecognized file type (not AIFF)");
   439 		was_error = 1;
   440 		goto done;
   441 	}
   442 
   443 	/* From what I understand of the specification, chunks may appear in
   444          * any order, and we should just ignore unknown ones.
   445 	 *
   446 	 * TODO: Better sanity-checking. E.g. what happens if the AIFF file
   447 	 *       contains compressed sound data?
   448          */
   449 
   450 	found_SSND = 0;
   451 	found_COMM = 0;
   452 
   453 	do {
   454 	    chunk_type		= SDL_ReadLE32(src);
   455 	    chunk_length	= SDL_ReadBE32(src);
   456 	    next_chunk		= SDL_RWtell(src) + chunk_length;
   457 
   458 	    /* Paranoia to avoid infinite loops */
   459 	    if (chunk_length == 0)
   460 		break;
   461 
   462             switch (chunk_type) {
   463 		case SSND:
   464 		    found_SSND		= 1;
   465 		    offset		= SDL_ReadBE32(src);
   466 		    blocksize		= SDL_ReadBE32(src);
   467 		    *start		= SDL_RWtell(src) + offset;
   468 		    break;
   469 
   470 		case COMM:
   471 		    found_COMM		= 1;
   472 
   473 		    /* Read the audio data format chunk */
   474 		    channels		= SDL_ReadBE16(src);
   475 		    numsamples		= SDL_ReadBE32(src);
   476 		    samplesize		= SDL_ReadBE16(src);
   477 		    SDL_RWread(src, sane_freq, sizeof(sane_freq), 1);
   478 		    frequency		= SANE_to_Uint32(sane_freq);
   479 		    break;
   480 
   481 		default:
   482 		    break;
   483 	    }
   484 	} while ((!found_SSND || !found_COMM)
   485 		 && SDL_RWseek(src, next_chunk, SEEK_SET) != -1);
   486 
   487 	if (!found_SSND) {
   488 	    Mix_SetError("Bad AIFF file (no SSND chunk)");
   489 	    was_error = 1;
   490 	    goto done;
   491 	}
   492 		    
   493 	if (!found_COMM) {
   494 	    Mix_SetError("Bad AIFF file (no COMM chunk)");
   495 	    was_error = 1;
   496 	    goto done;
   497 	}
   498 
   499 	*stop = *start + channels * numsamples * (samplesize / 8);
   500 
   501 	/* Decode the audio data format */
   502 	memset(spec, 0, (sizeof *spec));
   503 	spec->freq = frequency;
   504 	switch (samplesize) {
   505 		case 8:
   506 			spec->format = AUDIO_S8;
   507 			break;
   508 		case 16:
   509 			spec->format = AUDIO_S16MSB;
   510 			break;
   511 		default:
   512 			Mix_SetError("Unknown samplesize in data format");
   513 			was_error = 1;
   514 			goto done;
   515 	}
   516 	spec->channels = (Uint8) channels;
   517 	spec->samples = 4096;		/* Good default buffer size */
   518 
   519 done:
   520 	if ( was_error ) {
   521 		return NULL;
   522 	}
   523 	return(src);
   524 }
   525