diff --git a/research/_examples/resource_manager.c b/research/_examples/resource_manager.c new file mode 100644 index 00000000..c5da3885 --- /dev/null +++ b/research/_examples/resource_manager.c @@ -0,0 +1,184 @@ +/* +Demonstrates how you can use the resource manager to manage loaded sounds. + +The resource manager can be used to create a data source whose resources are managed internally by miniaudio. The data +sources can then be read just like any other data source such as decoders and audio buffers. + +In this example we use the resource manager independently of the `ma_engine` API so that we can demonstrate how it can +be used by itself without getting it confused with `ma_engine`. Audio data is mixed using the `ma_mixer` API, but you +can also use data sources with `ma_data_source_read_pcm_frames()` in the same way we do in the simple_looping example. + +The main feature of the resource manager is the ability to decode and stream audio data asynchronously. Asynchronicity +is achieved with a job system. The resource manager will issue jobs which are processed by a configurable number of job +threads. You can also implement your own custom job threads which this example also demonstrates. + +In this example we show how you can create a data source, mix them with other data sources, configure the number of job +threads to manage internally and how to implement your own custom job thread. +*/ +#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */ +#define MA_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../ma_engine.h" + +static ma_mixer g_mixer; +static ma_resource_manager_data_source g_dataSources[16]; +static ma_uint32 g_dataSourceCount; + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + /* In this example we're just going to play our data sources layered on top of each other. */ + ma_uint32 framesProcessed = 0; + while (framesProcessed < frameCount) { + ma_uint64 frameCountIn; + ma_uint64 frameCountOut = (frameCount - framesProcessed); + + ma_mixer_begin(&g_mixer, NULL, &frameCountOut, &frameCountIn); + { + size_t iDataSource; + for (iDataSource = 0; iDataSource < g_dataSourceCount; iDataSource += 1) { + ma_mixer_mix_data_source(&g_mixer, &g_dataSources[iDataSource], frameCountIn, 1, NULL, MA_TRUE); + } + } + ma_mixer_end(&g_mixer, NULL, ma_offset_ptr(pOutput, framesProcessed * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels))); + + framesProcessed += (ma_uint32)frameCountOut; /* Safe cast. */ + } + + (void)pInput; +} + +static ma_thread_result MA_THREADCALL custom_job_thread(void* pUserData) +{ + ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; + MA_ASSERT(pResourceManager != NULL); + + for (;;) { + ma_result result; + ma_job job; + + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + break; + } + + /* Terminate if we got a quit message. */ + if (job.toc.code == MA_JOB_QUIT) { + printf("CUSTOM JOB THREAD TERMINATING... "); + break; + } + + printf("PROCESSING IN CUSTOM JOB THREAD: %d\n", job.toc.code); + ma_resource_manager_process_job(pResourceManager, &job); + } + + printf("TERMINATED\n"); + return (ma_thread_result)0; +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_device_config deviceConfig; + ma_device device; + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + ma_mixer_config mixerConfig; + ma_thread jobThread; + int iFile; + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = NULL; + + result = ma_device_init(NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + printf("Failed to initialize device."); + return -1; + } + + /* + Before starting the device we'll need to initialize the mixer. If we don't do this first, the data callback will be + fired and will try to use the mixer without it being initialized. + */ + mixerConfig = ma_mixer_config_init(device.playback.format, device.playback.channels, 1024, NULL, NULL); + + result = ma_mixer_init(&mixerConfig, &g_mixer); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize mixer."); + return -1; + } + + /* We can start the device before loading any sounds. We'll just end up outputting silence. */ + result = ma_device_start(&device); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to start device."); + return -1; + } + + + /* + We have the device so now we want to initialize the resource manager. We'll use the resource manager to load some + sounds based on the command line. + */ + resourceManagerConfig = ma_resource_manager_config_init( + device.playback.format, + device.playback.channels, + device.sampleRate, + NULL); + + /* Create this number of job threads to be managed internally. Set this to 0 if you want to self-manage your job threads */ + resourceManagerConfig.jobThreadCount = 4; + + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + + /* Now that we have a resource manager we can set up our custom job thread. */ + ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager); + + /* Create each data source from the resource manager. Note that the caller is the owner. */ + for (iFile = 0; iFile < ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) { + result = ma_resource_manager_data_source_init(&resourceManager, argv[iFile+1], MA_DATA_SOURCE_FLAG_DECODE | MA_DATA_SOURCE_FLAG_ASYNC /*| MA_DATA_SOURCE_FLAG_STREAM*/, &g_dataSources[iFile]); + if (result != MA_SUCCESS) { + break; + } + + g_dataSourceCount += 1; + } + + printf("Press Enter to quit..."); + getchar(); + + + /* Teardown. */ + + /* Uninitialize the device first to ensure the data callback is stopped and doesn't try to access any data sources. */ + ma_device_uninit(&device); + + /* + Before uninitializing the resource manager we need to make sure a quit event has been posted to ensure we can get out of our custom thread. The + call to ma_resource_manager_uninit() will also do this, but we need to call it explicitly so that our thread can exit naturally. You only need + to post a quit job if you're using that as the exit indicator. You can instead use whatever variable you want to terminate your job thread, but + since this example is using a quit job we need to post one. + */ + ma_resource_manager_post_job_quit(&resourceManager); + ma_thread_wait(&jobThread); /* Wait for the custom job thread to finish before uninitializing the resource manager. */ + + /* Out data sources need to be explicitly uninitialized. ma_resource_manager_uninit() will not do it for us. */ + for (iFile = 0; (size_t)iFile < g_dataSourceCount; iFile += 1) { + ma_resource_manager_data_source_uninit(&resourceManager, &g_dataSources[iFile]); + } + + /* Uninitialize the resource manager after each data source. */ + ma_resource_manager_uninit(&resourceManager); + + /* We're uninitializing the mixer last, but it doesn't really when it's done, so long as it's after the device has been stopped/uninitialized. */ + ma_mixer_uninit(&g_mixer); + + return 0; +}