Files
miniaudio/examples/custom_decoder.c
T
David Reid 15da9bd7ff API CHANGE: Add onProp callback to ma_data_source_vtable.
A new callback called `onProp` has been added to
`ma_data_source_vtable`. This replaces the following callbacks:

    onGetDataFormat
    onGetCursor
    onGetLength
    onSetLooping

This new callback is for retrieving and setting various properties
relating to the data source. It takes a `prop` parameter which is an ID
for the property being handled, and a `void*` pointer for
property-specific data.

Typically onProp implementations would discriminate on the property type
using a switch. The example below shows how to handle the old callbacks:

    switch (prop)
    {
        // Replaces onGetDataFormat (format/channels/rate).
        case MA_DATA_SOURCE_GET_DATA_SOURCE:
        {
            ma_data_source_data_format* pDataFormat =
                (ma_data_source_data_format*)pData;
            pDataFormat->format     = pCustomDataSource->format;
            pDataFormat->channels   = pCustomDataSource->channels;
            pDataFormat->sampleRate = pCustomDataSource->sampleRate;

            return MA_SUCCESS;
        }

        // Replaces onGetDataFormat (channel map)
        case MA_DATA_SOURCE_GET_CHANNEL_MAP:
        {
            ma_channel_map_init_standard(
                ma_standard_channel_map_default,
                (ma_channel*)pData,
                MA_MAX_CHANNELS,
                pCustomDataSource->channels);

            return MA_SUCCESS;
        }

        // Replaces onGetCursor
        case MA_DATA_SOURCE_GET_CURSOR:
        {
            *((ma_uint64*)pData) = pCustomDataSource->cursor;
            return MA_SUCCESS;
        }

        // Replaces onGetLength
        case MA_DATA_SOURCE_GET_LENGTH:
        {
            *((ma_uint64*)pData) = pCustomDataSource->length;
            return MA_SUCCESS;
        }

        // Replaces onSetLooping
        case MA_DATA_SOURCE_SET_LOOPING:
        {
            pCustomDataSource->isLooping = *((ma_bool32*)pData);
            return MA_SUCCESS;
        }

        // Mandatory when MA_DATA_SOURCE_SET_LOOPING is implemented.
        case MA_DATA_SOURCE_GET_LOOPING:
        {
            *((ma_bool32*)pData) = pCustomDataSource->isLooping;
            return MA_SUCCESS;
        }

        // Return MA_NOT_IMPLEMENTED for any ignored properties.
        default: return MA_NOT_IMPLEMENTED;
    }

Note how the format/channels/rate and channel map properties have been
split across two separate properties, `MA_DATA_SOURCE_GET_DATA_SOURCE`
and `MA_DATA_SOURCE_GET_CHANNEL_MAP`. Along with this change, the
channel map parameters have been removed from
`ma_data_source_get_data_format()` and a new function called
`ma_data_source_get_channel_map()` has been added.

New properties have also been added for handling ranges and loop points.
This allows the data source implementation itself to handle it rather
than miniaudio doing it at a higher level. Where this is useful is if
your data source is a wrapper around another data source and you want to
route ranges and loop points to the internal data source.

The reason for this change is that it allows for properties to be added
without having to break the build due to yet another callback being
added. It also hides away the more niche properties that the majority of
data sources do not care about. For example, rarely does a data source
need to handle the `onSetLooping` callback, yet every data source needed
to add a `NULL` entry to their vtables just for this one extremely niche
property.

See documentation for further details.

Tag: release-notes
Tag: api-change
2026-05-02 15:31:09 +10:00

132 lines
4.6 KiB
C

/*
Demonstrates how to plug in custom decoders.
This example implements two custom decoders:
* Vorbis via libvorbis
* Opus via libopus
The files miniaudio_libvorbis.h and miniaudio_libopus.h are where the custom decoders are implemented.
Refer to these files for an example of how you can implement your own custom decoders.
To wire up your custom decoders to the `ma_decoder` API, you need to set up a `ma_decoder_config`
object and fill out the `ppBackendVTables` and `backendCount` members. The `ppBackendVTables` member
is an array of pointers to `ma_decoding_backend_vtable` objects. The order of the array defines the
order of priority, with the first being the highest priority. The `backendCount` member is the number
of items in the `ppBackendVTables` array.
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.
*/
#include "../miniaudio.c"
#include "../extras/decoders/libvorbis/miniaudio_libvorbis.c"
#include "../extras/decoders/libopus/miniaudio_libopus.c"
#include <stdio.h>
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, with the
first being the highest priority. The vtables are be passed in via the decoder config. If you want to
support stock backends in addition to custom backends, you must add the stock backend vtables here as
well. You should list the backends in your preferred order of priority.
The list below shows how you would set up your array to prioritize the custom decoders over the stock
decoders. If you want to prioritize the stock decoders over the custom decoders, you would simply
change the order.
*/
ma_decoding_backend_vtable* pBackendVTables[] =
{
ma_decoding_backend_libvorbis,
ma_decoding_backend_libopus,
ma_decoding_backend_wav,
ma_decoding_backend_flac,
ma_decoding_backend_mp3
};
if (argc < 2) {
printf("No input file.\n");
return -1;
}
/* Initialize the decoder. */
decoderConfig = ma_decoder_config_init_default();
decoderConfig.ppBackendVTables = pBackendVTables;
decoderConfig.backendCount = sizeof(pBackendVTables) / sizeof(pBackendVTables[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);
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;
}