wavestream.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 11 Sep 2001 18:24:38 +0000
changeset 111 3b709e102787
parent 109 6cbf105616e3
child 134 2d3417d1860a
permissions -rw-r--r--
Torbj´┐Żrn Andersson - Tue Sep 11 11:22:29 PDT 2001
* Added support for loading AIFF audio chunks
     1 /*
     2     MIXERLIB:  An audio mixer library based on the SDL library
     3     Copyright (C) 1997-1999  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     5635-34 Springhouse Dr.
    21     Pleasanton, CA 94588 (USA)
    22     slouken@devolution.com
    23 */
    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 "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 *theWave = NULL;
    91 
    92 /* This is initialized by the music mixer */
    93 static SDL_mutex *music_lock = NULL;
    94 
    95 /* This is the format of the audio mixer data */
    96 static SDL_AudioSpec mixer;
    97 
    98 /* Function to load the WAV/AIFF stream */
    99 static FILE *LoadWAVStream (const char *file, SDL_AudioSpec *spec,
   100 					long *start, long *stop);
   101 static FILE *LoadAIFFStream (const char *file, SDL_AudioSpec *spec,
   102 					long *start, long *stop);
   103 
   104 /* Initialize the WAVStream player, with the given mixer settings
   105    This function returns 0, or -1 if there was an error.
   106  */
   107 int WAVStream_Init(SDL_AudioSpec *mixerfmt)
   108 {
   109 	/* FIXME: clean up the mutex, or move it into music.c */
   110 	music_lock = SDL_CreateMutex();
   111 #ifndef macintosh /* Hmm.. */
   112 	if ( music_lock == NULL ) {
   113 		return(-1);
   114 	}
   115 #endif
   116 	mixer = *mixerfmt;
   117 	return(0);
   118 }
   119 
   120 /* Unimplemented */
   121 extern void WAVStream_SetVolume(int volume)
   122 {
   123 }
   124 
   125 /* Load a WAV stream from the given file */
   126 extern WAVStream *WAVStream_LoadSong(const char *file, const char *magic)
   127 {
   128 	WAVStream *wave;
   129 	SDL_AudioSpec wavespec;
   130 
   131 	if ( ! mixer.format ) {
   132 		SDL_SetError("WAV music output not started");
   133 		return(NULL);
   134 	}
   135 	wave = (WAVStream *)malloc(sizeof *wave);
   136 	if ( wave ) {
   137 		memset(wave, 0, (sizeof *wave));
   138 		if ( strcmp(magic, "RIFF") == 0 ) {
   139 			wave->wavefp = LoadWAVStream(file, &wavespec,
   140 					&wave->start, &wave->stop);
   141 		} else
   142 		if ( strcmp(magic, "FORM") == 0 ) {
   143 			wave->wavefp = LoadAIFFStream(file, &wavespec,
   144 					&wave->start, &wave->stop);
   145 		}
   146 		if ( wave->wavefp == NULL ) {
   147 			free(wave);
   148 			return(NULL);
   149 		}
   150 		SDL_BuildAudioCVT(&wave->cvt,
   151 			wavespec.format, wavespec.channels, wavespec.freq,
   152 			mixer.format, mixer.channels, mixer.freq);
   153 	}
   154 	return(wave);
   155 }
   156 
   157 /* Start playback of a given WAV stream */
   158 extern void WAVStream_Start(WAVStream *wave)
   159 {
   160 	SDL_mutexP(music_lock);
   161 	clearerr(wave->wavefp);
   162 	fseek(wave->wavefp, wave->start, SEEK_SET);
   163 	theWave = wave;
   164 	SDL_mutexV(music_lock);
   165 }
   166 
   167 /* Play some of a stream previously started with WAVStream_Start()
   168    The music_lock is held while this function is called.
   169  */
   170 extern void WAVStream_PlaySome(Uint8 *stream, int len)
   171 {
   172 	long pos;
   173 
   174 	SDL_mutexP(music_lock);
   175 	if ( theWave && ((pos=ftell(theWave->wavefp)) < theWave->stop) ) {
   176 		if ( theWave->cvt.needed ) {
   177 			int original_len;
   178 
   179 			original_len=(int)((double)len/theWave->cvt.len_ratio);
   180 			if ( theWave->cvt.len != original_len ) {
   181 				int worksize;
   182 				if ( theWave->cvt.buf != NULL ) {
   183 					free(theWave->cvt.buf);
   184 				}
   185 				worksize = original_len*theWave->cvt.len_mult;
   186 				theWave->cvt.buf=(Uint8 *)malloc(worksize);
   187 				if ( theWave->cvt.buf == NULL ) {
   188 					SDL_mutexV(music_lock);
   189 					return;
   190 				}
   191 				theWave->cvt.len = original_len;
   192 			}
   193 			if ( (theWave->stop - pos) < original_len ) {
   194 				original_len = (theWave->stop - pos);
   195 			}
   196 			original_len = fread(theWave->cvt.buf,1,original_len,theWave->wavefp);
   197 			/* At least at the time of writing, SDL_ConvertAudio()
   198 			   does byte-order swapping starting at the end of the
   199 			   buffer. Thus, if we are reading 16-bit samples, we
   200 			   had better make damn sure that we get an even
   201 			   number of bytes, or we'll get garbage.
   202 			 */
   203 			if ( (theWave->cvt.src_format & 0x0010) && (original_len & 1) ) {
   204 				original_len--;
   205 			}
   206 			theWave->cvt.len = original_len;
   207 			SDL_ConvertAudio(&theWave->cvt);
   208 			memcpy(stream, theWave->cvt.buf, theWave->cvt.len_cvt);
   209 		} else {
   210 			if ( (theWave->stop - pos) < len ) {
   211 				len = (theWave->stop - pos);
   212 			}
   213 			fread(stream, len, 1, theWave->wavefp);
   214 		}
   215 	}
   216 	SDL_mutexV(music_lock);
   217 }
   218 
   219 /* Stop playback of a stream previously started with WAVStream_Start() */
   220 extern void WAVStream_Stop(void)
   221 {
   222 	SDL_mutexP(music_lock);
   223 	theWave = NULL;
   224 	SDL_mutexV(music_lock);
   225 }
   226 
   227 /* Close the given WAV stream */
   228 extern void WAVStream_FreeSong(WAVStream *wave)
   229 {
   230 	if ( wave ) {
   231 		/* Remove song from the currently playing list */
   232 		SDL_mutexP(music_lock);
   233 		if ( wave == theWave ) {
   234 			theWave = NULL;
   235 		}
   236 		SDL_mutexV(music_lock);
   237 
   238 		/* Clean up associated data */
   239 		if ( wave->wavefp ) {
   240 			fclose(wave->wavefp);
   241 		}
   242 		if ( wave->cvt.buf ) {
   243 			free(wave->cvt.buf);
   244 		}
   245 		free(wave);
   246 	}
   247 }
   248 
   249 /* Return non-zero if a stream is currently playing */
   250 extern int WAVStream_Active(void)
   251 {
   252 	int active;
   253 
   254 	SDL_mutexP(music_lock);
   255 	active = 0;
   256 	if ( theWave && (ftell(theWave->wavefp) < theWave->stop) ) {
   257 		active = 1;
   258 	}
   259 	SDL_mutexV(music_lock);
   260 
   261 	return(active);
   262 }
   263 
   264 static int ReadChunk(SDL_RWops *src, Chunk *chunk, int read_data)
   265 {
   266 	chunk->magic	= SDL_ReadLE32(src);
   267 	chunk->length	= SDL_ReadLE32(src);
   268 	if ( read_data ) {
   269 		chunk->data = (Uint8 *)malloc(chunk->length);
   270 		if ( chunk->data == NULL ) {
   271 			SDL_SetError("Out of memory");
   272 			return(-1);
   273 		}
   274 		if ( SDL_RWread(src, chunk->data, chunk->length, 1) != 1 ) {
   275 			SDL_SetError("Couldn't read chunk");
   276 			free(chunk->data);
   277 			return(-1);
   278 		}
   279 	} else {
   280 		SDL_RWseek(src, chunk->length, SEEK_CUR);
   281 	}
   282 	return(chunk->length);
   283 }
   284 
   285 static FILE *LoadWAVStream (const char *file, SDL_AudioSpec *spec,
   286 					long *start, long *stop)
   287 {
   288 	int was_error;
   289 	FILE *wavefp;
   290 	SDL_RWops *src;
   291 	Chunk chunk;
   292 	int lenread;
   293 
   294 	/* WAV magic header */
   295 	Uint32 RIFFchunk;
   296 	Uint32 wavelen;
   297 	Uint32 WAVEmagic;
   298 
   299 	/* FMT chunk */
   300 	WaveFMT *format = NULL;
   301 
   302 	/* Make sure we are passed a valid data source */
   303 	was_error = 0;
   304 	wavefp = fopen(file, "rb");
   305 	src = NULL;
   306 	if ( wavefp ) {
   307 		src = SDL_RWFromFP(wavefp, 0);
   308 	}
   309 	if ( src == NULL ) {
   310 		was_error = 1;
   311 		goto done;
   312 	}
   313 
   314 	/* Check the magic header */
   315 	RIFFchunk	= SDL_ReadLE32(src);
   316 	wavelen		= SDL_ReadLE32(src);
   317 	WAVEmagic	= SDL_ReadLE32(src);
   318 	if ( (RIFFchunk != RIFF) || (WAVEmagic != WAVE) ) {
   319 		SDL_SetError("Unrecognized file type (not WAVE)");
   320 		was_error = 1;
   321 		goto done;
   322 	}
   323 
   324 	/* Read the audio data format chunk */
   325 	chunk.data = NULL;
   326 	do {
   327 		/* FIXME! Add this logic to SDL_LoadWAV_RW() */
   328 		if ( chunk.data ) {
   329 			free(chunk.data);
   330 		}
   331 		lenread = ReadChunk(src, &chunk, 1);
   332 		if ( lenread < 0 ) {
   333 			was_error = 1;
   334 			goto done;
   335 		}
   336 	} while ( (chunk.magic == FACT) || (chunk.magic == LIST) );
   337 
   338 	/* Decode the audio data format */
   339 	format = (WaveFMT *)chunk.data;
   340 	if ( chunk.magic != FMT ) {
   341 		free(chunk.data);
   342 		SDL_SetError("Complex WAVE files not supported");
   343 		was_error = 1;
   344 		goto done;
   345 	}
   346 	switch (SDL_SwapLE16(format->encoding)) {
   347 		case PCM_CODE:
   348 			/* We can understand this */
   349 			break;
   350 		default:
   351 			SDL_SetError("Unknown WAVE data format");
   352 			was_error = 1;
   353 			goto done;
   354 	}
   355 	memset(spec, 0, (sizeof *spec));
   356 	spec->freq = SDL_SwapLE32(format->frequency);
   357 	switch (SDL_SwapLE16(format->bitspersample)) {
   358 		case 8:
   359 			spec->format = AUDIO_U8;
   360 			break;
   361 		case 16:
   362 			spec->format = AUDIO_S16;
   363 			break;
   364 		default:
   365 			SDL_SetError("Unknown PCM data format");
   366 			was_error = 1;
   367 			goto done;
   368 	}
   369 	spec->channels = (Uint8) SDL_SwapLE16(format->channels);
   370 	spec->samples = 4096;		/* Good default buffer size */
   371 
   372 	/* Set the file offset to the DATA chunk data */
   373 	chunk.data = NULL;
   374 	do {
   375 		*start = SDL_RWtell(src) + 2*sizeof(Uint32);
   376 		lenread = ReadChunk(src, &chunk, 0);
   377 		if ( lenread < 0 ) {
   378 			was_error = 1;
   379 			goto done;
   380 		}
   381 	} while ( chunk.magic != DATA );
   382 	*stop = SDL_RWtell(src);
   383 
   384 done:
   385 	if ( format != NULL ) {
   386 		free(format);
   387 	}
   388 	if ( src ) {
   389 		SDL_RWclose(src);
   390 	}
   391 	if ( was_error ) {
   392 		if ( wavefp ) {
   393 			fclose(wavefp);
   394 			wavefp = NULL;
   395 		}
   396 	}
   397 	return(wavefp);
   398 }
   399 
   400 /* I couldn't get SANE_to_double() to work, so I stole this from libsndfile.
   401  * I don't pretend to fully understand it.
   402  */
   403 
   404 static Uint32 SANE_to_Uint32 (Uint8 *sanebuf)
   405 {
   406 	/* Negative number? */
   407 	if (sanebuf[0] & 0x80)
   408 		return 0;
   409 
   410 	/* Less than 1? */
   411 	if (sanebuf[0] <= 0x3F)
   412 		return 1;
   413 
   414 	/* Way too big? */
   415 	if (sanebuf[0] > 0x40)
   416 		return 0x4000000;
   417 
   418 	/* Still too big? */
   419 	if (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C)
   420 		return 800000000;
   421 
   422 	return ((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7)
   423 		| (sanebuf[5] >> 1)) >> (29 - sanebuf[1]);
   424 }
   425 
   426 static FILE *LoadAIFFStream (const char *file, SDL_AudioSpec *spec,
   427 					long *start, long *stop)
   428 {
   429 	int was_error;
   430 	int found_SSND;
   431 	int found_COMM;
   432 	FILE *wavefp;
   433 	SDL_RWops *src;
   434 
   435 	Uint32 chunk_type;
   436 	Uint32 chunk_length;
   437 	long next_chunk;
   438 
   439 	/* AIFF magic header */
   440 	Uint32 FORMchunk;
   441 	Uint32 AIFFmagic;
   442 	/* SSND chunk        */
   443 	Uint32 offset;
   444 	Uint32 blocksize;
   445 	/* COMM format chunk */
   446 	Uint16 channels;
   447 	Uint32 numsamples;
   448 	Uint16 samplesize;
   449 	Uint8 sane_freq[10];
   450 	Uint32 frequency;
   451 
   452 
   453 	/* Make sure we are passed a valid data source */
   454 	was_error = 0;
   455 	wavefp = fopen(file, "rb");
   456 	src = NULL;
   457 	if ( wavefp ) {
   458 		src = SDL_RWFromFP(wavefp, 0);
   459 	}
   460 	if ( src == NULL ) {
   461 		was_error = 1;
   462 		goto done;
   463 	}
   464 
   465 	/* Check the magic header */
   466 	FORMchunk	= SDL_ReadLE32(src);
   467 	chunk_length	= SDL_ReadBE32(src);
   468 	AIFFmagic	= SDL_ReadLE32(src);
   469 	if ( (FORMchunk != FORM) || (AIFFmagic != AIFF) ) {
   470 		SDL_SetError("Unrecognized file type (not AIFF)");
   471 		was_error = 1;
   472 		goto done;
   473 	}
   474 
   475 	/* From what I understand of the specification, chunks may appear in
   476          * any order, and we should just ignore unknown ones.
   477 	 *
   478 	 * TODO: Better sanity-checking. E.g. what happens if the AIFF file
   479 	 *       contains compressed sound data?
   480          */
   481 
   482 	found_SSND = 0;
   483 	found_COMM = 0;
   484 
   485 	do {
   486 	    chunk_type		= SDL_ReadLE32(src);
   487 	    chunk_length	= SDL_ReadBE32(src);
   488 	    next_chunk		= SDL_RWtell(src) + chunk_length;
   489 
   490 	    /* Paranoia to avoid infinite loops */
   491 	    if (chunk_length == 0)
   492 		break;
   493 
   494             switch (chunk_type) {
   495 		case SSND:
   496 		    found_SSND		= 1;
   497 		    offset		= SDL_ReadBE32(src);
   498 		    blocksize		= SDL_ReadBE32(src);
   499 		    *start		= SDL_RWtell(src) + offset;
   500 		    break;
   501 
   502 		case COMM:
   503 		    found_COMM		= 1;
   504 
   505 		    /* Read the audio data format chunk */
   506 		    channels		= SDL_ReadBE16(src);
   507 		    numsamples		= SDL_ReadBE32(src);
   508 		    samplesize		= SDL_ReadBE16(src);
   509 		    SDL_RWread(src, sane_freq, sizeof(sane_freq), 1);
   510 		    frequency		= SANE_to_Uint32(sane_freq);
   511 		    break;
   512 
   513 		default:
   514 		    break;
   515 	    }
   516 	} while ((!found_SSND || !found_COMM)
   517 		 && SDL_RWseek(src, next_chunk, SEEK_SET) != -1);
   518 
   519 	if (!found_SSND) {
   520 	    SDL_SetError("Bad AIFF file (no SSND chunk)");
   521 	    was_error = 1;
   522 	    goto done;
   523 	}
   524 		    
   525 	if (!found_COMM) {
   526 	    SDL_SetError("Bad AIFF file (no COMM chunk)");
   527 	    was_error = 1;
   528 	    goto done;
   529 	}
   530 
   531 	*stop = *start + channels * numsamples * (samplesize / 8);
   532 
   533 	/* Decode the audio data format */
   534 	memset(spec, 0, (sizeof *spec));
   535 	spec->freq = frequency;
   536 	switch (samplesize) {
   537 		case 8:
   538 			spec->format = AUDIO_S8;
   539 			break;
   540 		case 16:
   541 			spec->format = AUDIO_S16MSB;
   542 			break;
   543 		default:
   544 			SDL_SetError("Unknown samplesize in data format");
   545 			was_error = 1;
   546 			goto done;
   547 	}
   548 	spec->channels = (Uint8) channels;
   549 	spec->samples = 4096;		/* Good default buffer size */
   550 
   551 done:
   552 	if ( src ) {
   553 		SDL_RWclose(src);
   554 	}
   555 	if ( was_error ) {
   556 		if ( wavefp ) {
   557 			fclose(wavefp);
   558 			wavefp = NULL;
   559 		}
   560 	}
   561 	return(wavefp);
   562 }