miniaudio

About

  • miniaudio .

  • miniaudio - Examples .

  • miniaudio - High Level API in Odin .

  • vendor:miniaudio .

  • Single-header C library for audio playback, capture, decoding, mixing.

  • Supports decoding WAV, MP3 and FLAC out of the box

  • Cross-platform with minimal dependencies.

  • Designed for modern low-level control.

  • Built-in decoders and resamplers.

  • Full control over audio pipeline.

  • Good for procedural audio and DSP.

  • Cons :

    • No advanced spatial audio out of the box.

    • You build your own 3D audio, effects, scene graph, etc.

  • Backends:

    • WASAPI

    • DirectSound

    • WinMM

    • Core Audio (Apple)

    • ALSA

    • PulseAudio

    • JACK

    • sndio (OpenBSD)

    • audio(4) (NetBSD and OpenBSD)

    • OSS (FreeBSD)

    • AAudio (Android 8.0+)

    • OpenSL|ES (Android only)

    • Web Audio (Emscripten)

    • Null (Silence)

    • Custom

Definitions

  • Sample

    • Is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit floating point number.

  • Frame / PCM Frame

    • A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 samples, a mono frame is 1 sample, a 5.1 surround sound frame is 6 samples, etc. The terms "frame" and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. If ever miniaudio needs to refer to a compressed frame, such as a FLAC frame, it will always clarify what it's referring to with something like "FLAC frame".

  • Channel

    • A stream of monaural audio that is emitted from an individual speaker in a speaker system, or received from an individual microphone in a microphone system. A stereo stream has two channels (a left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio systems refer to a channel as a complex audio stream that's mixed with other channels to produce the final mix - this is completely different to miniaudio's use of the term "channel" and should not be confused.

  • Sample Rate

    • The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number of PCM frames that are processed per second.

  • Formats

    • Throughout miniaudio you will see references to different sample formats:

Device

  • The device is sleeping by default so you'll need to start it manually.

data_callback
  • Is where audio data is written and read from the device.

  • This function will be called when miniaudio needs more data.

  • The following APIs must never be called inside the callback:

    • ma_device_init()

    • ma_device_init_ex()

    • ma_device_uninit()

    • ma_device_start()

    • ma_device_stop()

Getting a device ID
  • To retrieve the device ID you will need to perform device enumeration, however this requires the use of a new concept called the "context".

  • etc.

  • Enumerating devices example

Starting the device
  • The device is sleeping by default so you'll need to start it manually.

  • Starts the device. For playback devices this begins playback. For capture devices it begins recording.

  • Use ma_device_stop()  to stop the device.

  • It is not safe to call this inside any callback.

  • It's safe to call this from any thread with the exception of the callback thread.

  • For a playback device, this will retrieve an initial chunk of audio data from the client before returning. The reason for this is to ensure there is valid audio data in the buffer, which needs to be done before the device begins playback.

  • This API waits until the backend device has been started for real by the worker thread. It also waits on a mutex for thread-safety.

Decoder

  • Used for reading audio files.

  • Decoders are completely decoupled from devices and can be used independently.

  • Built-in decoders:

    • WAV.

    • MP3.

    • FLAC.

    • You can disable the built-in decoders by specifying one or more of the following options before the miniaudio implementation:

    #define MA_NO_WAV
    #define MA_NO_MP3
    #define MA_NO_FLAC
    
  • readPointerInPCMFrames: u64,

    • In output sample rate. Used for keeping track of how many frames are available for decoding.

Initializing a Decoder
  • A decoder can be initialized from a file with ma_decoder_init_file() , a block of memory with ma_decoder_init_memory() , or from data delivered via callbacks with ma_decoder_init() .

Initializing a Decoder: Decoder Config
  • When initializing a decoder, you can optionally pass in a pointer to a ma_decoder_config  object (the NULL argument in the example above) which allows you to configure the output format, channel count, sample rate and channel map.

  • When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding backend. This can be unnecessarily inefficient if the type is already known. In this case you can use encodingFormat variable in the device config to specify a specific encoding format you want to decode: decoderConfig.encodingFormat = ma_encoding_format_wav; . The ma_decoder_init_file()  API will try using the file extension to determine which decoding backend to prefer.

  • When passing in NULL  for decoder config in ma_decoder_init() , the output format will be the same as that defined by the decoding backend.

Seek
  • Data is read from the decoder as PCM frames. This will output the number of PCM frames actually read.

  • Return:

    • If this is less than the requested number of PCM frames it means you've reached the end.

  • You can also seek to a specific frame with ma_decoder_seek_to_pcm_frame .

  • To loop from the start: ma_decoder_seek_to_pcm_frame(pDecoder, 0);

  • 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. This can be extremely inefficient depending on the type of data source and can result in glitching due to subtle changes to the state of internal filters. Instead, initialize multiple data sources for each instance.

Custom decoders
  • 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.

  • If, for example, you wanted to support Opus, you can do so with a custom decoder:

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]);

Encoding

  • Used for writing audio files.

  • The only supported output format is WAV.

Data Sources

  • The ma_data_source  API is a generic interface for reading from a data source.

  • Any object that implements the data source interface can be plugged into any ma_data_source  function.

    • When calling any data source function, with the exception of ma_data_source_init() and ma_data_source_uninit(), you can pass in any object that implements a data source.

Read
  • To read data from a data source:

ma_result result;
ma_uint64 framesRead;

result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead);
if (result != MA_SUCCESS) {
    return result;  // Failed to read data from the data source.
}
  • If you don't need the number of frames that were successfully read you can pass in NULL to the pFramesRead  parameter.

  • Return:

    • If this returns a value less than the number of frames requested it means the end of the file has been reached.

    • MA_AT_END  will be returned only when the number of frames read is 0.

Get Data Format
  • ma_data_source_get_data_format

Get Length
  • ma_data_source_get_length_in_pcm_frames

Get Cursor
  • ma_data_source_get_cursor_in_pcm_frames

Loop
  • Custom loop points can also be used with data sources.

  • The loop point is relative to the current range.

  • By default, data sources will loop after they reach the end of the data source, but if you need to loop at a specific location, you can do the following:

Chaining Data Sources
  • example

  • It's sometimes useful to chain data sources together so that a seamless transition can be achieved.

ma_decoder decoder1;
ma_decoder decoder2;

// ... initialize decoders with ma_decoder_init_*() ...

result = ma_data_source_set_next(&decoder1, &decoder2);
if (result != MA_SUCCESS) {
    return result;  // Failed to set the next data source.
}

result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead);
if (result != MA_SUCCESS) {
    return result;  // Failed to read from the decoder.
}
  • In the example above we're using decoders. When reading from a chain, you always want to read from the top level data source in the chain. In the example above, decoder1 is the top level data source in the chain. When decoder1 reaches the end, decoder2 will start seamlessly without any gaps.

  • Note that when looping is enabled, only the current data source will be looped. You can loop the entire chain by linking in a loop like so:

ma_data_source_set_next(&decoder1, &decoder2);  // decoder1 -> decoder2
ma_data_source_set_next(&decoder2, &decoder1);  // decoder2 -> decoder1 (loop back to the start).
  • 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.

Many files

  • When mixing multiple sounds together, you should not create multiple devices. Instead you should create only a single device and then mix your sounds together which you can do by simply summing their samples together.

  • The simplest way to do this is to use floating point samples and use miniaudio's built-in clipper to handling clipping for you. (Clipping is when sample are clamped to their minimum and maximum range, which for floating point is -1..1.)

Usage:   simple_mixing [input file 0] [input file 1] ... [input file n]
Example: simple_mixing file1.wav file2.flac
  • Mixing example .

    • The decoders will be mixed together in the callback.

    • Create only a single device.

    • In this example, all decoders need to have the same output format.

    • In this example the data format needs to be the same as the decoders.

    • We can't stop in the audio thread so we instead need to use an event. We wait on this thread in the main thread, and signal it in the audio thread. This needs to be done before starting the device.

    • We need a context to initialize the event, which we can get from the device.

    • Alternatively you can initialize a context separately, but we don't need to do that for this example.

    • The event will be signaled by the audio thread, waited on by the main thread.

  • ma_decoder  (a ma_data_source) advances an internal cursor when read; thatโ€™s why a single decoder canโ€™t be used by multiple concurrent players without separate state or locks.

  • For concurrency/overlap, either create multiple data sources or use an in-memory buffer data source.

  • Streaming / long files (music, long voice):

    • use a ma_decoder  (or streamed ma_resource_manager data source) per file/stream thatโ€™s being read by the audio callback. If you play several long files simultaneously, give each active stream its own decoder and mix their output.

  • Use the resource manager / audio buffer approach if you want automatic caching and lighter-weight playback instances.

Playback

Capture

Spatialization

High-Level API

  • Engine.

    • For managing and mixing sounds and effect processing.

    • When the engine is initialized, it will normally create a device internally. If you would rather manage the device yourself, you can do so and just pass a pointer to it via the engine config when you initialize the engine. You can also just use the engine without a device, which again can be configured via the engine config.

  • Sound.

    • Sounds can be associated with a mixing group called ma_sound_group which are also created from the engine. Both ma_sound and ma_sound_group objects are nodes within the engine's node graph.

  • Resource Manager.

    • ma_resource_manager .

    • Loading of sound files into memory with reference counting.

    • Streaming of sound data.