From 46aaa53f2c8275a74a14d023cc0f4e34f0b38fcd Mon Sep 17 00:00:00 2001 From: David Reid Date: Fri, 21 Oct 2016 10:33:13 +1000 Subject: [PATCH] Update documentation and add support for default buffer sizes and periods. --- README.md | 11 ++--- mini_al.h | 141 +++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 123 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 397743fd..5d4aaeda 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,7 @@ Features Examples ======== - -Asynchonous API ---------------- -In asynchronous mode, mini_al will request and deliver audio data through callbacks. +mini_al will request and deliver audio data through callbacks. ``` mal_uint32 on_send_audio_data(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples) @@ -34,11 +31,11 @@ int main() { mal_uint32 channels = 2; mal_uint32 sampleRate = 44100; - mal_uint32 fragmentSizeInFrames = 512; - mal_uint32 fragmentCount = 2; + mal_uint32 bufferSizeInFrames = 512; + mal_uint32 periods = 2; mal_device playbackDevice; - if (mal_device_init(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) { + if (mal_device_init(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, bufferSizeInFrames, periods) != MAL_SUCCESS) { return -1; } diff --git a/mini_al.h b/mini_al.h index e7503c34..e861ad21 100644 --- a/mini_al.h +++ b/mini_al.h @@ -1,23 +1,28 @@ // Mini audio library. Public domain. See "unlicense" statement at the end of this file. -// mini_al - v0.0 - UNRELEASED +// mini_al - v0.1 - 2016-10-21 // // David Reid - mackron@gmail.com // ABOUT // ===== -// mini_al is a small library for making it easy to do audio playback and recording. It's focused on -// simplicity and being light-weight so don't expect all the bells and whistles. +// mini_al is a small library for making it easy to connect to a playback or capture device and send +// or receive data from said device. It's focused on being simple and light-weight so don't expect +// all the bells and whistles. Indeed, this is not a full packaged audio library - it's just for +// connecting to a device and handling data transmission. // // mini_al uses an asynchronous API. Every device is created with it's own thread, with audio data // being either delivered to the application from the device (in the case of recording/capture) or // delivered from the application to the device in the case of playback. Synchronous APIs are not -// supported in the interest of keeping the library as small and lightweight as possible. +// supported in the interest of keeping the library as small and light-weight as possible. // // Supported backends: // - DirectSound (Windows Only) // - ALSA (Linux Only) // - null -// - ... and many more in the future. +// - ... and more in the future. +// - Core Audio (OSX, iOS) +// - Something for Android +// - Maybe OSS // // // USAGE @@ -26,6 +31,51 @@ // #define MAL_IMPLEMENTATION // #include "mini_al.h" // +// You can then #include this file in other parts of the program as you would with any other header file. +// +// +// Building (Windows) +// ------------------ +// You do not need to link to anything for the Windows build, but you will need dsound.h in your include paths. +// +// +// Building (Linux) +// ---------------- +// The Linux build uses ALSA for it's backend so you will need to install the relevant ALSA development pacakges +// for your preferred distro. It also uses pthreads. +// +// Linking: -lasound -lpthread +// +// +// Playback Example +// ---------------- +// mal_uint32 on_send_samples(mal_device* pDevice, mal_uint32 frameCount, void* pSamples) +// { +// // This callback is set with mal_device_set_send_callback() after initializing and will be +// // called when a playback device needs more data. You need to write as many frames as you can +// // to pSamples (but no more than frameCount) and then return the number of frames you wrote. +// // +// // You can pass in user data by setting pDevice->pUserData after initialization. +// return (mal_uint32)drwav_read_f32(&wav, frameCount * pDevice->channels, (float*)pSamples) / pDevice->channels; +// } +// +// ... +// +// mal_device device; +// mal_result result = mal_device_init(&device, mal_device_type_playback, &id, mal_format_f32, wav.channels, wav.sampleRate, 16384, 2, NULL); +// if (result != MAL_SUCCESS) { +// return -1; +// } +// +// device.pUserData = pMyData; // pUserData is reserved for you. Use it to pass data to callbacks. +// mal_device_set_send_callback(&device, on_send_samples); +// mal_device_start(&device); // The device is sleeping by default so you'll need to start it manually. +// +// ... +// +// mal_device_uninit(&device); // This will stop the device so no need to do that manually. +// +// // // NOTES // ===== @@ -39,8 +89,34 @@ // integer samples, interleaved. Let me know if you need non-interleaved and I'll look into it. // // +// // BACKEND NUANCES // =============== +// - The absolute best latency I am able to get on DirectSound is about 10 milliseconds. This seems very +// consistent so I'm suspecting there's some kind of hard coded limit there or something. +// - The ALSA backend does not support rewinding. +// +// +// +// OPTIONS +// ======= +// #define these options before including this file. +// +// #define MAL_NO_DSOUND +// Disables the DirectSound backend. Note that this is the only backend for the Windows platform. +// +// #define MAL_NO_ALSA +// Disables the ALSA backend. Note that this is the only backend for the Linux platform. +// +// #define MAL_NO_NULL +// Disables the null backend. +// +// #define MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS (Default = 15) +// When a buffer size of 0 is specified when a device is initialized, it will default to a size with +// this number of milliseconds worth of data. +// +// #define MAL_DEFAULT_PERIODS (Default = 2) +// When a period count of 0 is specified when a device is initialized, it will default to this. #ifndef mini_al_h #define mini_al_h @@ -49,6 +125,24 @@ extern "C" { #endif +// Config. + +// The default size of the device's buffer in milliseconds. In my testing, if this is a multiple of the +// periods it can result in audio glitching on the DirectSound backend, so make sure it's not a clean +// multiple of the period. +// +// If this is too small you may get underruns and overruns in which case you'll need to either increase +// this value or use an explicit buffer size. +#ifndef MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS +#define MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS 15 +#endif + +// Default periods when none is specified in mal_device_init(). More periods means more work on the CPU. +#ifndef MAL_DEFAULT_PERIODS +#define MAL_DEFAULT_PERIODS 2 +#endif + + // Platform/backend detection. #ifdef _WIN32 #define MAL_WIN32 @@ -111,10 +205,6 @@ typedef void* mal_ptr; } mal_event; #endif -#ifdef MAL_ENABLE_DSOUND - #define MAL_MAX_PERIODS_DSOUND 16 -#endif - typedef int mal_result; #define MAL_SUCCESS 0 #define MAL_ERROR -1 // A generic error. @@ -278,16 +368,22 @@ struct mal_device // This API dynamically links to backend DLLs/SOs (such as dsound.dll). mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo); -// Initializes a device in asynchronous mode. +// Initializes a device. // // The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you -// can retrieve the ID by calling mal_enumerate_devices() and retrieve the ID from the returned -// information. +// can retrieve the ID by calling mal_enumerate_devices() and using the ID from the returned data. // // This will try it's hardest to create a valid device, even if it means adjusting input arguments. // Look at pDevice->channels, pDevice->sampleRate, etc. to determine the actual properties after // initialization. // +// If is 0, it will default to MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If +// is set to 0 it will default to MAL_DEFAULT_PERIODS. +// +// The property controls how frequently the background thread is woken to check for more +// data. It's tied to the buffer size, so as an example, if your buffer size is equivalent to 10 +// milliseconds and you have 2 periods, the CPU will wake up approximately every 5 milliseconds. +// // Return Value: // - MAL_SUCCESS if successful. // - MAL_INVALID_ARGS @@ -299,14 +395,13 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d // that performs a memory allocation is ALSA when mmap mode is not supported. // - MAL_FORMAT_NOT_SUPPORTED // The specified format is not supported by the backend. mini_al does not currently do any -// software format conversions which means initialization must fail if the backend device does -// not natively support it. +// software format conversions which means initialization must fail if the backend does not +// natively support it. // - MAL_FAILED_TO_INIT_BACKEND // There was a backend-specific error during initialization. // -// Thread Safety: ??? -// This API is thread safe so long as the application does not try to use the device object before -// this call has returned. +// Thread Safety: UNSAFE +// Results are undefined if you try using a device before this function as returned. // // Efficiency: LOW // This API will dynamically link to backend DLLs/SOs like dsound.dll, and is otherwise just slow @@ -1508,9 +1603,6 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t return MAL_FORMAT_NOT_SUPPORTED; } - if (periods > MAL_MAX_PERIODS_DSOUND) { - periods = MAL_MAX_PERIODS_DSOUND; - } WAVEFORMATEXTENSIBLE wf; mal_zero_object(&wf); @@ -1583,6 +1675,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t pDevice->sampleRate = pActualFormat->Format.nSamplesPerSec; bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + // Meaning of dwFlags (from MSDN): // // DSBCAPS_GLOBALFOCUS @@ -2774,7 +2867,11 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device } } - if (channels == 0 || sampleRate == 0 || bufferSizeInFrames == 0 || periods == 0) return mal_post_error(pDevice, "mal_device_init() called with invalid arguments.", MAL_INVALID_ARGS); + if (channels == 0 || sampleRate == 0) return mal_post_error(pDevice, "mal_device_init() called with invalid arguments.", MAL_INVALID_ARGS); + + // Default buffer size and periods. + if (bufferSizeInFrames == 0) bufferSizeInFrames = (sampleRate/1000) * MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS; + if (periods == 0) periods = MAL_DEFAULT_PERIODS; pDevice->type = type; pDevice->format = format; @@ -3087,7 +3184,7 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format) // REVISION HISTORY // ================ // -// v0.1 - TBD +// v0.1 - 2016-10-21 // - Initial versioned release.