Commit 40c8235e authored by David Reid's avatar David Reid

Add documentation for custom decoders.

parent 7e708dcc
...@@ -873,6 +873,11 @@ the current data source will be looped. You can loop the entire chain by linking ...@@ -873,6 +873,11 @@ the current data source will be looped. You can loop the entire chain by linking
Note that setting up chaining is not thread safe, so care needs to be taken if you're dynamically Note that setting up chaining is not thread safe, so care needs to be taken if you're dynamically
changing links while the audio thread is in the middle of reading. changing links while the audio thread is in the middle of reading.
Do not use `ma_decoder_seek_to_pcm_frame()` as a means to reuse a data source to play multiple
instances of the same sound simultaneously. Instead, initialize multiple data sources for each
instance. This can be extremely inefficient depending on the data source and can result in
glitching due to subtle changes to the state of internal filters.
4.1. Custom Data Sources 4.1. Custom Data Sources
------------------------ ------------------------
...@@ -1418,6 +1423,11 @@ source, mainly for convenience: ...@@ -1418,6 +1423,11 @@ source, mainly for convenience:
Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do
not have any notion of a data source, anything relating to a data source is unavailable. not have any notion of a data source, anything relating to a data source is unavailable.
Internally, sound data is loaded via the `ma_decoder` API which means by default in only supports
file formats that have built-in support in miniaudio. You can extend this to support any kind of
file format through the use of custom decoders. To do this you'll need to use a self-managed
resource manager and configure it appropriately. See the "Resource Management" section below for
details on how to set this up.
6. Resource Management 6. Resource Management
...@@ -1471,6 +1481,29 @@ the data format of the file did not match the device's data format, you would ne ...@@ -1471,6 +1481,29 @@ the data format of the file did not match the device's data format, you would ne
data at mixing time which may be prohibitive in high-performance and large scale scenarios like data at mixing time which may be prohibitive in high-performance and large scale scenarios like
games. games.
Internally the resource manager uses the `ma_decoder` API to load sounds. This means by default it
only supports decoders that are built into miniaudio. It's possible to support additional encoding
formats through the use of custom decoders. To do so, pass in your `ma_decoding_backend_vtable`
vtables into the resource manager config:
```c
ma_decoding_backend_vtable* pCustomBackendVTables[] =
{
&g_ma_decoding_backend_vtable_libvorbis,
&g_ma_decoding_backend_vtable_libopus
};
...
resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables;
resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
resourceManagerConfig.pCustomDecodingBackendUserData = NULL;
```
This system can allow you to support any kind of file format. See the "Decoding" section for
details on how to implement custom decoders. The miniaudio repository includes examples for Opus
via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile.
Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the
decoding of a page, a job will be posted to a queue which will then be processed by a job thread. decoding of a page, a job will be posted to a queue which will then be processed by a job thread.
By default there will be only one job thread running, but this can be configured, like so: By default there will be only one job thread running, but this can be configured, like so:
...@@ -1620,6 +1653,15 @@ pointer. When `MA_DATA_SOURCE_STREAM` is specified, it will try loading the file ...@@ -1620,6 +1653,15 @@ pointer. When `MA_DATA_SOURCE_STREAM` is specified, it will try loading the file
VFS. VFS.
6.1. Custom Decoders
--------------------
Internally the resource manager uses the `ma_decoder` API to load sounds. This means by default it
only supports decoders that are built into miniaudio.
It's possible to support additional encoding formats through the use of custom decoders. To do so,
6.1. Resource Manager Implementation Details 6.1. Resource Manager Implementation Details
-------------------------------------------- --------------------------------------------
Resources are managed in two main ways: Resources are managed in two main ways:
...@@ -2311,6 +2353,74 @@ The `ma_decoder_init_file()` API will try using the file extension to determine ...@@ -2311,6 +2353,74 @@ The `ma_decoder_init_file()` API will try using the file extension to determine
backend to prefer. backend to prefer.
8.1. Custom Decoders
--------------------
It's possible to implement a custom decoder and plug it into miniaudio. This is extremely useful
when you want to use the `ma_decoder` API, but need to support an encoding format that's not one of
the stock formats supported by miniaudio. This can be put to particularly good use when using the
`ma_engine` and/or `ma_resource_manager` APIs because they use `ma_decoder` internally. If, for
example, you wanted to support Opus, you can do so with a custom decoder (there if a reference
Opus decoder in the "extras" folder of the miniaudio repository which uses libopus + libousfile).
A custom decoder must implement a data source. A vtable called `ma_decoding_backend_vtable` needs
to be implemented which is then passed into the decoder config:
```c
ma_decoding_backend_vtable* pCustomBackendVTables[] =
{
&g_ma_decoding_backend_vtable_libvorbis,
&g_ma_decoding_backend_vtable_libopus
};
...
decoderConfig = ma_decoder_config_init_default();
decoderConfig.pCustomBackendUserData = NULL;
decoderConfig.ppCustomBackendVTables = pCustomBackendVTables;
decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]);
```
The `ma_decoding_backend_vtable` vtable has the following functions:
```
onInit
onInitFile
onInitFileW
onInitMemory
onUninit
```
There are only two functions that must be implemented - `onInit` and `onUninit`. The other
functions can be implemented for a small optimization for loading from a file path or memory. If
these are not specified, miniaudio will deal with it for you via a generic implementation.
When you initialize a custom data source (by implementing the `onInit` function in the vtable) you
will need to output a pointer to a `ma_data_source` which implements your custom decoder. See the
section about data sources for details on how to implemen this. Alternatively, see the
"custom_decoders" example in the miniaudio repository.
The `onInit` function takes a pointer to some callbacks for the purpose of reading raw audio data
from some abitrary source. You'll use these functions to read from the raw data and perform the
decoding. When you call them, you will pass in the `pReadSeekTellUserData` pointer to the relevant
parameter.
The `pConfig` parameter in `onInit` can be used to configure the backend if appropriate. It's only
used as a hint and can be ignored. However, if any of the properties are relevant to your decoder,
an optimal implementation will handle the relevant properties appropriately.
If allocation memory is required, it should be done so via the specified allocation callbacks if
possible (the `pAllocationCallbacks` parameter).
If an error occurs when initializing the decoder, you should leave `ppBackend` unset, or set to
NULL, and make sure everything is cleaned up appropriately and an appropriate result code returned.
When multiple custom backends are specified, miniaudio will cycle through the vtables in the order
they're listed in the array that's passed into the decoder config so it's important that your
initialization routine is clean.
When a decoder is uninitialized, the `onUninit` callback will be fired which will give you an
opportunity to clean up and internal data.
9. Encoding 9. Encoding
=========== ===========
...@@ -8626,11 +8736,11 @@ MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format pref ...@@ -8626,11 +8736,11 @@ MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format pref
typedef struct typedef struct
{ {
ma_result (* onInit )(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 (* onInit )(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 (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */
ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */
ma_result (* onInitMemory )(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ ma_result (* onInitMemory)(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */
void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks);
} ma_decoding_backend_vtable; } ma_decoding_backend_vtable;
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