diff --git a/examples/custom_backend.c b/examples/custom_backend.c index 0e7577c2..9386a386 100644 --- a/examples/custom_backend.c +++ b/examples/custom_backend.c @@ -1,8 +1,8 @@ /* -This example show how a custom backend can be implemented. +This example shows how a custom backend can be implemented. 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 build-in +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. @@ -11,17 +11,21 @@ custom backends without needing to modify any of the base miniaudio initializati `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). +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, at a minimum you must set the `custom.onContextInit()` callback in the context config. You do not need to set the -other callbacks, but if you don't, you must set them in the implementation of the `onContextInit()` callback which is done via an output -parameter. This is the approach taken by this example because it's the simplest way to support multiple custom backends. The idea is that -the `onContextInit()` callback is set to a generic "loader", which then calls out to a backend-specific implementation which then sets the -remaining callbacks if it is successfully initialized. +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. 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 -backends would actually get hit. By default, the `ma_backend_custom` backend is the lowest priority backend, except for `ma_backend_null`. +backends would actually get hit. By default, the `ma_backend_custom` backend is the second-lowest priority backend, sitting just above +`ma_backend_null`. */ #define MINIAUDIO_IMPLEMENTATION #include "../miniaudio.h" @@ -173,7 +177,7 @@ ma_format ma_format_from_sdl(MA_SDL_AudioFormat format) } } -static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +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; @@ -182,6 +186,7 @@ static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum MA_ASSERT(pContext != NULL); MA_ASSERT(callback != NULL); + (void)pUserData; /* Playback */ if (!isTerminated) { @@ -197,7 +202,7 @@ static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum deviceInfo.isDefault = MA_TRUE; } - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pCallbackUserData); if (cbResult == MA_FALSE) { isTerminated = MA_TRUE; break; @@ -219,7 +224,7 @@ static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum deviceInfo.isDefault = MA_TRUE; } - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pCallbackUserData); if (cbResult == MA_FALSE) { isTerminated = MA_TRUE; break; @@ -230,7 +235,7 @@ static ma_result ma_context_enumerate_devices__sdl(ma_context* pContext, ma_enum return MA_SUCCESS; } -static ma_result ma_context_get_device_info__sdl(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +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; @@ -242,6 +247,7 @@ static ma_result ma_context_get_device_info__sdl(ma_context* pContext, ma_device #endif MA_ASSERT(pContext != NULL); + (void)pUserData; if (pDeviceID == NULL) { if (deviceType == ma_device_type_playback) { @@ -424,13 +430,14 @@ static ma_result ma_device_init_internal__sdl(ma_device_ex* pDeviceEx, const ma_ return MA_SUCCESS; } -static ma_result ma_device_init__sdl(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +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) { @@ -458,12 +465,13 @@ static ma_result ma_device_init__sdl(ma_device* pDevice, const ma_device_config* return MA_SUCCESS; } -static ma_result ma_device_uninit__sdl(ma_device* pDevice) +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); @@ -476,12 +484,13 @@ static ma_result ma_device_uninit__sdl(ma_device* pDevice) return MA_SUCCESS; } -static ma_result ma_device_start__sdl(ma_device* pDevice) +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); @@ -494,12 +503,13 @@ static ma_result ma_device_start__sdl(ma_device* pDevice) return MA_SUCCESS; } -static ma_result ma_device_stop__sdl(ma_device* pDevice) +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); @@ -512,11 +522,12 @@ static ma_result ma_device_stop__sdl(ma_device* pDevice) return MA_SUCCESS; } -static ma_result ma_context_uninit__sdl(ma_context* pContext) +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); @@ -527,7 +538,7 @@ static ma_result ma_context_uninit__sdl(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__sdl(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +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; @@ -547,6 +558,7 @@ static ma_result ma_context_init__sdl(ma_context* pContext, const ma_context_con 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. */ @@ -585,51 +597,26 @@ static ma_result ma_context_init__sdl(ma_context* pContext, const ma_context_con return MA_ERROR; } - /* - The last step is to make sure the callbacks are set properly in `pCallbacks`. Internally, miniaudio will copy these callbacks into the - context object and then use them for then on for calling into our custom backend. - */ - pCallbacks->onContextInit = ma_context_init__sdl; - pCallbacks->onContextUninit = ma_context_uninit__sdl; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__sdl; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__sdl; - pCallbacks->onDeviceInit = ma_device_init__sdl; - pCallbacks->onDeviceUninit = ma_device_uninit__sdl; - pCallbacks->onDeviceStart = ma_device_start__sdl; - pCallbacks->onDeviceStop = ma_device_stop__sdl; - return MA_SUCCESS; } -#endif /* MA_HAS_SDL */ - -/* -This is our custom backend "loader". All this does is attempts to initialize our custom backends in the order they are listed. The first -one to successfully initialize is the one that's chosen. In this example we're just listing them statically, but you can use whatever logic -you want to handle backend selection. - -This is used as the onContextInit() callback in the context config. -*/ -static ma_result ma_context_init__custom_loader(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static ma_device_backend_vtable ma_gDeviceBackendVTable_SDL = { - ma_result result = MA_NO_BACKEND; - - /* Silence some unused parameter warnings just in case no custom backends are enabled. */ - (void)pContext; - (void)pCallbacks; - - /* SDL. */ -#if !defined(MA_NO_SDL) - if (result != MA_SUCCESS) { - result = ma_context_init__sdl(pContext, pConfig, pCallbacks); - } -#endif - - /* ... plug in any other custom backends here ... */ - - /* If we have a success result we have initialized a backend. Otherwise we need to tell miniaudio about the error so it can skip over our custom backends. */ - return result; -} + 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 */ /* @@ -666,6 +653,7 @@ int main(int argc, char** argv) ma_device_ex device; ma_waveform_config sineWaveConfig; ma_waveform sineWave; + char name[256]; /* We're just using ma_backend_custom in this example for demonstration purposes, but a more realistic use case would probably want to include @@ -675,20 +663,20 @@ int main(int argc, char** argv) ma_backend_custom }; - /* - To implement a custom backend you need to implement the callbacks in the "custom" member of the context config. The only mandatory - callback required at this point is the onContextInit() callback. If you do not set the other callbacks, you must set them in - onContextInit() by setting them on the `pCallbacks` parameter. + /* 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. */ + }; - The way we're doing it in this example enables us to easily plug in multiple custom backends. What we do is set the onContextInit() - callback to a generic "loader" function (ma_context_init__custom_loader() in this example), which then calls out to backend-specific - context initialization routines, one of which will be for SDL. That way, if for example we wanted to add support for another backend, - we don't need to touch this part of the code. Instead we add logic to ma_context_init__custom_loader() to choose the most appropriate - custom backend. That will then fill out the other callbacks appropriately. - */ contextConfig = ma_context_config_init(); - contextConfig.custom.onContextInit = ma_context_init__custom_loader; - + 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; + result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &contextConfig, (ma_context*)&context); if (result != MA_SUCCESS) { return -1; @@ -715,7 +703,8 @@ int main(int argc, char** argv) } - printf("Device Name: %s\n", ((ma_device*)&device)->playback.name); + ma_device_get_name((ma_device*)&device, ma_device_type_playback, name, sizeof(name), NULL); + printf("Device Name: %s\n", name); if (ma_device_start((ma_device*)&device) != MA_SUCCESS) { ma_device_uninit((ma_device*)&device); diff --git a/miniaudio.h b/miniaudio.h index 8ea11e92..2fbdb586 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -7185,7 +7185,7 @@ struct ma_backend_callbacks struct ma_device_backend_vtable { - ma_result (* onContextInit )(void* pUserData, ma_context* pContext, const ma_context_config* pConfig, ma_device_backend_vtable** ppBackendVTable, void** ppBackendUserData); + ma_result (* onContextInit )(void* pUserData, ma_context* pContext, const ma_context_config* pConfig); ma_result (* onContextUninit )(void* pUserData, ma_context* pContext); ma_result (* onContextEnumerateDevices)(void* pUserData, ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData); ma_result (* onContextGetDeviceInfo )(void* pUserData, ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); @@ -7242,10 +7242,8 @@ struct ma_context_config { ma_device_backend_vtable** ppVTables; void** ppUserDatas; - ma_uint32 count; - } custom2; - - ma_backend_callbacks custom; + size_t count; + } custom; }; /* WASAPI specific structure for some commands which must run on a common thread due to bugs in WASAPI. */ @@ -7279,6 +7277,7 @@ 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. */ 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. */ ma_log* pLog; ma_log log; /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ @@ -7690,6 +7689,7 @@ struct ma_device ma_device_data_proc onData; /* Set once at initialization time and should not be changed after. */ ma_device_notification_proc onNotification; /* Set once at initialization time and should not be changed after. */ void* pUserData; /* Application defined data. */ + 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_mutex startStopLock; ma_event wakeupEvent; ma_event startEvent; @@ -17769,11 +17769,9 @@ DEVICE I/O -static ma_result ma_context_init__compat(void* pUserData, ma_context* pContext, const ma_context_config* pConfig, ma_device_backend_vtable** ppBackendVTable, void** ppBackendUserData) +static ma_result ma_context_init__compat(void* pUserData, ma_context* pContext, const ma_context_config* pConfig) { (void)pUserData; - (void)ppBackendVTable; - (void)ppBackendUserData; return pContext->callbacks.onContextInit(pContext, pConfig, &pContext->callbacks); } @@ -41316,6 +41314,8 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC /* Make sure all callbacks are reset so we don't accidentally drag in any from previously failed initialization attempts. */ MA_ZERO_OBJECT(&pContext->callbacks); + pContext->pVTable = NULL; + pContext->pVTableUserData = NULL; /* For stock backends we can just map the backend enum to the appropriate vtable. */ @@ -41419,15 +41419,7 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC #ifdef MA_HAS_CUSTOM case ma_backend_custom: { - /* Slightly different logic for custom backends. Custom backends can optionally set all of their callbacks in the config. */ - pContext->callbacks = pConfig->custom; - pContext->pVTable = &ma_gDeviceVTable_Compat; - - /* - TODO: We'll need to handle custom backends differently so we can iterate over each of the vtables in - order and choose the first one that works. When we do this, this branch here will be empty and we'll - just do the logic after this switch. - */ + /* Custom backends are handled differently. */ } break; #endif #ifdef MA_HAS_NULL @@ -41441,24 +41433,52 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC default: break; } - if (pContext->pVTable != NULL) { - MA_ASSERT(pContext->pVTable->onContextInit != NULL); /* onContextInit() must always be specified. */ + /* 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]; + } - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Attempting to initialize %s backend...\n", ma_get_backend_name(backend)); - result = pContext->pVTable->onContextInit(pContext->pVTableUserData, pContext, pConfig, &pContext->pVTable, &pContext->pVTableUserData); - } else { - /* Getting here means the vtable is not set which means the backend is not enabled. Special case for the custom backend. */ - if (backend != ma_backend_custom) { - result = MA_BACKEND_NOT_ENABLED; + pContext->pVTable = pConfig->custom.ppVTables[iVTable]; + pContext->pVTableUserData = 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); + + 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); + } + } + } } else { - #if !defined(MA_HAS_CUSTOM) - result = MA_BACKEND_NOT_ENABLED; - #else + /* No custom backend vtables defined. */ result = MA_NO_BACKEND; - #endif + } + } else { + /* It's not a custom backend. */ + 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 %s backend...\n", ma_get_backend_name(backend)); + result = pContext->pVTable->onContextInit(pContext->pVTableUserData, pContext, pConfig); + } else { + /* Getting here means the vtable is not set which means the backend is not enabled. */ + result = MA_BACKEND_NOT_ENABLED; } } + + /* If this iteration was successful, return. */ if (result == MA_SUCCESS) { result = ma_mutex_init(&pContext->deviceEnumLock);