view Isolated/SoundDecoder.c @ 76:12e4e093c6e0

Compiler warning cleanups for wav.c on Windows/CodeBlocks. Johnson Lin wanted to clean up warnings he was getting. I'm a little nervous about the struct definition change from signed to unsigned for the chunkSize in the data struct. But for the usage cases, it looks reasonable to me.
author Eric Wing <ewing . public |-at-| gmail . com>
date Mon, 13 Aug 2012 01:40:17 -0700
parents be97ae4f30c0
children
line wrap: on
line source

#ifndef ALMIXER_COMPILED_WITH_SDL

#include "SoundDecoder.h"
#include "SoundDecoder_Internal.h"
#include "tErrorLib.h"
#include "LinkedList.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#ifdef __ANDROID__
#include <android/log.h>
#endif

/* A partial shim reimplementation of SDL_sound to work around the LGPL issues.
 * This implementation is more limited than SDL_sound. 
 * For example, there is no generic software conversion routines.
 * Functions are also not necessarily thread safe.
 * This implementation relies on the back-end decoder much more heavily 
 * than SDL_sound. (For example, I bypass the internal->buffer.)
 */

static LinkedList* s_listOfLoadedSamples = NULL;

static signed char s_isInitialized = 0;
static TErrorPool* s_errorPool = NULL;

static const SoundDecoder_DecoderInfo** s_availableDecoders = NULL;

#ifdef __APPLE__ /* I'm making Apple use the Core Audio backend. */
	//extern const SoundDecoder_DecoderFunctions __SoundDecoder_DecoderFunctions_CoreAudio;
	extern const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio;
#else /* Not Apple */
	#ifdef SOUND_SUPPORTS_WAV
		extern const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV;
	#endif
	#ifdef SOUND_SUPPORTS_MPG123
		extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MPG123;
	#endif
#endif

/* Note: Make sure to compile only Vorbis xor Tremor, not both. */
#ifdef SOUND_SUPPORTS_OGG
	extern const Sound_DecoderFunctions __Sound_DecoderFunctions_OGG;
#endif

typedef struct
{
    int available;
    const SoundDecoder_DecoderFunctions* funcs;
} SoundElement;

static SoundElement s_linkedDecoders[] =
{
#if defined(__APPLE__)
		{ 0, &__Sound_DecoderFunctions_CoreAudio },
#else /* Not Apple */
	#ifdef SOUND_SUPPORTS_WAV
		{ 0, &__Sound_DecoderFunctions_WAV },
	#endif
	#ifdef SOUND_SUPPORTS_MPG123
		{ 0, &__Sound_DecoderFunctions_MPG123 },
	#endif
#endif
/* Note: Make sure to link only Vorbis xor Tremor, not both. */		
#ifdef SOUND_SUPPORTS_OGG
    { 0, &__Sound_DecoderFunctions_OGG },
#endif
    { 0, NULL }
};


#include <ctype.h>
int SoundDecoder_strcasecmp(const char* str1, const char* str2)
{
	int the_char1;
	int the_char2;
	int i = 0;
	if(str1 == str2)
	{
		return 0;
	}
	if(NULL == str1)
	{
		return -1;
	}
	if(NULL == str2)
	{
		return 1;
	}

	do
	{
		the_char1 = tolower(str1[i]);
		the_char2 = tolower(str2[i]);
		if(the_char1 < the_char2)
		{
			return -1;
		}
		else if(the_char1 > the_char2)
		{
			return 1;
		}
		i++;
	} while( (0 != the_char1) && (0 != the_char2) );

	return 0;
}


#ifdef __ANDROID__
#include <stdarg.h>
#include <android/log.h>
int SoundDecoder_DebugPrint(const char* format, ...)
{
	va_list arg_list; 
	int ret_val;

	va_start(arg_list, format); 
	ret_val = __android_log_vprint(ANDROID_LOG_INFO, "SoundDecoder", format, arg_list); 
	va_end(arg_list);
	return ret_val;
}
#endif

const char* SoundDecoder_GetError()
{
	const char* error_string = NULL;
	if(NULL == s_errorPool)
	{
		return "Error: You should not call SoundDecoder_GetError while Sound is not initialized";
	}
	error_string = TError_GetLastErrorStr(s_errorPool);
	/* SDL returns empty strings instead of NULL */
	if(NULL == error_string)
	{
		return "";
	}
	else
	{
		return error_string;
	}
}

void SoundDecoder_ClearError()
{
	if(NULL == s_errorPool)
	{
		return;
	}
	TError_SetError(s_errorPool, 0, NULL);
}

void SoundDecoder_SetError(const char* err_str, ...)
{
	if(NULL == s_errorPool)
	{
		fprintf(stderr, "Error: You should not call SoundDecoder_SetError while Sound is not initialized\n");
		return;
	}
	va_list argp;
	va_start(argp, err_str);
	// SDL_SetError which I'm emulating has no number parameter.
	TError_SetErrorv(s_errorPool, 1, err_str, argp);
	va_end(argp);
#ifdef __ANDROID__
	__android_log_print(ANDROID_LOG_INFO, "SoundDecoder_SetError", TError_GetLastErrorStr(s_errorPool));
#endif
}


void SoundDecoder_GetLinkedVersion(SoundDecoder_Version* the_version)
{
	if(NULL != the_version)
	{
		the_version->major = SOUNDDECODER_VER_MAJOR;
		the_version->minor = SOUNDDECODER_VER_MINOR;
		the_version->patch = SOUNDDECODER_VER_PATCH;
	}
}


const SoundDecoder_DecoderInfo** SoundDecoder_AvailableDecoders()
{
    return(s_availableDecoders);
}

int SoundDecoder_Init()
{
	size_t total_number_of_decoders;
	size_t i;
	size_t current_pos = 0;	
	if(1 == s_isInitialized)
	{
		return 1;
	}
	if(NULL == s_errorPool)
	{
		s_errorPool = TError_CreateErrorPool();
	}
	if(NULL == s_errorPool)
	{
		return 0;
	}

	total_number_of_decoders = sizeof(s_linkedDecoders) / sizeof(s_linkedDecoders[0]);
	s_availableDecoders = (const SoundDecoder_DecoderInfo **)malloc((total_number_of_decoders) * sizeof(SoundDecoder_DecoderInfo*));
	if(NULL == s_availableDecoders)
	{
		SoundDecoder_SetError(ERR_OUT_OF_MEMORY);
		return 0;
	}

	/* Allocate memory for linked list of sound samples. */
	s_listOfLoadedSamples = LinkedList_Create();
	if(NULL == s_listOfLoadedSamples)
	{
		free(s_availableDecoders);
		s_availableDecoders = NULL;
		SoundDecoder_SetError(ERR_OUT_OF_MEMORY);
		return 0;
	}

	for(i = 0; s_linkedDecoders[i].funcs != NULL; i++)
	{
		s_linkedDecoders[i].available = s_linkedDecoders[i].funcs->init();
		if(s_linkedDecoders[i].available)
		{
			s_availableDecoders[current_pos] = &(s_linkedDecoders[i].funcs->info);
			current_pos++;
		}
	}

	s_availableDecoders[current_pos] = NULL;
	s_isInitialized = 1;
	return 1;
}

void SoundDecoder_Quit()
{
    size_t i;	
	if(0 == s_isInitialized)
	{
		return;
	}

	/* 
	 * SDL_sound actually embeds the linked list in the internal data structure.
	 * So any sample can potentially reach any other sample.
	 * But I'm keeping my own separate list.
	 */
	while(LinkedList_Size(s_listOfLoadedSamples) > 0)
	{
		SoundDecoder_Sample* sound_sample = (SoundDecoder_Sample*)LinkedList_PopBack(s_listOfLoadedSamples);
		SoundDecoder_FreeSample(sound_sample);
	}
	LinkedList_Free(s_listOfLoadedSamples);
	s_listOfLoadedSamples = NULL;

	
    for(i = 0; s_linkedDecoders[i].funcs != NULL; i++)
    {
        if (s_linkedDecoders[i].available)
        {
            s_linkedDecoders[i].funcs->quit();
            s_linkedDecoders[i].available = 0;
        }
    }

    if(NULL != s_availableDecoders)
	{
        free(s_availableDecoders);
	}
	s_availableDecoders = NULL;


	/* Remember: ALmixer_SetError/GetError calls will not work while this is gone. */
	TError_FreeErrorPool(s_errorPool);
	s_errorPool = NULL;

	s_isInitialized = 0;	
}


void SoundDecoder_FreeSample(SoundDecoder_Sample* sound_sample)
{
	SoundDecoder_SampleInternal* sample_internal;

	/* Quit unloads all samples, so it is not possible to free a sample
	 * when not initialized.
	 */
	if(0 == s_isInitialized)
	{
		return;
	}

	if(sound_sample == NULL)
	{
		return;
	}

	/* SDL_sound keeps a linked list of all the loaded samples.
	 * We want to remove the current sample from that list.
	 */
	LinkedListNode* the_node = LinkedList_Find(s_listOfLoadedSamples, sound_sample, NULL);
	if(NULL == the_node)
	{
		SoundDecoder_SetError("SoundDecoder_FreeSample: Internal Error, sample does not exist in linked list.");
		return;
	}
	LinkedList_Remove(s_listOfLoadedSamples, the_node);

	sample_internal = (SoundDecoder_SampleInternal*)sound_sample->opaque;

	/* Ugh...SDL_sound has a lot of pointers. 
	 * I hope I didn't miss any dynamic memory.
	 */

	/* Call close on the decoder */
	sample_internal->funcs->close(sound_sample);

	if(NULL != sample_internal->rw)
	{
		sample_internal->rw->close(sample_internal->rw);
	}

	/* Ooops. The public buffer might be shared with the internal buffer.
	 * Make sure to not accidentally double delete.
	 */
	if((NULL != sample_internal->buffer)
		&& (sample_internal->buffer != sound_sample->buffer)
	)
	{
		free(sample_internal->buffer);
	}
	free(sample_internal);

	if(NULL != sound_sample->buffer)
	{
		free(sound_sample->buffer);
	}
	free(sound_sample);
}



static int Internal_LoadSample(const SoundDecoder_DecoderFunctions *funcs,
	SoundDecoder_Sample* sound_sample, const char *ext
)
{
    SoundDecoder_SampleInternal* internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque;
    long current_file_position = internal_sample->rw->seek(internal_sample->rw, 0, SEEK_CUR);

    /* fill in the funcs for this decoder... */
    sound_sample->decoder = &funcs->info;
	internal_sample->funcs = funcs;
	if (!funcs->open(sound_sample, ext))
	{
		internal_sample->rw->seek(internal_sample->rw, current_file_position, SEEK_SET);
		return 0;
	}

	/* we found a compatible decoder */

	/* SDL_sound normally goes on to setup a bunch of things to 
	 * support format conversion via SDL APIs.
	 * I am not porting any of that stuff.
	 * My goal is to simply setup the struct properties to values
	 * that will not cause any confusion with other parts of the implementation.
	 */


	if(0 == sound_sample->desired.format)
	{
		sound_sample->desired.format = sound_sample->actual.format;
	}
	if(0 == sound_sample->desired.channels)
	{
		sound_sample->desired.channels = sound_sample->actual.channels;
	}
	if(0 == sound_sample->desired.rate)
	{
		sound_sample->desired.rate = sound_sample->actual.rate;
	}

	/* I'm a little confused at the difference between the internal
	 * public buffer. I am going to make them the same.
	 * I assume I already allocated the public buffer.
	 */
	internal_sample->buffer = sound_sample->buffer;
	internal_sample->buffer_size = sound_sample->buffer_size;

	/* Insert the new sample into the linked list of samples. */
	LinkedList_PushBack(s_listOfLoadedSamples, sound_sample);
	
	return 1;
}



SoundDecoder_Sample* SoundDecoder_NewSampleFromFile(const char* file_name,
                                      SoundDecoder_AudioInfo* desired_format,
                                      size_t buffer_size)
{
	
    const char* file_extension;
    ALmixer_RWops* rw_ops;


	if(0 == s_isInitialized)
	{
		SoundDecoder_SetError(ERR_NOT_INITIALIZED);
		return NULL;
	}
	if(NULL == file_name)
	{
		SoundDecoder_SetError("No file specified");
		return NULL;
	}

    file_extension = strrchr(file_name, '.');
	if(NULL != file_extension)
	{
        file_extension++;
	}

    rw_ops = ALmixer_RWFromFile(file_name, "rb");

     return SoundDecoder_NewSample(rw_ops, file_extension, desired_format, buffer_size);
}


SoundDecoder_Sample* SoundDecoder_NewSample(ALmixer_RWops* rw_ops, const char* file_extension, SoundDecoder_AudioInfo* desired_format, size_t buffer_size)
{
    SoundDecoder_Sample* new_sample;
    SoundDecoder_SampleInternal* internal_sample;
    SoundElement* current_decoder;

	if(0 == s_isInitialized)
	{
		SoundDecoder_SetError(ERR_NOT_INITIALIZED);
		return NULL;
	}
	if(NULL == rw_ops)
	{
		SoundDecoder_SetError("No file specified");
		return NULL;
	}

	new_sample = (SoundDecoder_Sample*)calloc(1, sizeof(SoundDecoder_Sample));
	if(NULL == new_sample)
	{
		SoundDecoder_SetError(ERR_OUT_OF_MEMORY);
		return NULL;
	}
	internal_sample = (SoundDecoder_SampleInternal*)calloc(1, sizeof(SoundDecoder_SampleInternal));
	if(NULL == internal_sample)
	{
		free(new_sample);
		SoundDecoder_SetError(ERR_OUT_OF_MEMORY);
		return NULL;
	}
	new_sample->buffer = calloc(1, buffer_size);
	if(NULL == new_sample->buffer)
	{
		free(internal_sample);
		free(new_sample);
		SoundDecoder_SetError(ERR_OUT_OF_MEMORY);
		return NULL;
	}

	new_sample->buffer_size = buffer_size;

    if(NULL != desired_format)
	{
		memcpy(&new_sample->desired, desired_format, sizeof(SoundDecoder_AudioInfo));
	}

	internal_sample->rw = rw_ops;
	new_sample->opaque = internal_sample;


    if(NULL != file_extension)
    {
        for(current_decoder = &s_linkedDecoders[0]; current_decoder->funcs != NULL; current_decoder++)
        {
            if(current_decoder->available)
            {
                const char** decoder_file_extension = current_decoder->funcs->info.extensions;
                while(*decoder_file_extension)
                {
                    if(0 == (SoundDecoder_strcasecmp(*decoder_file_extension, file_extension)))
                    {
                        if(Internal_LoadSample(current_decoder->funcs, new_sample, file_extension))
						{
							return(new_sample);
						}
                        break;  /* go to the next decoder */ 
                    }
					decoder_file_extension++;
                }
            }
        }
    }

    /* no direct file_extensionension match? Try everything we've got... */
	for(current_decoder = &s_linkedDecoders[0]; current_decoder->funcs != NULL; current_decoder++)
	{
		if(current_decoder->available)
		{
			int already_tried_decoder = 0;
			const char** decoder_file_extension = current_decoder->funcs->info.extensions;

			/* skip decoders we already tried from the above loop */
			while(*decoder_file_extension)
			{
				if(SoundDecoder_strcasecmp(*decoder_file_extension, file_extension) == 0)
				{
					already_tried_decoder = 1;
					break;
				}
				decoder_file_extension++;
			}

			if(0 == already_tried_decoder)
			{
				if (Internal_LoadSample(current_decoder->funcs, new_sample, file_extension))
				{
					return new_sample;
				}
			}
		}
	}

	/* could not find a decoder */
	SoundDecoder_SetError(ERR_UNSUPPORTED_FORMAT);
	/* clean up the memory */
	free(new_sample->opaque);
	if(NULL != new_sample->buffer)
	{
		free(new_sample->buffer);
	}
	free(new_sample);
	
	rw_ops->close(rw_ops);
	return NULL;
}


int SoundDecoder_SetBufferSize(SoundDecoder_Sample* sound_sample, size_t new_buffer_size)
{
	SoundDecoder_SampleInternal* internal_sample = NULL;
	void* new_buffer_ptr = NULL;

    BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0);
	BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0);

    internal_sample = ((SoundDecoder_SampleInternal*)sound_sample->opaque);


    new_buffer_ptr = realloc(sound_sample->buffer, new_buffer_size);
    BAIL_IF_MACRO(NULL == new_buffer_ptr, ERR_OUT_OF_MEMORY, 0);

	sound_sample->buffer = new_buffer_ptr;
    sound_sample->buffer_size = new_buffer_size;
	internal_sample->buffer = sound_sample->buffer;
	internal_sample->buffer_size = sound_sample->buffer_size;

    return 1;
}


size_t SoundDecoder_Decode(SoundDecoder_Sample* sound_sample)
{
    SoundDecoder_SampleInternal* internal_sample = NULL;
    size_t bytes_read = 0;

    BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0);
	BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0);
	BAIL_IF_MACRO(sound_sample->flags & SOUND_SAMPLEFLAG_ERROR, ERR_PREVIOUS_SAMPLE_ERROR, 0);
	BAIL_IF_MACRO(sound_sample->flags & SOUND_SAMPLEFLAG_EOF, ERR_ALREADY_AT_EOF_ERROR, 0);

    internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque;

    assert(sound_sample->buffer != NULL);
    assert(sound_sample->buffer_size > 0);
    assert(internal_sample->buffer != NULL);
    assert(internal_sample->buffer_size > 0);

 	/* reset EAGAIN. Decoder can flip it back on if it needs to. */
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN;
    bytes_read = internal_sample->funcs->read(sound_sample);
    return bytes_read;
}


size_t SoundDecoder_DecodeAll(SoundDecoder_Sample* sound_sample)
{
    SoundDecoder_SampleInternal* internal_sample = NULL;
    void* data_buffer = NULL;
    size_t updated_buffer_size = 0;

    BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0);
	BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0);

	/* My original thought was to call SetBufferSize and resize to 
	 * the size needed to hold the entire decoded file utilizing total_time,
	 * but it appears SDL_sound simply loops on SoundDecoder_Decode.
	 * I suppose it is possible to partially decode or seek a file, and then
	 * call DecodeAll so you won't have the whole thing in which case
	 * my idea would waste memory.
	 */
    while( (0 == (sound_sample->flags & SOUND_SAMPLEFLAG_EOF) )
		&& (0 == (sound_sample->flags & SOUND_SAMPLEFLAG_ERROR))
	)
    {
        size_t bytes_decoded = SoundDecoder_Decode(sound_sample);
        void* realloced_ptr = realloc(data_buffer, updated_buffer_size + bytes_decoded);
        if(NULL == realloced_ptr)
        {
            sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR;
            SoundDecoder_SetError(ERR_OUT_OF_MEMORY);
			if(NULL != data_buffer)
			{
				free(data_buffer);
			}
			return bytes_decoded;
        }
		data_buffer = realloced_ptr;
		/* copy the chunk of decoded PCM to the end of our new data buffer */
		memcpy( ((char*)data_buffer) + updated_buffer_size, sound_sample->buffer, bytes_decoded );
		updated_buffer_size += bytes_decoded;
    }

    internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque;
	if(internal_sample->buffer != sound_sample->buffer)
	{
		free(internal_sample->buffer);
	}
    free(sound_sample->buffer);

	sound_sample->buffer = data_buffer;
	sound_sample->buffer_size = updated_buffer_size;
	internal_sample->buffer = sound_sample->buffer;
	internal_sample->buffer_size = sound_sample->buffer_size;

    return sound_sample->buffer_size;
}


int SoundDecoder_Rewind(SoundDecoder_Sample* sound_sample)
{
    SoundDecoder_SampleInternal* internal_sample;
	int ret_val;
	
    BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0);
	BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0);

    internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque;
	ret_val = internal_sample->funcs->rewind(sound_sample);
    if(0 == ret_val)
    {
        sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR;
		SoundDecoder_SetError("Rewind failed");		
        return 0;
    }
	/* reset flags */
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN;
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_ERROR;
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_EOF;

    return 1;
}


int SoundDecoder_Seek(SoundDecoder_Sample* sound_sample, size_t ms)
{
    SoundDecoder_SampleInternal* internal_sample;
	int ret_val;

    BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0);
	BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0);
	
	BAIL_IF_MACRO(!(sound_sample->flags & SOUND_SAMPLEFLAG_CANSEEK), "Sound sample is not seekable", 0);

    internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque;
	ret_val = internal_sample->funcs->seek(sound_sample, ms);
	if(0 == ret_val)
	{
        sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR;		
		SoundDecoder_SetError("Seek failed");
		return 0;
	}
	/* reset flags */
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN;
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_ERROR;
    sound_sample->flags &= ~SOUND_SAMPLEFLAG_EOF;

    return 1;
}


ptrdiff_t SoundDecoder_GetDuration(SoundDecoder_Sample* sound_sample)
{
    SoundDecoder_SampleInternal* internal_sample;
    BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, -1);
	BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0);	
    internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque;
    return internal_sample->total_time;
}

#endif