From 7e01c6535ba8eff12485232ee60f37099db2e82e Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 7 Aug 2023 16:14:05 +1000 Subject: [PATCH] More work on custom device backends. With this commit, custom backends can now be implemented as self-contained modules that can easily be plugged in and mixed and matched depending on a programs requirements. The order in which custom backends are specified in the context config determine their priority. This commit updates the custom_backend example by moving the SDL code out into its own file in "extras/backends/sdl". The example will now just include the SDL code files like normal. This represents a more realistic scenario. --- examples/custom_backend.c | 661 +++--------------------------- extras/backends/sdl/backend_sdl.c | 647 +++++++++++++++++++++++++++++ extras/backends/sdl/backend_sdl.h | 34 ++ miniaudio.h | 100 ++++- 4 files changed, 812 insertions(+), 630 deletions(-) create mode 100644 extras/backends/sdl/backend_sdl.c create mode 100644 extras/backends/sdl/backend_sdl.h diff --git a/examples/custom_backend.c b/examples/custom_backend.c index b1303ac9..db8ef455 100644 --- a/examples/custom_backend.c +++ b/examples/custom_backend.c @@ -1,26 +1,9 @@ /* -This example shows how a custom backend can be implemented. +This example shows how to plug in custom backends. -This implements a full-featured SDL2 backend. It's intentionally built using the same paradigms as the built-in backends in order to make -it suitable as a solid basis for a custom implementation. The SDL2 backend can be disabled with MA_NO_SDL, exactly like the built-in -backends. It supports both runtime and compile-time linking and respects the MA_NO_RUNTIME_LINKING option. It also works on Emscripten -which requires the `-s USE_SDL=2` option. - -There may be times where you want to support more than one custom backend. This example has been designed to make it easy to plug-in extra -custom backends without needing to modify any of the base miniaudio initialization code. A custom context structure is declared called -`ma_context_ex`. The first member of this structure is a `ma_context` object which allows it to be cast between the two. The same is done -for devices, which is called `ma_device_ex`. In these structures there is a section for each custom backend, which in this example is just -SDL. These are only enabled at compile time if `MA_SUPPORT_SDL` is defined, which it always is in this example (you may want to have some -logic which more intelligently enables or disables SDL support). This is not the only way to associate backend-specific data with the -context and/or device. In both the `ma_context` and `ma_device` structures there is a member named `pBackendData` which is a `void*` and -can be used to reference any data you like. It is not used by miniaudio and exists purely for the purpose of allowing you to associate -backend-specific data with the context and/or device. - -To use a custom backend, you must implement a `ma_device_backend_vtable`. You pass a pointer to this into the context config along with -an optional user data pointer. In this example we just declare our `ma_device_backend_vtable` statically and pass in NULL for the user -data. The device config accepts an array of vtable pointers which is how you can plug in multiple custom backends. In this example we just -have the one vtable pointer, but it's designed to easily allow you to plug in other custom backends. Any functions that you don't need -can be defined as NULL in the vtable. +To use a custom backend you need to plug in a `ma_device_backend_vtable` pointer into the context config. You can plug in multiple +custom backends, but for this example we're just using the SDL backend which you can find in the extras folder in the miniaudio +repository. If your custom backend requires it, you can also plug in a user data pointer which will be passed to the backend callbacks. Custom backends are identified with the `ma_backend_custom` backend type. For the purpose of demonstration, this example only uses the `ma_backend_custom` backend type because otherwise the built-in backends would always get chosen first and none of the code for the custom @@ -30,6 +13,9 @@ backends would actually get hit. By default, the `ma_backend_custom` backend is #define MINIAUDIO_IMPLEMENTATION #include "../miniaudio.h" +/* We're using SDL for this example. To use this in your own program, you need to include backend_sdl.h after miniaudio.h. */ +#include "../extras/backends/sdl/backend_sdl.h" + #ifdef __EMSCRIPTEN__ #include @@ -38,586 +24,6 @@ void main_loop__em() } #endif -/* Support SDL on everything. */ -#define MA_SUPPORT_SDL - -/* -Only enable SDL if it's hasn't been explicitly disabled (MA_NO_SDL) or enabled (MA_ENABLE_SDL with -MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_SDL). -*/ -#if defined(MA_SUPPORT_SDL) && !defined(MA_NO_SDL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SDL)) - #define MA_HAS_SDL -#endif - - -typedef struct -{ - ma_context context; /* Make this the first member so we can cast between ma_context and ma_context_ex. */ -#if defined(MA_SUPPORT_SDL) - struct - { - ma_handle hSDL; /* A handle to the SDL2 shared object. We dynamically load function pointers at runtime so we can avoid linking. */ - ma_proc SDL_InitSubSystem; - ma_proc SDL_QuitSubSystem; - ma_proc SDL_GetNumAudioDevices; - ma_proc SDL_GetAudioDeviceName; - ma_proc SDL_CloseAudioDevice; - ma_proc SDL_OpenAudioDevice; - ma_proc SDL_PauseAudioDevice; - } sdl; -#endif -} ma_context_ex; - -typedef struct -{ - ma_device device; /* Make this the first member so we can cast between ma_device and ma_device_ex. */ -#if defined(MA_SUPPORT_SDL) - struct - { - int deviceIDPlayback; - int deviceIDCapture; - } sdl; -#endif -} ma_device_ex; - - - -#if defined(MA_HAS_SDL) - /* SDL headers are necessary if using compile-time linking. */ - #ifdef MA_NO_RUNTIME_LINKING - #ifdef __has_include - #ifdef MA_EMSCRIPTEN - #if !__has_include() - #undef MA_HAS_SDL - #endif - #else - #if !__has_include() - #undef MA_HAS_SDL - #endif - #endif - #endif - #endif -#endif - - -#if defined(MA_HAS_SDL) -#define MA_SDL_INIT_AUDIO 0x00000010 -#define MA_AUDIO_U8 0x0008 -#define MA_AUDIO_S16 0x8010 -#define MA_AUDIO_S32 0x8020 -#define MA_AUDIO_F32 0x8120 -#define MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 -#define MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 -#define MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 -#define MA_SDL_AUDIO_ALLOW_ANY_CHANGE (MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE | MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE) - -/* If we are linking at compile time we'll just #include SDL.h. Otherwise we can just redeclare some stuff to avoid the need for development packages to be installed. */ -#ifdef MA_NO_RUNTIME_LINKING - #define SDL_MAIN_HANDLED - #ifdef MA_EMSCRIPTEN - #include - #else - #include - #endif - - typedef SDL_AudioCallback MA_SDL_AudioCallback; - typedef SDL_AudioSpec MA_SDL_AudioSpec; - typedef SDL_AudioFormat MA_SDL_AudioFormat; - typedef SDL_AudioDeviceID MA_SDL_AudioDeviceID; -#else - typedef void (* MA_SDL_AudioCallback)(void* userdata, ma_uint8* stream, int len); - typedef ma_uint16 MA_SDL_AudioFormat; - typedef ma_uint32 MA_SDL_AudioDeviceID; - - typedef struct MA_SDL_AudioSpec - { - int freq; - MA_SDL_AudioFormat format; - ma_uint8 channels; - ma_uint8 silence; - ma_uint16 samples; - ma_uint16 padding; - ma_uint32 size; - MA_SDL_AudioCallback callback; - void* userdata; - } MA_SDL_AudioSpec; -#endif - -typedef int (* MA_PFN_SDL_InitSubSystem)(ma_uint32 flags); -typedef void (* MA_PFN_SDL_QuitSubSystem)(ma_uint32 flags); -typedef int (* MA_PFN_SDL_GetNumAudioDevices)(int iscapture); -typedef const char* (* MA_PFN_SDL_GetAudioDeviceName)(int index, int iscapture); -typedef void (* MA_PFN_SDL_CloseAudioDevice)(MA_SDL_AudioDeviceID dev); -typedef MA_SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice)(const char* device, int iscapture, const MA_SDL_AudioSpec* desired, MA_SDL_AudioSpec* obtained, int allowed_changes); -typedef void (* MA_PFN_SDL_PauseAudioDevice)(MA_SDL_AudioDeviceID dev, int pause_on); - -MA_SDL_AudioFormat ma_format_to_sdl(ma_format format) -{ - switch (format) - { - case ma_format_unknown: return 0; - case ma_format_u8: return MA_AUDIO_U8; - case ma_format_s16: return MA_AUDIO_S16; - case ma_format_s24: return MA_AUDIO_S32; /* Closest match. */ - case ma_format_s32: return MA_AUDIO_S32; - case ma_format_f32: return MA_AUDIO_F32; - default: return 0; - } -} - -ma_format ma_format_from_sdl(MA_SDL_AudioFormat format) -{ - switch (format) - { - case MA_AUDIO_U8: return ma_format_u8; - case MA_AUDIO_S16: return ma_format_s16; - case MA_AUDIO_S32: return ma_format_s32; - case MA_AUDIO_F32: return ma_format_f32; - default: return ma_format_unknown; - } -} - -static ma_result ma_context_enumerate_devices__sdl(void* pUserData, ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData) -{ - ma_context_ex* pContextEx = (ma_context_ex*)pContext; - ma_bool32 isTerminated = MA_FALSE; - ma_bool32 cbResult; - int iDevice; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); - (void)pUserData; - - /* Playback */ - if (!isTerminated) { - int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(0); - for (iDevice = 0; iDevice < deviceCount; ++iDevice) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - - deviceInfo.id.custom.i = iDevice; - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 0), (size_t)-1); - - if (iDevice == 0) { - deviceInfo.isDefault = MA_TRUE; - } - - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pCallbackUserData); - if (cbResult == MA_FALSE) { - isTerminated = MA_TRUE; - break; - } - } - } - - /* Capture */ - if (!isTerminated) { - int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(1); - for (iDevice = 0; iDevice < deviceCount; ++iDevice) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - - deviceInfo.id.custom.i = iDevice; - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 1), (size_t)-1); - - if (iDevice == 0) { - deviceInfo.isDefault = MA_TRUE; - } - - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pCallbackUserData); - if (cbResult == MA_FALSE) { - isTerminated = MA_TRUE; - break; - } - } - } - - return MA_SUCCESS; -} - -static ma_result ma_context_get_device_info__sdl(void* pUserData, ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) -{ - ma_context_ex* pContextEx = (ma_context_ex*)pContext; - -#if !defined(__EMSCRIPTEN__) - MA_SDL_AudioSpec desiredSpec; - MA_SDL_AudioSpec obtainedSpec; - MA_SDL_AudioDeviceID tempDeviceID; - const char* pDeviceName; -#endif - - MA_ASSERT(pContext != NULL); - (void)pUserData; - - if (pDeviceID == NULL) { - if (deviceType == ma_device_type_playback) { - pDeviceInfo->id.custom.i = 0; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - pDeviceInfo->id.custom.i = 0; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } - } else { - pDeviceInfo->id.custom.i = pDeviceID->custom.i; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1), (size_t)-1); - } - - if (pDeviceInfo->id.custom.i == 0) { - pDeviceInfo->isDefault = MA_TRUE; - } - - /* - To get an accurate idea on the backend's native format we need to open the device. Not ideal, but it's the only way. An - alternative to this is to report all channel counts, sample rates and formats, but that doesn't offer a good representation - of the device's _actual_ ideal format. - - Note: With Emscripten, it looks like non-zero values need to be specified for desiredSpec. Whatever is specified in - desiredSpec will be used by SDL since it uses it just does it's own format conversion internally. Therefore, from what - I can tell, there's no real way to know the device's actual format which means I'm just going to fall back to the full - range of channels and sample rates on Emscripten builds. - */ -#if defined(__EMSCRIPTEN__) - /* Good practice to prioritize the best format first so that the application can use the first data format as their chosen one if desired. */ - pDeviceInfo->nativeDataFormatCount = 3; - pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; - pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channel counts supported. */ - pDeviceInfo->nativeDataFormats[0].sampleRate = 0; /* All sample rates supported. */ - pDeviceInfo->nativeDataFormats[0].flags = 0; - pDeviceInfo->nativeDataFormats[1].format = ma_format_s32; - pDeviceInfo->nativeDataFormats[1].channels = 0; /* All channel counts supported. */ - pDeviceInfo->nativeDataFormats[1].sampleRate = 0; /* All sample rates supported. */ - pDeviceInfo->nativeDataFormats[1].flags = 0; - pDeviceInfo->nativeDataFormats[2].format = ma_format_u8; - pDeviceInfo->nativeDataFormats[2].channels = 0; /* All channel counts supported. */ - pDeviceInfo->nativeDataFormats[2].sampleRate = 0; /* All sample rates supported. */ - pDeviceInfo->nativeDataFormats[2].flags = 0; -#else - MA_ZERO_MEMORY(&desiredSpec, sizeof(desiredSpec)); - - pDeviceName = NULL; - if (pDeviceID != NULL) { - pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1); - } - - tempDeviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); - if (tempDeviceID == 0) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to open SDL device."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } - - ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(tempDeviceID); - - /* Only reporting a single native data format. It'll be whatever SDL decides is the best. */ - pDeviceInfo->nativeDataFormatCount = 1; - pDeviceInfo->nativeDataFormats[0].format = ma_format_from_sdl(obtainedSpec.format); - pDeviceInfo->nativeDataFormats[0].channels = obtainedSpec.channels; - pDeviceInfo->nativeDataFormats[0].sampleRate = obtainedSpec.freq; - pDeviceInfo->nativeDataFormats[0].flags = 0; - - /* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */ - if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; - } -#endif /* __EMSCRIPTEN__ */ - - return MA_SUCCESS; -} - - -void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) -{ - ma_device_ex* pDeviceEx = (ma_device_ex*)pUserData; - - MA_ASSERT(pDeviceEx != NULL); - - ma_device_handle_backend_data_callback((ma_device*)pDeviceEx, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.capture.internalFormat, pDeviceEx->device.capture.internalChannels)); -} - -void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) -{ - ma_device_ex* pDeviceEx = (ma_device_ex*)pUserData; - - MA_ASSERT(pDeviceEx != NULL); - - ma_device_handle_backend_data_callback((ma_device*)pDeviceEx, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.playback.internalFormat, pDeviceEx->device.playback.internalChannels)); -} - -static ma_result ma_device_init_internal__sdl(ma_device_ex* pDeviceEx, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor) -{ - ma_context_ex* pContextEx = (ma_context_ex*)pDeviceEx->device.pContext; - MA_SDL_AudioSpec desiredSpec; - MA_SDL_AudioSpec obtainedSpec; - const char* pDeviceName; - int deviceID; - - MA_ASSERT(pDeviceEx != NULL); - MA_ASSERT(pDescriptor != NULL); - - /* - SDL is a little bit awkward with specifying the buffer size, You need to specify the size of the buffer in frames, but since we may - have requested a period size in milliseconds we'll need to convert, which depends on the sample rate. But there's a possibility that - the sample rate just set to 0, which indicates that the native sample rate should be used. There's no practical way to calculate this - that I can think of right now so I'm just using MA_DEFAULT_SAMPLE_RATE. - */ - if (pDescriptor->sampleRate == 0) { - pDescriptor->sampleRate = MA_DEFAULT_SAMPLE_RATE; - } - - /* - When determining the period size, you need to take defaults into account. This is how the size of the period should be determined. - - 1) If periodSizeInFrames is not 0, use periodSizeInFrames; else - 2) If periodSizeInMilliseconds is not 0, use periodSizeInMilliseconds; else - 3) If both periodSizeInFrames and periodSizeInMilliseconds is 0, use the backend's default. If the backend does not allow a default - buffer size, use a default value of MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY or - MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE depending on the value of pConfig->performanceProfile. - - Note that options 2 and 3 require knowledge of the sample rate in order to convert it to a frame count. You should try to keep the - calculation of the period size as accurate as possible, but sometimes it's just not practical so just use whatever you can. - - A helper function called ma_calculate_buffer_size_in_frames_from_descriptor() is available to do all of this for you which is what - we'll be using here. - */ - pDescriptor->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile); - - /* SDL wants the buffer size to be a power of 2 for some reason. */ - if (pDescriptor->periodSizeInFrames > 32768) { - pDescriptor->periodSizeInFrames = 32768; - } else { - pDescriptor->periodSizeInFrames = ma_next_power_of_2(pDescriptor->periodSizeInFrames); - } - - - /* We now have enough information to set up the device. */ - MA_ZERO_OBJECT(&desiredSpec); - desiredSpec.freq = (int)pDescriptor->sampleRate; - desiredSpec.format = ma_format_to_sdl(pDescriptor->format); - desiredSpec.channels = (ma_uint8)pDescriptor->channels; - desiredSpec.samples = (ma_uint16)pDescriptor->periodSizeInFrames; - desiredSpec.callback = (pConfig->deviceType == ma_device_type_capture) ? ma_audio_callback_capture__sdl : ma_audio_callback_playback__sdl; - desiredSpec.userdata = pDeviceEx; - - /* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */ - if (desiredSpec.format == 0) { - desiredSpec.format = MA_AUDIO_F32; - } - - pDeviceName = NULL; - if (pDescriptor->pDeviceID != NULL) { - pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDescriptor->pDeviceID->custom.i, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1); - } - - deviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); - if (deviceID == 0) { - ma_log_postf(ma_device_get_log((ma_device*)pDeviceEx), MA_LOG_LEVEL_ERROR, "Failed to open SDL2 device."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } - - if (pConfig->deviceType == ma_device_type_playback) { - pDeviceEx->sdl.deviceIDPlayback = deviceID; - } else { - pDeviceEx->sdl.deviceIDCapture = deviceID; - } - - /* The descriptor needs to be updated with our actual settings. */ - pDescriptor->format = ma_format_from_sdl(obtainedSpec.format); - pDescriptor->channels = obtainedSpec.channels; - pDescriptor->sampleRate = (ma_uint32)obtainedSpec.freq; - ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); - pDescriptor->periodSizeInFrames = obtainedSpec.samples; - pDescriptor->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */ - - return MA_SUCCESS; -} - -static ma_result ma_device_init__sdl(void* pUserData, ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice; - ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext; - ma_result result; - - MA_ASSERT(pDevice != NULL); - (void)pUserData; - - /* SDL does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */ - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } - - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorCapture); - if (result != MA_SUCCESS) { - return result; - } - } - - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorPlayback); - if (result != MA_SUCCESS) { - if (pConfig->deviceType == ma_device_type_duplex) { - ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); - } - - return result; - } - } - - return MA_SUCCESS; -} - -static ma_result ma_device_uninit__sdl(void* pUserData, ma_device* pDevice) -{ - ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice; - ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext; - - MA_ASSERT(pDevice != NULL); - (void)pUserData; - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); - } - - return MA_SUCCESS; -} - -static ma_result ma_device_start__sdl(void* pUserData, ma_device* pDevice) -{ - ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice; - ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext; - - MA_ASSERT(pDevice != NULL); - (void)pUserData; - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 0); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 0); - } - - return MA_SUCCESS; -} - -static ma_result ma_device_stop__sdl(void* pUserData, ma_device* pDevice) -{ - ma_device_ex* pDeviceEx = (ma_device_ex*)pDevice; - ma_context_ex* pContextEx = (ma_context_ex*)pDevice->pContext; - - MA_ASSERT(pDevice != NULL); - (void)pUserData; - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 1); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 1); - } - - return MA_SUCCESS; -} - -static ma_result ma_context_uninit__sdl(void* pUserData, ma_context* pContext) -{ - ma_context_ex* pContextEx = (ma_context_ex*)pContext; - - MA_ASSERT(pContext != NULL); - (void)pUserData; - - ((MA_PFN_SDL_QuitSubSystem)pContextEx->sdl.SDL_QuitSubSystem)(MA_SDL_INIT_AUDIO); - - /* Close the handle to the SDL shared object last. */ - ma_dlclose(ma_context_get_log(pContext), pContextEx->sdl.hSDL); - pContextEx->sdl.hSDL = NULL; - - return MA_SUCCESS; -} - -static ma_result ma_context_init__sdl(void* pUserData, ma_context* pContext, const ma_context_config* pConfig) -{ - ma_context_ex* pContextEx = (ma_context_ex*)pContext; - int resultSDL; - -#ifndef MA_NO_RUNTIME_LINKING - /* We'll use a list of possible shared object names for easier extensibility. */ - size_t iName; - const char* pSDLNames[] = { -#if defined(_WIN32) - "SDL2.dll" -#elif defined(__APPLE__) - "SDL2.framework/SDL2" -#else - "libSDL2-2.0.so.0" -#endif - }; - - MA_ASSERT(pContext != NULL); - - (void)pUserData; - (void)pConfig; - - /* Check if we have SDL2 installed somewhere. If not it's not usable and we need to abort. */ - for (iName = 0; iName < ma_countof(pSDLNames); iName += 1) { - pContextEx->sdl.hSDL = ma_dlopen(ma_context_get_log(pContext), pSDLNames[iName]); - if (pContextEx->sdl.hSDL != NULL) { - break; - } - } - - if (pContextEx->sdl.hSDL == NULL) { - return MA_NO_BACKEND; /* SDL2 could not be loaded. */ - } - - /* Now that we have the handle to the shared object we can go ahead and load some function pointers. */ - pContextEx->sdl.SDL_InitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_InitSubSystem"); - pContextEx->sdl.SDL_QuitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_QuitSubSystem"); - pContextEx->sdl.SDL_GetNumAudioDevices = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_GetNumAudioDevices"); - pContextEx->sdl.SDL_GetAudioDeviceName = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_GetAudioDeviceName"); - pContextEx->sdl.SDL_CloseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_CloseAudioDevice"); - pContextEx->sdl.SDL_OpenAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_OpenAudioDevice"); - pContextEx->sdl.SDL_PauseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_PauseAudioDevice"); -#else - pContextEx->sdl.SDL_InitSubSystem = (ma_proc)SDL_InitSubSystem; - pContextEx->sdl.SDL_QuitSubSystem = (ma_proc)SDL_QuitSubSystem; - pContextEx->sdl.SDL_GetNumAudioDevices = (ma_proc)SDL_GetNumAudioDevices; - pContextEx->sdl.SDL_GetAudioDeviceName = (ma_proc)SDL_GetAudioDeviceName; - pContextEx->sdl.SDL_CloseAudioDevice = (ma_proc)SDL_CloseAudioDevice; - pContextEx->sdl.SDL_OpenAudioDevice = (ma_proc)SDL_OpenAudioDevice; - pContextEx->sdl.SDL_PauseAudioDevice = (ma_proc)SDL_PauseAudioDevice; -#endif /* MA_NO_RUNTIME_LINKING */ - - resultSDL = ((MA_PFN_SDL_InitSubSystem)pContextEx->sdl.SDL_InitSubSystem)(MA_SDL_INIT_AUDIO); - if (resultSDL != 0) { - ma_dlclose(ma_context_get_log(pContext), pContextEx->sdl.hSDL); - return MA_ERROR; - } - - return MA_SUCCESS; -} - -static ma_device_backend_vtable ma_gDeviceBackendVTable_SDL = -{ - ma_context_init__sdl, - ma_context_uninit__sdl, - ma_context_enumerate_devices__sdl, - ma_context_get_device_info__sdl, - ma_device_init__sdl, - ma_device_uninit__sdl, - ma_device_start__sdl, - ma_device_stop__sdl, - NULL, /* onDeviceRead */ - NULL, /* onDeviceWrite */ - NULL, /* onDeviceDataLoop */ - NULL, /* onDeviceDataLoopWakeup */ - NULL /* onDeviceGetInfo */ -}; -#endif /* MA_HAS_SDL */ - /* Main program starts here. @@ -648,9 +54,9 @@ int main(int argc, char** argv) { ma_result result; ma_context_config contextConfig; - ma_context_ex context; + ma_context context; ma_device_config deviceConfig; - ma_device_ex device; + ma_device device; ma_waveform_config sineWaveConfig; ma_waveform sineWave; char name[256]; @@ -663,19 +69,24 @@ int main(int argc, char** argv) ma_backend_custom }; - /* Plug in our vtable pointers. Add any custom backends to this list. */ - ma_device_backend_vtable* pBackendVTables[] = - { - #if !defined(MA_NO_SDL) - &ma_gDeviceBackendVTable_SDL, - #endif - NULL, /* Just so we don't end up with an empty array. */ + + /* + Here is where we would config the SDL-specific context-level config. The custom SDL backend allows this to be null, but we're + defining it here just for the sake of demonstration. Whether or not this is required depends on the backend. If you're not sure, + check the documentation for the backend. + */ + ma_context_config_sdl sdlContextConfig = ma_context_config_sdl_init(); + sdlContextConfig._unused = 0; + + /* You must include an entry for each backend you're using, even if the config is NULL. This is how miniaudio knows . */ + ma_device_backend_spec pCustomContextConfigs[] = { + { MA_DEVICE_BACKEND_VTABLE_SDL, &sdlContextConfig, NULL } }; contextConfig = ma_context_config_init(); - contextConfig.custom.ppVTables = pBackendVTables; - contextConfig.custom.ppUserDatas = NULL; /* We're not using user data in this example so can set this to NULL, but if you need it, declare an array here, one for each item in ppVTables. */ - contextConfig.custom.count = (sizeof(pBackendVTables) / sizeof(pBackendVTables[0])) - 1; + contextConfig.custom.pBackends = pCustomContextConfigs; + contextConfig.custom.count = (sizeof(pCustomContextConfigs) / sizeof(pCustomContextConfigs[0])); + result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &contextConfig, (ma_context*)&context); if (result != MA_SUCCESS) { @@ -686,7 +97,22 @@ int main(int argc, char** argv) sineWaveConfig = ma_waveform_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, ma_waveform_type_sine, 0.2, 220); ma_waveform_init(&sineWaveConfig, &sineWave); - /* The device is created exactly as per normal. */ + + + /* + Just like with context configs, we can define some device-level configs as well. It works the same way, except you will pass in + a backend-specific device-level config. If the backend doesn't require a device-level config, you can set this to NULL. + */ + ma_device_config_sdl sdlDeviceConfig = ma_device_config_sdl_init(); + sdlDeviceConfig._unused = 0; + + /* + Unlike with contexts, if your backend does not require a device-level config, you can just leave it out of this list entirely. + */ + ma_device_backend_spec pCustomDeviceConfigs[] = { + { MA_DEVICE_BACKEND_VTABLE_SDL, &sdlDeviceConfig, NULL } + }; + deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig.playback.format = DEVICE_FORMAT; deviceConfig.playback.channels = DEVICE_CHANNELS; @@ -695,6 +121,8 @@ int main(int argc, char** argv) deviceConfig.sampleRate = DEVICE_SAMPLE_RATE; deviceConfig.dataCallback = data_callback; deviceConfig.pUserData = &sineWave; + deviceConfig.custom.pBackends = pCustomDeviceConfigs; + deviceConfig.custom.count = sizeof(pCustomDeviceConfigs) / sizeof(pCustomDeviceConfigs[0]); result = ma_device_init((ma_context*)&context, &deviceConfig, (ma_device*)&device); if (result != MA_SUCCESS) { @@ -726,4 +154,7 @@ int main(int argc, char** argv) (void)argv; return 0; -} \ No newline at end of file +} + +/* We put the SDL implementation here just to simplify the compilation process. This way you need only compile custom_backend.c. */ +#include "../extras/backends/sdl/backend_sdl.c" diff --git a/extras/backends/sdl/backend_sdl.c b/extras/backends/sdl/backend_sdl.c new file mode 100644 index 00000000..977f4825 --- /dev/null +++ b/extras/backends/sdl/backend_sdl.c @@ -0,0 +1,647 @@ +/* +This implements a full-featured SDL2 backend. It's intentionally built using the same paradigms as the built-in backends in order to make +it suitable as a solid basis for a custom implementation. The SDL2 backend can be disabled with MA_NO_SDL, exactly like the built-in +backends. It supports both runtime and compile-time linking and respects the MA_NO_RUNTIME_LINKING option. It also works on Emscripten +which requires the `-s USE_SDL=2` option. +*/ +#ifndef miniaudio_backend_sdl_c +#define miniaudio_backend_sdl_c + +/* Include miniaudio.h if we're not including this file after the implementation. */ +#if !defined(MINIAUDIO_IMPLEMENTATION) && !defined(MA_IMPLEMENTATION) +#include "../../../miniaudio.h" +#endif + +#include "backend_sdl.h" + +#include /* memset() */ + +/* Support SDL on everything. */ +#define MA_SUPPORT_SDL + +/* +Only enable SDL if it's hasn't been explicitly disabled (MA_NO_SDL) or enabled (MA_ENABLE_SDL with +MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_SDL). +*/ +#if defined(MA_SUPPORT_SDL) && !defined(MA_NO_SDL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SDL)) + #define MA_HAS_SDL +#endif + +/* SDL headers are necessary if using compile-time linking. Necessary for Emscripten. */ +#if defined(MA_HAS_SDL) + #if defined(MA_NO_RUNTIME_LINKING) || defined(MA_EMSCRIPTEN) + #ifdef __has_include + #ifdef MA_EMSCRIPTEN + #if !__has_include() + #undef MA_HAS_SDL + #endif + #else + #if !__has_include() + #undef MA_HAS_SDL + #endif + #endif + #endif + #endif +#endif + +/* Don't compile in the SDL backend if it's been disabled. */ +#if defined(MA_HAS_SDL) +#define MA_SDL_INIT_AUDIO 0x00000010 +#define MA_AUDIO_U8 0x0008 +#define MA_AUDIO_S16 0x8010 +#define MA_AUDIO_S32 0x8020 +#define MA_AUDIO_F32 0x8120 +#define MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 +#define MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 +#define MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define MA_SDL_AUDIO_ALLOW_ANY_CHANGE (MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE | MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE) + +typedef struct +{ + ma_handle hSDL; /* A handle to the SDL2 shared object. We dynamically load function pointers at runtime so we can avoid linking. */ + ma_proc SDL_InitSubSystem; + ma_proc SDL_QuitSubSystem; + ma_proc SDL_GetNumAudioDevices; + ma_proc SDL_GetAudioDeviceName; + ma_proc SDL_CloseAudioDevice; + ma_proc SDL_OpenAudioDevice; + ma_proc SDL_PauseAudioDevice; +} ma_context_data_sdl; + +typedef struct +{ + int deviceIDPlayback; + int deviceIDCapture; +} ma_device_data_sdl; + +/* If we are linking at compile time we'll just #include SDL.h. Otherwise we can just redeclare some stuff to avoid the need for development packages to be installed. */ +#ifdef MA_NO_RUNTIME_LINKING + #define SDL_MAIN_HANDLED + #ifdef MA_EMSCRIPTEN + #include + #else + #include + #endif + + typedef SDL_AudioCallback MA_SDL_AudioCallback; + typedef SDL_AudioSpec MA_SDL_AudioSpec; + typedef SDL_AudioFormat MA_SDL_AudioFormat; + typedef SDL_AudioDeviceID MA_SDL_AudioDeviceID; +#else + typedef void (* MA_SDL_AudioCallback)(void* userdata, ma_uint8* stream, int len); + typedef ma_uint16 MA_SDL_AudioFormat; + typedef ma_uint32 MA_SDL_AudioDeviceID; + + typedef struct MA_SDL_AudioSpec + { + int freq; + MA_SDL_AudioFormat format; + ma_uint8 channels; + ma_uint8 silence; + ma_uint16 samples; + ma_uint16 padding; + ma_uint32 size; + MA_SDL_AudioCallback callback; + void* userdata; + } MA_SDL_AudioSpec; +#endif + +typedef int (* MA_PFN_SDL_InitSubSystem)(ma_uint32 flags); +typedef void (* MA_PFN_SDL_QuitSubSystem)(ma_uint32 flags); +typedef int (* MA_PFN_SDL_GetNumAudioDevices)(int iscapture); +typedef const char* (* MA_PFN_SDL_GetAudioDeviceName)(int index, int iscapture); +typedef void (* MA_PFN_SDL_CloseAudioDevice)(MA_SDL_AudioDeviceID dev); +typedef MA_SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice)(const char* device, int iscapture, const MA_SDL_AudioSpec* desired, MA_SDL_AudioSpec* obtained, int allowed_changes); +typedef void (* MA_PFN_SDL_PauseAudioDevice)(MA_SDL_AudioDeviceID dev, int pause_on); + +MA_SDL_AudioFormat ma_format_to_sdl(ma_format format) +{ + switch (format) + { + case ma_format_unknown: return 0; + case ma_format_u8: return MA_AUDIO_U8; + case ma_format_s16: return MA_AUDIO_S16; + case ma_format_s24: return MA_AUDIO_S32; /* Closest match. */ + case ma_format_s32: return MA_AUDIO_S32; + case ma_format_f32: return MA_AUDIO_F32; + default: return 0; + } +} + +ma_format ma_format_from_sdl(MA_SDL_AudioFormat format) +{ + switch (format) + { + case MA_AUDIO_U8: return ma_format_u8; + case MA_AUDIO_S16: return ma_format_s16; + case MA_AUDIO_S32: return ma_format_s32; + case MA_AUDIO_F32: return ma_format_f32; + default: return ma_format_unknown; + } +} + +static ma_result ma_context_enumerate_devices__sdl(void* pUserData, ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData) +{ + ma_context_data_sdl* pContextDataSDL; + ma_bool32 isTerminated = MA_FALSE; + ma_bool32 cbResult; + int iDevice; + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pContext->pBackendData; + + /* Playback */ + if (!isTerminated) { + int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextDataSDL->SDL_GetNumAudioDevices)(0); + for (iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + memset(&deviceInfo, 0, sizeof(deviceInfo)); + + deviceInfo.id.custom.i = iDevice; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextDataSDL->SDL_GetAudioDeviceName)(iDevice, 0), (size_t)-1); + + if (iDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pCallbackUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + + /* Capture */ + if (!isTerminated) { + int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextDataSDL->SDL_GetNumAudioDevices)(1); + for (iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + memset(&deviceInfo, 0, sizeof(deviceInfo)); + + deviceInfo.id.custom.i = iDevice; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextDataSDL->SDL_GetAudioDeviceName)(iDevice, 1), (size_t)-1); + + if (iDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pCallbackUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + + return MA_SUCCESS; +} + +static ma_result ma_context_get_device_info__sdl(void* pUserData, ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +{ + ma_context_data_sdl* pContextDataSDL; + +#if !defined(__EMSCRIPTEN__) + MA_SDL_AudioSpec desiredSpec; + MA_SDL_AudioSpec obtainedSpec; + MA_SDL_AudioDeviceID tempDeviceID; + const char* pDeviceName; +#endif + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pContext->pBackendData; + + if (pDeviceID == NULL) { + if (deviceType == ma_device_type_playback) { + pDeviceInfo->id.custom.i = 0; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "Default Playback Device", (size_t)-1); + } else { + pDeviceInfo->id.custom.i = 0; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "Default Capture Device", (size_t)-1); + } + } else { + pDeviceInfo->id.custom.i = pDeviceID->custom.i; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ((MA_PFN_SDL_GetAudioDeviceName)pContextDataSDL->SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1), (size_t)-1); + } + + if (pDeviceInfo->id.custom.i == 0) { + pDeviceInfo->isDefault = MA_TRUE; + } + + /* + To get an accurate idea on the backend's native format we need to open the device. Not ideal, but it's the only way. An + alternative to this is to report all channel counts, sample rates and formats, but that doesn't offer a good representation + of the device's _actual_ ideal format. + + Note: With Emscripten, it looks like non-zero values need to be specified for desiredSpec. Whatever is specified in + desiredSpec will be used by SDL since it uses it just does it's own format conversion internally. Therefore, from what + I can tell, there's no real way to know the device's actual format which means I'm just going to fall back to the full + range of channels and sample rates on Emscripten builds. + */ +#if defined(__EMSCRIPTEN__) + /* Good practice to prioritize the best format first so that the application can use the first data format as their chosen one if desired. */ + pDeviceInfo->nativeDataFormatCount = 3; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[0].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormats[1].format = ma_format_s32; + pDeviceInfo->nativeDataFormats[1].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[1].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[1].flags = 0; + pDeviceInfo->nativeDataFormats[2].format = ma_format_u8; + pDeviceInfo->nativeDataFormats[2].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[2].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[2].flags = 0; +#else + memset(&desiredSpec, 0, sizeof(desiredSpec)); + + pDeviceName = NULL; + if (pDeviceID != NULL) { + pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextDataSDL->SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1); + } + + tempDeviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextDataSDL->SDL_OpenAudioDevice)(pDeviceName, (deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + if (tempDeviceID == 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to open SDL device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + ((MA_PFN_SDL_CloseAudioDevice)pContextDataSDL->SDL_CloseAudioDevice)(tempDeviceID); + + /* Only reporting a single native data format. It'll be whatever SDL decides is the best. */ + pDeviceInfo->nativeDataFormatCount = 1; + pDeviceInfo->nativeDataFormats[0].format = ma_format_from_sdl(obtainedSpec.format); + pDeviceInfo->nativeDataFormats[0].channels = obtainedSpec.channels; + pDeviceInfo->nativeDataFormats[0].sampleRate = obtainedSpec.freq; + pDeviceInfo->nativeDataFormats[0].flags = 0; + + /* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */ + if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) { + pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; + } +#endif /* __EMSCRIPTEN__ */ + + return MA_SUCCESS; +} + + +void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_handle_backend_data_callback(pDevice, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); +} + +void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_handle_backend_data_callback(pDevice, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); +} + +static ma_result ma_device_init_internal__sdl(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor) +{ + ma_context_data_sdl* pContextDataSDL; + ma_device_data_sdl* pDeviceDataSDL; + const ma_device_config_sdl* pDeviceConfigSDL; + MA_SDL_AudioSpec desiredSpec; + MA_SDL_AudioSpec obtainedSpec; + const char* pDeviceName; + int deviceID; + + pContextDataSDL = (ma_context_data_sdl*)pDevice->pContext->pBackendData; + pDeviceDataSDL = (ma_device_data_sdl*)pDevice->pBackendData; + + /* Grab the SDL backend config. This is not currently used. */ + pDeviceConfigSDL = (const ma_device_config_sdl*)ma_device_config_find_custom_backend_config(pConfig, MA_DEVICE_BACKEND_VTABLE_SDL); + (void)pDeviceConfigSDL; + + + /* + SDL is a little bit awkward with specifying the buffer size, You need to specify the size of the buffer in frames, but since we may + have requested a period size in milliseconds we'll need to convert, which depends on the sample rate. But there's a possibility that + the sample rate just set to 0, which indicates that the native sample rate should be used. There's no practical way to calculate this + that I can think of right now so I'm just using MA_DEFAULT_SAMPLE_RATE. + */ + if (pDescriptor->sampleRate == 0) { + pDescriptor->sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + /* + When determining the period size, you need to take defaults into account. This is how the size of the period should be determined. + + 1) If periodSizeInFrames is not 0, use periodSizeInFrames; else + 2) If periodSizeInMilliseconds is not 0, use periodSizeInMilliseconds; else + 3) If both periodSizeInFrames and periodSizeInMilliseconds is 0, use the backend's default. If the backend does not allow a default + buffer size, use a default value of MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY or + MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE depending on the value of pConfig->performanceProfile. + + Note that options 2 and 3 require knowledge of the sample rate in order to convert it to a frame count. You should try to keep the + calculation of the period size as accurate as possible, but sometimes it's just not practical so just use whatever you can. + + A helper function called ma_calculate_buffer_size_in_frames_from_descriptor() is available to do all of this for you which is what + we'll be using here. + */ + pDescriptor->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile); + + /* SDL wants the buffer size to be a power of 2 for some reason. */ + if (pDescriptor->periodSizeInFrames > 32768) { + pDescriptor->periodSizeInFrames = 32768; + } else { + pDescriptor->periodSizeInFrames = ma_next_power_of_2(pDescriptor->periodSizeInFrames); + } + + + /* We now have enough information to set up the device. */ + memset(&desiredSpec, 0, sizeof(desiredSpec)); + desiredSpec.freq = (int)pDescriptor->sampleRate; + desiredSpec.format = ma_format_to_sdl(pDescriptor->format); + desiredSpec.channels = (ma_uint8)pDescriptor->channels; + desiredSpec.samples = (ma_uint16)pDescriptor->periodSizeInFrames; + desiredSpec.callback = (pConfig->deviceType == ma_device_type_capture) ? ma_audio_callback_capture__sdl : ma_audio_callback_playback__sdl; + desiredSpec.userdata = pDevice; + + /* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */ + if (desiredSpec.format == 0) { + desiredSpec.format = MA_AUDIO_F32; + } + + pDeviceName = NULL; + if (pDescriptor->pDeviceID != NULL) { + pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextDataSDL->SDL_GetAudioDeviceName)(pDescriptor->pDeviceID->custom.i, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1); + } + + deviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextDataSDL->SDL_OpenAudioDevice)(pDeviceName, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + if (deviceID == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to open SDL2 device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + if (pConfig->deviceType == ma_device_type_playback) { + pDeviceDataSDL->deviceIDPlayback = deviceID; + } else { + pDeviceDataSDL->deviceIDCapture = deviceID; + } + + /* The descriptor needs to be updated with our actual settings. */ + pDescriptor->format = ma_format_from_sdl(obtainedSpec.format); + pDescriptor->channels = obtainedSpec.channels; + pDescriptor->sampleRate = (ma_uint32)obtainedSpec.freq; + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->periodSizeInFrames = obtainedSpec.samples; + pDescriptor->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */ + + return MA_SUCCESS; +} + +static ma_result ma_device_init__sdl(void* pUserData, ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +{ + ma_context_data_sdl* pContextDataSDL; + ma_device_data_sdl* pDeviceDataSDL; + ma_result result; + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pDevice->pContext->pBackendData; + + /* SDL does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */ + if (pConfig->deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + /* We need to allocate our backend-specific data. */ + pDeviceDataSDL = (ma_device_data_sdl*)ma_calloc(sizeof(*pDeviceDataSDL), &pDevice->pContext->allocationCallbacks); + if (pDeviceDataSDL == NULL) { + return MA_OUT_OF_MEMORY; + } + + pDevice->pBackendData = pDeviceDataSDL; + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__sdl(pDevice, pConfig, pDescriptorCapture); + if (result != MA_SUCCESS) { + ma_free(pDeviceDataSDL, &pDevice->pContext->allocationCallbacks); + return result; + } + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__sdl(pDevice, pConfig, pDescriptorPlayback); + if (result != MA_SUCCESS) { + if (pConfig->deviceType == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextDataSDL->SDL_CloseAudioDevice)(pDeviceDataSDL->deviceIDCapture); + } + + ma_free(pDeviceDataSDL, &pDevice->pContext->allocationCallbacks); + return result; + } + } + + return MA_SUCCESS; +} + +static ma_result ma_device_uninit__sdl(void* pUserData, ma_device* pDevice) +{ + ma_context_data_sdl* pContextDataSDL; + ma_device_data_sdl* pDeviceDataSDL; + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pDevice->pContext->pBackendData; + pDeviceDataSDL = (ma_device_data_sdl*)pDevice->pBackendData; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextDataSDL->SDL_CloseAudioDevice)(pDeviceDataSDL->deviceIDCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextDataSDL->SDL_CloseAudioDevice)(pDeviceDataSDL->deviceIDCapture); + } + + ma_free(pDeviceDataSDL, &pDevice->pContext->allocationCallbacks); + + return MA_SUCCESS; +} + +static ma_result ma_device_start__sdl(void* pUserData, ma_device* pDevice) +{ + ma_context_data_sdl* pContextDataSDL; + ma_device_data_sdl* pDeviceDataSDL; + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pDevice->pContext->pBackendData; + pDeviceDataSDL = (ma_device_data_sdl*)pDevice->pBackendData; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextDataSDL->SDL_PauseAudioDevice)(pDeviceDataSDL->deviceIDCapture, 0); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextDataSDL->SDL_PauseAudioDevice)(pDeviceDataSDL->deviceIDPlayback, 0); + } + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__sdl(void* pUserData, ma_device* pDevice) +{ + ma_context_data_sdl* pContextDataSDL; + ma_device_data_sdl* pDeviceDataSDL; + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pDevice->pContext->pBackendData; + pDeviceDataSDL = (ma_device_data_sdl*)pDevice->pBackendData; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextDataSDL->SDL_PauseAudioDevice)(pDeviceDataSDL->deviceIDCapture, 1); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextDataSDL->SDL_PauseAudioDevice)(pDeviceDataSDL->deviceIDPlayback, 1); + } + + return MA_SUCCESS; +} + +static ma_result ma_context_uninit__sdl(void* pUserData, ma_context* pContext) +{ + ma_context_data_sdl* pContextDataSDL; + + (void)pUserData; + + pContextDataSDL = (ma_context_data_sdl*)pContext->pBackendData; + + ((MA_PFN_SDL_QuitSubSystem)pContextDataSDL->SDL_QuitSubSystem)(MA_SDL_INIT_AUDIO); + + /* Close the handle to the SDL shared object last. */ + ma_dlclose(ma_context_get_log(pContext), pContextDataSDL->hSDL); + pContextDataSDL->hSDL = NULL; + + ma_free(pContextDataSDL, &pContext->allocationCallbacks); + + return MA_SUCCESS; +} + +static ma_result ma_context_init__sdl(void* pUserData, ma_context* pContext, const ma_context_config* pConfig) +{ + ma_context_data_sdl* pContextDataSDL; + const ma_context_config_sdl* pContextConfigSDL; + int resultSDL; + +#ifndef MA_NO_RUNTIME_LINKING + /* We'll use a list of possible shared object names for easier extensibility. */ + size_t iName; + const char* pSDLNames[] = { +#if defined(_WIN32) + "SDL2.dll" +#elif defined(__APPLE__) + "SDL2.framework/SDL2" +#else + "libSDL2-2.0.so.0" +#endif + }; + + (void)pUserData; + + /* Grab the config. This is not currently being used. */ + pContextConfigSDL = (const ma_context_config_sdl*)ma_context_config_find_custom_backend_config(pConfig, MA_DEVICE_BACKEND_VTABLE_SDL); + (void)pContextConfigSDL; + + + /* Allocate our SDL-specific context data. */ + pContextDataSDL = (ma_context_data_sdl*)ma_calloc(sizeof(*pContextDataSDL), &pContext->allocationCallbacks); + if (pContextDataSDL == NULL) { + return MA_OUT_OF_MEMORY; + } + + pContext->pBackendData = pContextDataSDL; + + + /* Check if we have SDL2 installed somewhere. If not it's not usable and we need to abort. */ + for (iName = 0; iName < ma_countof(pSDLNames); iName += 1) { + pContextDataSDL->hSDL = ma_dlopen(ma_context_get_log(pContext), pSDLNames[iName]); + if (pContextDataSDL->hSDL != NULL) { + break; + } + } + + if (pContextDataSDL->hSDL == NULL) { + ma_free(pContextDataSDL, &pContext->allocationCallbacks); + return MA_NO_BACKEND; /* SDL2 could not be loaded. */ + } + + /* Now that we have the handle to the shared object we can go ahead and load some function pointers. */ + pContextDataSDL->SDL_InitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_InitSubSystem"); + pContextDataSDL->SDL_QuitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_QuitSubSystem"); + pContextDataSDL->SDL_GetNumAudioDevices = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_GetNumAudioDevices"); + pContextDataSDL->SDL_GetAudioDeviceName = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_GetAudioDeviceName"); + pContextDataSDL->SDL_CloseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_CloseAudioDevice"); + pContextDataSDL->SDL_OpenAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_OpenAudioDevice"); + pContextDataSDL->SDL_PauseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextDataSDL->hSDL, "SDL_PauseAudioDevice"); +#else + pContextDataSDL->SDL_InitSubSystem = (ma_proc)SDL_InitSubSystem; + pContextDataSDL->SDL_QuitSubSystem = (ma_proc)SDL_QuitSubSystem; + pContextDataSDL->SDL_GetNumAudioDevices = (ma_proc)SDL_GetNumAudioDevices; + pContextDataSDL->SDL_GetAudioDeviceName = (ma_proc)SDL_GetAudioDeviceName; + pContextDataSDL->SDL_CloseAudioDevice = (ma_proc)SDL_CloseAudioDevice; + pContextDataSDL->SDL_OpenAudioDevice = (ma_proc)SDL_OpenAudioDevice; + pContextDataSDL->SDL_PauseAudioDevice = (ma_proc)SDL_PauseAudioDevice; +#endif /* MA_NO_RUNTIME_LINKING */ + + resultSDL = ((MA_PFN_SDL_InitSubSystem)pContextDataSDL->SDL_InitSubSystem)(MA_SDL_INIT_AUDIO); + if (resultSDL != 0) { + ma_dlclose(ma_context_get_log(pContext), pContextDataSDL->hSDL); + ma_free(pContextDataSDL, &pContext->allocationCallbacks); + return MA_ERROR; + } + + return MA_SUCCESS; +} + + +static ma_device_backend_vtable ma_gDeviceBackendVTable_SDL = +{ + ma_context_init__sdl, + ma_context_uninit__sdl, + ma_context_enumerate_devices__sdl, + ma_context_get_device_info__sdl, + ma_device_init__sdl, + ma_device_uninit__sdl, + ma_device_start__sdl, + ma_device_stop__sdl, + NULL, /* onDeviceRead */ + NULL, /* onDeviceWrite */ + NULL, /* onDeviceDataLoop */ + NULL, /* onDeviceDataLoopWakeup */ + NULL /* onDeviceGetInfo */ +}; + +MA_API const ma_device_backend_vtable* MA_DEVICE_BACKEND_VTABLE_SDL = &ma_gDeviceBackendVTable_SDL; +#else +MA_API const ma_device_backend_vtable* MA_DEVICE_BACKEND_VTABLE_SDL = NULL; +#endif /* MA_HAS_SDL */ + + +MA_API ma_context_config_sdl ma_context_config_sdl_init(void) +{ + ma_context_config_sdl config; + + memset(&config, 0, sizeof(config)); + + return config; +} + +MA_API ma_device_config_sdl ma_device_config_sdl_init(void) +{ + ma_device_config_sdl config; + + memset(&config, 0, sizeof(config)); + + return config; +} + + +#endif /* miniaudio_backend_sdl_c */ diff --git a/extras/backends/sdl/backend_sdl.h b/extras/backends/sdl/backend_sdl.h new file mode 100644 index 00000000..9d70972b --- /dev/null +++ b/extras/backends/sdl/backend_sdl.h @@ -0,0 +1,34 @@ +/* +The SDL backend does not require any user data, nor configs. Configs are provided here in case +they are needed in the future, however you can safely pass in NULL when setting up your context +and device configs. +*/ +#ifndef miniaudio_backend_sdl_h +#define miniaudio_backend_sdl_h + +#ifdef __cplusplus +extern "C" { +#endif + +MA_API const ma_device_backend_vtable* MA_DEVICE_BACKEND_VTABLE_SDL; + + +typedef struct +{ + int _unused; +} ma_context_config_sdl; + +MA_API ma_context_config_sdl ma_context_config_sdl_init(void); + + +typedef struct +{ + int _unused; +} ma_device_config_sdl; + +MA_API ma_device_config_sdl ma_device_config_sdl_init(void); + +#ifdef __cplusplus +} +#endif +#endif /* miniaudio_backend_sdl_h */ diff --git a/miniaudio.h b/miniaudio.h index 1edc316e..dd9f24b1 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -7051,6 +7051,15 @@ typedef struct ma_device_config ma_device_config; typedef struct ma_device_backend_vtable ma_device_backend_vtable; typedef struct ma_backend_callbacks ma_backend_callbacks; +/* For defining vtables, configs and user data pointers for custom backends. */ +typedef struct +{ + const ma_device_backend_vtable* pVTable; /* The vtable of the backend. */ + const void* pConfig; /* Contextual based on whether or not this is being used for a context or device. The object this points to depends on the backend. */ + void* pUserData; /* User data pointer passed into each callback in the vtable. */ +} ma_device_backend_spec; + + #define MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE (1U << 1) /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ #ifndef MA_MAX_DEVICE_NAME_LENGTH @@ -7152,6 +7161,11 @@ struct ma_device_config ma_bool32 noAutoStartAfterReroute; ma_bool32 enableCompatibilityWorkarounds; } aaudio; + struct + { + ma_device_backend_spec* pBackends; + size_t count; + } custom; }; @@ -7336,8 +7350,7 @@ struct ma_context_config } jack; struct { - ma_device_backend_vtable** ppVTables; - void** ppUserDatas; + ma_device_backend_spec* pBackends; size_t count; } custom; }; @@ -7371,7 +7384,7 @@ typedef struct struct ma_context { ma_backend_callbacks callbacks; /* Old system. Will be removed when all stock backends have been converted over to the new system. */ - ma_device_backend_vtable* pVTable; /* New system. */ + const ma_device_backend_vtable* pVTable; /* New system. */ void* pVTableUserData; void* pBackendData; /* This is not used by miniaudio, but is a way for custom backends to store associate some backend-specific data with the device. Custom backends are free to use this pointer however they like. */ ma_backend backend; /* DirectSound, ALSA, etc. */ @@ -8100,6 +8113,19 @@ ma_context_init() */ MA_API ma_context_config ma_context_config_init(void); + +/* +Helper function for retrieving a pointer to a custom backend's context config. + + +Remarks +------- +This should only ever be used by custom backend implementations. It's used for retrieving the +pConfig pointer that has been associated with the specified backend vtable. +*/ +MA_API const void* ma_context_config_find_custom_backend_config(const ma_context_config* pConfig, const ma_device_backend_vtable* pVTable); + + /* Initializes a context. @@ -8608,6 +8634,18 @@ ma_device_init_ex() MA_API ma_device_config ma_device_config_init(ma_device_type deviceType); +/* +Helper function for retrieving a pointer to a custom backend's context config. + + +Remarks +------- +This should only ever be used by custom backend implementations. It's used for retrieving the +pConfig pointer that has been associated with the specified backend vtable. +*/ +MA_API const void* ma_device_config_find_custom_backend_config(const ma_device_config* pConfig, const ma_device_backend_vtable* pVTable); + + /* Initializes a device. @@ -41310,6 +41348,23 @@ MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_ } +static const void* ma_find_device_backend_config(const ma_device_backend_spec* pBackends, size_t count, const ma_device_backend_vtable* pVTable) +{ + size_t iBackend; + + if (pBackends == NULL || count == 0) { + return NULL; + } + + for (iBackend = 0; iBackend < count; iBackend += 1) { + if (pBackends[iBackend].pVTable == pVTable) { + return pBackends[iBackend].pConfig; + } + } + + return NULL; +} + MA_API ma_context_config ma_context_config_init(void) { @@ -41319,6 +41374,16 @@ MA_API ma_context_config ma_context_config_init(void) return config; } +MA_API const void* ma_context_config_find_custom_backend_config(const ma_context_config* pConfig, const ma_device_backend_vtable* pVTable) +{ + if (pVTable == NULL || pConfig == NULL) { + return NULL; + } + + return ma_find_device_backend_config(pConfig->custom.pBackends, pConfig->custom.count, pVTable); +} + + MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext) { ma_result result; @@ -41508,27 +41573,22 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC /* Special case for custom backends. */ if (backend == ma_backend_custom) { /* It's a custom backend. We need to iterate over each vtable and use the first one that works. */ - if (pConfig->custom.ppVTables != NULL && pConfig->custom.count > 0) { - size_t iVTable; - for (iVTable = 0; iVTable < pConfig->custom.count; iVTable += 1) { - void* pUserData = NULL; - if (pConfig->custom.ppUserDatas != NULL) { - pUserData = pConfig->custom.ppUserDatas[iVTable]; - } - - pContext->pVTable = pConfig->custom.ppVTables[iVTable]; - pContext->pVTableUserData = pUserData; + if (pConfig->custom.pBackends != NULL && pConfig->custom.count > 0) { + size_t iCustomBackend; + for (iCustomBackend = 0; iCustomBackend < pConfig->custom.count; iCustomBackend += 1) { + pContext->pVTable = pConfig->custom.pBackends[iCustomBackend].pVTable; + pContext->pVTableUserData = pConfig->custom.pBackends[iCustomBackend].pUserData; if (pContext->pVTable != NULL) { MA_ASSERT(pContext->pVTable->onContextInit != NULL); /* onContextInit() must always be specified. */ - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Attempting to initialize custom backend %d...\n", (int)iVTable); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Attempting to initialize custom backend %d...\n", (int)iCustomBackend); result = pContext->pVTable->onContextInit(pContext->pVTableUserData, pContext, pConfig); if (result == MA_SUCCESS) { break; } else { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Failed to initialize custom backend %d.\n", (int)iVTable); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Failed to initialize custom backend %d.\n", (int)iCustomBackend); } } } @@ -41799,6 +41859,16 @@ MA_API ma_device_config ma_device_config_init(ma_device_type deviceType) return config; } +MA_API const void* ma_device_config_find_custom_backend_config(const ma_device_config* pConfig, const ma_device_backend_vtable* pVTable) +{ + if (pVTable == NULL || pConfig == NULL) { + return NULL; + } + + return ma_find_device_backend_config(pConfig->custom.pBackends, pConfig->custom.count, pVTable); +} + + MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) { ma_result result;