Update miniaudio.h

This commit is contained in:
Ray 2021-03-09 00:07:08 +01:00
parent 8dc3057fe5
commit b7f2059dc3

View file

@ -1,6 +1,6 @@
/*
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
miniaudio - v0.10.32 - TBD
miniaudio - v0.10.32 - 2020-02-23
David Reid - mackron@gmail.com
@ -1510,7 +1510,7 @@ extern "C" {
#define MA_VERSION_MAJOR 0
#define MA_VERSION_MINOR 10
#define MA_VERSION_REVISION 31
#define MA_VERSION_REVISION 32
#define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION)
#if defined(_MSC_VER) && !defined(__clang__)
@ -1981,7 +1981,8 @@ typedef enum
ma_thread_priority_default = 0
} ma_thread_priority;
typedef unsigned char ma_spinlock;
/* Spinlocks are 32-bit for compatibility reasons. */
typedef ma_uint32 ma_spinlock;
#if defined(MA_WIN32)
typedef ma_handle ma_thread;
@ -3588,6 +3589,32 @@ struct ma_context_config
ma_backend_callbacks custom;
};
/* WASAPI specific structure for some commands which must run on a common thread due to bugs in WASAPI. */
typedef struct
{
int code;
ma_event* pEvent; /* This will be signalled when the event is complete. */
union
{
struct
{
int _unused;
} quit;
struct
{
ma_device_type deviceType;
void* pAudioClient;
void** ppAudioClientService;
ma_result result; /* The result from creating the audio client service. */
} createAudioClient;
struct
{
ma_device* pDevice;
ma_device_type deviceType;
} releaseAudioClient;
} data;
} ma_context_command__wasapi;
struct ma_context
{
ma_backend_callbacks callbacks;
@ -3609,7 +3636,12 @@ struct ma_context
#ifdef MA_SUPPORT_WASAPI
struct
{
int _unused;
ma_thread commandThread;
ma_mutex commandLock;
ma_semaphore commandSem;
ma_uint32 commandIndex;
ma_uint32 commandCount;
ma_context_command__wasapi commands[4];
} wasapi;
#endif
#ifdef MA_SUPPORT_DSOUND
@ -4072,8 +4104,8 @@ struct ma_device
ma_performance_profile originalPerformanceProfile;
ma_uint32 periodSizeInFramesPlayback;
ma_uint32 periodSizeInFramesCapture;
MA_ATOMIC ma_bool8 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically. */
MA_ATOMIC ma_bool8 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically. */
MA_ATOMIC ma_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
MA_ATOMIC ma_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */
ma_bool8 noHardwareOffloading;
@ -4230,7 +4262,7 @@ struct ma_device
ma_uint32 currentPeriodFramesRemainingCapture;
ma_uint64 lastProcessedFramePlayback;
ma_uint64 lastProcessedFrameCapture;
MA_ATOMIC ma_bool8 isStarted; /* Read and written by multiple threads. Must be used atomically. */
MA_ATOMIC ma_bool32 isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */
} null_device;
#endif
};
@ -7863,7 +7895,7 @@ static ma_result ma_result_from_errno(int e)
MA_API ma_result ma_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)
{
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
errno_t err;
#endif
@ -7875,7 +7907,7 @@ MA_API ma_result ma_fopen(FILE** ppFile, const char* pFilePath, const char* pOpe
return MA_INVALID_ARGS;
}
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
err = fopen_s(ppFile, pFilePath, pOpenMode);
if (err != 0) {
return ma_result_from_errno(err);
@ -10155,11 +10187,11 @@ static MA_INLINE ma_result ma_spinlock_lock_ex(volatile ma_spinlock* pSpinlock,
}
for (;;) {
if (c89atomic_flag_test_and_set_explicit(pSpinlock, c89atomic_memory_order_acquire) == 0) {
if (c89atomic_exchange_explicit_32(pSpinlock, 1, c89atomic_memory_order_acquire) == 0) {
break;
}
while (c89atomic_load_explicit_8(pSpinlock, c89atomic_memory_order_relaxed) == 1) {
while (c89atomic_load_explicit_32(pSpinlock, c89atomic_memory_order_relaxed) == 1) {
if (yield) {
ma_yield();
}
@ -10185,7 +10217,7 @@ MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock)
return MA_INVALID_ARGS;
}
c89atomic_flag_clear_explicit(pSpinlock, c89atomic_memory_order_release);
c89atomic_store_explicit_32(pSpinlock, 0, c89atomic_memory_order_release);
return MA_SUCCESS;
}
@ -12457,7 +12489,7 @@ static ma_result ma_device_start__null(ma_device* pDevice)
ma_device_do_operation__null(pDevice, MA_DEVICE_OP_START__NULL);
c89atomic_exchange_8(&pDevice->null_device.isStarted, MA_TRUE);
c89atomic_exchange_32(&pDevice->null_device.isStarted, MA_TRUE);
return MA_SUCCESS;
}
@ -12467,7 +12499,7 @@ static ma_result ma_device_stop__null(ma_device* pDevice)
ma_device_do_operation__null(pDevice, MA_DEVICE_OP_SUSPEND__NULL);
c89atomic_exchange_8(&pDevice->null_device.isStarted, MA_FALSE);
c89atomic_exchange_32(&pDevice->null_device.isStarted, MA_FALSE);
return MA_SUCCESS;
}
@ -12481,7 +12513,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame
*pFramesWritten = 0;
}
wasStartedOnEntry = c89atomic_load_8(&pDevice->null_device.isStarted);
wasStartedOnEntry = c89atomic_load_32(&pDevice->null_device.isStarted);
/* Keep going until everything has been read. */
totalPCMFramesProcessed = 0;
@ -12507,7 +12539,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame
if (pDevice->null_device.currentPeriodFramesRemainingPlayback == 0) {
pDevice->null_device.currentPeriodFramesRemainingPlayback = 0;
if (!c89atomic_load_8(&pDevice->null_device.isStarted) && !wasStartedOnEntry) {
if (!c89atomic_load_32(&pDevice->null_device.isStarted) && !wasStartedOnEntry) {
result = ma_device_start__null(pDevice);
if (result != MA_SUCCESS) {
break;
@ -12527,7 +12559,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame
ma_uint64 currentFrame;
/* Stop waiting if the device has been stopped. */
if (!c89atomic_load_8(&pDevice->null_device.isStarted)) {
if (!c89atomic_load_32(&pDevice->null_device.isStarted)) {
break;
}
@ -12598,7 +12630,7 @@ static ma_result ma_device_read__null(ma_device* pDevice, void* pPCMFrames, ma_u
ma_uint64 currentFrame;
/* Stop waiting if the device has been stopped. */
if (!c89atomic_load_8(&pDevice->null_device.isStarted)) {
if (!c89atomic_load_32(&pDevice->null_device.isStarted)) {
break;
}
@ -13812,6 +13844,189 @@ typedef ma_IUnknown ma_WASAPIDeviceInterface;
#endif
#define MA_CONTEXT_COMMAND_QUIT__WASAPI 1
#define MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI 2
#define MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI 3
static ma_context_command__wasapi ma_context_init_command__wasapi(int code)
{
ma_context_command__wasapi cmd;
MA_ZERO_OBJECT(&cmd);
cmd.code = code;
return cmd;
}
static ma_result ma_context_post_command__wasapi(ma_context* pContext, const ma_context_command__wasapi* pCmd)
{
/* For now we are doing everything synchronously, but I might relax this later if the need arises. */
ma_result result;
ma_bool32 isUsingLocalEvent = MA_FALSE;
ma_event localEvent;
MA_ASSERT(pContext != NULL);
MA_ASSERT(pCmd != NULL);
if (pCmd->pEvent == NULL) {
isUsingLocalEvent = MA_TRUE;
result = ma_event_init(&localEvent);
if (result != MA_SUCCESS) {
return result; /* Failed to create the event for this command. */
}
}
/* Here is where we add the command to the list. If there's not enough room we'll spin until there is. */
ma_mutex_lock(&pContext->wasapi.commandLock);
{
ma_uint32 index;
/* Spin until we've got some space available. */
while (pContext->wasapi.commandCount == ma_countof(pContext->wasapi.commands)) {
ma_yield();
}
/* Space is now available. Can safely add to the list. */
index = (pContext->wasapi.commandIndex + pContext->wasapi.commandCount) % ma_countof(pContext->wasapi.commands);
pContext->wasapi.commands[index] = *pCmd;
pContext->wasapi.commands[index].pEvent = &localEvent;
pContext->wasapi.commandCount += 1;
/* Now that the command has been added, release the semaphore so ma_context_next_command__wasapi() can return. */
ma_semaphore_release(&pContext->wasapi.commandSem);
}
ma_mutex_unlock(&pContext->wasapi.commandLock);
if (isUsingLocalEvent) {
ma_event_wait(&localEvent);
ma_event_uninit(&localEvent);
}
return MA_SUCCESS;
}
static ma_result ma_context_next_command__wasapi(ma_context* pContext, ma_context_command__wasapi* pCmd)
{
ma_result result = MA_SUCCESS;
MA_ASSERT(pContext != NULL);
MA_ASSERT(pCmd != NULL);
result = ma_semaphore_wait(&pContext->wasapi.commandSem);
if (result == MA_SUCCESS) {
ma_mutex_lock(&pContext->wasapi.commandLock);
{
*pCmd = pContext->wasapi.commands[pContext->wasapi.commandIndex];
pContext->wasapi.commandIndex = (pContext->wasapi.commandIndex + 1) % ma_countof(pContext->wasapi.commands);
pContext->wasapi.commandCount -= 1;
}
ma_mutex_unlock(&pContext->wasapi.commandLock);
}
return result;
}
static ma_thread_result MA_THREADCALL ma_context_command_thread__wasapi(void* pUserData)
{
ma_result result;
ma_context* pContext = (ma_context*)pUserData;
MA_ASSERT(pContext != NULL);
for (;;) {
ma_context_command__wasapi cmd;
result = ma_context_next_command__wasapi(pContext, &cmd);
if (result != MA_SUCCESS) {
break;
}
switch (cmd.code)
{
case MA_CONTEXT_COMMAND_QUIT__WASAPI:
{
/* Do nothing. Handled after the switch. */
} break;
case MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI:
{
if (cmd.data.createAudioClient.deviceType == ma_device_type_playback) {
result = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioRenderClient, cmd.data.createAudioClient.ppAudioClientService));
} else {
result = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioCaptureClient, cmd.data.createAudioClient.ppAudioClientService));
}
} break;
case MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI:
{
if (cmd.data.releaseAudioClient.deviceType == ma_device_type_playback) {
if (cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback != NULL) {
ma_IAudioClient_Release((ma_IAudioClient*)cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback);
cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback = NULL;
}
}
if (cmd.data.releaseAudioClient.deviceType == ma_device_type_capture) {
if (cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture != NULL) {
ma_IAudioClient_Release((ma_IAudioClient*)cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture);
cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture = NULL;
}
}
} break;
default:
{
/* Unknown command. Ignore it, but trigger an assert in debug mode so we're aware of it. */
MA_ASSERT(MA_FALSE);
} break;
}
if (cmd.pEvent != NULL) {
ma_event_signal(cmd.pEvent);
}
if (cmd.code == MA_CONTEXT_COMMAND_QUIT__WASAPI) {
break; /* Received a quit message. Get out of here. */
}
}
return (ma_thread_result)0;
}
static ma_result ma_device_create_IAudioClient_service__wasapi(ma_context* pContext, ma_device_type deviceType, ma_IAudioClient* pAudioClient, void** ppAudioClientService)
{
ma_result result;
ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI);
cmd.data.createAudioClient.deviceType = deviceType;
cmd.data.createAudioClient.pAudioClient = (void*)pAudioClient;
cmd.data.createAudioClient.ppAudioClientService = ppAudioClientService;
cmd.data.createAudioClient.result = MA_SUCCESS;
result = ma_context_post_command__wasapi(pContext, &cmd); /* This will not return until the command has actually been run. */
if (result != MA_SUCCESS) {
return result;
}
return cmd.data.createAudioClient.result;
}
#if 0 /* Not used at the moment, but leaving here for future use. */
static ma_result ma_device_release_IAudioClient_service__wasapi(ma_device* pDevice, ma_device_type deviceType)
{
ma_result result;
ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI);
cmd.data.releaseAudioClient.pDevice = pDevice;
cmd.data.releaseAudioClient.deviceType = deviceType;
result = ma_context_post_command__wasapi(pDevice->pContext, &cmd); /* This will not return until the command has actually been run. */
if (result != MA_SUCCESS) {
return result;
}
return MA_SUCCESS;
}
#endif
static void ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(const WAVEFORMATEX* pWF, ma_share_mode shareMode, ma_device_info* pInfo)
{
MA_ASSERT(pWF != NULL);
@ -13832,9 +14047,6 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context
{
HRESULT hr;
WAVEFORMATEX* pWF = NULL;
#ifdef MA_WIN32_DESKTOP
ma_IPropertyStore *pProperties;
#endif
MA_ASSERT(pAudioClient != NULL);
MA_ASSERT(pInfo != NULL);
@ -13848,11 +14060,14 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context
}
/*
Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently support on
UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here
on out, MA_SUCCESS is guaranteed to be returned.
Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently supported on
UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here on
out, MA_SUCCESS is guaranteed to be returned.
*/
#ifdef MA_WIN32_DESKTOP
#ifdef MA_WIN32_DESKTOP
{
ma_IPropertyStore *pProperties;
/*
The first thing to do is get the format from PKEY_AudioEngine_DeviceFormat. This should give us a channel count we assume is
correct which will simplify our searching.
@ -13943,7 +14158,8 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context
} else {
ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval.", ma_result_from_HRESULT(hr));
}
#endif
}
#endif
return MA_SUCCESS;
}
@ -14831,14 +15047,16 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device
pData->usingAudioClient3 = wasInitializedUsingIAudioClient3;
if (deviceType == ma_device_type_playback) {
hr = ma_IAudioClient_GetService((ma_IAudioClient*)pData->pAudioClient, &MA_IID_IAudioRenderClient, (void**)&pData->pRenderClient);
result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pRenderClient);
} else {
hr = ma_IAudioClient_GetService((ma_IAudioClient*)pData->pAudioClient, &MA_IID_IAudioCaptureClient, (void**)&pData->pCaptureClient);
result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pCaptureClient);
}
if (FAILED(hr)) {
errorMsg = "[WASAPI] Failed to get audio client service.", result = ma_result_from_HRESULT(hr);
/*if (FAILED(hr)) {*/
if (result != MA_SUCCESS) {
errorMsg = "[WASAPI] Failed to get audio client service.";
goto done;
}
@ -14927,6 +15145,42 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev
return MA_INVALID_ARGS;
}
/*
Before reinitializing the device we need to free the previous audio clients.
There's a known memory leak here. We will be calling this from the routing change callback that
is fired by WASAPI. If we attempt to release the IAudioClient we will deadlock. In my opinion
this is a bug. I'm not sure what I need to do to handle this cleanly, but I think we'll probably
need some system where we post an event, but delay the execution of it until the callback has
returned. I'm not sure how to do this reliably, however. I have set up some infrastructure for
a command thread which might be useful for this.
*/
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) {
if (pDevice->wasapi.pCaptureClient) {
ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient);
pDevice->wasapi.pCaptureClient = NULL;
}
if (pDevice->wasapi.pAudioClientCapture) {
/*ma_device_release_IAudioClient_service__wasapi(pDevice, ma_device_type_capture);*/
pDevice->wasapi.pAudioClientCapture = NULL;
}
}
if (deviceType == ma_device_type_playback) {
if (pDevice->wasapi.pRenderClient) {
ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient);
pDevice->wasapi.pRenderClient = NULL;
}
if (pDevice->wasapi.pAudioClientPlayback) {
/*ma_device_release_IAudioClient_service__wasapi(pDevice, ma_device_type_playback);*/
pDevice->wasapi.pAudioClientPlayback = NULL;
}
}
if (deviceType == ma_device_type_playback) {
data.formatIn = pDevice->playback.format;
data.channelsIn = pDevice->playback.channels;
@ -14954,16 +15208,6 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev
/* At this point we have some new objects ready to go. We need to uninitialize the previous ones and then set the new ones. */
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) {
if (pDevice->wasapi.pCaptureClient) {
ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient);
pDevice->wasapi.pCaptureClient = NULL;
}
if (pDevice->wasapi.pAudioClientCapture) {
/*ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture);*/
pDevice->wasapi.pAudioClientCapture = NULL;
}
pDevice->wasapi.pAudioClientCapture = data.pAudioClient;
pDevice->wasapi.pCaptureClient = data.pCaptureClient;
@ -14985,16 +15229,6 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev
}
if (deviceType == ma_device_type_playback) {
if (pDevice->wasapi.pRenderClient) {
ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient);
pDevice->wasapi.pRenderClient = NULL;
}
if (pDevice->wasapi.pAudioClientPlayback) {
/*ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);*/
pDevice->wasapi.pAudioClientPlayback = NULL;
}
pDevice->wasapi.pAudioClientPlayback = data.pAudioClient;
pDevice->wasapi.pRenderClient = data.pRenderClient;
@ -15229,8 +15463,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf
}
#endif
c89atomic_exchange_8(&pDevice->wasapi.isStartedCapture, MA_FALSE);
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
return MA_SUCCESS;
}
@ -15328,7 +15562,7 @@ static ma_result ma_device_start__wasapi(ma_device* pDevice)
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_8(&pDevice->wasapi.isStartedCapture, MA_TRUE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE);
}
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
@ -15357,7 +15591,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice)
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_8(&pDevice->wasapi.isStartedCapture, MA_FALSE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE);
}
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
@ -15365,7 +15599,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice)
The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to
the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played.
*/
if (c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
if (c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
/* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */
DWORD waitTime = pDevice->wasapi.actualPeriodSizeInFramesPlayback / pDevice->playback.internalSampleRate;
@ -15410,7 +15644,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice)
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
}
return MA_SUCCESS;
@ -15732,7 +15966,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice)
mappedDeviceBufferSizeInFramesPlayback = 0;
}
if (!c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
ma_uint32 startThreshold = pDevice->playback.internalPeriodSizeInFrames * 1;
/* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */
@ -15748,7 +15982,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice)
return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr));
}
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
}
}
@ -15897,7 +16131,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice)
framesWrittenToPlaybackDevice += framesAvailablePlayback;
}
if (!c89atomic_load_8(&pDevice->wasapi.isStartedPlayback)) {
if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) {
hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);
if (FAILED(hr)) {
ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr));
@ -15905,7 +16139,7 @@ static ma_result ma_device_data_loop__wasapi(ma_device* pDevice)
break;
}
c89atomic_exchange_8(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE);
}
/* Make sure we don't wait on the event before we've started the device or we may end up deadlocking. */
@ -15952,11 +16186,21 @@ static ma_result ma_device_data_loop_wakeup__wasapi(ma_device* pDevice)
return MA_SUCCESS;
}
static ma_result ma_context_uninit__wasapi(ma_context* pContext)
{
MA_ASSERT(pContext != NULL);
MA_ASSERT(pContext->backend == ma_backend_wasapi);
(void)pContext;
if (pContext->wasapi.commandThread != NULL) {
ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_QUIT__WASAPI);
ma_context_post_command__wasapi(pContext, &cmd);
ma_thread_wait(&pContext->wasapi.commandThread);
/* Only after the thread has been terminated can we uninitialize the sync objects for the command thread. */
ma_semaphore_uninit(&pContext->wasapi.commandSem);
ma_mutex_uninit(&pContext->wasapi.commandLock);
}
return MA_SUCCESS;
}
@ -16013,6 +16257,52 @@ static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_
return result;
}
/*
Annoyingly, WASAPI does not allow you to release an IAudioClient object from a different thread
than the one that retrieved it with GetService(). This can result in a deadlock in two
situations:
1) When calling ma_device_uninit() from a different thread to ma_device_init(); and
2) When uninitializing and reinitializing the internal IAudioClient object in response to
automatic stream routing.
We could define ma_device_uninit() such that it must be called on the same thread as
ma_device_init(). We could also just not release the IAudioClient when performing automatic
stream routing to avoid the deadlock. Neither of these are acceptable solutions in my view so
we're going to have to work around this with a worker thread. This is not ideal, but I can't
think of a better way to do this.
More information about this can be found here:
https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nn-audioclient-iaudiorenderclient
Note this section:
When releasing an IAudioRenderClient interface instance, the client must call the interface's
Release method from the same thread as the call to IAudioClient::GetService that created the
object.
*/
{
result = ma_mutex_init(&pContext->wasapi.commandLock);
if (result != MA_SUCCESS) {
return result;
}
result = ma_semaphore_init(0, &pContext->wasapi.commandSem);
if (result != MA_SUCCESS) {
ma_mutex_uninit(&pContext->wasapi.commandLock);
return result;
}
result = ma_thread_create(&pContext->wasapi.commandThread, ma_thread_priority_normal, 0, ma_context_command_thread__wasapi, pContext);
if (result != MA_SUCCESS) {
ma_semaphore_uninit(&pContext->wasapi.commandSem);
ma_mutex_uninit(&pContext->wasapi.commandLock);
return result;
}
}
pCallbacks->onContextInit = ma_context_init__wasapi;
pCallbacks->onContextUninit = ma_context_uninit__wasapi;
pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__wasapi;
@ -16026,7 +16316,7 @@ static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_
pCallbacks->onDeviceDataLoop = ma_device_data_loop__wasapi;
pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__wasapi;
return result;
return MA_SUCCESS;
}
#endif
@ -25652,7 +25942,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice)
-(void)remove_handler
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionRouteChangeNotification" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
}
-(void)handle_route_change:(NSNotification*)pNotification
@ -31270,7 +31560,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d
/* We're going to calculate some stuff in C just to simplify the JS code. */
channels = (pDescriptor->channels > 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS;
sampleRate = (pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE;
periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile);
periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptor, sampleRate, pConfig->performanceProfile);
/* We create the device on the JavaScript side and reference it using an index. We use this to make it possible to reference the device between JavaScript and C. */
@ -31290,6 +31580,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d
/* The AudioContext must be created in a suspended state. */
device.webaudio = new (window.AudioContext || window.webkitAudioContext)({sampleRate:sampleRate});
device.webaudio.suspend();
device.state = 1; /* MA_STATE_STOPPED */
/*
We need an intermediary buffer which we use for JavaScript and C interop. This buffer stores interleaved f32 PCM data. Because it's passed between
@ -31497,13 +31788,17 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice)
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
EM_ASM({
miniaudio.get_device_by_index($0).webaudio.resume();
var device = miniaudio.get_device_by_index($0);
device.webaudio.resume();
device.state = 2; /* MA_STATE_STARTED */
}, pDevice->webaudio.indexCapture);
}
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
EM_ASM({
miniaudio.get_device_by_index($0).webaudio.resume();
var device = miniaudio.get_device_by_index($0);
device.webaudio.resume();
device.state = 2; /* MA_STATE_STARTED */
}, pDevice->webaudio.indexPlayback);
}
@ -31526,13 +31821,17 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice)
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) {
EM_ASM({
miniaudio.get_device_by_index($0).webaudio.suspend();
var device = miniaudio.get_device_by_index($0);
device.webaudio.suspend();
device.state = 1; /* MA_STATE_STOPPED */
}, pDevice->webaudio.indexCapture);
}
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
EM_ASM({
miniaudio.get_device_by_index($0).webaudio.suspend();
var device = miniaudio.get_device_by_index($0);
device.webaudio.suspend();
device.state = 1; /* MA_STATE_STOPPED */
}, pDevice->webaudio.indexPlayback);
}
@ -31612,6 +31911,26 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex
miniaudio.get_device_by_index = function(deviceIndex) {
return miniaudio.devices[deviceIndex];
};
miniaudio.unlock_event_types = (function(){
return ['touchstart', 'touchend', 'click'];
})();
miniaudio.unlock = function() {
for(var i = 0; i < miniaudio.devices.length; ++i) {
var device = miniaudio.devices[i];
if (device != null && device.webaudio != null && device.state === 2 /* MA_STATE_STARTED */) {
device.webaudio.resume();
}
}
miniaudio.unlock_event_types.map(function(event_type) {
document.removeEventListener(event_type, miniaudio.unlock, true);
});
};
miniaudio.unlock_event_types.map(function(event_type) {
document.addEventListener(event_type, miniaudio.unlock, true);
});
}
return 1;
@ -44086,7 +44405,7 @@ extern "C" {
#define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x)
#define DRWAV_VERSION_MAJOR 0
#define DRWAV_VERSION_MINOR 12
#define DRWAV_VERSION_REVISION 17
#define DRWAV_VERSION_REVISION 19
#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION)
#include <stddef.h>
typedef signed char drwav_int8;
@ -44459,7 +44778,7 @@ extern "C" {
#define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x)
#define DRFLAC_VERSION_MAJOR 0
#define DRFLAC_VERSION_MINOR 12
#define DRFLAC_VERSION_REVISION 26
#define DRFLAC_VERSION_REVISION 28
#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
#include <stddef.h>
typedef signed char drflac_int8;
@ -44820,7 +45139,7 @@ extern "C" {
#define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x)
#define DRMP3_VERSION_MAJOR 0
#define DRMP3_VERSION_MINOR 6
#define DRMP3_VERSION_REVISION 25
#define DRMP3_VERSION_REVISION 27
#define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION)
#include <stddef.h>
typedef signed char drmp3_int8;
@ -49756,7 +50075,6 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_d
runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec);
runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign);
runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample);
pWav->dataChunkDataPos = runningPos;
if (pFormat->container == drwav_container_riff) {
drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize;
runningPos += drwav__write(pWav, "data", 4);
@ -49769,12 +50087,12 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_d
runningPos += drwav__write(pWav, "data", 4);
runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF);
}
(void)runningPos;
pWav->container = pFormat->container;
pWav->channels = (drwav_uint16)pFormat->channels;
pWav->sampleRate = pFormat->sampleRate;
pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample;
pWav->translatedFormatTag = (drwav_uint16)pFormat->format;
pWav->dataChunkDataPos = runningPos;
return DRWAV_TRUE;
}
DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks)
@ -50220,7 +50538,7 @@ DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e)
}
DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)
{
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
errno_t err;
#endif
if (ppFile != NULL) {
@ -50229,7 +50547,7 @@ DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, con
if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) {
return DRWAV_INVALID_ARGS;
}
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
err = fopen_s(ppFile, pFilePath, pOpenMode);
if (err != 0) {
return drwav_result_from_errno(err);
@ -50598,7 +50916,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav)
drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize);
drwav__write_u32ne_to_le(pWav, riffChunkSize);
}
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) {
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) {
drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize);
drwav__write_u32ne_to_le(pWav, dataChunkSize);
}
@ -50607,7 +50925,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav)
drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize);
drwav__write_u64ne_to_le(pWav, riffChunkSize);
}
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) {
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) {
drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize);
drwav__write_u64ne_to_le(pWav, dataChunkSize);
}
@ -50897,7 +51215,8 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
drwav_uint64 totalFramesRead = 0;
DRWAV_ASSERT(pWav != NULL);
DRWAV_ASSERT(framesToRead > 0);
while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {
while (pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {
DRWAV_ASSERT(framesToRead > 0);
if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) {
if (pWav->channels == 1) {
drwav_uint8 header[7];
@ -50947,7 +51266,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
pWav->msadpcm.cachedFrameCount -= 1;
}
if (framesToRead == 0) {
return totalFramesRead;
break;
}
if (pWav->msadpcm.cachedFrameCount == 0) {
if (pWav->msadpcm.bytesRemainingInBlock == 0) {
@ -51043,7 +51362,8 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin
};
DRWAV_ASSERT(pWav != NULL);
DRWAV_ASSERT(framesToRead > 0);
while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {
while (pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {
DRWAV_ASSERT(framesToRead > 0);
if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) {
if (pWav->channels == 1) {
drwav_uint8 header[4];
@ -51094,7 +51414,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin
pWav->ima.cachedFrameCount -= 1;
}
if (framesToRead == 0) {
return totalFramesRead;
break;
}
if (pWav->ima.cachedFrameCount == 0) {
if (pWav->ima.bytesRemainingInBlock == 0) {
@ -55352,7 +55672,8 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u
DRFLAC_ASSERT(blockSize > 0);
if (blockSize == 1) {
header->blockSizeInPCMFrames = 192;
} else if (blockSize >= 2 && blockSize <= 5) {
} else if (blockSize <= 5) {
DRFLAC_ASSERT(blockSize >= 2);
header->blockSizeInPCMFrames = 576 * (1 << (blockSize - 2));
} else if (blockSize == 6) {
if (!drflac__read_uint16(bs, 8, &header->blockSizeInPCMFrames)) {
@ -57771,7 +58092,7 @@ static drflac_result drflac_result_from_errno(int e)
}
static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)
{
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
errno_t err;
#endif
if (ppFile != NULL) {
@ -57780,7 +58101,7 @@ static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const ch
if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) {
return DRFLAC_INVALID_ARGS;
}
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
err = fopen_s(ppFile, pFilePath, pOpenMode);
if (err != 0) {
return drflac_result_from_errno(err);
@ -63081,7 +63402,7 @@ static drmp3_result drmp3_result_from_errno(int e)
}
static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)
{
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
errno_t err;
#endif
if (ppFile != NULL) {
@ -63090,7 +63411,7 @@ static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char
if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) {
return DRMP3_INVALID_ARGS;
}
#if _MSC_VER && _MSC_VER >= 1400
#if defined(_MSC_VER) && _MSC_VER >= 1400
err = fopen_s(ppFile, pFilePath, pOpenMode);
if (err != 0) {
return drmp3_result_from_errno(err);
@ -64149,9 +64470,19 @@ The following miscellaneous changes have also been made.
/*
REVISION HISTORY
================
v0.10.32 - TBD
v0.10.32 - 2020-02-23
- WASAPI: Fix a deadlock in exclusive mode.
- WASAPI: No longer return an error from ma_context_get_device_info() when an exclusive mode format
cannot be retrieved.
- WASAPI: Attempt to fix some bugs with device uninitialization.
- PulseAudio: Yet another refactor, this time to remove the dependency on `pa_threaded_mainloop`.
- Web Audio: Fix a bug on Chrome and any other browser using the same engine.
- Web Audio: Automatically start the device on some user input if the device has been started. This
is to work around Google's policy of not starting audio if no user input has yet been performed.
- Fix a bug where thread handles are not being freed.
- Fix some static analysis warnings in FLAC, WAV and MP3 decoders.
- Fix a warning due to referencing _MSC_VER when it is undefined.
- Update to latest version of c89atomic.
- Internal refactoring to migrate over to the new backend callback system for the following backends:
- PulseAudio
- ALSA
@ -64161,9 +64492,6 @@ v0.10.32 - TBD
- OSS
- audio(4)
- sndio
- Fix a bug where thread handles are not being freed.
- Fix some static analysis warnings in FLAC, WAV and MP3 decoders.
- Update to latest version of c89atomic.
v0.10.31 - 2021-01-17
- Make some functions const correct.