From 2f759f7b628c6d15bbdd2c7c6231d8bc3d3f255d Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 1 Dec 2025 10:46:59 +1000 Subject: [PATCH] Convert the SDL2 backend to the new backend architecture. --- extras/backends/sdl/backend_sdl.c | 65 ++++++-- miniaudio.h | 244 ++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 12 deletions(-) diff --git a/extras/backends/sdl/backend_sdl.c b/extras/backends/sdl/backend_sdl.c index b713cf5e..caa51418 100644 --- a/extras/backends/sdl/backend_sdl.c +++ b/extras/backends/sdl/backend_sdl.c @@ -119,17 +119,14 @@ typedef struct typedef struct { + ma_device_state_async async; struct { int deviceID; - ma_format format; - ma_uint32 channels; } capture; struct { int deviceID; - ma_format format; - ma_uint32 channels; } playback; } ma_device_state_sdl; @@ -172,6 +169,9 @@ static ma_device_state_sdl* ma_device_get_backend_state__sdl(ma_device* pDevice) } +static void ma_device_step__sdl(ma_device* pDevice); + + static void ma_backend_info__sdl(ma_device_backend_info* pBackendInfo) { MA_SDL_ASSERT(pBackendInfo != NULL); @@ -384,14 +384,14 @@ void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int buff { ma_device* pDevice = (ma_device*)pUserData; ma_device_state_sdl* pDeviceStateSDL = ma_device_get_backend_state__sdl(pDevice); - ma_device_handle_backend_data_callback(pDevice, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceStateSDL->capture.format, pDeviceStateSDL->capture.channels)); + ma_device_state_async_process(&pDeviceStateSDL->async, pDevice, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceStateSDL->async.capture.format, pDeviceStateSDL->async.capture.channels)); } void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) { ma_device* pDevice = (ma_device*)pUserData; ma_device_state_sdl* pDeviceStateSDL = ma_device_get_backend_state__sdl(pDevice); - ma_device_handle_backend_data_callback(pDevice, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceStateSDL->playback.format, pDeviceStateSDL->playback.channels)); + ma_device_state_async_process(&pDeviceStateSDL->async, pDevice, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceStateSDL->async.playback.format, pDeviceStateSDL->async.playback.channels)); } static ma_result ma_device_init_internal__sdl(ma_device* pDevice, ma_context_state_sdl* pContextStateSDL, ma_device_state_sdl* pDeviceStateSDL, const ma_device_config_sdl* pDeviceConfigSDL, ma_device_type deviceType, ma_device_descriptor* pDescriptor) @@ -472,12 +472,8 @@ static ma_result ma_device_init_internal__sdl(ma_device* pDevice, ma_context_sta if (deviceType == ma_device_type_playback) { pDeviceStateSDL->playback.deviceID = deviceID; - pDeviceStateSDL->playback.format = pDescriptor->format; - pDeviceStateSDL->playback.channels = pDescriptor->channels; } else { - pDeviceStateSDL->capture.deviceID = deviceID; - pDeviceStateSDL->capture.format = pDescriptor->format; - pDeviceStateSDL->capture.channels = pDescriptor->channels; + pDeviceStateSDL->capture.deviceID = deviceID; } return MA_SUCCESS; @@ -522,6 +518,19 @@ static ma_result ma_device_init__sdl(ma_device* pDevice, const void* pDeviceBack } } + result = ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateSDL->async); + if (result != MA_SUCCESS) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->capture.deviceID); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->playback.deviceID); + } + + ma_free(pDeviceStateSDL, ma_device_get_allocation_callbacks(pDevice)); + return result; + } + *ppDeviceState = pDeviceStateSDL; return MA_SUCCESS; @@ -541,6 +550,8 @@ static void ma_device_uninit__sdl(ma_device* pDevice) pContextStateSDL->SDL_CloseAudioDevice(pDeviceStateSDL->playback.deviceID); } + ma_device_state_async_uninit(&pDeviceStateSDL->async, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateSDL, ma_device_get_allocation_callbacks(pDevice)); } @@ -550,6 +561,9 @@ static ma_result ma_device_start__sdl(ma_device* pDevice) ma_context_state_sdl* pContextStateSDL = ma_context_get_backend_state__sdl(ma_device_get_context(pDevice)); ma_device_type deviceType = ma_device_get_type(pDevice); + /* Step the device once to ensure buffers are pre-filled before starting. */ + ma_device_step__sdl(pDevice); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { pContextStateSDL->SDL_PauseAudioDevice(pDeviceStateSDL->capture.deviceID, 0); } @@ -579,6 +593,33 @@ static ma_result ma_device_stop__sdl(ma_device* pDevice) } +static void ma_device_wait__sdl(ma_device* pDevice) +{ + ma_device_state_sdl* pDeviceStateSDL = ma_device_get_backend_state__sdl(pDevice); + ma_device_state_async_wait(&pDeviceStateSDL->async); +} + +static void ma_device_step__sdl(ma_device* pDevice) +{ + ma_device_state_sdl* pDeviceStateSDL = ma_device_get_backend_state__sdl(pDevice); + ma_device_state_async_step(&pDeviceStateSDL->async, pDevice); +} + +static void ma_device_loop__sdl(ma_device* pDevice) +{ + for (;;) { + ma_device_wait__sdl(pDevice); + + /* If the wait terminated due to the device being stopped, abort now. */ + if (!ma_device_is_started(pDevice)) { + break; + } + + ma_device_step__sdl(pDevice); + } +} + + static ma_device_backend_vtable ma_gDeviceBackendVTable_SDL = { ma_backend_info__sdl, @@ -591,7 +632,7 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_SDL = ma_device_stop__sdl, NULL, /* onDeviceRead */ NULL, /* onDeviceWrite */ - NULL, /* onDeviceLoop */ + ma_device_loop__sdl, NULL /* onDeviceWakeup */ }; diff --git a/miniaudio.h b/miniaudio.h index 8a1bd7d7..54194dc4 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -9489,6 +9489,34 @@ is also zero, `MA_DEFAULT_SAMPLE_RATE` will be used instead. */ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate); + +/* BEG ma_device_state_async.h */ +typedef struct ma_device_state_async +{ + ma_device_type deviceType; + struct + { + ma_semaphore semaphore; + ma_spinlock lock; + ma_format format; + ma_uint32 channels; + ma_uint32 frameCap; + ma_uint32 frameCount; + void* pBuffer; /* An offset of internal.pBuffer. */ + } playback, capture; + struct + { + void* pBuffer; /* One buffer allocation for both playback and capture. */ + } internal; +} ma_device_state_async; + +MA_API ma_result ma_device_state_async_init(ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_state_async* pAsyncDeviceState); +MA_API void ma_device_state_async_uninit(ma_device_state_async* pAsyncDeviceState, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_device_state_async_wait(ma_device_state_async* pAsyncDeviceState); +MA_API void ma_device_state_async_step(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice); +MA_API void ma_device_state_async_process(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +/* END ma_device_state_async.h */ + #endif /* MA_NO_DEVICE_IO */ @@ -45930,6 +45958,222 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_dev return pDescriptor->periodSizeInFrames; } } + + +/* BEG ma_device_state_async.c */ +MA_API ma_result ma_device_state_async_init(ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_state_async* pAsyncDeviceState) +{ + ma_result result; + size_t bufferAllocSizeInBytesCapture = 0; + size_t bufferAllocSizeInBytesPlayback = 0; + + if (pAsyncDeviceState == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pAsyncDeviceState); + pAsyncDeviceState->deviceType = deviceType; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDescriptorCapture == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_semaphore_init(0, &pAsyncDeviceState->capture.semaphore); + if (result != MA_SUCCESS) { + return result; + } + + pAsyncDeviceState->capture.format = pDescriptorCapture->format; + pAsyncDeviceState->capture.channels = pDescriptorCapture->channels; + pAsyncDeviceState->capture.frameCap = pDescriptorCapture->periodSizeInFrames; + + bufferAllocSizeInBytesCapture = ma_align_64(ma_get_bytes_per_frame(pAsyncDeviceState->capture.format, pAsyncDeviceState->capture.channels) * pAsyncDeviceState->capture.frameCap); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (pDescriptorPlayback == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_semaphore_init(0, &pAsyncDeviceState->playback.semaphore); + if (result != MA_SUCCESS) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_semaphore_uninit(&pAsyncDeviceState->capture.semaphore); + } + + return result; + } + + pAsyncDeviceState->playback.format = pDescriptorPlayback->format; + pAsyncDeviceState->playback.channels = pDescriptorPlayback->channels; + pAsyncDeviceState->playback.frameCap = pDescriptorPlayback->periodSizeInFrames; + + bufferAllocSizeInBytesPlayback = ma_align_64(ma_get_bytes_per_frame(pAsyncDeviceState->playback.format, pAsyncDeviceState->playback.channels) * pAsyncDeviceState->playback.frameCap); + } + + pAsyncDeviceState->internal.pBuffer = ma_malloc(bufferAllocSizeInBytesCapture + bufferAllocSizeInBytesPlayback, pAllocationCallbacks); + if (pAsyncDeviceState->internal.pBuffer == NULL) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_semaphore_uninit(&pAsyncDeviceState->capture.semaphore); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_semaphore_uninit(&pAsyncDeviceState->playback.semaphore); + } + + return MA_OUT_OF_MEMORY; + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pAsyncDeviceState->capture.pBuffer = pAsyncDeviceState->internal.pBuffer; + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pAsyncDeviceState->playback.pBuffer = ma_offset_ptr(pAsyncDeviceState->internal.pBuffer, bufferAllocSizeInBytesCapture); + } + + return MA_SUCCESS; +} + +MA_API void ma_device_state_async_uninit(ma_device_state_async* pAsyncDeviceState, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pAsyncDeviceState == NULL) { + return; + } + + ma_free(pAsyncDeviceState->internal.pBuffer, pAllocationCallbacks); + pAsyncDeviceState->internal.pBuffer = NULL; + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_semaphore_uninit(&pAsyncDeviceState->capture.semaphore); + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_semaphore_uninit(&pAsyncDeviceState->playback.semaphore); + } +} + +MA_API void ma_device_state_async_wait(ma_device_state_async* pAsyncDeviceState) +{ + if (pAsyncDeviceState == NULL) { + return; + } + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_semaphore_wait(&pAsyncDeviceState->capture.semaphore); + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_semaphore_wait(&pAsyncDeviceState->playback.semaphore); + } +} + +MA_API void ma_device_state_async_step(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice) +{ + if (pAsyncDeviceState == NULL || pDevice == NULL) { + return; + } + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->capture.lock); + { + if (pAsyncDeviceState->capture.frameCount > 0) { + ma_device_handle_backend_data_callback(pDevice, NULL, pAsyncDeviceState->capture.pBuffer, pAsyncDeviceState->capture.frameCount); + pAsyncDeviceState->capture.frameCount = 0; + } + } + ma_spinlock_unlock(&pAsyncDeviceState->capture.lock); + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pAsyncDeviceState->playback.format, pAsyncDeviceState->playback.channels); + + ma_spinlock_lock(&pAsyncDeviceState->playback.lock); + { + if (pAsyncDeviceState->playback.frameCount < pAsyncDeviceState->playback.frameCap) { + ma_device_handle_backend_data_callback(pDevice, ma_offset_ptr(pAsyncDeviceState->playback.pBuffer, bytesPerFrame * pAsyncDeviceState->playback.frameCount), NULL, (pAsyncDeviceState->playback.frameCap - pAsyncDeviceState->playback.frameCount)); + pAsyncDeviceState->playback.frameCount = pAsyncDeviceState->playback.frameCap; + } + } + ma_spinlock_unlock(&pAsyncDeviceState->playback.lock); + } +} + +MA_API void ma_device_state_async_process(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + if (pAsyncDeviceState == NULL || pDevice == NULL) { + return; + } + + if (pInput != NULL) { + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->capture.lock); + { + ma_uint32 framesToCopy; + ma_uint32 framesAvailable; + + MA_ASSERT(pAsyncDeviceState->capture.frameCap >= pAsyncDeviceState->capture.frameCount); + framesAvailable = pAsyncDeviceState->capture.frameCap - pAsyncDeviceState->capture.frameCount; + + framesToCopy = frameCount; + if (framesToCopy > framesAvailable) { + framesToCopy = framesAvailable; + } + + if (framesToCopy > 0) { + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pAsyncDeviceState->capture.format, pAsyncDeviceState->capture.channels); + + MA_COPY_MEMORY(ma_offset_ptr(pAsyncDeviceState->capture.pBuffer, bytesPerFrame * pAsyncDeviceState->capture.frameCount), pInput, bytesPerFrame * framesToCopy); + pAsyncDeviceState->capture.frameCount += framesToCopy; + + /* If we just filled up the buffer with data, it's time to release the semaphore. */ + if (pAsyncDeviceState->capture.frameCount == pAsyncDeviceState->capture.frameCap) { + ma_semaphore_release(&pAsyncDeviceState->capture.semaphore); + } + } + } + ma_spinlock_unlock(&pAsyncDeviceState->capture.lock); + } else { + MA_ASSERT(MA_FALSE); /* Should never get here. */ + } + } + + if (pOutput != NULL) { + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->playback.lock); + { + ma_uint32 framesToCopy; + + framesToCopy = frameCount; + if (framesToCopy > pAsyncDeviceState->playback.frameCount) { + framesToCopy = pAsyncDeviceState->playback.frameCount; + } + + if (framesToCopy > 0) { + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pAsyncDeviceState->playback.format, pAsyncDeviceState->playback.channels); + MA_COPY_MEMORY(pOutput, pAsyncDeviceState->playback.pBuffer, bytesPerFrame * framesToCopy); + + /* Move any remaining data to the front of the buffer. */ + if (framesToCopy < pAsyncDeviceState->playback.frameCount) { + MA_COPY_MEMORY(pAsyncDeviceState->playback.pBuffer, ma_offset_ptr(pAsyncDeviceState->playback.pBuffer, bytesPerFrame * framesToCopy), bytesPerFrame * (pAsyncDeviceState->playback.frameCount - framesToCopy)); + } + + pAsyncDeviceState->playback.frameCount -= framesToCopy; + } + + /* If we just emptied the buffer, it's time to release the semaphore. */ + if (pAsyncDeviceState->playback.frameCount == 0) { + ma_semaphore_release(&pAsyncDeviceState->playback.semaphore); + } + } + ma_spinlock_unlock(&pAsyncDeviceState->playback.lock); + } else { + MA_ASSERT(MA_FALSE); /* Should never get here. */ + } + } +} +/* END ma_device_state_async.c */ + #endif /* MA_NO_DEVICE_IO */