Commit b0a1c925 authored by David Reid's avatar David Reid

WASAPI: Experiment with some improvements to full-duplex.

parent 595dd1fb
...@@ -3264,7 +3264,7 @@ static MAL_INLINE mal_bool32 mal_is_big_endian() ...@@ -3264,7 +3264,7 @@ static MAL_INLINE mal_bool32 mal_is_big_endian()
// Default periods when none is specified in mal_device_init(). More periods means more work on the CPU. // Default periods when none is specified in mal_device_init(). More periods means more work on the CPU.
#ifndef MAL_DEFAULT_PERIODS #ifndef MAL_DEFAULT_PERIODS
#define MAL_DEFAULT_PERIODS 2 #define MAL_DEFAULT_PERIODS 3
#endif #endif
// The base buffer size in milliseconds for low latency mode. // The base buffer size in milliseconds for low latency mode.
...@@ -4665,7 +4665,7 @@ void mal_log(mal_context* pContext, mal_device* pDevice, mal_uint32 logLevel, co ...@@ -4665,7 +4665,7 @@ void mal_log(mal_context* pContext, mal_device* pDevice, mal_uint32 logLevel, co
if (logLevel <= MAL_LOG_LEVEL) { if (logLevel <= MAL_LOG_LEVEL) {
#if defined(MAL_DEBUG_OUTPUT) #if defined(MAL_DEBUG_OUTPUT)
if (logLevel <= MAL_LOG_LEVEL) { if (logLevel <= MAL_LOG_LEVEL) {
printf("%s: %s", mal_log_level_to_string(logLevel), message); printf("%s: %s\n", mal_log_level_to_string(logLevel), message);
} }
#endif #endif
...@@ -7138,7 +7138,7 @@ mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_ty ...@@ -7138,7 +7138,7 @@ mal_result mal_device_init_internal__wasapi(mal_context* pContext, mal_device_ty
// Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode. // Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode.
if (shareMode == MAL_AUDCLNT_SHAREMODE_EXCLUSIVE) { if (shareMode == MAL_AUDCLNT_SHAREMODE_EXCLUSIVE) {
MAL_REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; MAL_REFERENCE_TIME bufferDuration = (bufferDurationInMicroseconds / pData->periodsOut) * 10;
// If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing // If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing
// it and trying it again. // it and trying it again.
...@@ -7672,6 +7672,9 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice) ...@@ -7672,6 +7672,9 @@ mal_result mal_device_stop__wasapi(mal_device* pDevice)
/* 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.pAudioClientCapture); hr = mal_IAudioClient_Reset((mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture);
if (FAILED(hr)) { if (FAILED(hr)) {
#ifdef MAL_DEBUG_OUTPUT
printf("IAudioClient_Reset (Capture) Returned %d\n", (int)hr);
#endif
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE);
} }
} }
...@@ -7779,6 +7782,7 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames, ...@@ -7779,6 +7782,7 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames,
{ {
mal_result result = MAL_SUCCESS; mal_result result = MAL_SUCCESS;
mal_bool32 wasStartedOnEntry; mal_bool32 wasStartedOnEntry;
mal_bool32 exitOuterLoop = MAL_FALSE;
mal_uint32 totalFramesWritten; mal_uint32 totalFramesWritten;
HRESULT hr; HRESULT hr;
DWORD waitResult; DWORD waitResult;
...@@ -7840,42 +7844,73 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames, ...@@ -7840,42 +7844,73 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames,
break; break;
} }
/* Wait for data to become available. Exclusive mode is slightly different. We always wait and then use the exact frame count returned by GetCurrentPadding(). */
/* Wait for data. */ for (;;) {
waitResult = WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE); if (pDevice->playback.shareMode == mal_share_mode_exclusive) {
if (waitResult == WAIT_FAILED) { waitResult = WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE);
result = MAL_ERROR; if (waitResult == WAIT_FAILED) {
break; /* An error occurred while waiting for the event. */ result = MAL_ERROR;
} exitOuterLoop = MAL_TRUE;
break; /* An error occurred while waiting for the event. */
}
}
/* If the device has been stopped don't continue. */ /* If the device has been stopped don't continue. */
if (!pDevice->wasapi.isStarted && wasStartedOnEntry) { if (!pDevice->wasapi.isStarted && wasStartedOnEntry) {
break; exitOuterLoop = MAL_TRUE;
} break;
}
/* We may need to reroute the device. */ /* We may need to reroute the device. */
if (mal_device_is_reroute_required__wasapi(pDevice, mal_device_type_playback)) { if (mal_device_is_reroute_required__wasapi(pDevice, mal_device_type_playback)) {
result = mal_device_reroute__wasapi(pDevice, mal_device_type_playback); result = mal_device_reroute__wasapi(pDevice, mal_device_type_playback);
if (result != MAL_SUCCESS) {
exitOuterLoop = MAL_TRUE;
break;
}
}
/*
Check what's available. If there's not enough data available we need to wait. How much data must be available depends on whether or not the
device is in playback-only mode or duplex mode. In playback-only mode we only care about a period being available. In duplex mode we want at
least a whole period in the buffer ready for playback in addition to a whole period being available.
*/
result = mal_device__get_available_frames__wasapi(pDevice, (mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.deviceBufferFramesCapacityPlayback);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
exitOuterLoop = MAL_TRUE;
break; 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, (mal_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.deviceBufferFramesCapacityPlayback);
if (result != MAL_SUCCESS) {
break;
}
//printf("TRACE 1: capacity playback: %d, %d\n", pDevice->wasapi.deviceBufferFramesCapacityPlayback, pDevice->wasapi.periodSizeInFramesPlayback); /* In exclusive mode, the frame count needs to exactly match the value returned by GetCurrentPadding(). */
if (pDevice->playback.shareMode == mal_share_mode_exclusive && pDevice->wasapi.deviceBufferFramesCapacityPlayback > 0) {
break;
}
/* In exclusive mode, the frame count needs to exactly match the value returned by GetCurrentPadding(). */ mal_uint32 minAvailableFrames = pDevice->wasapi.periodSizeInFramesPlayback;
if (pDevice->playback.shareMode != mal_share_mode_exclusive) { if (pDevice->type == mal_device_type_duplex) {
if (pDevice->type != mal_device_type_duplex) { /* Full-duplex mode should also require the whole buffer be mapped at a time. */ if (!pDevice->wasapi.isStarted) {
if (pDevice->wasapi.deviceBufferFramesCapacityPlayback > pDevice->wasapi.periodSizeInFramesPlayback) { minAvailableFrames = pDevice->wasapi.periodSizeInFramesPlayback*2;
pDevice->wasapi.deviceBufferFramesCapacityPlayback = pDevice->wasapi.periodSizeInFramesPlayback;
} }
} }
if (pDevice->wasapi.deviceBufferFramesCapacityPlayback >= minAvailableFrames) {
pDevice->wasapi.deviceBufferFramesCapacityPlayback = minAvailableFrames;
break;
}
//printf("TRACE: WAITING\n");
/* Getting here means we need to wait for more data. */
waitResult = WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE);
if (waitResult == WAIT_FAILED) {
result = MAL_ERROR;
exitOuterLoop = MAL_TRUE;
break; /* An error occurred while waiting for the event. */
}
}
if (exitOuterLoop) {
break;
} }
hr = mal_IAudioRenderClient_GetBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.deviceBufferFramesCapacityPlayback, (BYTE**)&pDevice->wasapi.pDeviceBufferPlayback); hr = mal_IAudioRenderClient_GetBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.deviceBufferFramesCapacityPlayback, (BYTE**)&pDevice->wasapi.pDeviceBufferPlayback);
...@@ -7886,6 +7921,7 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames, ...@@ -7886,6 +7921,7 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, const void* pPCMFrames,
} }
pDevice->wasapi.deviceBufferFramesRemainingPlayback = pDevice->wasapi.deviceBufferFramesCapacityPlayback; pDevice->wasapi.deviceBufferFramesRemainingPlayback = pDevice->wasapi.deviceBufferFramesCapacityPlayback;
//printf("TRACE 1: Playback: %d, %d\n", pDevice->wasapi.deviceBufferFramesCapacityPlayback, pDevice->wasapi.periodSizeInFramesPlayback);
} }
return result; return result;
...@@ -7948,12 +7984,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui ...@@ -7948,12 +7984,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui
mal_uint32 framesAvailable; mal_uint32 framesAvailable;
result = mal_device__get_available_frames__wasapi(pDevice, (mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailable); result = mal_device__get_available_frames__wasapi(pDevice, (mal_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailable);
if (result == MAL_SUCCESS) { if (result == MAL_SUCCESS) {
if (framesAvailable > pDevice->wasapi.periodSizeInFramesCapture) { if (framesAvailable > (pDevice->wasapi.periodSizeInFramesCapture*(pDevice->capture.internalPeriods-1))) {
mal_uint32 framesToDiscard = framesAvailable;// - pDevice->wasapi.periodSizeInFramesCapture; mal_uint32 framesToDiscard = framesAvailable - pDevice->wasapi.periodSizeInFramesCapture;
if (framesToDiscard > 0) { if (framesToDiscard > 0) {
BYTE* pUnused; BYTE* pUnused;
hr = mal_IAudioCaptureClient_GetBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, &pUnused, &framesToDiscard, &flags, NULL, NULL); hr = mal_IAudioCaptureClient_GetBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, &pUnused, &framesToDiscard, &flags, NULL, NULL);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
#ifdef MAL_DEBUG_OUTPUT
printf("[WASAPI] (Duplex/Capture) Discarding %d frames...\n", framesToDiscard);
#endif
mal_IAudioCaptureClient_ReleaseBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, framesToDiscard); mal_IAudioCaptureClient_ReleaseBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, framesToDiscard);
} }
} }
...@@ -7970,7 +8009,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui ...@@ -7970,7 +8009,15 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, void* pPCMFrames, mal_ui
} }
/* Wait for data. */ /* Wait for data. */
waitResult = WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE); if (pDevice->type == mal_device_type_capture) {
waitResult = WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE);
} else {
if (pDevice->playback.shareMode == mal_share_mode_shared) {
waitResult = WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE);
} else {
waitResult = WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE); /* Wait on the exclusive-mode playback event instead. */
}
}
if (waitResult == WAIT_FAILED) { if (waitResult == WAIT_FAILED) {
result = MAL_ERROR; result = MAL_ERROR;
break; /* An error occurred while waiting for the event. */ break; /* An error occurred while waiting for the event. */
...@@ -22576,6 +22623,15 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf ...@@ -22576,6 +22623,15 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf
pDevice->usingDefaultPeriods = MAL_TRUE; pDevice->usingDefaultPeriods = MAL_TRUE;
} }
/*
Must have at least 3 periods for full-duplex mode. The idea is that the playback and capture positions hang out in the middle period, with the surrounding
periods acting as a buffer in case the capture and playback devices get's slightly out of sync.
*/
if (config.deviceType == mal_device_type_duplex && config.periods < 3) {
config.periods = 3;
}
pDevice->type = config.deviceType; pDevice->type = config.deviceType;
pDevice->sampleRate = config.sampleRate; pDevice->sampleRate = config.sampleRate;
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