From 95ded1d57c5227cc4e433a824d2bf699c1e1e43f Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 7 Jun 2020 15:33:49 +1000 Subject: [PATCH] Fix sound uninitialization. --- research/ma_engine.c | 5 ++ research/ma_engine.h | 200 +++++++++++++++++++++++++++++-------------- 2 files changed, 143 insertions(+), 62 deletions(-) diff --git a/research/ma_engine.c b/research/ma_engine.c index 9a549b6e..52e72e2f 100644 --- a/research/ma_engine.c +++ b/research/ma_engine.c @@ -39,7 +39,11 @@ int main(int argc, char** argv) ma_engine_sound_set_looping(&engine, &sound, MA_TRUE); ma_engine_sound_start(&engine, &sound); + ma_engine_play_sound(&engine, argv[1], NULL); + ma_engine_play_sound(&engine, argv[2], NULL); + ma_engine_play_sound(&engine, argv[3], NULL); +#if 0 float pitch = 1; float pitchStep = 0.01f; float pitchMin = 0.125f; @@ -59,6 +63,7 @@ int main(int argc, char** argv) Sleep(1); } +#endif printf("Press Enter to quit..."); getchar(); diff --git a/research/ma_engine.h b/research/ma_engine.h index 3d83ce07..f7660dc6 100644 --- a/research/ma_engine.h +++ b/research/ma_engine.h @@ -135,7 +135,7 @@ Resource Manager Data Source Flags ================================== The flags below are used for controlling how the resource manager should handle the loading and caching of data sources. */ -#define MA_DATA_SOURCE_FLAG_DECODE 0x00000001 /* Decode data before storing in memory. When set, decoding happens on the resource manager thread rather than the mixing thread. Results in faster mixing, but higher memory usage. */ +#define MA_DATA_SOURCE_FLAG_DECODE 0x00000001 /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ #define MA_DATA_SOURCE_FLAG_STREAM 0x00000002 /* When set, does not load the entire data source in memory. Disk I/O will happen on the resource manager thread. */ #define MA_DATA_SOURCE_FLAG_ASYNC 0x00000004 /* When set, the resource manager will load the data source asynchronously. */ @@ -203,6 +203,7 @@ struct ma_resource_manager_node ma_decoded_data decoded; ma_encoded_data encoded; } data; + ma_bool32 isDataOwnedByResourceManager; ma_resource_manager_node* pParent; ma_resource_manager_node* pChildLo; ma_resource_manager_node* pChildHi; @@ -468,9 +469,9 @@ MA_API ma_result ma_engine_sound_set_volume(ma_engine* pEngine, ma_sound* pSound MA_API ma_result ma_engine_sound_set_gain_db(ma_engine* pEngine, ma_sound* pSound, float gainDB); MA_API ma_result ma_engine_sound_set_pan(ma_engine* pEngine, ma_sound* pSound, float pan); MA_API ma_result ma_engine_sound_set_pitch(ma_engine* pEngine, ma_sound* pSound, float pitch); -MA_API ma_result ma_engine_sound_set_effect(ma_engine* pEngine, ma_sound* pSound, ma_effect* pEffect); MA_API ma_result ma_engine_sound_set_position(ma_engine* pEngine, ma_sound* pSound, ma_vec3 position); MA_API ma_result ma_engine_sound_set_rotation(ma_engine* pEngine, ma_sound* pSound, ma_quat rotation); +MA_API ma_result ma_engine_sound_set_effect(ma_engine* pEngine, ma_sound* pSound, ma_effect* pEffect); MA_API ma_result ma_engine_sound_set_looping(ma_engine* pEngine, ma_sound* pSound, ma_bool32 isLooping); MA_API ma_bool32 ma_engine_sound_at_end(ma_engine* pEngine, const ma_sound* pSound); MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup); /* Fire and forget. */ @@ -1267,6 +1268,17 @@ static ma_result ma_resource_manager_data_source_init(ma_resource_manager* pReso return MA_SUCCESS; } +static void ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource) +{ + MA_ASSERT(pDataSource != NULL); + + if (pDataSource->type == ma_resource_manager_data_source_type_buffer) { + ma_audio_buffer_uninit(&pDataSource->backend.buffer); + } else { + ma_decoder_uninit(&pDataSource->backend.decoder); + } +} + MA_API ma_resource_manager_config ma_resource_manager_config_init(ma_format decodedFormat, ma_uint32 decodedChannels, ma_uint32 decodedSampleRate, const ma_allocation_callbacks* pAllocationCallbacks) @@ -1571,7 +1583,54 @@ static ma_result ma_resource_manager_acquire_data_node(ma_resource_manager* pRes return result; } -static ma_result ma_resource_manager_release_data_node_nolock(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_uint32* pReferenceCount) + +static ma_result ma_resource_manager_release_data_node_nolock(ma_resource_manager* pResourceManager, ma_resource_manager_node* pDataNode, ma_uint32* pReferenceCount) +{ + ma_result result; + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataNode != NULL); + MA_ASSERT(pReferenceCount != NULL); + MA_ASSERT(pDataNode->refCount > 0); /* We should never find the node in the first place if the reference counter is 0. */ + + refCount = ma_atomic_decrement_32(&pDataNode->refCount); + if (refCount == 0) { + /* Standard node delete. */ + result = ma_resource_manager_remove_data_node_nolock(pResourceManager, pDataNode); + if (result != MA_SUCCESS) { + return result; /* This should never happen. */ + } + + /* We've removed the node from the binary tree so now we can free it's memory. */ + ma__free_from_callbacks(pDataNode, &pResourceManager->config.allocationCallbacks); + } + + if (pReferenceCount != NULL) { + *pReferenceCount = refCount; + } + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_release_data_node(ma_resource_manager* pResourceManager, ma_resource_manager_node* pDataNode, ma_uint32* pReferenceCount) +{ + ma_result result; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataNode != NULL); + MA_ASSERT(pReferenceCount != NULL); + + ma_mutex_lock(&pResourceManager->dataNodeLock); + { + result = ma_resource_manager_release_data_node_nolock(pResourceManager, pDataNode, pReferenceCount); + } + ma_mutex_unlock(&pResourceManager->dataNodeLock); + + return result; +} + +static ma_result ma_resource_manager_release_data_node_by_name_nolock(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_uint32* pReferenceCount) { ma_result result; ma_uint32 refCount; @@ -1605,7 +1664,7 @@ static ma_result ma_resource_manager_release_data_node_nolock(ma_resource_manage return MA_SUCCESS; } -static ma_result ma_resource_manager_release_data_node(ma_resource_manager* pResourceManager, const char* pName, ma_uint32* pReferenceCount) +static ma_result ma_resource_manager_release_data_node_by_name(ma_resource_manager* pResourceManager, const char* pName, ma_uint32* pReferenceCount) { ma_result result; ma_uint32 hashedName32; @@ -1617,7 +1676,7 @@ static ma_result ma_resource_manager_release_data_node(ma_resource_manager* pRes ma_mutex_lock(&pResourceManager->dataNodeLock); { - result = ma_resource_manager_release_data_node_nolock(pResourceManager, hashedName32, pReferenceCount); + result = ma_resource_manager_release_data_node_by_name_nolock(pResourceManager, hashedName32, pReferenceCount); } ma_mutex_unlock(&pResourceManager->dataNodeLock); @@ -1702,7 +1761,7 @@ static ma_result ma_resource_manager_register_data(ma_resource_manager* pResourc return result; } -static ma_result ma_resource_manager_register_decoded_data_ex(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_resource_manager_node** ppDataNode) +static ma_result ma_resource_manager_register_decoded_data_ex(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_bool32 isOwnedByResourceManager, ma_resource_manager_node** ppDataNode) { ma_resource_manager_node node; @@ -1710,22 +1769,23 @@ static ma_result ma_resource_manager_register_decoded_data_ex(ma_resource_manage return MA_INVALID_ARGS; } - node.type = ma_resource_manager_data_type_decoded; - node.data.decoded.pData = pData; - node.data.decoded.frameCount = frameCount; - node.data.decoded.format = format; - node.data.decoded.channels = channels; - node.data.decoded.sampleRate = sampleRate; + node.isDataOwnedByResourceManager = isOwnedByResourceManager; + node.type = ma_resource_manager_data_type_decoded; + node.data.decoded.pData = pData; + node.data.decoded.frameCount = frameCount; + node.data.decoded.format = format; + node.data.decoded.channels = channels; + node.data.decoded.sampleRate = sampleRate; return ma_resource_manager_register_data(pResourceManager, pName, &node, ppDataNode); } MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { - return ma_resource_manager_register_decoded_data_ex(pResourceManager, pName, pData, frameCount, format, channels, sampleRate, NULL); + return ma_resource_manager_register_decoded_data_ex(pResourceManager, pName, pData, frameCount, format, channels, sampleRate, MA_FALSE, NULL); } -static ma_result ma_resource_manager_register_encoded_data_ex(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes, ma_resource_manager_node** ppDataNode) +static ma_result ma_resource_manager_register_encoded_data_ex(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes, ma_bool32 isOwnedByResourceManager, ma_resource_manager_node** ppDataNode) { ma_resource_manager_node node; @@ -1733,21 +1793,22 @@ static ma_result ma_resource_manager_register_encoded_data_ex(ma_resource_manage return MA_INVALID_ARGS; } - node.type = ma_resource_manager_data_type_encoded; - node.data.encoded.pData = pData; - node.data.encoded.sizeInBytes = sizeInBytes; + node.isDataOwnedByResourceManager = isOwnedByResourceManager; + node.type = ma_resource_manager_data_type_encoded; + node.data.encoded.pData = pData; + node.data.encoded.sizeInBytes = sizeInBytes; return ma_resource_manager_register_data(pResourceManager, pName, &node, ppDataNode); } MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes) { - return ma_resource_manager_register_encoded_data_ex(pResourceManager, pName, pData, sizeInBytes, NULL); + return ma_resource_manager_register_encoded_data_ex(pResourceManager, pName, pData, sizeInBytes, MA_FALSE, NULL); } MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName) { - return ma_resource_manager_release_data_node(pResourceManager, pName, NULL); + return ma_resource_manager_release_data_node_by_name(pResourceManager, pName, NULL); } MA_API ma_result ma_resource_manager_create_data_source(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, ma_data_source** ppDataSource) @@ -1756,9 +1817,6 @@ MA_API ma_result ma_resource_manager_create_data_source(ma_resource_manager* pRe ma_resource_manager_node* pDataNode; ma_resource_manager_data_source* pDataSource; - ma_decoder* pDecoder; - ma_decoder_config decoderConfig; - if (ppDataSource == NULL) { return MA_INVALID_ARGS; } @@ -1798,7 +1856,7 @@ MA_API ma_result ma_resource_manager_create_data_source(ma_resource_manager* pRe } /* We have the encoded data so now we need to register it. */ - result = ma_resource_manager_register_encoded_data_ex(pResourceManager, pName, pFileData, fileSize, &pDataNode); + result = ma_resource_manager_register_encoded_data_ex(pResourceManager, pName, pFileData, fileSize, MA_TRUE, &pDataNode); if (result != MA_SUCCESS) { ma__free_from_callbacks(pFileData, &pResourceManager->config.allocationCallbacks); return result; @@ -1809,8 +1867,7 @@ MA_API ma_result ma_resource_manager_create_data_source(ma_resource_manager* pRe ma_uint64 frameCount; void* pDecodedData; - //config = ma_decoder_config_init(ma_format_unknown, 0, 0); /* For now we'll decode into native format, but we may want to change this to the standard output format. */ - config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate); + config = ma_decoder_config_init(pResourceManager->config.decodedFormat, 0, pResourceManager->config.decodedSampleRate); /* Need to keep the native channel count because we'll be using that for spatialization. */ config.allocationCallbacks = pResourceManager->config.allocationCallbacks; result = ma_decode_from_vfs(pResourceManager->config.pVFS, pName, &config, &frameCount, &pDecodedData); @@ -1819,7 +1876,7 @@ MA_API ma_result ma_resource_manager_create_data_source(ma_resource_manager* pRe } /* We have the decoded data so now we need to register it. */ - result = ma_resource_manager_register_decoded_data_ex(pResourceManager, pName, pDecodedData, frameCount, config.format, config.channels, config.sampleRate, &pDataNode); + result = ma_resource_manager_register_decoded_data_ex(pResourceManager, pName, pDecodedData, frameCount, config.format, config.channels, config.sampleRate, MA_TRUE, &pDataNode); if (result != MA_SUCCESS) { ma__free_from_callbacks(pDecodedData, &pResourceManager->config.allocationCallbacks); return result; @@ -1845,41 +1902,41 @@ MA_API ma_result ma_resource_manager_create_data_source(ma_resource_manager* pRe *ppDataSource = pDataSource; return MA_SUCCESS; - - -#if 0 - /* For testing and prototyping we're just allocating a decoder on the heap. Later on this will be a custom resource manager specific data source. */ - pDecoder = ma_malloc(sizeof(*pDecoder), NULL); - if (pDecoder == NULL) { - return MA_OUT_OF_MEMORY; - } - - decoderConfig = ma_decoder_config_init(pResourceManager->config.decodedFormat, 0, pResourceManager->config.decodedSampleRate); - result = ma_decoder_init_file(pName, &decoderConfig, pDecoder); - if (result != MA_SUCCESS) { - ma_free(pDecoder, NULL); - return result; - } - - *ppDataSource = (ma_data_source*)pDecoder; - - return MA_SUCCESS; -#endif } MA_API ma_result ma_resource_manager_delete_data_source(ma_resource_manager* pResourceManager, ma_data_source* pDataSource) { - ma_decoder* pDecoder; + ma_result result; + ma_resource_manager_data_source* pRMDataSource = (ma_resource_manager_data_source*)pDataSource; + ma_resource_manager_node* pDataNode = NULL; if (pResourceManager == NULL || pDataSource == NULL) { return MA_INVALID_ARGS; } - /* Everything is a ma_decoder while we're prototyping. */ - pDecoder = (ma_decoder*)pDataSource; - ma_decoder_uninit(pDecoder); + /* If the data source is not being streamed we will need to unacquire the data source so the reference counter is decremented. */ + pDataNode = pRMDataSource->pDataNode; + if (pDataNode != NULL) { + ma_resource_manager_node nodeCopy = *pDataNode; /* We need this so we can keep track of the pointer. */ + ma_uint32 refCount; + result = ma_resource_manager_release_data_node(pResourceManager, pDataNode, &refCount); + if (result == MA_SUCCESS) { + if (refCount == 0) { + /* The backing data of the data source needs to be deleted if it's owned by us. */ + if (pDataNode->isDataOwnedByResourceManager) { + if (nodeCopy.type == ma_resource_manager_data_type_encoded) { + ma__free_from_callbacks((void*)nodeCopy.data.encoded.pData, &pResourceManager->config.allocationCallbacks); /* MA_ALLOCATION_TYPE_ENCODED_BUFFER */ + } else { + ma__free_from_callbacks((void*)nodeCopy.data.decoded.pData, &pResourceManager->config.allocationCallbacks); /* MA_ALLOCATION_TYPE_DECODED_BUFFER */ + } + } + } + } + } - ma_free(pDecoder, NULL); + /* The data source can now be freed. */ + ma_resource_manager_data_source_uninit(pRMDataSource); + ma__free_from_callbacks(pRMDataSource, &pResourceManager->config.allocationCallbacks); /* MA_ALLOCATION_TYPE_RESOURCE_MANAGER_DATA_SOURCE */ return MA_SUCCESS; } @@ -3182,17 +3239,6 @@ MA_API ma_result ma_engine_sound_set_pan(ma_engine* pEngine, ma_sound* pSound, f return ma_panner_set_pan(&pSound->effect.panner, pan); } -MA_API ma_result ma_engine_sound_set_effect(ma_engine* pEngine, ma_sound* pSound, ma_effect* pEffect) -{ - if (pEngine == NULL || pSound == NULL) { - return MA_INVALID_ARGS; - } - - pSound->effect.pPreEffect = pEffect; - - return MA_SUCCESS; -} - MA_API ma_result ma_engine_sound_set_position(ma_engine* pEngine, ma_sound* pSound, ma_vec3 position) { if (pEngine == NULL || pSound == NULL) { @@ -3211,6 +3257,17 @@ MA_API ma_result ma_engine_sound_set_rotation(ma_engine* pEngine, ma_sound* pSou return ma_spatializer_set_rotation(&pSound->effect.spatializer, rotation); } +MA_API ma_result ma_engine_sound_set_effect(ma_engine* pEngine, ma_sound* pSound, ma_effect* pEffect) +{ + if (pEngine == NULL || pSound == NULL) { + return MA_INVALID_ARGS; + } + + pSound->effect.pPreEffect = pEffect; + + return MA_SUCCESS; +} + MA_API ma_result ma_engine_sound_set_looping(ma_engine* pEngine, ma_sound* pSound, ma_bool32 isLooping) { if (pEngine == NULL || pSound == NULL) { @@ -3457,6 +3514,22 @@ MA_API ma_result ma_engine_sound_group_init(ma_engine* pEngine, ma_sound_group* return MA_SUCCESS; } +static void ma_engine_sound_group_uninit_all_internal_sounds(ma_engine* pEngine, ma_sound_group* pGroup) +{ + ma_sound* pCurrentSound; + + /* We need to be careful here that we keep our iteration valid. */ + pCurrentSound = pGroup->pFirstSoundInGroup; + while (pCurrentSound != NULL) { + ma_sound* pSoundToDelete = pCurrentSound; + pCurrentSound = pCurrentSound->pNextSoundInGroup; + + if (pSoundToDelete->_isInternal) { + ma_engine_sound_uninit(pEngine, pSoundToDelete); + } + } +} + MA_API void ma_engine_sound_group_uninit(ma_engine* pEngine, ma_sound_group* pGroup) { ma_result result; @@ -3466,6 +3539,9 @@ MA_API void ma_engine_sound_group_uninit(ma_engine* pEngine, ma_sound_group* pGr MA_ASSERT(MA_FALSE); /* Should never happen. Trigger an assert for debugging, but don't stop uninitializing in production to ensure we free memory down below. */ } + /* Any in-place sounds need to be uninitialized. */ + ma_engine_sound_group_uninit_all_internal_sounds(pEngine, pGroup); + result = ma_engine_sound_group_detach(pEngine, pGroup); if (result != MA_SUCCESS) { MA_ASSERT(MA_FALSE); /* As above, should never happen, but just in case trigger an assert in debug mode, but continue processing. */