wavestream.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 31 Dec 2011 18:32:49 -0500
changeset 521 565549e046b0
parent 518 8bc9b5fd2aae
child 542 3de4970b36d4
permissions -rw-r--r--
Fixed bugs 1003, 1021, 1168 - fixed memory leak loading music

jjs@jjs.at 2011-03-11 11:37:57 PST
When using SDL_Mixer to play WAVE music, the call to Mix_FreeMusic does not
close the associated file handle.

There is a check in WAVStream_FreeSong() of wavestream.c like this:

if ( wave->freerw ) {
SDL_FreeRW(wave->rw);
}

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