From b0736de6b1e60107a01954440a1ddc2b894682a2 Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 9 Sep 2020 19:15:40 +1000 Subject: [PATCH] Add support for initializing an engine using a pre-initialized device. --- research/miniaudio_engine.h | 265 ++++++++++++++++++------------------ 1 file changed, 132 insertions(+), 133 deletions(-) diff --git a/research/miniaudio_engine.h b/research/miniaudio_engine.h index c728d496..f93897b2 100644 --- a/research/miniaudio_engine.h +++ b/research/miniaudio_engine.h @@ -1565,8 +1565,6 @@ struct ma_sound_group struct ma_listener { - ma_device device; /* The playback device associated with this listener. */ - ma_pcm_rb fixedRB; /* The intermediary ring buffer for helping with fixed sized updates. */ ma_vec3 position; ma_quat rotation; }; @@ -1574,6 +1572,9 @@ struct ma_listener typedef struct { ma_resource_manager* pResourceManager; /* Can be null in which case a resource manager will be created for you. */ + ma_context* pContext; + ma_device* pDevice; /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + ma_pcm_rb fixedRB; /* The intermediary ring buffer for helping with fixed sized updates. */ ma_format format; /* The format to use when mixing and spatializing. When set to 0 will use the native format of the device. */ ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */ @@ -1591,7 +1592,8 @@ MA_API ma_engine_config ma_engine_config_init_default(); struct ma_engine { ma_resource_manager* pResourceManager; - ma_context context; + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ + ma_pcm_rb fixedRB; /* The intermediary ring buffer for helping with fixed sized updates. */ ma_listener listener; ma_sound_group masterSoundGroup; /* Sounds are associated with this group by default. */ ma_format format; @@ -1601,10 +1603,12 @@ struct ma_engine ma_uint32 periodSizeInMilliseconds; ma_allocation_callbacks allocationCallbacks; ma_bool32 ownsResourceManager : 1; + ma_bool32 ownsDevice : 1; }; MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine); MA_API void ma_engine_uninit(ma_engine* pEngine); +MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pOutput, const void* pInput, ma_uint32 frameCount); MA_API ma_result ma_engine_start(ma_engine* pEngine); MA_API ma_result ma_engine_stop(ma_engine* pEngine); @@ -8460,108 +8464,9 @@ static void ma_engine_listener__data_callback_fixed(ma_engine* pEngine, void* pF ma_engine_mix_sound_group(pEngine, &pEngine->masterSoundGroup, pFramesOut, frameCount); } -static void ma_engine_listener__data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) { - ma_uint32 pcmFramesAvailableInRB; - ma_uint32 pcmFramesProcessed = 0; - ma_uint8* pRunningOutput = (ma_uint8*)pFramesOut; - ma_engine* pEngine = (ma_engine*)pDevice->pUserData; - MA_ASSERT(pEngine != NULL); - - /* We need to do updates in fixed sizes based on the engine's period size in frames. */ - - /* - The first thing to do is check if there's enough data available in the ring buffer. If so we can read from it. Otherwise we need to keep filling - the ring buffer until there's enough, making sure we only fill the ring buffer in chunks of pEngine->periodSizeInFrames. - */ - while (pcmFramesProcessed < frameCount) { /* Keep going until we've filled the output buffer. */ - ma_uint32 framesRemaining = frameCount - pcmFramesProcessed; - - pcmFramesAvailableInRB = ma_pcm_rb_available_read(&pEngine->listener.fixedRB); - if (pcmFramesAvailableInRB > 0) { - ma_uint32 framesToRead = (framesRemaining < pcmFramesAvailableInRB) ? framesRemaining : pcmFramesAvailableInRB; - void* pReadBuffer; - - ma_pcm_rb_acquire_read(&pEngine->listener.fixedRB, &framesToRead, &pReadBuffer); - { - memcpy(pRunningOutput, pReadBuffer, framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels)); - } - ma_pcm_rb_commit_read(&pEngine->listener.fixedRB, framesToRead, pReadBuffer); - - pRunningOutput += framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - pcmFramesProcessed += framesToRead; - } else { - /* - There's nothing in the buffer. Fill it with more data from the callback. We reset the buffer first so that the read and write pointers - are reset back to the start so we can fill the ring buffer in chunks of pEngine->periodSizeInFrames which is what we initialized it - with. Note that this is not how you would want to do it in a multi-threaded environment. In this case you would want to seek the write - pointer forward via the producer thread and the read pointer forward via the consumer thread (this thread). - */ - ma_uint32 framesToWrite = pEngine->periodSizeInFrames; - void* pWriteBuffer; - - ma_pcm_rb_reset(&pEngine->listener.fixedRB); - ma_pcm_rb_acquire_write(&pEngine->listener.fixedRB, &framesToWrite, &pWriteBuffer); - { - MA_ASSERT(framesToWrite == pEngine->periodSizeInFrames); /* <-- This should always work in this example because we just reset the ring buffer. */ - ma_engine_listener__data_callback_fixed(pEngine, pWriteBuffer, framesToWrite); - } - ma_pcm_rb_commit_write(&pEngine->listener.fixedRB, framesToWrite, pWriteBuffer); - } - } - - (void)pFramesIn; -} - - -static ma_result ma_engine_listener_init(ma_engine* pEngine, const ma_device_id* pPlaybackDeviceID, ma_listener* pListener) -{ - ma_result result; - ma_device_config deviceConfig; - - if (pListener == NULL) { - return MA_INVALID_ARGS; - } - - MA_ZERO_OBJECT(pListener); - - if (pEngine == NULL) { - return MA_INVALID_ARGS; - } - - deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.playback.pDeviceID = pPlaybackDeviceID; - deviceConfig.playback.format = pEngine->format; - deviceConfig.playback.channels = pEngine->channels; - deviceConfig.sampleRate = pEngine->sampleRate; - deviceConfig.dataCallback = ma_engine_listener__data_callback; - deviceConfig.pUserData = pEngine; - deviceConfig.periodSizeInFrames = pEngine->periodSizeInFrames; - deviceConfig.periodSizeInMilliseconds = pEngine->periodSizeInMilliseconds; - deviceConfig.noPreZeroedOutputBuffer = MA_TRUE; /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */ - deviceConfig.noClip = MA_TRUE; /* The mixing engine will do clipping itself. */ - - result = ma_device_init(&pEngine->context, &deviceConfig, &pListener->device); - if (result != MA_SUCCESS) { - return result; - } - - /* With the device initialized we need an intermediary buffer for handling fixed sized updates. Currently using a ring buffer for this, but can probably use something a bit more optimal. */ - result = ma_pcm_rb_init(pListener->device.playback.format, pListener->device.playback.channels, pListener->device.playback.internalPeriodSizeInFrames, NULL, &pEngine->allocationCallbacks, &pListener->fixedRB); - if (result != MA_SUCCESS) { - return result; - } - - return MA_SUCCESS; -} - -static void ma_engine_listener_uninit(ma_engine* pEngine, ma_listener* pListener) -{ - if (pEngine == NULL || pListener == NULL) { - return; - } - - ma_device_uninit(&pListener->device); + ma_engine_data_callback((ma_engine*)pDevice->pUserData, pFramesOut, pFramesIn, frameCount); } MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) @@ -8585,6 +8490,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng } pEngine->pResourceManager = engineConfig.pResourceManager; + pEngine->pDevice = engineConfig.pDevice; pEngine->format = engineConfig.format; pEngine->channels = engineConfig.channels; pEngine->sampleRate = engineConfig.sampleRate; @@ -8597,32 +8503,55 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng contextConfig = ma_context_config_init(); contextConfig.allocationCallbacks = pEngine->allocationCallbacks; - result = ma_context_init(NULL, 0, &contextConfig, &pEngine->context); - if (result != MA_SUCCESS) { - return result; /* Failed to initialize context. */ + /* If we don't have a device, we need one. */ + if (pEngine->pDevice == NULL) { + ma_device_config deviceConfig; + + pEngine->pDevice = (ma_device*)ma__malloc_from_callbacks(sizeof(*pEngine->pDevice), &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/); + if (pEngine->pDevice == NULL) { + return MA_OUT_OF_MEMORY; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = engineConfig.pPlaybackDeviceID; + deviceConfig.playback.format = pEngine->format; + deviceConfig.playback.channels = pEngine->channels; + deviceConfig.sampleRate = pEngine->sampleRate; + deviceConfig.dataCallback = ma_engine_data_callback_internal; + deviceConfig.pUserData = pEngine; + deviceConfig.periodSizeInFrames = pEngine->periodSizeInFrames; + deviceConfig.periodSizeInMilliseconds = pEngine->periodSizeInMilliseconds; + deviceConfig.noPreZeroedOutputBuffer = MA_TRUE; /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */ + deviceConfig.noClip = MA_TRUE; /* The mixing engine will do clipping itself. */ + + result = ma_device_init(engineConfig.pContext, &deviceConfig, pEngine->pDevice); + if (result != MA_SUCCESS) { + ma__free_from_callbacks(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/); + pEngine->pDevice = NULL; + return result; + } + + pEngine->ownsDevice = MA_TRUE; } - /* With the context create we can now create the default listener. After we have the listener we can set the format, channels and sample rate appropriately. */ - result = ma_engine_listener_init(pEngine, engineConfig.pPlaybackDeviceID, &pEngine->listener); + /* With the device initialized we need an intermediary buffer for handling fixed sized updates. Currently using a ring buffer for this, but can probably use something a bit more optimal. */ + result = ma_pcm_rb_init(pEngine->pDevice->playback.format, pEngine->pDevice->playback.channels, pEngine->pDevice->playback.internalPeriodSizeInFrames, NULL, &pEngine->allocationCallbacks, &pEngine->fixedRB); if (result != MA_SUCCESS) { - ma_context_uninit(&pEngine->context); - return result; /* Failed to initialize default listener. */ + goto on_error_1; } /* Now that have the default listener we can ensure we have the format, channels and sample rate set to proper values to ensure future listeners are configured consistently. */ - pEngine->format = pEngine->listener.device.playback.format; - pEngine->channels = pEngine->listener.device.playback.channels; - pEngine->sampleRate = pEngine->listener.device.sampleRate; - pEngine->periodSizeInFrames = pEngine->listener.device.playback.internalPeriodSizeInFrames; + pEngine->format = pEngine->pDevice->playback.format; + pEngine->channels = pEngine->pDevice->playback.channels; + pEngine->sampleRate = pEngine->pDevice->sampleRate; + pEngine->periodSizeInFrames = pEngine->pDevice->playback.internalPeriodSizeInFrames; pEngine->periodSizeInMilliseconds = (pEngine->periodSizeInFrames * pEngine->sampleRate) / 1000; /* We need a default sound group. This must be done after setting the format, channels and sample rate to their proper values. */ result = ma_sound_group_init(pEngine, NULL, &pEngine->masterSoundGroup); if (result != MA_SUCCESS) { - ma_engine_listener_uninit(pEngine, &pEngine->listener); - ma_context_uninit(&pEngine->context); - return result; /* Failed to initialize master sound group. */ + goto on_error_2; /* Failed to initialize master sound group. */ } @@ -8633,10 +8562,8 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng pEngine->pResourceManager = (ma_resource_manager*)ma__malloc_from_callbacks(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks); if (pEngine->pResourceManager == NULL) { - ma_sound_group_uninit(&pEngine->masterSoundGroup); - ma_engine_listener_uninit(pEngine, &pEngine->listener); - ma_context_uninit(&pEngine->context); - return MA_OUT_OF_MEMORY; + result = MA_OUT_OF_MEMORY; + goto on_error_3; } resourceManagerConfig = ma_resource_manager_config_init(); @@ -8648,11 +8575,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks); - ma_sound_group_uninit(&pEngine->masterSoundGroup); - ma_engine_listener_uninit(pEngine, &pEngine->listener); - ma_context_uninit(&pEngine->context); - return result; + goto on_error_4; } pEngine->ownsResourceManager = MA_TRUE; @@ -8669,6 +8592,24 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng } return MA_SUCCESS; + +#ifndef MA_NO_RESOURCE_MANAGER +on_error_4: + if (pEngine->ownsResourceManager) { + ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +on_error_3: + ma_sound_group_uninit(&pEngine->masterSoundGroup); +#endif /* MA_NO_RESOURCE_MANAGER */ +on_error_2: + ma_pcm_rb_uninit(&pEngine->fixedRB); +on_error_1: + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma__free_from_callbacks(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/); + } + + return result; } MA_API void ma_engine_uninit(ma_engine* pEngine) @@ -8678,8 +8619,11 @@ MA_API void ma_engine_uninit(ma_engine* pEngine) } ma_sound_group_uninit(&pEngine->masterSoundGroup); - ma_engine_listener_uninit(pEngine, &pEngine->listener); - ma_context_uninit(&pEngine->context); + + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma__free_from_callbacks(pEngine->pDevice, &pEngine->allocationCallbacks/*, MA_ALLOCATION_TYPE_CONTEXT*/); + } /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */ #ifndef MA_NO_RESOURCE_MANAGER @@ -8690,6 +8634,61 @@ MA_API void ma_engine_uninit(ma_engine* pEngine) #endif } +MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_uint32 pcmFramesAvailableInRB; + ma_uint32 pcmFramesProcessed = 0; + ma_uint8* pRunningOutput = (ma_uint8*)pFramesOut; + + if (pEngine == NULL) { + return; + } + + /* We need to do updates in fixed sizes based on the engine's period size in frames. */ + + /* + The first thing to do is check if there's enough data available in the ring buffer. If so we can read from it. Otherwise we need to keep filling + the ring buffer until there's enough, making sure we only fill the ring buffer in chunks of pEngine->periodSizeInFrames. + */ + while (pcmFramesProcessed < frameCount) { /* Keep going until we've filled the output buffer. */ + ma_uint32 framesRemaining = frameCount - pcmFramesProcessed; + + pcmFramesAvailableInRB = ma_pcm_rb_available_read(&pEngine->fixedRB); + if (pcmFramesAvailableInRB > 0) { + ma_uint32 framesToRead = (framesRemaining < pcmFramesAvailableInRB) ? framesRemaining : pcmFramesAvailableInRB; + void* pReadBuffer; + + ma_pcm_rb_acquire_read(&pEngine->fixedRB, &framesToRead, &pReadBuffer); + { + memcpy(pRunningOutput, pReadBuffer, framesToRead * ma_get_bytes_per_frame(pEngine->pDevice->playback.format, pEngine->pDevice->playback.channels)); + } + ma_pcm_rb_commit_read(&pEngine->fixedRB, framesToRead, pReadBuffer); + + pRunningOutput += framesToRead * ma_get_bytes_per_frame(pEngine->pDevice->playback.format, pEngine->pDevice->playback.channels); + pcmFramesProcessed += framesToRead; + } else { + /* + There's nothing in the buffer. Fill it with more data from the callback. We reset the buffer first so that the read and write pointers + are reset back to the start so we can fill the ring buffer in chunks of pEngine->periodSizeInFrames which is what we initialized it + with. Note that this is not how you would want to do it in a multi-threaded environment. In this case you would want to seek the write + pointer forward via the producer thread and the read pointer forward via the consumer thread (this thread). + */ + ma_uint32 framesToWrite = pEngine->periodSizeInFrames; + void* pWriteBuffer; + + ma_pcm_rb_reset(&pEngine->fixedRB); + ma_pcm_rb_acquire_write(&pEngine->fixedRB, &framesToWrite, &pWriteBuffer); + { + MA_ASSERT(framesToWrite == pEngine->periodSizeInFrames); /* <-- This should always work in this example because we just reset the ring buffer. */ + ma_engine_listener__data_callback_fixed(pEngine, pWriteBuffer, framesToWrite); + } + ma_pcm_rb_commit_write(&pEngine->fixedRB, framesToWrite, pWriteBuffer); + } + } + + (void)pFramesIn; /* Not doing anything with input right now. */ +} + MA_API ma_result ma_engine_start(ma_engine* pEngine) { @@ -8699,7 +8698,7 @@ MA_API ma_result ma_engine_start(ma_engine* pEngine) return MA_INVALID_ARGS; } - result = ma_device_start(&pEngine->listener.device); + result = ma_device_start(pEngine->pDevice); if (result != MA_SUCCESS) { return result; } @@ -8715,7 +8714,7 @@ MA_API ma_result ma_engine_stop(ma_engine* pEngine) return MA_INVALID_ARGS; } - result = ma_device_stop(&pEngine->listener.device); + result = ma_device_stop(pEngine->pDevice); if (result != MA_SUCCESS) { return result; } @@ -8729,7 +8728,7 @@ MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume) return MA_INVALID_ARGS; } - return ma_device_set_master_volume(&pEngine->listener.device, volume); + return ma_device_set_master_volume(pEngine->pDevice, volume); } MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB) @@ -8738,7 +8737,7 @@ MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB) return MA_INVALID_ARGS; } - return ma_device_set_master_gain_db(&pEngine->listener.device, gainDB); + return ma_device_set_master_gain_db(pEngine->pDevice, gainDB); }