view Isolated/SoundDecoder.c @ 79:358b0bd5df43 tip

Added support for Apportable's alcSuspend()/alcResume() in BeginInterruption()/EndInterruption. You must define ALMIXER_USE_APPORTABLE_OPENAL_EXTENSIONS to compile in this support.
author Eric Wing <ewing@coronalabs.com>
date Tue, 30 Oct 2012 16:01:30 -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