mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-23 16:54:03 +02:00
WASAPI: Refactoring for the new backend architecture.
This commit is contained in:
+62
-269
@@ -22147,12 +22147,6 @@ typedef struct ma_device_state_wasapi
|
|||||||
ma_uint32 originalPeriods;
|
ma_uint32 originalPeriods;
|
||||||
ma_uint32 periodSizeInFramesPlayback;
|
ma_uint32 periodSizeInFramesPlayback;
|
||||||
ma_uint32 periodSizeInFramesCapture;
|
ma_uint32 periodSizeInFramesCapture;
|
||||||
void* pMappedBufferCapture;
|
|
||||||
ma_uint32 mappedBufferCaptureCap;
|
|
||||||
ma_uint32 mappedBufferCaptureLen;
|
|
||||||
void* pMappedBufferPlayback;
|
|
||||||
ma_uint32 mappedBufferPlaybackCap;
|
|
||||||
ma_uint32 mappedBufferPlaybackLen;
|
|
||||||
ma_atomic_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
|
ma_atomic_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
|
||||||
ma_atomic_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
|
ma_atomic_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
|
||||||
ma_uint32 loopbackProcessID;
|
ma_uint32 loopbackProcessID;
|
||||||
@@ -24557,23 +24551,9 @@ static void ma_device_uninit__wasapi(ma_device* pDevice)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (pDeviceStateWASAPI->pRenderClient) {
|
if (pDeviceStateWASAPI->pRenderClient) {
|
||||||
if (pDeviceStateWASAPI->pMappedBufferPlayback != NULL) {
|
|
||||||
ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDeviceStateWASAPI->pRenderClient, pDeviceStateWASAPI->mappedBufferPlaybackCap, 0);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferPlayback = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackLen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDeviceStateWASAPI->pRenderClient);
|
ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDeviceStateWASAPI->pRenderClient);
|
||||||
}
|
}
|
||||||
if (pDeviceStateWASAPI->pCaptureClient) {
|
if (pDeviceStateWASAPI->pCaptureClient) {
|
||||||
if (pDeviceStateWASAPI->pMappedBufferCapture != NULL) {
|
|
||||||
ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDeviceStateWASAPI->pCaptureClient, pDeviceStateWASAPI->mappedBufferCaptureCap);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferCapture = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDeviceStateWASAPI->pCaptureClient);
|
ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDeviceStateWASAPI->pCaptureClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24744,14 +24724,6 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) {
|
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) {
|
||||||
/* If we have a mapped buffer we need to release it. */
|
|
||||||
if (pDeviceStateWASAPI->pMappedBufferCapture != NULL) {
|
|
||||||
ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, pDeviceStateWASAPI->mappedBufferCaptureCap);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferCapture = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = ma_IAudioClient_Stop(pDeviceStateWASAPI->pAudioClientCapture);
|
hr = ma_IAudioClient_Stop(pDeviceStateWASAPI->pAudioClientCapture);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.");
|
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.");
|
||||||
@@ -24769,18 +24741,6 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
|
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
|
||||||
if (pDeviceStateWASAPI->pMappedBufferPlayback != NULL) {
|
|
||||||
ma_silence_pcm_frames(
|
|
||||||
ma_offset_pcm_frames_ptr(pDeviceStateWASAPI->pMappedBufferPlayback, pDeviceStateWASAPI->mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels),
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackCap - pDeviceStateWASAPI->mappedBufferPlaybackLen,
|
|
||||||
pDevice->playback.internalFormat, pDevice->playback.internalChannels
|
|
||||||
);
|
|
||||||
ma_IAudioRenderClient_ReleaseBuffer(pDeviceStateWASAPI->pRenderClient, pDeviceStateWASAPI->mappedBufferPlaybackCap, 0);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferPlayback = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackLen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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 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.
|
the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played.
|
||||||
@@ -24868,58 +24828,53 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice)
|
|||||||
#define MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS 5000
|
#define MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS 5000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead)
|
static ma_result ma_device_step__wasapi(ma_device* pDevice, ma_blocking_mode blockingMode)
|
||||||
{
|
{
|
||||||
ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice);
|
ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice);
|
||||||
ma_result result = MA_SUCCESS;
|
ma_device_type deviceType = ma_device_get_type(pDevice);
|
||||||
ma_uint32 totalFramesProcessed = 0;
|
HRESULT hr;
|
||||||
|
HANDLE hEvents[2];
|
||||||
|
DWORD eventCount;
|
||||||
|
DWORD waitResult;
|
||||||
|
DWORD timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS : 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
When reading, we need to get a buffer and process all of it before releasing it. Because the
|
With loopback mode, a timeout from WaitForMultipleObjects() almost certainly means there is simply
|
||||||
frame count (frameCount) can be different to the size of the buffer, we'll need to cache the
|
nothing being played through the speakers. We don't want to be stalling for ages in this case, so
|
||||||
pointer to the buffer.
|
for loopback we'll set a timeout of 10ms.
|
||||||
*/
|
*/
|
||||||
|
if (deviceType == ma_device_type_loopback && blockingMode == MA_BLOCKING_MODE_BLOCKING) {
|
||||||
|
timeout = 10;
|
||||||
|
}
|
||||||
|
|
||||||
/* Keep running until we've processed the requested number of frames. */
|
eventCount = 0;
|
||||||
while (ma_device_get_status(pDevice) == ma_device_status_started && totalFramesProcessed < frameCount) {
|
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) {
|
||||||
ma_uint32 framesRemaining = frameCount - totalFramesProcessed;
|
hEvents[eventCount++] = pDeviceStateWASAPI->hEventCapture;
|
||||||
|
}
|
||||||
|
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
|
||||||
|
hEvents[eventCount++] = pDeviceStateWASAPI->hEventPlayback;
|
||||||
|
}
|
||||||
|
|
||||||
/* If we have a mapped data buffer, consume that first. */
|
waitResult = WaitForMultipleObjects(eventCount, hEvents, FALSE, timeout);
|
||||||
if (pDeviceStateWASAPI->pMappedBufferCapture != NULL) {
|
if (waitResult >= WAIT_OBJECT_0 && waitResult <= WAIT_OBJECT_0 + eventCount-1) {
|
||||||
/* We have a cached data pointer so consume that before grabbing another one from WASAPI. */
|
HANDLE hEvent = hEvents[waitResult - WAIT_OBJECT_0];
|
||||||
ma_uint32 framesToProcessNow = framesRemaining;
|
|
||||||
if (framesToProcessNow > pDeviceStateWASAPI->mappedBufferCaptureLen) {
|
|
||||||
framesToProcessNow = pDeviceStateWASAPI->mappedBufferCaptureLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now just copy the data over to the output buffer. */
|
/* We may have been woken up due to the device stopping. Check for that. */
|
||||||
ma_copy_pcm_frames(
|
if (!ma_device_is_started(pDevice)) {
|
||||||
ma_offset_pcm_frames_ptr(pFrames, totalFramesProcessed, pDevice->capture.internalFormat, pDevice->capture.internalChannels),
|
return MA_DEVICE_NOT_STARTED;
|
||||||
ma_offset_pcm_frames_const_ptr(pDeviceStateWASAPI->pMappedBufferCapture, pDeviceStateWASAPI->mappedBufferCaptureCap - pDeviceStateWASAPI->mappedBufferCaptureLen, pDevice->capture.internalFormat, pDevice->capture.internalChannels),
|
}
|
||||||
framesToProcessNow,
|
|
||||||
pDevice->capture.internalFormat, pDevice->capture.internalChannels
|
|
||||||
);
|
|
||||||
|
|
||||||
totalFramesProcessed += framesToProcessNow;
|
/* TODO: Do rerouting here. */
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen -= framesToProcessNow;
|
|
||||||
|
|
||||||
/* If the data buffer has been fully consumed we need to release it. */
|
/* Capture. */
|
||||||
if (pDeviceStateWASAPI->mappedBufferCaptureLen == 0) {
|
if (hEvent == pDeviceStateWASAPI->hEventCapture) {
|
||||||
ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, pDeviceStateWASAPI->mappedBufferCaptureCap);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferCapture = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureCap = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* We don't have any cached data pointer, so grab another one. */
|
|
||||||
HRESULT hr;
|
|
||||||
DWORD flags = 0;
|
DWORD flags = 0;
|
||||||
|
ma_uint32 bufferSizeInFrames;
|
||||||
|
BYTE* pMappedBuffer;
|
||||||
|
|
||||||
/* First just ask WASAPI for a data buffer. If it's not available, we'll wait for more. */
|
/* First just ask WASAPI for a data buffer. If it's not available, we'll wait for more. */
|
||||||
hr = ma_IAudioCaptureClient_GetBuffer(pDeviceStateWASAPI->pCaptureClient, (BYTE**)&pDeviceStateWASAPI->pMappedBufferCapture, &pDeviceStateWASAPI->mappedBufferCaptureCap, &flags, NULL, NULL);
|
hr = ma_IAudioCaptureClient_GetBuffer(pDeviceStateWASAPI->pCaptureClient, &pMappedBuffer, &bufferSizeInFrames, &flags, NULL, NULL);
|
||||||
if (hr == S_OK) {
|
if (hr == S_OK) {
|
||||||
/* We got a data buffer. Continue to the next loop iteration which will then read from the mapped pointer. */
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen = pDeviceStateWASAPI->mappedBufferCaptureCap;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
There have been reports that indicate that at times the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY is reported for every
|
There have been reports that indicate that at times the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY is reported for every
|
||||||
call to IAudioCaptureClient_GetBuffer() above which results in spamming of the debug messages below. To partially
|
call to IAudioCaptureClient_GetBuffer() above which results in spamming of the debug messages below. To partially
|
||||||
@@ -24932,7 +24887,7 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui
|
|||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld", flags);
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld", flags);
|
||||||
|
|
||||||
if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
|
if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=%d", pDeviceStateWASAPI->mappedBufferCaptureCap);
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). Attempting recovery. bufferSizeInFrames=%d", bufferSizeInFrames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24940,189 +24895,26 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui
|
|||||||
|
|
||||||
/* Overrun detection. */
|
/* Overrun detection. */
|
||||||
if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
|
if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
|
||||||
/* Glitched. Probably due to an overrun. */
|
/* Glitched. Maybe try some recovery system here? */
|
||||||
|
|
||||||
/*
|
|
||||||
If we got an overrun it probably means we're straddling the end of the buffer. In normal capture
|
|
||||||
mode this is the fault of the client application because they're responsible for ensuring data is
|
|
||||||
processed fast enough. In duplex mode, however, the processing of audio is tied to the playback
|
|
||||||
device, so this can possibly be the result of a timing de-sync.
|
|
||||||
|
|
||||||
In capture mode we're not going to do any kind of recovery because the real fix is for the client
|
|
||||||
application to process faster. In duplex mode, we'll treat this as a desync and reset the buffers
|
|
||||||
to prevent a never-ending sequence of glitches due to straddling the end of the buffer.
|
|
||||||
*/
|
|
||||||
if (ma_device_get_type(pDevice) == ma_device_type_duplex) {
|
|
||||||
/*
|
|
||||||
Experiment:
|
|
||||||
|
|
||||||
If we empty out the *entire* buffer we may end up putting ourselves into an underrun position
|
|
||||||
which isn't really any better than the overrun we're probably in right now. Instead we'll just
|
|
||||||
empty out about half.
|
|
||||||
*/
|
|
||||||
ma_uint32 i;
|
|
||||||
ma_uint32 periodCount = (pDeviceStateWASAPI->actualBufferSizeInFramesCapture / pDeviceStateWASAPI->periodSizeInFramesCapture);
|
|
||||||
ma_uint32 iterationCount = periodCount / 2;
|
|
||||||
if ((periodCount % 2) > 0) {
|
|
||||||
iterationCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < iterationCount; i += 1) {
|
|
||||||
hr = ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, pDeviceStateWASAPI->mappedBufferCaptureCap);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: IAudioCaptureClient_ReleaseBuffer() failed with %ld.", hr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = 0;
|
|
||||||
hr = ma_IAudioCaptureClient_GetBuffer(pDeviceStateWASAPI->pCaptureClient, (BYTE**)&pDeviceStateWASAPI->pMappedBufferCapture, &pDeviceStateWASAPI->mappedBufferCaptureCap, &flags, NULL, NULL);
|
|
||||||
if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || FAILED(hr)) {
|
|
||||||
/*
|
|
||||||
The buffer has been completely emptied or an error occurred. In this case we'll need
|
|
||||||
to reset the state of the mapped buffer which will trigger the next iteration to get
|
|
||||||
a fresh buffer from WASAPI.
|
|
||||||
*/
|
|
||||||
pDeviceStateWASAPI->pMappedBufferCapture = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen = 0;
|
|
||||||
|
|
||||||
if (hr == MA_AUDCLNT_S_BUFFER_EMPTY) {
|
|
||||||
if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
|
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: Buffer emptied, and data discontinuity still reported.");
|
|
||||||
} else {
|
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: Buffer emptied.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: IAudioCaptureClient_GetBuffer() failed with %ld.", hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If at this point we have a valid buffer mapped, make sure the buffer length is set appropriately. */
|
|
||||||
if (pDeviceStateWASAPI->pMappedBufferCapture != NULL) {
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen = pDeviceStateWASAPI->mappedBufferCaptureCap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
ma_device_handle_backend_data_callback(pDevice, NULL, pMappedBuffer, bufferSizeInFrames);
|
||||||
|
ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, bufferSizeInFrames);
|
||||||
} else {
|
} else {
|
||||||
if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || hr == MA_AUDCLNT_E_BUFFER_ERROR) {
|
if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || hr == MA_AUDCLNT_E_BUFFER_ERROR) {
|
||||||
/*
|
/* No data available. */
|
||||||
No data is available. We need to wait for more. There's two situations to consider
|
|
||||||
here. The first is normal capture mode. If this times out it probably means the
|
|
||||||
microphone isn't delivering data for whatever reason. In this case we'll just
|
|
||||||
abort the read and return whatever we were able to get. The other situations is
|
|
||||||
loopback mode, in which case a timeout probably just means the nothing is playing
|
|
||||||
through the speakers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Experiment: Use a shorter timeout for loopback mode. */
|
|
||||||
DWORD timeoutInMilliseconds = MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS;
|
|
||||||
if (pDevice->type == ma_device_type_loopback) {
|
|
||||||
timeoutInMilliseconds = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WaitForSingleObject(pDeviceStateWASAPI->hEventCapture, timeoutInMilliseconds) != WAIT_OBJECT_0) {
|
|
||||||
if (pDevice->type == ma_device_type_loopback) {
|
|
||||||
continue; /* Keep waiting in loopback mode. */
|
|
||||||
} else {
|
|
||||||
result = MA_ERROR;
|
|
||||||
break; /* Wait failed. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* At this point we should be able to loop back to the start of the loop and try retrieving a data buffer again. */
|
|
||||||
} else {
|
} else {
|
||||||
/* An error occurred and we need to abort. */
|
/* An error occurred and we need to abort. */
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.", (int)hr);
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.", (int)hr);
|
||||||
result = ma_result_from_HRESULT(hr);
|
return ma_result_from_HRESULT(hr);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Playback. */
|
||||||
If we were unable to process the entire requested frame count, but we still have a mapped buffer,
|
if (hEvent == pDeviceStateWASAPI->hEventPlayback) {
|
||||||
there's a good chance either an error occurred or the device was stopped mid-read. In this case
|
|
||||||
we'll need to make sure the buffer is released.
|
|
||||||
*/
|
|
||||||
if (totalFramesProcessed < frameCount && pDeviceStateWASAPI->pMappedBufferCapture != NULL) {
|
|
||||||
ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, pDeviceStateWASAPI->mappedBufferCaptureCap);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferCapture = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferCaptureLen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pFramesRead != NULL) {
|
|
||||||
*pFramesRead = totalFramesProcessed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten)
|
|
||||||
{
|
|
||||||
ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice);
|
|
||||||
ma_result result = MA_SUCCESS;
|
|
||||||
ma_uint32 totalFramesProcessed = 0;
|
|
||||||
|
|
||||||
/* Keep writing to the device until it's stopped or we've consumed all of our input. */
|
|
||||||
while (ma_device_get_status(pDevice) == ma_device_status_started && totalFramesProcessed < frameCount) {
|
|
||||||
ma_uint32 framesRemaining = frameCount - totalFramesProcessed;
|
|
||||||
|
|
||||||
/*
|
|
||||||
We're going to do this in a similar way to capture. We'll first check if the cached data pointer
|
|
||||||
is valid, and if so, read from that. Otherwise We will call IAudioRenderClient_GetBuffer() with
|
|
||||||
a requested buffer size equal to our actual period size. If it returns AUDCLNT_E_BUFFER_TOO_LARGE
|
|
||||||
it means we need to wait for some data to become available.
|
|
||||||
*/
|
|
||||||
if (pDeviceStateWASAPI->pMappedBufferPlayback != NULL) {
|
|
||||||
/* We still have some space available in the mapped data buffer. Write to it. */
|
|
||||||
ma_uint32 framesToProcessNow = framesRemaining;
|
|
||||||
if (framesToProcessNow > (pDeviceStateWASAPI->mappedBufferPlaybackCap - pDeviceStateWASAPI->mappedBufferPlaybackLen)) {
|
|
||||||
framesToProcessNow = (pDeviceStateWASAPI->mappedBufferPlaybackCap - pDeviceStateWASAPI->mappedBufferPlaybackLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now just copy the data over to the output buffer. */
|
|
||||||
ma_copy_pcm_frames(
|
|
||||||
ma_offset_pcm_frames_ptr(pDeviceStateWASAPI->pMappedBufferPlayback, pDeviceStateWASAPI->mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels),
|
|
||||||
ma_offset_pcm_frames_const_ptr(pFrames, totalFramesProcessed, pDevice->playback.internalFormat, pDevice->playback.internalChannels),
|
|
||||||
framesToProcessNow,
|
|
||||||
pDevice->playback.internalFormat, pDevice->playback.internalChannels
|
|
||||||
);
|
|
||||||
|
|
||||||
totalFramesProcessed += framesToProcessNow;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackLen += framesToProcessNow;
|
|
||||||
|
|
||||||
/* If the data buffer has been fully consumed we need to release it. */
|
|
||||||
if (pDeviceStateWASAPI->mappedBufferPlaybackLen == pDeviceStateWASAPI->mappedBufferPlaybackCap) {
|
|
||||||
ma_IAudioRenderClient_ReleaseBuffer(pDeviceStateWASAPI->pRenderClient, pDeviceStateWASAPI->mappedBufferPlaybackCap, 0);
|
|
||||||
pDeviceStateWASAPI->pMappedBufferPlayback = NULL;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackCap = 0;
|
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackLen = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never
|
|
||||||
seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine
|
|
||||||
whether or not we need to wait for more data.
|
|
||||||
*/
|
|
||||||
if (pDevice->playback.shareMode == ma_share_mode_exclusive) {
|
|
||||||
if (WaitForSingleObject(pDeviceStateWASAPI->hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) {
|
|
||||||
result = MA_ERROR;
|
|
||||||
break; /* Wait failed. Probably timed out. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* We don't have a mapped data buffer so we'll need to get one. */
|
|
||||||
HRESULT hr;
|
|
||||||
ma_uint32 bufferSizeInFrames;
|
ma_uint32 bufferSizeInFrames;
|
||||||
|
BYTE* pMappedBuffer;
|
||||||
|
|
||||||
/* Special rules for exclusive mode. */
|
/* Special rules for exclusive mode. */
|
||||||
if (pDevice->playback.shareMode == ma_share_mode_exclusive) {
|
if (pDevice->playback.shareMode == ma_share_mode_exclusive) {
|
||||||
@@ -25131,33 +24923,36 @@ static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames
|
|||||||
bufferSizeInFrames = pDeviceStateWASAPI->periodSizeInFramesPlayback;
|
bufferSizeInFrames = pDeviceStateWASAPI->periodSizeInFramesPlayback;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = ma_IAudioRenderClient_GetBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, (BYTE**)&pDeviceStateWASAPI->pMappedBufferPlayback);
|
hr = ma_IAudioRenderClient_GetBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, &pMappedBuffer);
|
||||||
if (hr == S_OK) {
|
if (hr == S_OK) {
|
||||||
/* We have data available. */
|
/* We have data available. */
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackCap = bufferSizeInFrames;
|
ma_device_handle_backend_data_callback(pDevice, pMappedBuffer, NULL, bufferSizeInFrames);
|
||||||
pDeviceStateWASAPI->mappedBufferPlaybackLen = 0;
|
ma_IAudioRenderClient_ReleaseBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, 0);
|
||||||
} else {
|
} else {
|
||||||
if (hr == MA_AUDCLNT_E_BUFFER_TOO_LARGE || hr == MA_AUDCLNT_E_BUFFER_ERROR) {
|
if (hr == MA_AUDCLNT_E_BUFFER_TOO_LARGE || hr == MA_AUDCLNT_E_BUFFER_ERROR) {
|
||||||
/* Not enough data available. We need to wait for more. */
|
/* Not enough data available. Nothing to do. */
|
||||||
if (WaitForSingleObject((HANDLE)pDeviceStateWASAPI->hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) {
|
|
||||||
result = MA_ERROR;
|
|
||||||
break; /* Wait failed. Probably timed out. */
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/* Some error occurred. We'll need to abort. */
|
/* Some error occurred. We'll need to abort. */
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device. HRESULT = %d. Stopping device.", (int)hr);
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device. HRESULT = %d. Stopping device.", (int)hr);
|
||||||
result = ma_result_from_HRESULT(hr);
|
return ma_result_from_HRESULT(hr);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
/* Most likely timed out. */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pFramesWritten != NULL) {
|
return MA_SUCCESS;
|
||||||
*pFramesWritten = totalFramesProcessed;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
static void ma_device_loop__wasapi(ma_device* pDevice)
|
||||||
|
{
|
||||||
|
while (ma_device_is_started(pDevice)) {
|
||||||
|
ma_result result = ma_device_step__wasapi(pDevice, MA_BLOCKING_MODE_BLOCKING);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ma_device_wakeup__wasapi(ma_device* pDevice)
|
static void ma_device_wakeup__wasapi(ma_device* pDevice)
|
||||||
@@ -25176,8 +24971,6 @@ static void ma_device_wakeup__wasapi(ma_device* pDevice)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static ma_device_backend_vtable ma_gDeviceBackendVTable_WASAPI =
|
static ma_device_backend_vtable ma_gDeviceBackendVTable_WASAPI =
|
||||||
{
|
{
|
||||||
ma_backend_info__wasapi,
|
ma_backend_info__wasapi,
|
||||||
@@ -25188,9 +24981,9 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_WASAPI =
|
|||||||
ma_device_uninit__wasapi,
|
ma_device_uninit__wasapi,
|
||||||
ma_device_start__wasapi,
|
ma_device_start__wasapi,
|
||||||
ma_device_stop__wasapi,
|
ma_device_stop__wasapi,
|
||||||
ma_device_read__wasapi,
|
NULL,
|
||||||
ma_device_write__wasapi,
|
NULL,
|
||||||
NULL, /* onDeviceLoop */
|
ma_device_loop__wasapi,
|
||||||
ma_device_wakeup__wasapi
|
ma_device_wakeup__wasapi
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user