From fd3a7e259552907ec713450d6ddacfc8df21f885 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 2 Feb 2019 17:35:58 +1000 Subject: [PATCH] WASAPI: Infrastructure work in preparation for full-duplex. --- mini_al.h | 600 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 404 insertions(+), 196 deletions(-) diff --git a/mini_al.h b/mini_al.h index 9d042943..9bc5fbc4 100644 --- a/mini_al.h +++ b/mini_al.h @@ -1997,7 +1997,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device mal_stop_proc onStop; void* pUserData; // Application defined data. char name[256]; - mal_device_config initConfig; // The configuration passed in to mal_device_init(). Mainly used for reinitializing the backend device. + mal_device_config initConfig; // TODO: Get rid of this. The configuration passed in to mal_device_init(). Mainly used for reinitializing the backend device. mal_mutex lock; mal_event wakeupEvent; mal_event startEvent; @@ -2020,26 +2020,68 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device mal_uint32 _dspFrameCount; // Internal use only. Used when running the device -> DSP -> client pipeline. See mal_device__on_read_from_device(). const mal_uint8* _dspFrames; // ^^^ AS ABOVE ^^^ + struct + { + char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ + mal_bool32 usingDefaultFormat : 1; + mal_bool32 usingDefaultChannels : 1; + mal_bool32 usingDefaultSampleRate : 1; + mal_bool32 usingDefaultChannelMap : 1; + mal_format format; + mal_uint32 channels; + mal_uint32 sampleRate; + mal_channel channelMap[MAL_MAX_CHANNELS]; + mal_format internalFormat; + mal_uint32 internalChannels; + mal_uint32 internalSampleRate; + mal_channel internalChannelMap[MAL_MAX_CHANNELS]; + mal_uint32 internalBufferSizeInFrames; + mal_uint32 internalPeriods; + mal_pcm_converter converter; + } playback; + struct + { + char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ + mal_bool32 usingDefaultFormat : 1; + mal_bool32 usingDefaultChannels : 1; + mal_bool32 usingDefaultSampleRate : 1; + mal_bool32 usingDefaultChannelMap : 1; + mal_format format; + mal_uint32 channels; + mal_uint32 sampleRate; + mal_channel channelMap[MAL_MAX_CHANNELS]; + mal_format internalFormat; + mal_uint32 internalChannels; + mal_uint32 internalSampleRate; + mal_channel internalChannelMap[MAL_MAX_CHANNELS]; + mal_uint32 internalBufferSizeInFrames; + mal_uint32 internalPeriods; + mal_pcm_converter converter; + } capture; + union { #ifdef MAL_SUPPORT_WASAPI struct { - /*IAudioClient**/ mal_ptr pAudioClient; + /*IAudioClient**/ mal_ptr pAudioClientPlayback; + /*IAudioClient**/ mal_ptr pAudioClientCapture; /*IAudioRenderClient**/ mal_ptr pRenderClient; /*IAudioCaptureClient**/ mal_ptr pCaptureClient; - /*IMMDeviceEnumerator**/ mal_ptr pDeviceEnumerator; /* <-- Used for IMMNotificationClient notifications. Required for detecting default device changes. */ + /*IMMDeviceEnumerator**/ mal_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */ mal_IMMNotificationClient notificationClient; - /*HANDLE*/ mal_handle hEventPlayback; /* Used with the blocking API. Manual reset. Initialized to signaled. */ - /*HANDLE*/ mal_handle hEventCapture; /* Used with the blocking API. Manual reset. Initialized to unsignaled. */ + /*HANDLE*/ mal_handle hEventPlayback; /* Used with the blocking API. Manual reset. Initialized to signaled. */ + /*HANDLE*/ mal_handle hEventCapture; /* Used with the blocking API. Manual reset. Initialized to unsignaled. */ void* pDeviceBufferPlayback; void* pDeviceBufferCapture; mal_uint32 deviceBufferFramesRemainingPlayback; mal_uint32 deviceBufferFramesRemainingCapture; mal_uint32 deviceBufferFramesCapacityPlayback; mal_uint32 deviceBufferFramesCapacityCapture; + mal_bool32 hasDefaultPlaybackDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ + mal_bool32 hasDefaultCaptureDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ mal_bool32 isStarted; - mal_bool32 hasDefaultDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ + } wasapi; #endif #ifdef MAL_SUPPORT_DSOUND @@ -6074,8 +6116,8 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I } // We only care about devices with the same data flow and role as the current device. - if ((pThis->pDevice->type == mal_device_type_playback && dataFlow != mal_eRender ) || - (pThis->pDevice->type == mal_device_type_capture && dataFlow != mal_eCapture)) { + if (((pThis->pDevice->type == mal_device_type_playback || pThis->pDevice->type == mal_device_type_duplex) && dataFlow != mal_eRender ) || + ((pThis->pDevice->type == mal_device_type_capture || pThis->pDevice->type == mal_device_type_duplex) && dataFlow != mal_eCapture)) { return S_OK; } @@ -6086,9 +6128,14 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I return S_OK; } - // We don't change the device here - we change it in the worker thread to keep synchronization simple. To this I'm just setting a flag to + // We don't change the device here - we change it in the worker thread to keep synchronization simple. To do this I'm just setting a flag to // indicate that the default device has changed. - mal_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultDeviceChanged, MAL_TRUE); + if (dataFlow == mal_eRender) { + mal_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MAL_TRUE); + } + if (dataFlow == mal_eCapture) { + mal_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultCaptureDeviceChanged, MAL_TRUE); + } #if 0 SetEvent(pThis->pDevice->wasapi.hBreakEvent); // <-- The main loop will be waiting on some events. We want to break from this wait ASAP so we can change the device as quickly as possible. @@ -6611,8 +6658,12 @@ void mal_device_uninit__wasapi(mal_device* pDevice) if (pDevice->wasapi.pCaptureClient) { mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); } - if (pDevice->wasapi.pAudioClient) { - mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClient); + + if (pDevice->wasapi.pAudioClientPlayback) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + } + if (pDevice->wasapi.pAudioClientCapture) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); } if (pDevice->wasapi.hEventPlayback) { @@ -6653,14 +6704,14 @@ typedef struct char deviceName[256]; } mal_device_init_internal_data__wasapi; -mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pPlaybackDeviceID, const mal_device_id* pCaptureDeviceID, mal_device_init_internal_data__wasapi* pData) +mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_device_init_internal_data__wasapi* pData) { (void)pContext; mal_assert(pContext != NULL); mal_assert(pData != NULL); - /* Not currently supporting full-duplex. */ + /* This function is only used to initialize one device type: either playback or capture. Never full-duplex. */ if (deviceType == mal_device_type_duplex) { return MAL_INVALID_ARGS; } @@ -6677,55 +6728,9 @@ mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_ty MAL_REFERENCE_TIME bufferDurationInMicroseconds; mal_bool32 wasInitializedUsingIAudioClient3 = MAL_FALSE; WAVEFORMATEXTENSIBLE wf; - //mal_WASAPIDeviceInterface* pDeviceInterfacePlayback = NULL; - //mal_WASAPIDeviceInterface* pDeviceInterfaceCapture = NULL; - mal_WASAPIDeviceInterface* pDeviceInterface = NULL; // TEMP: Will be split between playback and capture when full-duplex support is implemented. + mal_WASAPIDeviceInterface* pDeviceInterface = NULL; -#if 0 - /* - We first try initializing the capture device if applicable. If the device is full-duplex we will try re-using the capture audio client, but - if that fails we'll need to initialize a separate playback audio client. - */ - if (deviceType == mal_device_type_capture || deviceType == mal_device_type_duplex) { - - } - - /* We may need a playback render client. If it's a duplex device we want to try re-using the same audio client. Otherwise we need a separate one. */ - if (deviceType == mal_device_type_playback || deviceType == mal_device_type_duplex) { - if (deviceType == mal_device_type_duplex) { - if (pPlaybackDeviceID != NULL && pCaptureDeviceID != NULL && mal_context_is_device_id_equal__wasapi(pContext, pPlaybackDeviceID, pCaptureDeviceID)) { - - } - } - - /* Getting here means we need to initialize a separate playback audio client. */ - - } -#endif - -#if 0 -#ifdef MAL_WIN32_DESKTOP - result = mal_context_get_MMDevice__wasapi(pContext, deviceType, (deviceType == mal_device_type_playback) ? pPlaybackDeviceID : pCaptureDeviceID, &pMMDevice); - if (result != MAL_SUCCESS) { - goto done; - } - - hr = mal_IMMDevice_Activate(pMMDevice, &MAL_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pData->pAudioClient); - if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to activate device.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; - goto done; - } -#else - mal_IUnknown* pActivatedInterface = NULL; - result = mal_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, pDeviceID, &pData->pAudioClient, &pActivatedInterface); - if (result != MAL_SUCCESS) { - goto done; - } -#endif -#endif - - /* TEMP: Will be replaced when full-duplex is added. */ - result = mal_context_get_IAudioClient__wasapi(pContext, deviceType, (deviceType == mal_device_type_playback) ? pPlaybackDeviceID : pCaptureDeviceID, &pData->pAudioClient, &pDeviceInterface); + result = mal_context_get_IAudioClient__wasapi(pContext, deviceType, pDeviceID, &pData->pAudioClient, &pDeviceInterface); if (result != MAL_SUCCESS) { goto done; } @@ -7002,104 +7007,281 @@ done: } } -mal_result mal_device_reinit__wasapi(mal_device* pDevice) +mal_result mal_device_reinit__wasapi(mal_device* pDevice, mal_device_type deviceType) { + mal_assert(pDevice != NULL); + + // We only re-initialize the playback or capture device. Never a full-duplex device. + if (deviceType == mal_device_type_duplex) { + return MAL_INVALID_ARGS; + } + mal_device_init_internal_data__wasapi data; - data.formatIn = pDevice->format; - data.channelsIn = pDevice->channels; - data.sampleRateIn = pDevice->sampleRate; + data.formatIn = pDevice->format; + data.channelsIn = pDevice->channels; + data.sampleRateIn = pDevice->sampleRate; mal_copy_memory(data.channelMapIn, pDevice->channelMap, sizeof(pDevice->channelMap)); - data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames; + data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames; + data.usingDefaultFormat = pDevice->usingDefaultFormat; + data.usingDefaultChannels = pDevice->usingDefaultChannels; + data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; + data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; + data.shareMode = pDevice->initConfig.shareMode; data.bufferSizeInMillisecondsIn = pDevice->bufferSizeInMilliseconds; - data.periodsIn = pDevice->periods; - data.usingDefaultFormat = pDevice->usingDefaultFormat; - data.usingDefaultChannels = pDevice->usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; - data.shareMode = pDevice->initConfig.shareMode; - mal_result result = mal_device_init_internal__wasapi(pDevice->pContext, pDevice->type, NULL, NULL, &data); + data.periodsIn = pDevice->periods; + mal_result result = mal_device_init_internal__wasapi(pDevice->pContext, deviceType, NULL, &data); if (result != MAL_SUCCESS) { return result; } // 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 (pDevice->wasapi.pRenderClient) { - mal_IAudioRenderClient_Release((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient); - pDevice->wasapi.pRenderClient = NULL; - } - if (pDevice->wasapi.pCaptureClient) { - mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - pDevice->wasapi.pCaptureClient = NULL; - } - if (pDevice->wasapi.pAudioClient) { - mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClient); - pDevice->wasapi.pAudioClient = NULL; + if (deviceType == mal_device_type_capture) { + if (pDevice->wasapi.pCaptureClient) { + mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + + if (pDevice->wasapi.pAudioClientCapture) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + pDevice->wasapi.pAudioClientCapture = data.pAudioClient; + pDevice->wasapi.pCaptureClient = data.pCaptureClient; + + pDevice->capture.internalFormat = data.formatOut; + pDevice->capture.internalChannels = data.channelsOut; + pDevice->capture.internalSampleRate = data.sampleRateOut; + mal_copy_memory(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDevice->capture.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->capture.internalPeriods = data.periodsOut; + mal_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName); + + mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); + } + + if (deviceType == mal_device_type_playback) { + if (pDevice->wasapi.pRenderClient) { + mal_IAudioRenderClient_Release((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient); + pDevice->wasapi.pRenderClient = NULL; + } + + if (pDevice->wasapi.pAudioClientPlayback) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + pDevice->wasapi.pAudioClientPlayback = NULL; + } + + pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; + pDevice->wasapi.pRenderClient = data.pRenderClient; + + pDevice->playback.internalFormat = data.formatOut; + pDevice->playback.internalChannels = data.channelsOut; + pDevice->playback.internalSampleRate = data.sampleRateOut; + mal_copy_memory(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDevice->playback.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->playback.internalPeriods = data.periodsOut; + mal_strcpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), data.deviceName); + + mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); + } + + + + // BACKWARDS COMPATIBILITY. TEMP. + if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) { + pDevice->internalFormat = pDevice->capture.internalFormat; + pDevice->internalChannels = pDevice->capture.internalChannels; + pDevice->internalSampleRate = pDevice->capture.internalSampleRate; + mal_copy_memory(pDevice->internalChannelMap, pDevice->capture.internalChannelMap, sizeof(pDevice->capture.internalChannelMap)); + pDevice->bufferSizeInFrames = pDevice->capture.internalBufferSizeInFrames; + pDevice->periods = pDevice->capture.internalChannels; + mal_strcpy_s(pDevice->name, sizeof(pDevice->name), pDevice->capture.name); + } else { + pDevice->internalFormat = pDevice->playback.internalFormat; + pDevice->internalChannels = pDevice->playback.internalChannels; + pDevice->internalSampleRate = pDevice->playback.internalSampleRate; + mal_copy_memory(pDevice->internalChannelMap, pDevice->playback.internalChannelMap, sizeof(pDevice->playback.internalChannelMap)); + pDevice->bufferSizeInFrames = pDevice->playback.internalBufferSizeInFrames; + pDevice->periods = pDevice->playback.internalChannels; + mal_strcpy_s(pDevice->name, sizeof(pDevice->name), pDevice->playback.name); } - pDevice->wasapi.pAudioClient = data.pAudioClient; - pDevice->wasapi.pRenderClient = data.pRenderClient; - pDevice->wasapi.pCaptureClient = data.pCaptureClient; mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE); - pDevice->internalFormat = data.formatOut; - pDevice->internalChannels = data.channelsOut; - pDevice->internalSampleRate = data.sampleRateOut; - mal_copy_memory(pDevice->internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->bufferSizeInFrames = data.bufferSizeInFramesOut; - pDevice->periods = data.periodsOut; - mal_strcpy_s(pDevice->name, sizeof(pDevice->name), data.deviceName); - - if (pDevice->type == mal_device_type_playback) { - mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventPlayback); - } - if (pDevice->type == mal_device_type_capture) { - mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventCapture); - } - return MAL_SUCCESS; } mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_config* pConfig, mal_device* pDevice) { - (void)pContext; - - mal_assert(pDevice != NULL); - mal_zero_object(&pDevice->wasapi); - mal_result result = MAL_SUCCESS; const char* errorMsg = ""; - mal_device_init_internal_data__wasapi data; - data.formatIn = pConfig->format; - data.channelsIn = pConfig->channels; - data.sampleRateIn = pConfig->sampleRate; - mal_copy_memory(data.channelMapIn, pConfig->channelMap, sizeof(pConfig->channelMap)); - data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; - data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; - data.periodsIn = pConfig->periods; - data.usingDefaultFormat = pDevice->usingDefaultFormat; - data.usingDefaultChannels = pDevice->usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; - data.shareMode = pConfig->shareMode; - result = mal_device_init_internal__wasapi(pDevice->pContext, pConfig->deviceType, pConfig->pPlaybackDeviceID, pConfig->pCaptureDeviceID, &data); - if (result != MAL_SUCCESS) { - return result; + (void)pContext; + + mal_assert(pContext != NULL); + mal_assert(pDevice != NULL); + + mal_zero_object(&pDevice->wasapi); + + if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) { + mal_device_init_internal_data__wasapi data; + data.formatIn = pConfig->format; + data.channelsIn = pConfig->channels; + data.sampleRateIn = pConfig->sampleRate; + mal_copy_memory(data.channelMapIn, pConfig->channelMap, sizeof(pConfig->channelMap)); + data.usingDefaultFormat = pDevice->usingDefaultFormat; + data.usingDefaultChannels = pDevice->usingDefaultChannels; + data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; + data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; + data.shareMode = pConfig->shareMode; + data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; + data.periodsIn = pConfig->periods; + + result = mal_device_init_internal__wasapi(pDevice->pContext, mal_device_type_capture, pConfig->pCaptureDeviceID, &data); + if (result != MAL_SUCCESS) { + return result; + } + + pDevice->wasapi.pAudioClientCapture = data.pAudioClient; + pDevice->wasapi.pCaptureClient = data.pCaptureClient; + + pDevice->capture.internalFormat = data.formatOut; + pDevice->capture.internalChannels = data.channelsOut; + pDevice->capture.internalSampleRate = data.sampleRateOut; + mal_copy_memory(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDevice->capture.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->capture.internalPeriods = data.periodsOut; + mal_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName); + + /* + The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled, + however, because we want to block until we actually have something for the first call to mal_device_read(). + */ + pDevice->wasapi.hEventCapture = CreateEventA(NULL, TRUE, FALSE, NULL); /* Manual reset, unsignaled by default. */ + if (pDevice->wasapi.hEventCapture == NULL) { + if (pDevice->wasapi.pCaptureClient != NULL) { + mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + if (pDevice->wasapi.pAudioClientCapture != NULL) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture.", MAL_FAILED_TO_CREATE_EVENT); + } + mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); } - pDevice->wasapi.pAudioClient = data.pAudioClient; - pDevice->wasapi.pRenderClient = data.pRenderClient; - pDevice->wasapi.pCaptureClient = data.pCaptureClient; - mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE); - - pDevice->internalFormat = data.formatOut; - pDevice->internalChannels = data.channelsOut; - pDevice->internalSampleRate = data.sampleRateOut; - mal_copy_memory(pDevice->internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->bufferSizeInFrames = data.bufferSizeInFramesOut; - pDevice->periods = data.periodsOut; - mal_strcpy_s(pDevice->name, sizeof(pDevice->name), data.deviceName); + if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) { + mal_device_init_internal_data__wasapi data; + data.formatIn = pConfig->format; + data.channelsIn = pConfig->channels; + data.sampleRateIn = pConfig->sampleRate; + mal_copy_memory(data.channelMapIn, pConfig->channelMap, sizeof(pConfig->channelMap)); + data.usingDefaultFormat = pDevice->usingDefaultFormat; + data.usingDefaultChannels = pDevice->usingDefaultChannels; + data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; + data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; + data.shareMode = pConfig->shareMode; + // In duplex mode we want the playback device to have the same buffer size and period count as the capture device. + if (pConfig->deviceType == mal_device_type_duplex) { + data.bufferSizeInFramesIn = pDevice->capture.internalBufferSizeInFrames; + data.periodsIn = pDevice->capture.internalPeriods; + } else { + data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames; + data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; + data.periodsIn = pConfig->periods; + } + + result = mal_device_init_internal__wasapi(pDevice->pContext, mal_device_type_playback, pConfig->pPlaybackDeviceID, &data); + if (result != MAL_SUCCESS) { + if (pConfig->deviceType == mal_device_type_duplex) { + if (pDevice->wasapi.pCaptureClient != NULL) { + mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + if (pDevice->wasapi.pAudioClientCapture != NULL) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + CloseHandle(pDevice->wasapi.hEventCapture); + pDevice->wasapi.hEventCapture = NULL; + } + return result; + } + + pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; + pDevice->wasapi.pRenderClient = data.pRenderClient; + + pDevice->playback.internalFormat = data.formatOut; + pDevice->playback.internalChannels = data.channelsOut; + pDevice->playback.internalSampleRate = data.sampleRateOut; + mal_copy_memory(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDevice->playback.internalBufferSizeInFrames = data.bufferSizeInFramesOut; + pDevice->playback.internalPeriods = data.periodsOut; + mal_strcpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), data.deviceName); + + /* + The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled + only after the whole available space has been filled, never before. + + The playback event also needs to be initially set to a signaled state so that the first call to mal_device_write() is able + to get passed WaitForMultipleObjects(). + */ + pDevice->wasapi.hEventPlayback = CreateEventA(NULL, TRUE, TRUE, NULL); /* Manual reset, signaled by default. */ + if (pDevice->wasapi.hEventPlayback == NULL) { + if (pConfig->deviceType == mal_device_type_duplex) { + if (pDevice->wasapi.pCaptureClient != NULL) { + mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + if (pDevice->wasapi.pAudioClientCapture != NULL) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + pDevice->wasapi.pAudioClientCapture = NULL; + } + + CloseHandle(pDevice->wasapi.hEventCapture); + pDevice->wasapi.hEventCapture = NULL; + } + + if (pDevice->wasapi.pRenderClient != NULL) { + mal_IAudioRenderClient_Release((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient); + pDevice->wasapi.pRenderClient = NULL; + } + if (pDevice->wasapi.pAudioClientPlayback != NULL) { + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + pDevice->wasapi.pAudioClientPlayback = NULL; + } + + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback.", MAL_FAILED_TO_CREATE_EVENT); + } + mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); + } + + + // BACKWARDS COMPATIBILITY. TEMP. + if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) { + pDevice->internalFormat = pDevice->capture.internalFormat; + pDevice->internalChannels = pDevice->capture.internalChannels; + pDevice->internalSampleRate = pDevice->capture.internalSampleRate; + mal_copy_memory(pDevice->internalChannelMap, pDevice->capture.internalChannelMap, sizeof(pDevice->capture.internalChannelMap)); + pDevice->bufferSizeInFrames = pDevice->capture.internalBufferSizeInFrames; + pDevice->periods = pDevice->capture.internalChannels; + mal_strcpy_s(pDevice->name, sizeof(pDevice->name), pDevice->capture.name); + } else { + pDevice->internalFormat = pDevice->playback.internalFormat; + pDevice->internalChannels = pDevice->playback.internalChannels; + pDevice->internalSampleRate = pDevice->playback.internalSampleRate; + mal_copy_memory(pDevice->internalChannelMap, pDevice->playback.internalChannelMap, sizeof(pDevice->playback.internalChannelMap)); + pDevice->bufferSizeInFrames = pDevice->playback.internalBufferSizeInFrames; + pDevice->periods = pDevice->playback.internalChannels; + mal_strcpy_s(pDevice->name, sizeof(pDevice->name), pDevice->playback.name); + } + // We need to get notifications of when the default device changes. We do this through a device enumerator by @@ -7125,38 +7307,7 @@ mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_confi } #endif - /* Events. */ - - /* - The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled - only after the whole available space has been filled, never before. - - The playback event also needs to be initially set to a signaled state so that the first call to mal_device_write() is able - to get passed WaitForMultipleObjects(). - */ - if (pConfig->deviceType == mal_device_type_playback) { - pDevice->wasapi.hEventPlayback = CreateEventA(NULL, TRUE, TRUE, NULL); /* Manual reset, signaled by default. */ - if (pDevice->wasapi.hEventPlayback == NULL) { - errorMsg = "[WASAPI] Failed to create event for playback."; result = MAL_FAILED_TO_CREATE_EVENT; - goto done; - } - - mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventPlayback); - } - - /* - The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled, - however, because we want to block until we actually have something for the first call to mal_device_read(). - */ - if (pConfig->deviceType == mal_device_type_capture) { - pDevice->wasapi.hEventCapture = CreateEventA(NULL, TRUE, FALSE, NULL); /* Manual reset, unsignaled by default. */ - if (pDevice->wasapi.hEventCapture == NULL) { - errorMsg = "[WASAPI] Failed to create event for capture."; result = MAL_FAILED_TO_CREATE_EVENT; - goto done; - } - - mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventCapture); - } + mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE); result = MAL_SUCCESS; @@ -7174,9 +7325,24 @@ mal_result mal_device_start__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); - HRESULT hr = mal_IAudioClient_Start((mal_IAudioClient*)pDevice->wasapi.pAudioClient); - if (FAILED(hr)) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + HRESULT hr; + + if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) { + hr = mal_IAudioClient_Start((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (FAILED(hr)) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + + if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) { + hr = mal_IAudioClient_Start((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + if (pDevice->type == mal_device_type_duplex) { + mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + } + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } } mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_TRUE); @@ -7187,21 +7353,40 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); - if (pDevice->wasapi.pAudioClient == NULL) { - return MAL_DEVICE_NOT_INITIALIZED; + if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) { + if (pDevice->wasapi.pAudioClientCapture == NULL) { + return MAL_DEVICE_NOT_INITIALIZED; + } + + HRESULT hr = mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (FAILED(hr)) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + + /* The audio client needs to be reset otherwise restarting will fail. */ + hr = mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (FAILED(hr)) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } } - /* TODO: Wait until every sample that was written by the callback has been processed. */ + if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) { + if (pDevice->wasapi.pAudioClientPlayback == NULL) { + return MAL_DEVICE_NOT_INITIALIZED; + } - HRESULT hr = mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClient); - if (FAILED(hr)) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); - } + /* TODO: Wait until every sample that was written by the callback has been processed. */ - /* The audio client needs to be reset otherwise restarting will fail. */ - hr = mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClient); - if (FAILED(hr)) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + HRESULT hr = mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + + /* The audio client needs to be reset otherwise restarting will fail. */ + hr = mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } } mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE); @@ -7209,15 +7394,19 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice) } -mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uint32* pFrameCount) +mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_IAudioClient* pAudioClient, mal_uint32* pFrameCount) { mal_assert(pDevice != NULL); mal_assert(pFrameCount != NULL); *pFrameCount = 0; + if ((mal_ptr)pAudioClient != pDevice->wasapi.pAudioClientPlayback && (mal_ptr)pAudioClient != pDevice->wasapi.pAudioClientCapture) { + return MAL_INVALID_OPERATION; + } + mal_uint32 paddingFramesCount; - HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount); + HRESULT hr = mal_IAudioClient_GetCurrentPadding(pAudioClient, &paddingFramesCount); if (FAILED(hr)) { return MAL_DEVICE_UNAVAILABLE; } @@ -7226,7 +7415,7 @@ mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uin if (pDevice->initConfig.shareMode == mal_share_mode_exclusive) { *pFrameCount = paddingFramesCount; } else { - if (pDevice->type == mal_device_type_playback) { + if ((mal_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { *pFrameCount = pDevice->bufferSizeInFrames - paddingFramesCount; } else { *pFrameCount = paddingFramesCount; @@ -7236,21 +7425,40 @@ mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uin return MAL_SUCCESS; } -mal_bool32 mal_device_is_reroute_required__wasapi(mal_device* pDevice) +mal_bool32 mal_device_is_reroute_required__wasapi(mal_device* pDevice, mal_device_type deviceType) { mal_assert(pDevice != NULL); - return pDevice->wasapi.hasDefaultDeviceChanged; + + if (deviceType == mal_device_type_playback) { + return pDevice->wasapi.hasDefaultPlaybackDeviceChanged; + } + + if (deviceType == mal_device_type_capture) { + return pDevice->wasapi.hasDefaultCaptureDeviceChanged; + } + + return MAL_FALSE; } -mal_result mal_device_reroute__wasapi(mal_device* pDevice) +mal_result mal_device_reroute__wasapi(mal_device* pDevice, mal_device_type deviceType) { - mal_atomic_exchange_32(&pDevice->wasapi.hasDefaultDeviceChanged, MAL_FALSE); + if (deviceType == mal_device_type_duplex) { + return MAL_INVALID_ARGS; + } + + if (deviceType == mal_device_type_playback) { + mal_atomic_exchange_32(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MAL_FALSE); + } + if (deviceType == mal_device_type_capture) { + mal_atomic_exchange_32(&pDevice->wasapi.hasDefaultCaptureDeviceChanged, MAL_FALSE); + } + #ifdef MAL_DEBUG_OUTPUT printf("=== CHANGING DEVICE ===\n"); #endif - mal_result result = mal_device_reinit__wasapi(pDevice); + mal_result result = mal_device_reinit__wasapi(pDevice, deviceType); if (result != MAL_SUCCESS) { return result; } @@ -7339,15 +7547,15 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames, } /* We may need to reroute the device. */ - if (mal_device_is_reroute_required__wasapi(pDevice)) { - result = mal_device_reroute__wasapi(pDevice); + if (mal_device_is_reroute_required__wasapi(pDevice, mal_device_type_capture)) { + result = mal_device_reroute__wasapi(pDevice, mal_device_type_capture); if (result != MAL_SUCCESS) { break; } } /* The device buffer has become available, so now we need to get a pointer to it. */ - result = mal_device__get_available_frames__wasapi(pDevice, &pDevice->wasapi.deviceBufferFramesCapacityPlayback); + result = mal_device__get_available_frames__wasapi(pDevice, (mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.deviceBufferFramesCapacityPlayback); if (result != MAL_SUCCESS) { break; } @@ -7435,15 +7643,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui } /* We may need to reroute the device. */ - if (mal_device_is_reroute_required__wasapi(pDevice)) { - result = mal_device_reroute__wasapi(pDevice); + if (mal_device_is_reroute_required__wasapi(pDevice, mal_device_type_capture)) { + result = mal_device_reroute__wasapi(pDevice, mal_device_type_capture); if (result != MAL_SUCCESS) { break; } } /* The device buffer has become available, so now we need to get a pointer to it. */ - result = mal_device__get_available_frames__wasapi(pDevice, &pDevice->wasapi.deviceBufferFramesCapacityCapture); + result = mal_device__get_available_frames__wasapi(pDevice, (mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.deviceBufferFramesCapacityCapture); if (result != MAL_SUCCESS) { break; }