/* Demonstrates how to implement a custom decoder. 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 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); (void)pInput; } int main(int argc, char** argv) { ma_result result; ma_decoder_config decoderConfig; ma_decoder decoder; ma_device_config deviceConfig; ma_device device; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; /* Add your custom backend vtables here. The order in the array defines the order of priority. The vtables will be passed in via the decoder 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; } /* Initialize the decoder. */ decoderConfig = ma_decoder_config_init_default(); decoderConfig.pCustomBackendUserData = NULL; /* In this example our backend objects are contained within a ma_decoder_ex object to avoid a malloc. Our vtables need to know about this. */ decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); result = ma_decoder_init_file(argv[1], &decoderConfig, &decoder); if (result != MA_SUCCESS) { printf("Failed to initialize decoder."); return -1; } ma_data_source_set_looping(&decoder, MA_TRUE); /* Initialize the device. */ result = ma_data_source_get_data_format(&decoder, &format, &channels, &sampleRate, NULL, 0); if (result != MA_SUCCESS) { printf("Failed to retrieve decoder data format."); ma_decoder_uninit(&decoder); return -1; } deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig.playback.format = format; deviceConfig.playback.channels = channels; deviceConfig.sampleRate = sampleRate; deviceConfig.dataCallback = data_callback; deviceConfig.pUserData = &decoder; if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) { printf("Failed to open playback device.\n"); ma_decoder_uninit(&decoder); return -1; } if (ma_device_start(&device) != MA_SUCCESS) { printf("Failed to start playback device.\n"); ma_device_uninit(&device); ma_decoder_uninit(&decoder); return -1; } printf("Press Enter to quit..."); getchar(); ma_device_uninit(&device); ma_decoder_uninit(&decoder); return 0; }