Commit fd3a7e25 authored by David Reid's avatar David Reid

WASAPI: Infrastructure work in preparation for full-duplex.

parent 6548f4e2
...@@ -1997,7 +1997,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -1997,7 +1997,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_stop_proc onStop; mal_stop_proc onStop;
void* pUserData; // Application defined data. void* pUserData; // Application defined data.
char name[256]; 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_mutex lock;
mal_event wakeupEvent; mal_event wakeupEvent;
mal_event startEvent; mal_event startEvent;
...@@ -2020,15 +2020,55 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -2020,15 +2020,55 @@ 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(). 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 ^^^ 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 union
{ {
#ifdef MAL_SUPPORT_WASAPI #ifdef MAL_SUPPORT_WASAPI
struct struct
{ {
/*IAudioClient**/ mal_ptr pAudioClient; /*IAudioClient**/ mal_ptr pAudioClientPlayback;
/*IAudioClient**/ mal_ptr pAudioClientCapture;
/*IAudioRenderClient**/ mal_ptr pRenderClient; /*IAudioRenderClient**/ mal_ptr pRenderClient;
/*IAudioCaptureClient**/ mal_ptr pCaptureClient; /*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; mal_IMMNotificationClient notificationClient;
/*HANDLE*/ mal_handle hEventPlayback; /* Used with the blocking API. Manual reset. Initialized to signaled. */ /*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 hEventCapture; /* Used with the blocking API. Manual reset. Initialized to unsignaled. */
...@@ -2038,8 +2078,10 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -2038,8 +2078,10 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_uint32 deviceBufferFramesRemainingCapture; mal_uint32 deviceBufferFramesRemainingCapture;
mal_uint32 deviceBufferFramesCapacityPlayback; mal_uint32 deviceBufferFramesCapacityPlayback;
mal_uint32 deviceBufferFramesCapacityCapture; 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 isStarted;
mal_bool32 hasDefaultDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */
} wasapi; } wasapi;
#endif #endif
#ifdef MAL_SUPPORT_DSOUND #ifdef MAL_SUPPORT_DSOUND
...@@ -6074,8 +6116,8 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I ...@@ -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. // 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 ) || 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 && dataFlow != mal_eCapture)) { ((pThis->pDevice->type == mal_device_type_capture || pThis->pDevice->type == mal_device_type_duplex) && dataFlow != mal_eCapture)) {
return S_OK; return S_OK;
} }
...@@ -6086,9 +6128,14 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I ...@@ -6086,9 +6128,14 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I
return S_OK; 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. // 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 #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. 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) ...@@ -6611,8 +6658,12 @@ void mal_device_uninit__wasapi(mal_device* pDevice)
if (pDevice->wasapi.pCaptureClient) { if (pDevice->wasapi.pCaptureClient) {
mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)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) { if (pDevice->wasapi.hEventPlayback) {
...@@ -6653,14 +6704,14 @@ typedef struct ...@@ -6653,14 +6704,14 @@ typedef struct
char deviceName[256]; char deviceName[256];
} mal_device_init_internal_data__wasapi; } 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; (void)pContext;
mal_assert(pContext != NULL); mal_assert(pContext != NULL);
mal_assert(pData != 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) { if (deviceType == mal_device_type_duplex) {
return MAL_INVALID_ARGS; return MAL_INVALID_ARGS;
} }
...@@ -6677,55 +6728,9 @@ mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_ty ...@@ -6677,55 +6728,9 @@ mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_ty
MAL_REFERENCE_TIME bufferDurationInMicroseconds; MAL_REFERENCE_TIME bufferDurationInMicroseconds;
mal_bool32 wasInitializedUsingIAudioClient3 = MAL_FALSE; mal_bool32 wasInitializedUsingIAudioClient3 = MAL_FALSE;
WAVEFORMATEXTENSIBLE wf; WAVEFORMATEXTENSIBLE wf;
//mal_WASAPIDeviceInterface* pDeviceInterfacePlayback = NULL; mal_WASAPIDeviceInterface* pDeviceInterface = NULL;
//mal_WASAPIDeviceInterface* pDeviceInterfaceCapture = NULL;
mal_WASAPIDeviceInterface* pDeviceInterface = NULL; // TEMP: Will be split between playback and capture when full-duplex support is implemented.
#if 0 result = mal_context_get_IAudioClient__wasapi(pContext, deviceType, pDeviceID, &pData->pAudioClient, &pDeviceInterface);
/*
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);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
goto done; goto done;
} }
...@@ -7002,103 +7007,280 @@ done: ...@@ -7002,103 +7007,280 @@ 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; mal_device_init_internal_data__wasapi data;
data.formatIn = pDevice->format; data.formatIn = pDevice->format;
data.channelsIn = pDevice->channels; data.channelsIn = pDevice->channels;
data.sampleRateIn = pDevice->sampleRate; data.sampleRateIn = pDevice->sampleRate;
mal_copy_memory(data.channelMapIn, pDevice->channelMap, sizeof(pDevice->channelMap)); mal_copy_memory(data.channelMapIn, pDevice->channelMap, sizeof(pDevice->channelMap));
data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames; data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames;
data.bufferSizeInMillisecondsIn = pDevice->bufferSizeInMilliseconds;
data.periodsIn = pDevice->periods;
data.usingDefaultFormat = pDevice->usingDefaultFormat; data.usingDefaultFormat = pDevice->usingDefaultFormat;
data.usingDefaultChannels = pDevice->usingDefaultChannels; data.usingDefaultChannels = pDevice->usingDefaultChannels;
data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate;
data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap;
data.shareMode = pDevice->initConfig.shareMode; data.shareMode = pDevice->initConfig.shareMode;
mal_result result = mal_device_init_internal__wasapi(pDevice->pContext, pDevice->type, NULL, NULL, &data); data.bufferSizeInMillisecondsIn = pDevice->bufferSizeInMilliseconds;
data.periodsIn = pDevice->periods;
mal_result result = mal_device_init_internal__wasapi(pDevice->pContext, deviceType, NULL, &data);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
return result; 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. // 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) { if (deviceType == mal_device_type_capture) {
mal_IAudioRenderClient_Release((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient);
pDevice->wasapi.pRenderClient = NULL;
}
if (pDevice->wasapi.pCaptureClient) { if (pDevice->wasapi.pCaptureClient) {
mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient);
pDevice->wasapi.pCaptureClient = NULL; pDevice->wasapi.pCaptureClient = NULL;
} }
if (pDevice->wasapi.pAudioClient) {
mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClient); if (pDevice->wasapi.pAudioClientCapture) {
pDevice->wasapi.pAudioClient = NULL; mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture);
pDevice->wasapi.pAudioClientCapture = NULL;
} }
pDevice->wasapi.pAudioClient = data.pAudioClient; pDevice->wasapi.pAudioClientCapture = data.pAudioClient;
pDevice->wasapi.pRenderClient = data.pRenderClient;
pDevice->wasapi.pCaptureClient = data.pCaptureClient; pDevice->wasapi.pCaptureClient = data.pCaptureClient;
mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE);
pDevice->internalFormat = data.formatOut; pDevice->capture.internalFormat = data.formatOut;
pDevice->internalChannels = data.channelsOut; pDevice->capture.internalChannels = data.channelsOut;
pDevice->internalSampleRate = data.sampleRateOut; pDevice->capture.internalSampleRate = data.sampleRateOut;
mal_copy_memory(pDevice->internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); mal_copy_memory(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut));
pDevice->bufferSizeInFrames = data.bufferSizeInFramesOut; pDevice->capture.internalBufferSizeInFrames = data.bufferSizeInFramesOut;
pDevice->periods = data.periodsOut; pDevice->capture.internalPeriods = data.periodsOut;
mal_strcpy_s(pDevice->name, sizeof(pDevice->name), data.deviceName); mal_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName);
if (pDevice->type == mal_device_type_playback) { mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture);
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); 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);
}
mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE);
return MAL_SUCCESS; return MAL_SUCCESS;
} }
mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_config* pConfig, mal_device* pDevice) mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_config* pConfig, mal_device* pDevice)
{ {
mal_result result = MAL_SUCCESS;
const char* errorMsg = "";
(void)pContext; (void)pContext;
mal_assert(pContext != NULL);
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
mal_zero_object(&pDevice->wasapi);
mal_result result = MAL_SUCCESS; mal_zero_object(&pDevice->wasapi);
const char* errorMsg = "";
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
mal_device_init_internal_data__wasapi data; mal_device_init_internal_data__wasapi data;
data.formatIn = pConfig->format; data.formatIn = pConfig->format;
data.channelsIn = pConfig->channels; data.channelsIn = pConfig->channels;
data.sampleRateIn = pConfig->sampleRate; data.sampleRateIn = pConfig->sampleRate;
mal_copy_memory(data.channelMapIn, pConfig->channelMap, sizeof(pConfig->channelMap)); 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.bufferSizeInFramesIn = pConfig->bufferSizeInFrames;
data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds; data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds;
data.periodsIn = pConfig->periods; 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);
}
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.usingDefaultFormat = pDevice->usingDefaultFormat;
data.usingDefaultChannels = pDevice->usingDefaultChannels; data.usingDefaultChannels = pDevice->usingDefaultChannels;
data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate;
data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap;
data.shareMode = pConfig->shareMode; data.shareMode = pConfig->shareMode;
result = mal_device_init_internal__wasapi(pDevice->pContext, pConfig->deviceType, pConfig->pPlaybackDeviceID, pConfig->pCaptureDeviceID, &data);
// 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 (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; return result;
} }
pDevice->wasapi.pAudioClient = data.pAudioClient; pDevice->wasapi.pAudioClientPlayback = data.pAudioClient;
pDevice->wasapi.pRenderClient = data.pRenderClient; pDevice->wasapi.pRenderClient = data.pRenderClient;
pDevice->wasapi.pCaptureClient = data.pCaptureClient;
mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE);
pDevice->internalFormat = data.formatOut; pDevice->playback.internalFormat = data.formatOut;
pDevice->internalChannels = data.channelsOut; pDevice->playback.internalChannels = data.channelsOut;
pDevice->internalSampleRate = data.sampleRateOut; pDevice->playback.internalSampleRate = data.sampleRateOut;
mal_copy_memory(pDevice->internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); mal_copy_memory(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut));
pDevice->bufferSizeInFrames = data.bufferSizeInFramesOut; pDevice->playback.internalBufferSizeInFrames = data.bufferSizeInFramesOut;
pDevice->periods = data.periodsOut; pDevice->playback.internalPeriods = data.periodsOut;
mal_strcpy_s(pDevice->name, sizeof(pDevice->name), data.deviceName); 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);
}
...@@ -7125,38 +7307,7 @@ mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_confi ...@@ -7125,38 +7307,7 @@ mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_confi
} }
#endif #endif
/* Events. */ mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE);
/*
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);
}
result = MAL_SUCCESS; result = MAL_SUCCESS;
...@@ -7174,9 +7325,24 @@ mal_result mal_device_start__wasapi(mal_device* pDevice) ...@@ -7174,9 +7325,24 @@ mal_result mal_device_start__wasapi(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
HRESULT hr = mal_IAudioClient_Start((mal_IAudioClient*)pDevice->wasapi.pAudioClient); 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 (FAILED(hr)) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal device.", MAL_FAILED_TO_START_BACKEND_DEVICE); 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); mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_TRUE);
...@@ -7187,21 +7353,40 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice) ...@@ -7187,21 +7353,40 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (pDevice->wasapi.pAudioClient == NULL) { 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);
}
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
if (pDevice->wasapi.pAudioClientPlayback == NULL) {
return MAL_DEVICE_NOT_INITIALIZED; return MAL_DEVICE_NOT_INITIALIZED;
} }
/* TODO: Wait until every sample that was written by the callback has been processed. */ /* TODO: Wait until every sample that was written by the callback has been processed. */
HRESULT hr = mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClient); HRESULT hr = mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);
if (FAILED(hr)) { if (FAILED(hr)) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); 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. */ /* The audio client needs to be reset otherwise restarting will fail. */
hr = mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClient); hr = mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);
if (FAILED(hr)) { if (FAILED(hr)) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); 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); mal_atomic_exchange_32(&pDevice->wasapi.isStarted, MAL_FALSE);
...@@ -7209,15 +7394,19 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice) ...@@ -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(pDevice != NULL);
mal_assert(pFrameCount != NULL); mal_assert(pFrameCount != NULL);
*pFrameCount = 0; *pFrameCount = 0;
if ((mal_ptr)pAudioClient != pDevice->wasapi.pAudioClientPlayback && (mal_ptr)pAudioClient != pDevice->wasapi.pAudioClientCapture) {
return MAL_INVALID_OPERATION;
}
mal_uint32 paddingFramesCount; mal_uint32 paddingFramesCount;
HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount); HRESULT hr = mal_IAudioClient_GetCurrentPadding(pAudioClient, &paddingFramesCount);
if (FAILED(hr)) { if (FAILED(hr)) {
return MAL_DEVICE_UNAVAILABLE; return MAL_DEVICE_UNAVAILABLE;
} }
...@@ -7226,7 +7415,7 @@ mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uin ...@@ -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) { if (pDevice->initConfig.shareMode == mal_share_mode_exclusive) {
*pFrameCount = paddingFramesCount; *pFrameCount = paddingFramesCount;
} else { } else {
if (pDevice->type == mal_device_type_playback) { if ((mal_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) {
*pFrameCount = pDevice->bufferSizeInFrames - paddingFramesCount; *pFrameCount = pDevice->bufferSizeInFrames - paddingFramesCount;
} else { } else {
*pFrameCount = paddingFramesCount; *pFrameCount = paddingFramesCount;
...@@ -7236,21 +7425,40 @@ mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uin ...@@ -7236,21 +7425,40 @@ mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_uin
return MAL_SUCCESS; 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); 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 #ifdef MAL_DEBUG_OUTPUT
printf("=== CHANGING DEVICE ===\n"); printf("=== CHANGING DEVICE ===\n");
#endif #endif
mal_result result = mal_device_reinit__wasapi(pDevice); mal_result result = mal_device_reinit__wasapi(pDevice, deviceType);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
return result; return result;
} }
...@@ -7339,15 +7547,15 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames, ...@@ -7339,15 +7547,15 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames,
} }
/* We may need to reroute the device. */ /* We may need to reroute the device. */
if (mal_device_is_reroute_required__wasapi(pDevice)) { if (mal_device_is_reroute_required__wasapi(pDevice, mal_device_type_capture)) {
result = mal_device_reroute__wasapi(pDevice); result = mal_device_reroute__wasapi(pDevice, mal_device_type_capture);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
break; break;
} }
} }
/* The device buffer has become available, so now we need to get a pointer to it. */ /* 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) { if (result != MAL_SUCCESS) {
break; break;
} }
...@@ -7435,15 +7643,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui ...@@ -7435,15 +7643,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui
} }
/* We may need to reroute the device. */ /* We may need to reroute the device. */
if (mal_device_is_reroute_required__wasapi(pDevice)) { if (mal_device_is_reroute_required__wasapi(pDevice, mal_device_type_capture)) {
result = mal_device_reroute__wasapi(pDevice); result = mal_device_reroute__wasapi(pDevice, mal_device_type_capture);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
break; break;
} }
} }
/* The device buffer has become available, so now we need to get a pointer to it. */ /* 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) { if (result != MAL_SUCCESS) {
break; break;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment