Commit 2671e075 authored by David Reid's avatar David Reid

Add examples for the high level API.

parent eac61ddc
/*
Demonstrates how to implement a custom decoder and use it with the high level API.
This is the same as the custom_decoder example, only it's used with the high level engine API
rather than the low level decoding API. You can use this to add support for Opus to your games, for
example (via libopus).
*/
#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */
#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../../extras/miniaudio_libvorbis.h"
#include "../../extras/miniaudio_libopus.h"
#include <stdio.h>
static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libvorbis* pVorbis;
(void)pUserData;
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
if (pVorbis == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis);
if (result != MA_SUCCESS) {
ma_free(pVorbis, pAllocationCallbacks);
return result;
}
*ppBackend = pVorbis;
return MA_SUCCESS;
}
static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libvorbis* pVorbis;
(void)pUserData;
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
if (pVorbis == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis);
if (result != MA_SUCCESS) {
ma_free(pVorbis, pAllocationCallbacks);
return result;
}
*ppBackend = pVorbis;
return MA_SUCCESS;
}
static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
(void)pUserData;
ma_libvorbis_uninit(pVorbis, pAllocationCallbacks);
ma_free(pVorbis, pAllocationCallbacks);
}
static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
(void)pUserData;
return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap);
}
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis =
{
ma_decoding_backend_init__libvorbis,
ma_decoding_backend_init_file__libvorbis,
NULL, /* onInitFileW() */
NULL, /* onInitMemory() */
ma_decoding_backend_uninit__libvorbis
};
static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libopus* pOpus;
(void)pUserData;
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
if (pOpus == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus);
if (result != MA_SUCCESS) {
ma_free(pOpus, pAllocationCallbacks);
return result;
}
*ppBackend = pOpus;
return MA_SUCCESS;
}
static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libopus* pOpus;
(void)pUserData;
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
if (pOpus == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus);
if (result != MA_SUCCESS) {
ma_free(pOpus, pAllocationCallbacks);
return result;
}
*ppBackend = pOpus;
return MA_SUCCESS;
}
static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_libopus* pOpus = (ma_libopus*)pBackend;
(void)pUserData;
ma_libopus_uninit(pOpus, pAllocationCallbacks);
ma_free(pOpus, pAllocationCallbacks);
}
static ma_result ma_decoding_backend_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
{
ma_libopus* pOpus = (ma_libopus*)pBackend;
(void)pUserData;
return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap);
}
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus =
{
ma_decoding_backend_init__libopus,
ma_decoding_backend_init_file__libopus,
NULL, /* onInitFileW() */
NULL, /* onInitMemory() */
ma_decoding_backend_uninit__libopus
};
int main(int argc, char** argv)
{
ma_result result;
ma_resource_manager_config resourceManagerConfig;
ma_resource_manager resourceManager;
ma_engine_config engineConfig;
ma_engine engine;
/*
Add your custom backend vtables here. The order in the array defines the order of priority. The
vtables will be passed in to the resource manager config.
*/
ma_decoding_backend_vtable* pCustomBackendVTables[] =
{
&g_ma_decoding_backend_vtable_libvorbis,
&g_ma_decoding_backend_vtable_libopus
};
if (argc < 2) {
printf("No input file.\n");
return -1;
}
/* Using custom decoding backends requires a resource manager. */
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables;
resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
resourceManagerConfig.pCustomDecodingBackendUserData = NULL; /* <-- This will be passed in to the pUserData parameter of each function in the decoding backend vtables. */
result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager);
if (result != MA_SUCCESS) {
printf("Failed to initialize resource manager.");
return -1;
}
/* Once we have a resource manager we can create the engine. */
engineConfig = ma_engine_config_init();
engineConfig.pResourceManager = &resourceManager;
result = ma_engine_init(&engineConfig, &engine);
if (result != MA_SUCCESS) {
printf("Failed to initialize engine.");
return -1;
}
/* Now we can play our sound. */
result = ma_engine_play_sound(&engine, argv[1], NULL);
if (result != MA_SUCCESS) {
printf("Failed to play sound.");
return -1;
}
printf("Press Enter to quit...");
getchar();
return 0;
}
\ No newline at end of file
/*
Demonstrates how to apply an effect to a duplex stream using the node graph system.
This example applies a vocoder effect to the input stream before outputting it. A custom node
called `ma_vocoder_node` is used to achieve the effect which can be found in the extras folder in
the miniaudio repository. The vocoder node uses https://github.com/blastbay/voclib to achieve the
effect.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
#include "../_extras/nodes/ma_vocoder_node/ma_vocoder_node.c"
#include <stdio.h>
#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
static ma_waveform g_sourceData; /* The underlying data source of the excite node. */
static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the source node. */
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
static ma_node_graph g_nodeGraph;
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
MA_ASSERT(pDevice->capture.format == pDevice->playback.format);
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
/*
The node graph system is a pulling style of API. At the lowest level of the chain will be a
node acting as a data source for the purpose of delivering the initial audio data. In our case,
the data source is our `pInput` buffer. We need to update the underlying data source so that it
read data from `pInput`.
*/
ma_audio_buffer_ref_set_data(&g_exciteData, pInput, frameCount);
/* With the source buffer configured we can now read directly from the node graph. */
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
}
int main(int argc, char** argv)
{
ma_result result;
ma_device_config deviceConfig;
ma_device device;
ma_node_graph_config nodeGraphConfig;
ma_vocoder_node_config vocoderNodeConfig;
ma_data_source_node_config sourceNodeConfig;
ma_data_source_node_config exciteNodeConfig;
ma_waveform_config waveformConfig;
deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.capture.pDeviceID = NULL;
deviceConfig.capture.format = DEVICE_FORMAT;
deviceConfig.capture.channels = DEVICE_CHANNELS;
deviceConfig.capture.shareMode = ma_share_mode_shared;
deviceConfig.playback.pDeviceID = NULL;
deviceConfig.playback.format = DEVICE_FORMAT;
deviceConfig.playback.channels = DEVICE_CHANNELS;
deviceConfig.dataCallback = data_callback;
result = ma_device_init(NULL, &deviceConfig, &device);
if (result != MA_SUCCESS) {
return result;
}
/* Now we can setup our node graph. */
nodeGraphConfig = ma_node_graph_config_init(device.capture.channels);
result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph);
if (result != MA_SUCCESS) {
printf("Failed to initialize node graph.");
goto done0;
}
/* Vocoder. Attached straight to the endpoint. */
vocoderNodeConfig = ma_vocoder_node_config_init(device.capture.channels, device.sampleRate);
result = ma_vocoder_node_init(&g_nodeGraph, &vocoderNodeConfig, NULL, &g_vocoderNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize vocoder node.");
goto done1;
}
ma_node_attach_output_bus(&g_vocoderNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
/* Amplify the volume of the vocoder output because in my testing it is a bit quiet. */
ma_node_set_output_bus_volume(&g_vocoderNode, 0, 4);
/* Source/carrier. Attached to input bus 0 of the vocoder node. */
waveformConfig = ma_waveform_config_init(device.capture.format, device.capture.channels, device.sampleRate, ma_waveform_type_sawtooth, 1.0, 50);
result = ma_waveform_init(&waveformConfig, &g_sourceData);
if (result != MA_SUCCESS) {
printf("Failed to initialize waveform for excite node.");
goto done3;
}
sourceNodeConfig = ma_data_source_node_config_init(&g_sourceData, MA_FALSE);
result = ma_data_source_node_init(&g_nodeGraph, &sourceNodeConfig, NULL, &g_sourceNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize excite node.");
goto done3;
}
ma_node_attach_output_bus(&g_sourceNode, 0, &g_vocoderNode, 0);
/* Excite/modulator. Attached to input bus 1 of the vocoder node. */
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_exciteData);
if (result != MA_SUCCESS) {
printf("Failed to initialize audio buffer for source.");
goto done2;
}
exciteNodeConfig = ma_data_source_node_config_init(&g_exciteData, MA_FALSE);
result = ma_data_source_node_init(&g_nodeGraph, &exciteNodeConfig, NULL, &g_exciteNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize source node.");
goto done2;
}
ma_node_attach_output_bus(&g_exciteNode, 0, &g_vocoderNode, 1);
ma_device_start(&device);
printf("Press Enter to quit...\n");
getchar();
/* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */
ma_device_stop(&device);
/*done4:*/ ma_data_source_node_uninit(&g_exciteNode, NULL);
done3: ma_data_source_node_uninit(&g_sourceNode, NULL);
done2: ma_vocoder_node_uninit(&g_vocoderNode, NULL);
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
done0: ma_device_uninit(&device);
(void)argc;
(void)argv;
return 0;
}
/*
This example demonstrates some of the advanced features of the high level engine API.
The following features are demonstrated:
* Initialization of the engine from a pre-initialized device.
* Self-managed resource managers.
* Multiple engines with a shared resource manager.
* Creation and management of `ma_sound` objects.
This example will play the sound that's passed in the command line.
If you were wanting to support multiple listeners, this example will show you how to do that. You achieve this by
initializing one `ma_engine` object for each listener, each of which share a single self-managed resource manager.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
#define MAX_DEVICES 2
#define MAX_SOUNDS 32
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
(void)pInput;
/*
Since we're managing the underlying device ourselves, we need to manually call the engine's data handler. To do
this we need access to the `ma_engine` object which we passed in to the user data. The advantage of this is that
you could do your own audio processing in addition to the engine's standard processing.
*/
ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL);
}
int main(int argc, char** argv)
{
ma_result result;
ma_context context;
ma_resource_manager_config resourceManagerConfig;
ma_resource_manager resourceManager;
ma_engine engines[MAX_DEVICES];
ma_device devices[MAX_DEVICES];
ma_uint32 engineCount = 0;
ma_uint32 iEngine;
ma_device_info* pPlaybackDeviceInfos;
ma_uint32 playbackDeviceCount;
ma_uint32 iAvailableDevice;
ma_uint32 iChosenDevice;
ma_sound sounds[MAX_SOUNDS];
ma_uint32 soundCount;
ma_uint32 iSound;
if (argc < 2) {
printf("No input file.");
return -1;
}
/*
We are going to be initializing multiple engines. In order to save on memory usage we can use a self managed
resource manager so we can share a single resource manager across multiple engines.
*/
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = ma_format_f32; /* ma_format_f32 should almost always be used as that's what the engine (and most everything else) uses for mixing. */
resourceManagerConfig.decodedChannels = 0; /* Setting the channel count to 0 will cause sounds to use their native channel count. */
resourceManagerConfig.decodedSampleRate = 48000; /* Using a consistent sample rate is useful for avoiding expensive resampling in the audio thread. This will result in resampling being performed by the loading thread(s). */
result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager);
if (result != MA_SUCCESS) {
printf("Failed to initialize resource manager.");
return -1;
}
/* We're going to want a context so we can enumerate our playback devices. */
result = ma_context_init(NULL, 0, NULL, &context);
if (result != MA_SUCCESS) {
printf("Failed to initialize context.");
return -1;
}
/*
Now that we have a context we will want to enumerate over each device so we can display them to the user and give
them a chance to select the output devices they want to use.
*/
result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL);
if (result != MA_SUCCESS) {
printf("Failed to enumerate playback devices.");
ma_context_uninit(&context);
return -1;
}
/* We have our devices, so now we want to get the user to select the devices they want to output to. */
engineCount = 0;
for (iChosenDevice = 0; iChosenDevice < MAX_DEVICES; iChosenDevice += 1) {
int c = 0;
for (;;) {
printf("Select playback device %d ([%d - %d], Q to quit):\n", iChosenDevice+1, 0, ma_min((int)playbackDeviceCount, 9));
for (iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) {
printf(" %d: %s\n", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name);
}
for (;;) {
c = getchar();
if (c != '\n') {
break;
}
}
if (c == 'q' || c == 'Q') {
return 0; /* User aborted. */
}
if (c >= '0' && c <= '9') {
c -= '0';
if (c < (int)playbackDeviceCount) {
ma_device_config deviceConfig;
ma_engine_config engineConfig;
/*
Create the device first before the engine. We'll specify the device in the engine's config. This is optional. When a device is
not pre-initialized the engine will create one for you internally. The device does not need to be started here - the engine will
do that for us in `ma_engine_start()`. The device's format is derived from the resource manager, but can be whatever you want.
It's useful to keep the format consistent with the resource manager to avoid data conversions costs in the audio callback. In
this example we're using the resource manager's sample format and sample rate, but leaving the channel count set to the device's
native channels. You can use whatever format/channels/rate you like.
*/
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[c].id;
deviceConfig.playback.format = resourceManager.config.decodedFormat;
deviceConfig.playback.channels = 0;
deviceConfig.sampleRate = resourceManager.config.decodedSampleRate;
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = &engines[engineCount];
result = ma_device_init(&context, &deviceConfig, &devices[engineCount]);
if (result != MA_SUCCESS) {
printf("Failed to initialize device for %s.\n", pPlaybackDeviceInfos[c].name);
return -1;
}
/* Now that we have the device we can initialize the engine. The device is passed into the engine's config. */
engineConfig = ma_engine_config_init();
engineConfig.pDevice = &devices[engineCount];
engineConfig.pResourceManager = &resourceManager;
engineConfig.noAutoStart = MA_TRUE; /* Don't start the engine by default - we'll do that manually below. */
result = ma_engine_init(&engineConfig, &engines[engineCount]);
if (result != MA_SUCCESS) {
printf("Failed to initialize engine for %s.\n", pPlaybackDeviceInfos[c].name);
ma_device_uninit(&devices[engineCount]);
return -1;
}
engineCount += 1;
break;
} else {
printf("Invalid device number.\n");
}
} else {
printf("Invalid device number.\n");
}
}
printf("Device %d: %s\n", iChosenDevice+1, pPlaybackDeviceInfos[c].name);
}
/* We should now have our engine's initialized. We can now start them. */
for (iEngine = 0; iEngine < engineCount; iEngine += 1) {
result = ma_engine_start(&engines[iEngine]);
if (result != MA_SUCCESS) {
printf("WARNING: Failed to start engine %d.\n", iEngine);
}
}
/*
At this point our engine's are running and outputting nothing but silence. To get them playing something we'll need
some sounds. In this example we're just using one sound per engine, but you can create as many as you like. Since
we're using a shared resource manager, the sound data will only be loaded once. This is how you would implement
multiple listeners.
*/
soundCount = 0;
for (iEngine = 0; iEngine < engineCount; iEngine += 1) {
/* Just one sound per engine in this example. We're going to be loading this asynchronously. */
result = ma_sound_init_from_file(&engines[iEngine], argv[1], MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM, NULL, NULL, &sounds[iEngine]);
if (result != MA_SUCCESS) {
printf("WARNING: Failed to load sound \"%s\"", argv[1]);
break;
}
/*
The sound can be started as soon as ma_sound_init_from_file() returns, even for sounds that are initialized
with MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC. The sound will start playing while it's being loaded. Note that if the
asynchronous loading process cannot keep up with the rate at which you try reading you'll end up glitching.
If this is an issue, you need to not load sounds asynchronously.
*/
result = ma_sound_start(&sounds[iEngine]);
if (result != MA_SUCCESS) {
printf("WARNING: Failed to start sound.");
}
soundCount += 1;
}
printf("Press Enter to quit...");
getchar();
for (;;) {
int c = getchar();
if (c == '\n') {
break;
}
}
/* Teardown. */
/* The application owns the `ma_sound` object which means you're responsible for uninitializing them. */
for (iSound = 0; iSound < soundCount; iSound += 1) {
ma_sound_uninit(&sounds[iSound]);
}
/* We can now uninitialize each engine. */
for (iEngine = 0; iEngine < engineCount; iEngine += 1) {
ma_engine_uninit(&engines[iEngine]);
/*
The engine has been uninitialized so now lets uninitialize the device. Do this first to ensure we don't
uninitialize the resource manager from under the device while the data callback is running.
*/
ma_device_uninit(&devices[iEngine]);
}
/*
Do the resource manager last. This way we can guarantee the data callbacks of each device aren't trying to access
and data managed by the resource manager.
*/
ma_resource_manager_uninit(&resourceManager);
return 0;
}
/*
This example demonstrates how to initialize an audio engine and play a sound.
This will play the sound specified on the command line.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
int main(int argc, char** argv)
{
ma_result result;
ma_engine engine;
if (argc < 2) {
printf("No input file.");
return -1;
}
result = ma_engine_init(NULL, &engine);
if (result != MA_SUCCESS) {
printf("Failed to initialize audio engine.");
return -1;
}
ma_engine_play_sound(&engine, argv[1], NULL);
printf("Press Enter to quit...");
getchar();
ma_engine_uninit(&engine);
return 0;
}
/*
Demonstrates how you can use the resource manager to manage loaded sounds.
This example loads the first sound specified on the command line via the resource manager and then plays it using the
low level API.
You can control whether or not you want to load the sound asynchronously and whether or not you want to store the data
in-memory or stream it. When storing the sound in-memory you can also control whether or not it is decoded. To do this,
specify a combination of the following options in `ma_resource_manager_data_source_init()`:
* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC - Load asynchronously.
* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE - Store the sound in-memory in uncompressed/decoded format.
* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM - Stream the sound from disk rather than storing entirely in memory. Useful for music.
The object returned by the resource manager is just a standard data source which means it can be plugged into any of
`ma_data_source_*()` APIs just like any other data source and it should just work.
Internally, there's a background thread that's used to process jobs and enable asynchronicity. By default there is only
a single job thread, but this can be configured in the resource manager config. You can also implement your own threads
for processing jobs. That is more advanced, and beyond the scope of this example.
When you initialize a resource manager you can specify the sample format, channels and sample rate to use when reading
data from the data source. This means the resource manager will ensure all sounds will have a standard format. When not
set, each sound will have their own formats and you'll need to do the necessary data conversion yourself.
*/
#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
void main_loop__em(void* pUserData)
{
ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData;
MA_ASSERT(pResourceManager != NULL);
/*
The Emscripten build does not support threading which means we need to process jobs manually. If
there are no jobs needing to be processed this will return immediately with MA_NO_DATA_AVAILABLE.
*/
ma_resource_manager_process_next_job(pResourceManager);
}
#endif
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
ma_data_source_read_pcm_frames((ma_data_source*)pDevice->pUserData, pOutput, frameCount, NULL, MA_TRUE);
(void)pInput;
}
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_resource_manager_data_source dataSource;
if (argc < 2) {
printf("No input file.");
return -1;
}
/* We'll initialize the device first. */
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = &dataSource; /* <-- We'll be reading from this in the data callback. */
result = ma_device_init(NULL, &deviceConfig, &device);
if (result != MA_SUCCESS) {
printf("Failed to initialize device.");
return -1;
}
/*
We have the device so now we want to initialize the resource manager. We'll use the resource manager to load a
sound based on the command line.
*/
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = device.playback.format;
resourceManagerConfig.decodedChannels = device.playback.channels;
resourceManagerConfig.decodedSampleRate = device.sampleRate;
/*
We're not supporting threading with Emscripten so go ahead and disable threading. It's important
that we set the appropriate flag and also the job thread count to 0.
*/
#ifdef __EMSCRIPTEN__
resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING;
resourceManagerConfig.jobThreadCount = 0;
#endif
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 load a sound. */
result = ma_resource_manager_data_source_init(
&resourceManager,
argv[1],
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM,
NULL, /* Async notification. */
&dataSource);
if (result != MA_SUCCESS) {
printf("Failed to load sound \"%s\".", argv[1]);
return -1;
}
/* Now that we have a sound we can start the device. */
result = ma_device_start(&device);
if (result != MA_SUCCESS) {
ma_device_uninit(&device);
printf("Failed to start device.");
return -1;
}
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(main_loop__em, &resourceManager, 0, 1);
#else
printf("Press Enter to quit...\n");
getchar();
#endif
/* Teardown. */
/* Uninitialize the device first to ensure the data callback is stopped and doesn't try to access any data. */
ma_device_uninit(&device);
/*
Before uninitializing the resource manager we need to uninitialize every data source. The data source is owned by
the caller which means you're responsible for uninitializing it.
*/
ma_resource_manager_data_source_uninit(&dataSource);
/* Uninitialize the resource manager after each data source. */
ma_resource_manager_uninit(&resourceManager);
return 0;
}
/*
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`.
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 MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
static ma_resource_manager_data_source g_dataSources[16];
static ma_uint32 g_dataSourceCount;
/*
TODO: Consider putting these public functions in miniaudio.h. Will depend on ma_mix_pcm_frames_f32()
being merged into miniaudio.h (it's currently in miniaudio_engine.h).
*/
static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, ma_format dataSourceFormat, ma_uint32 dataSourceChannels)
{
/*
This function is intended to be used when the format and channel count of the data source is
known beforehand. The idea is to avoid overhead due to redundant calls to ma_data_source_get_data_format().
*/
MA_ASSERT(pDataSource != NULL);
if (dataSourceFormat == ma_format_f32) {
/* Fast path. No conversion necessary. */
return ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, pFramesRead, loop);
} else {
/* Slow path. Conversion necessary. */
ma_result result;
ma_uint64 totalFramesRead;
ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels);
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
ma_uint64 framesJustRead;
ma_uint64 framesToRead = frameCount - totalFramesRead;
if (framesToRead > tempCapInFrames) {
framesToRead = tempCapInFrames;
}
result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, framesToRead, &framesJustRead, loop);
ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none);
totalFramesRead += framesJustRead;
if (result != MA_SUCCESS) {
break;
}
}
return MA_SUCCESS;
}
}
MA_API ma_result ma_data_source_read_pcm_frames_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop)
{
ma_result result;
ma_format format;
ma_uint32 channels;
result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0);
if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the data format of the data source. */
}
return ma_data_source_read_pcm_frames_f32_ex(pDataSource, pFramesOut, frameCount, pFramesRead, loop, format, channels);
}
MA_API ma_result ma_data_source_read_pcm_frames_and_mix_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, float volume)
{
ma_result result;
ma_format format;
ma_uint32 channels;
ma_uint64 totalFramesRead;
if (pFramesRead != NULL) {
*pFramesRead = 0;
}
if (pDataSource == NULL) {
return MA_INVALID_ARGS;
}
result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0);
if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the data format of the data source. */
}
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)];
ma_uint64 tempCapInFrames = ma_countof(temp) / channels;
ma_uint64 framesJustRead;
ma_uint64 framesToRead = frameCount - totalFramesRead;
if (framesToRead > tempCapInFrames) {
framesToRead = tempCapInFrames;
}
result = ma_data_source_read_pcm_frames_f32_ex(pDataSource, temp, framesToRead, &framesJustRead, loop, format, channels);
ma_mix_pcm_frames_f32(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), temp, framesJustRead, channels, volume);
totalFramesRead += framesJustRead;
if (result != MA_SUCCESS) {
break;
}
}
if (pFramesRead != NULL) {
*pFramesRead = totalFramesRead;
}
return MA_SUCCESS;
}
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. This
assumes the device's format is f32 and that the buffer is not pre-silenced.
*/
ma_uint32 iDataSource;
MA_ASSERT(pDevice->playback.format == ma_format_f32);
(void)pInput; /* Unused. */
/*
If the device was configured with noPreSilencedOutputBuffer then you would need to silence the
buffer here, or make sure the first data source to be mixed is copied rather than mixed.
*/
/*ma_silence_pcm_frames(pOutput, frameCount, ma_format_f32, pDevice->playback.channels);*/
/* For each sound, mix as much data as we can. */
for (iDataSource = 0; iDataSource < g_dataSourceCount; iDataSource += 1) {
ma_data_source_read_pcm_frames_and_mix_f32(&g_dataSources[iDataSource], (float*)pOutput, frameCount, NULL, MA_TRUE, /* volume = */1);
}
}
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_resource_manager_job job;
/*
Retrieve a job from the queue first. This defines what it is you're about to do. By default this will be
blocking. You can initialize the resource manager with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING to not block in
which case MA_NO_DATA_AVAILABLE will be returned if no jobs are available.
When the quit job is returned (MA_RESOURCE_MANAGER_JOB_QUIT), the return value will always be MA_CANCELLED. If you don't want
to check the return value (you should), you can instead check if the job code is MA_RESOURCE_MANAGER_JOB_QUIT and use that
instead.
*/
result = ma_resource_manager_next_job(pResourceManager, &job);
if (result != MA_SUCCESS) {
if (result == MA_CANCELLED) {
printf("CUSTOM JOB THREAD TERMINATING VIA MA_CANCELLED... ");
} else {
printf("CUSTOM JOB THREAD ERROR: %s. TERMINATING... ", ma_result_description(result));
}
break;
}
/*
Terminate if we got a quit message. You don't need to terminate like this, but's a bit more robust. You can
just use a global variable or something similar if it's easier for your particular situation. The quit job
remains in the queue and will continue to be returned by future calls to ma_resource_manager_next_job(). The
reason for this is to give every job thread visibility to the quit job so they have a chance to exit.
We won't actually be hitting this code because the call above will return MA_CANCELLED when the MA_RESOURCE_MANAGER_JOB_QUIT
event is received which means the `result != MA_SUCCESS` logic above will catch it. If you do not check the
return value of ma_resource_manager_next_job() you will want to check for MA_RESOURCE_MANAGER_JOB_QUIT like the code below.
*/
if (job.toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) {
printf("CUSTOM JOB THREAD TERMINATING VIA MA_RESOURCE_MANAGER_JOB_QUIT... ");
break;
}
/* Call ma_resource_manager_process_job() to actually do the work to process the job. */
printf("PROCESSING IN CUSTOM JOB THREAD: %d\n", job.toc.breakup.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_thread jobThread;
int iFile;
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = ma_format_f32;
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;
}
/* 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();
/*
We'll set a standard decoding format to save us to processing time at mixing time. If you're wanting to use
spatialization with your decoded sounds, you may want to consider leaving this as 0 to ensure the file's native
channel count is used so you can do proper spatialization.
*/
resourceManagerConfig.decodedFormat = device.playback.format;
resourceManagerConfig.decodedChannels = device.playback.channels;
resourceManagerConfig.decodedSampleRate = device.sampleRate;
/* The 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. This is optional. Normally when doing
self-managed job threads you would set the internal job thread count to zero. We're doing both internal and
self-managed job threads in this example just for demonstration purposes.
*/
ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager, NULL);
/* 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_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC /*| MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM*/,
NULL, /* Async notification. */
&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.
*/
ma_device_uninit(&device);
/*
Our data sources need to be explicitly uninitialized. ma_resource_manager_uninit() will not do
it for us. This needs to be done before posting the quit event and uninitializing the resource
manager or else we'll get stuck in a deadlock because ma_resource_manager_data_source_uninit()
will be waiting for the job thread(s) to finish work, which will never happen because they were
just terminated.
*/
for (iFile = 0; (size_t)iFile < g_dataSourceCount; iFile += 1) {
ma_resource_manager_data_source_uninit(&g_dataSources[iFile]);
}
/*
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 self-managed 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. Note that you don't need to do this if you're not managing your
own threads - ma_resource_manager_uninit() alone will suffice in that case.
*/
ma_resource_manager_post_job_quit(&resourceManager);
ma_thread_wait(&jobThread); /* Wait for the custom job thread to finish so it doesn't try to access any data. */
/* Uninitialize the resource manager after each data source. */
ma_resource_manager_uninit(&resourceManager);
return 0;
}
/* #include "../../examples/custom_decoder_engine.c"
Demonstrates how to implement a custom decoder. \ No newline at end of file
This example implements two custom decoders:
* Vorbis via libvorbis
* Opus via libopus
A custom decoder must implement a data source. In this example, the libvorbis data source is called
`ma_libvorbis` and the Opus data source is called `ma_libopus`. These two objects are compatible
with the `ma_data_source` APIs and can be taken straight from this example and used in real code.
The custom decoding data sources (`ma_libvorbis` and `ma_libopus` in this example) are connected to
the decoder via the decoder config (`ma_decoder_config`). You need to implement a vtable for each
of your custom decoders. See `ma_decoding_backend_vtable` for the functions you need to implement.
The `onInitFile`, `onInitFileW` and `onInitMemory` functions are optional.
*/
#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */
#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../../extras/miniaudio_libvorbis.h"
#include "../../extras/miniaudio_libopus.h"
#include "../miniaudio_engine.h"
#include <stdio.h>
static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libvorbis* pVorbis;
(void)pUserData;
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
if (pVorbis == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis);
if (result != MA_SUCCESS) {
ma_free(pVorbis, pAllocationCallbacks);
return result;
}
*ppBackend = pVorbis;
return MA_SUCCESS;
}
static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libvorbis* pVorbis;
(void)pUserData;
pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks);
if (pVorbis == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis);
if (result != MA_SUCCESS) {
ma_free(pVorbis, pAllocationCallbacks);
return result;
}
*ppBackend = pVorbis;
return MA_SUCCESS;
}
static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
(void)pUserData;
ma_libvorbis_uninit(pVorbis, pAllocationCallbacks);
ma_free(pVorbis, pAllocationCallbacks);
}
static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend;
(void)pUserData;
return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap);
}
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis =
{
ma_decoding_backend_init__libvorbis,
ma_decoding_backend_init_file__libvorbis,
NULL, /* onInitFileW() */
NULL, /* onInitMemory() */
ma_decoding_backend_uninit__libvorbis
};
static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libopus* pOpus;
(void)pUserData;
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
if (pOpus == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus);
if (result != MA_SUCCESS) {
ma_free(pOpus, pAllocationCallbacks);
return result;
}
*ppBackend = pOpus;
return MA_SUCCESS;
}
static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend)
{
ma_result result;
ma_libopus* pOpus;
(void)pUserData;
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
if (pOpus == NULL) {
return MA_OUT_OF_MEMORY;
}
result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus);
if (result != MA_SUCCESS) {
ma_free(pOpus, pAllocationCallbacks);
return result;
}
*ppBackend = pOpus;
return MA_SUCCESS;
}
static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_libopus* pOpus = (ma_libopus*)pBackend;
(void)pUserData;
ma_libopus_uninit(pOpus, pAllocationCallbacks);
ma_free(pOpus, pAllocationCallbacks);
}
static ma_result ma_decoding_backend_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap)
{
ma_libopus* pOpus = (ma_libopus*)pBackend;
(void)pUserData;
return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap);
}
static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus =
{
ma_decoding_backend_init__libopus,
ma_decoding_backend_init_file__libopus,
NULL, /* onInitFileW() */
NULL, /* onInitMemory() */
ma_decoding_backend_uninit__libopus
};
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
ma_data_source* pDataSource = (ma_data_source*)pDevice->pUserData;
if (pDataSource == NULL) {
return;
}
ma_data_source_read_pcm_frames(pDataSource, pOutput, frameCount, NULL, MA_TRUE);
(void)pInput;
}
int main(int argc, char** argv)
{
ma_result result;
ma_resource_manager_config resourceManagerConfig;
ma_resource_manager resourceManager;
ma_engine_config engineConfig;
ma_engine engine;
/*
Add your custom backend vtables here. The order in the array defines the order of priority. The
vtables will be passed in to the resource manager config.
*/
ma_decoding_backend_vtable* pCustomBackendVTables[] =
{
&g_ma_decoding_backend_vtable_libvorbis,
&g_ma_decoding_backend_vtable_libopus
};
if (argc < 2) {
printf("No input file.\n");
return -1;
}
/* Using custom decoding backends requires a resource manager. */
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables;
resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
resourceManagerConfig.pCustomDecodingBackendUserData = NULL; /* <-- This will be passed in to the pUserData parameter of each function in the decoding backend vtables. */
result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager);
if (result != MA_SUCCESS) {
printf("Failed to initialize resource manager.");
return -1;
}
/* Once we have a resource manager we can create the engine. */
engineConfig = ma_engine_config_init();
engineConfig.pResourceManager = &resourceManager;
result = ma_engine_init(&engineConfig, &engine);
if (result != MA_SUCCESS) {
printf("Failed to initialize engine.");
return -1;
}
/* Now we can play our sound. */
result = ma_engine_play_sound(&engine, argv[1], NULL);
if (result != MA_SUCCESS) {
printf("Failed to play sound.");
return -1;
}
printf("Press Enter to quit...");
getchar();
return 0;
}
\ No newline at end of file
/* #include "../../examples/duplex_effect.c"
Demonstrates how to apply an effect to a duplex stream using the node graph system. \ No newline at end of file
This example applies a vocoder effect to the input stream before outputting it. A custom node
called `ma_vocoder_node` is used to achieve the effect which can be found in the extras folder in
the miniaudio repository. The vocoder node uses https://github.com/blastbay/voclib to achieve the
effect.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
#include "../_extras/nodes/ma_vocoder_node/ma_vocoder_node.c"
#include <stdio.h>
#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
static ma_waveform g_sourceData; /* The underlying data source of the excite node. */
static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the source node. */
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
static ma_node_graph g_nodeGraph;
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
MA_ASSERT(pDevice->capture.format == pDevice->playback.format);
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
/*
The node graph system is a pulling style of API. At the lowest level of the chain will be a
node acting as a data source for the purpose of delivering the initial audio data. In our case,
the data source is our `pInput` buffer. We need to update the underlying data source so that it
read data from `pInput`.
*/
ma_audio_buffer_ref_set_data(&g_exciteData, pInput, frameCount);
/* With the source buffer configured we can now read directly from the node graph. */
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
}
int main(int argc, char** argv)
{
ma_result result;
ma_device_config deviceConfig;
ma_device device;
ma_node_graph_config nodeGraphConfig;
ma_vocoder_node_config vocoderNodeConfig;
ma_data_source_node_config sourceNodeConfig;
ma_data_source_node_config exciteNodeConfig;
ma_waveform_config waveformConfig;
deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.capture.pDeviceID = NULL;
deviceConfig.capture.format = DEVICE_FORMAT;
deviceConfig.capture.channels = DEVICE_CHANNELS;
deviceConfig.capture.shareMode = ma_share_mode_shared;
deviceConfig.playback.pDeviceID = NULL;
deviceConfig.playback.format = DEVICE_FORMAT;
deviceConfig.playback.channels = DEVICE_CHANNELS;
deviceConfig.dataCallback = data_callback;
result = ma_device_init(NULL, &deviceConfig, &device);
if (result != MA_SUCCESS) {
return result;
}
/* Now we can setup our node graph. */
nodeGraphConfig = ma_node_graph_config_init(device.capture.channels);
result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph);
if (result != MA_SUCCESS) {
printf("Failed to initialize node graph.");
goto done0;
}
/* Vocoder. Attached straight to the endpoint. */
vocoderNodeConfig = ma_vocoder_node_config_init(device.capture.channels, device.sampleRate);
result = ma_vocoder_node_init(&g_nodeGraph, &vocoderNodeConfig, NULL, &g_vocoderNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize vocoder node.");
goto done1;
}
ma_node_attach_output_bus(&g_vocoderNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
/* Amplify the volume of the vocoder output because in my testing it is a bit quiet. */
ma_node_set_output_bus_volume(&g_vocoderNode, 0, 4);
/* Source/carrier. Attached to input bus 0 of the vocoder node. */
waveformConfig = ma_waveform_config_init(device.capture.format, device.capture.channels, device.sampleRate, ma_waveform_type_sawtooth, 1.0, 50);
result = ma_waveform_init(&waveformConfig, &g_sourceData);
if (result != MA_SUCCESS) {
printf("Failed to initialize waveform for excite node.");
goto done3;
}
sourceNodeConfig = ma_data_source_node_config_init(&g_sourceData, MA_FALSE);
result = ma_data_source_node_init(&g_nodeGraph, &sourceNodeConfig, NULL, &g_sourceNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize excite node.");
goto done3;
}
ma_node_attach_output_bus(&g_sourceNode, 0, &g_vocoderNode, 0);
/* Excite/modulator. Attached to input bus 1 of the vocoder node. */
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_exciteData);
if (result != MA_SUCCESS) {
printf("Failed to initialize audio buffer for source.");
goto done2;
}
exciteNodeConfig = ma_data_source_node_config_init(&g_exciteData, MA_FALSE);
result = ma_data_source_node_init(&g_nodeGraph, &exciteNodeConfig, NULL, &g_exciteNode);
if (result != MA_SUCCESS) {
printf("Failed to initialize source node.");
goto done2;
}
ma_node_attach_output_bus(&g_exciteNode, 0, &g_vocoderNode, 1);
ma_device_start(&device);
printf("Press Enter to quit...\n");
getchar();
/* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */
ma_device_stop(&device);
/*done4:*/ ma_data_source_node_uninit(&g_exciteNode, NULL);
done3: ma_data_source_node_uninit(&g_sourceNode, NULL);
done2: ma_vocoder_node_uninit(&g_vocoderNode, NULL);
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
done0: ma_device_uninit(&device);
(void)argc;
(void)argv;
return 0;
}
/* #include "../../examples/engine_hello_world.c"
This example demonstrates how to initialize an audio engine and play a sound. \ No newline at end of file
This will play the sound specified on the command line.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
int main(int argc, char** argv)
{
ma_result result;
ma_engine engine;
if (argc < 2) {
printf("No input file.");
return -1;
}
result = ma_engine_init(NULL, &engine);
if (result != MA_SUCCESS) {
printf("Failed to initialize audio engine.");
return -1;
}
ma_engine_play_sound(&engine, argv[1], NULL);
printf("Press Enter to quit...");
getchar();
ma_engine_uninit(&engine);
return 0;
}
/* #include "../../examples/resource_manager.c"
Demonstrates how you can use the resource manager to manage loaded sounds. \ No newline at end of file
This example loads the first sound specified on the command line via the resource manager and then plays it using the
low level API.
You can control whether or not you want to load the sound asynchronously and whether or not you want to store the data
in-memory or stream it. When storing the sound in-memory you can also control whether or not it is decoded. To do this,
specify a combination of the following options in `ma_resource_manager_data_source_init()`:
* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC - Load asynchronously.
* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE - Store the sound in-memory in uncompressed/decoded format.
* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM - Stream the sound from disk rather than storing entirely in memory. Useful for music.
The object returned by the resource manager is just a standard data source which means it can be plugged into any of
`ma_data_source_*()` APIs just like any other data source and it should just work.
Internally, there's a background thread that's used to process jobs and enable asynchronicity. By default there is only
a single job thread, but this can be configured in the resource manager config. You can also implement your own threads
for processing jobs. That is more advanced, and beyond the scope of this example.
When you initialize a resource manager you can specify the sample format, channels and sample rate to use when reading
data from the data source. This means the resource manager will ensure all sounds will have a standard format. When not
set, each sound will have their own formats and you'll need to do the necessary data conversion yourself.
*/
#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */
#define MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
void main_loop__em(void* pUserData)
{
ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData;
MA_ASSERT(pResourceManager != NULL);
/*
The Emscripten build does not support threading which means we need to process jobs manually. If
there are no jobs needing to be processed this will return immediately with MA_NO_DATA_AVAILABLE.
*/
ma_resource_manager_process_next_job(pResourceManager);
}
#endif
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
ma_data_source_read_pcm_frames((ma_data_source*)pDevice->pUserData, pOutput, frameCount, NULL, MA_TRUE);
(void)pInput;
}
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_resource_manager_data_source dataSource;
if (argc < 2) {
printf("No input file.");
return -1;
}
/* We'll initialize the device first. */
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = &dataSource; /* <-- We'll be reading from this in the data callback. */
result = ma_device_init(NULL, &deviceConfig, &device);
if (result != MA_SUCCESS) {
printf("Failed to initialize device.");
return -1;
}
/*
We have the device so now we want to initialize the resource manager. We'll use the resource manager to load a
sound based on the command line.
*/
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = device.playback.format;
resourceManagerConfig.decodedChannels = device.playback.channels;
resourceManagerConfig.decodedSampleRate = device.sampleRate;
/*
We're not supporting threading with Emscripten so go ahead and disable threading. It's important
that we set the appropriate flag and also the job thread count to 0.
*/
#ifdef __EMSCRIPTEN__
resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING;
resourceManagerConfig.jobThreadCount = 0;
#endif
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 load a sound. */
result = ma_resource_manager_data_source_init(
&resourceManager,
argv[1],
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM,
NULL, /* Async notification. */
&dataSource);
if (result != MA_SUCCESS) {
printf("Failed to load sound \"%s\".", argv[1]);
return -1;
}
/* Now that we have a sound we can start the device. */
result = ma_device_start(&device);
if (result != MA_SUCCESS) {
ma_device_uninit(&device);
printf("Failed to start device.");
return -1;
}
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(main_loop__em, &resourceManager, 0, 1);
#else
printf("Press Enter to quit...\n");
getchar();
#endif
/* Teardown. */
/* Uninitialize the device first to ensure the data callback is stopped and doesn't try to access any data. */
ma_device_uninit(&device);
/*
Before uninitializing the resource manager we need to uninitialize every data source. The data source is owned by
the caller which means you're responsible for uninitializing it.
*/
ma_resource_manager_data_source_uninit(&dataSource);
/* Uninitialize the resource manager after each data source. */
ma_resource_manager_uninit(&resourceManager);
return 0;
}
/* #include "../../examples/resource_manager_advanced.c"
Demonstrates how you can use the resource manager to manage loaded sounds. \ No newline at end of file
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`.
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 MINIAUDIO_IMPLEMENTATION
#include "../../miniaudio.h"
#include "../miniaudio_engine.h"
static ma_resource_manager_data_source g_dataSources[16];
static ma_uint32 g_dataSourceCount;
/*
TODO: Consider putting these public functions in miniaudio.h. Will depend on ma_mix_pcm_frames_f32()
being merged into miniaudio.h (it's currently in miniaudio_engine.h).
*/
static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, ma_format dataSourceFormat, ma_uint32 dataSourceChannels)
{
/*
This function is intended to be used when the format and channel count of the data source is
known beforehand. The idea is to avoid overhead due to redundant calls to ma_data_source_get_data_format().
*/
MA_ASSERT(pDataSource != NULL);
if (dataSourceFormat == ma_format_f32) {
/* Fast path. No conversion necessary. */
return ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, pFramesRead, loop);
} else {
/* Slow path. Conversion necessary. */
ma_result result;
ma_uint64 totalFramesRead;
ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels);
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
ma_uint64 framesJustRead;
ma_uint64 framesToRead = frameCount - totalFramesRead;
if (framesToRead > tempCapInFrames) {
framesToRead = tempCapInFrames;
}
result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, framesToRead, &framesJustRead, loop);
ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none);
totalFramesRead += framesJustRead;
if (result != MA_SUCCESS) {
break;
}
}
return MA_SUCCESS;
}
}
MA_API ma_result ma_data_source_read_pcm_frames_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop)
{
ma_result result;
ma_format format;
ma_uint32 channels;
result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0);
if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the data format of the data source. */
}
return ma_data_source_read_pcm_frames_f32_ex(pDataSource, pFramesOut, frameCount, pFramesRead, loop, format, channels);
}
MA_API ma_result ma_data_source_read_pcm_frames_and_mix_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, float volume)
{
ma_result result;
ma_format format;
ma_uint32 channels;
ma_uint64 totalFramesRead;
if (pFramesRead != NULL) {
*pFramesRead = 0;
}
if (pDataSource == NULL) {
return MA_INVALID_ARGS;
}
result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0);
if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the data format of the data source. */
}
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)];
ma_uint64 tempCapInFrames = ma_countof(temp) / channels;
ma_uint64 framesJustRead;
ma_uint64 framesToRead = frameCount - totalFramesRead;
if (framesToRead > tempCapInFrames) {
framesToRead = tempCapInFrames;
}
result = ma_data_source_read_pcm_frames_f32_ex(pDataSource, temp, framesToRead, &framesJustRead, loop, format, channels);
ma_mix_pcm_frames_f32(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), temp, framesJustRead, channels, volume);
totalFramesRead += framesJustRead;
if (result != MA_SUCCESS) {
break;
}
}
if (pFramesRead != NULL) {
*pFramesRead = totalFramesRead;
}
return MA_SUCCESS;
}
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. This
assumes the device's format is f32 and that the buffer is not pre-silenced.
*/
ma_uint32 iDataSource;
MA_ASSERT(pDevice->playback.format == ma_format_f32);
(void)pInput; /* Unused. */
/*
If the device was configured with noPreSilencedOutputBuffer then you would need to silence the
buffer here, or make sure the first data source to be mixed is copied rather than mixed.
*/
/*ma_silence_pcm_frames(pOutput, frameCount, ma_format_f32, pDevice->playback.channels);*/
/* For each sound, mix as much data as we can. */
for (iDataSource = 0; iDataSource < g_dataSourceCount; iDataSource += 1) {
ma_data_source_read_pcm_frames_and_mix_f32(&g_dataSources[iDataSource], (float*)pOutput, frameCount, NULL, MA_TRUE, /* volume = */1);
}
}
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_resource_manager_job job;
/*
Retrieve a job from the queue first. This defines what it is you're about to do. By default this will be
blocking. You can initialize the resource manager with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING to not block in
which case MA_NO_DATA_AVAILABLE will be returned if no jobs are available.
When the quit job is returned (MA_RESOURCE_MANAGER_JOB_QUIT), the return value will always be MA_CANCELLED. If you don't want
to check the return value (you should), you can instead check if the job code is MA_RESOURCE_MANAGER_JOB_QUIT and use that
instead.
*/
result = ma_resource_manager_next_job(pResourceManager, &job);
if (result != MA_SUCCESS) {
if (result == MA_CANCELLED) {
printf("CUSTOM JOB THREAD TERMINATING VIA MA_CANCELLED... ");
} else {
printf("CUSTOM JOB THREAD ERROR: %s. TERMINATING... ", ma_result_description(result));
}
break;
}
/*
Terminate if we got a quit message. You don't need to terminate like this, but's a bit more robust. You can
just use a global variable or something similar if it's easier for your particular situation. The quit job
remains in the queue and will continue to be returned by future calls to ma_resource_manager_next_job(). The
reason for this is to give every job thread visibility to the quit job so they have a chance to exit.
We won't actually be hitting this code because the call above will return MA_CANCELLED when the MA_RESOURCE_MANAGER_JOB_QUIT
event is received which means the `result != MA_SUCCESS` logic above will catch it. If you do not check the
return value of ma_resource_manager_next_job() you will want to check for MA_RESOURCE_MANAGER_JOB_QUIT like the code below.
*/
if (job.toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) {
printf("CUSTOM JOB THREAD TERMINATING VIA MA_RESOURCE_MANAGER_JOB_QUIT... ");
break;
}
/* Call ma_resource_manager_process_job() to actually do the work to process the job. */
printf("PROCESSING IN CUSTOM JOB THREAD: %d\n", job.toc.breakup.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_thread jobThread;
int iFile;
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = ma_format_f32;
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;
}
/* 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();
/*
We'll set a standard decoding format to save us to processing time at mixing time. If you're wanting to use
spatialization with your decoded sounds, you may want to consider leaving this as 0 to ensure the file's native
channel count is used so you can do proper spatialization.
*/
resourceManagerConfig.decodedFormat = device.playback.format;
resourceManagerConfig.decodedChannels = device.playback.channels;
resourceManagerConfig.decodedSampleRate = device.sampleRate;
/* The 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. This is optional. Normally when doing
self-managed job threads you would set the internal job thread count to zero. We're doing both internal and
self-managed job threads in this example just for demonstration purposes.
*/
ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager, NULL);
/* 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_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC /*| MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM*/,
NULL, /* Async notification. */
&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.
*/
ma_device_uninit(&device);
/*
Our data sources need to be explicitly uninitialized. ma_resource_manager_uninit() will not do
it for us. This needs to be done before posting the quit event and uninitializing the resource
manager or else we'll get stuck in a deadlock because ma_resource_manager_data_source_uninit()
will be waiting for the job thread(s) to finish work, which will never happen because they were
just terminated.
*/
for (iFile = 0; (size_t)iFile < g_dataSourceCount; iFile += 1) {
ma_resource_manager_data_source_uninit(&g_dataSources[iFile]);
}
/*
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 self-managed 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. Note that you don't need to do this if you're not managing your
own threads - ma_resource_manager_uninit() alone will suffice in that case.
*/
ma_resource_manager_post_job_quit(&resourceManager);
ma_thread_wait(&jobThread); /* Wait for the custom job thread to finish so it doesn't try to access any data. */
/* Uninitialize the resource manager after each data source. */
ma_resource_manager_uninit(&resourceManager);
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment