mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-21 15:56:58 +02:00
252 lines
8.0 KiB
C
252 lines
8.0 KiB
C
/*
|
|
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 "../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) {
|
|
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) {
|
|
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,
|
|
ma_decoding_backend_get_channel_map__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) {
|
|
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) {
|
|
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,
|
|
ma_decoding_backend_get_channel_map__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 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;
|
|
}
|
|
|
|
|
|
/* Using custom decoding backends requires a resource manager. */
|
|
resourceManagerConfig = ma_resource_manager_config_init();
|
|
resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables;
|
|
resourceManagerConfig.customDecodingBackendVTableCount = 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 start 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;
|
|
} |