mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
DirectSound: Work on the blocking read and writes.
This commit is contained in:
@@ -1967,8 +1967,16 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
|
||||
/*LPDIRECTSOUNDNOTIFY*/ mal_ptr pNotify;
|
||||
/*HANDLE*/ mal_handle pNotifyEvents[MAL_MAX_PERIODS_DSOUND]; // One event handle for each period.
|
||||
/*HANDLE*/ mal_handle hStopEvent;
|
||||
mal_uint32 iNextPeriod; /* Circular. Keeps track of the next period that's due for an update. */
|
||||
void* pMappedBufferPlayback;
|
||||
void* pMappedBufferCapture;
|
||||
mal_uint32 mappedBufferFramesRemainingPlayback;
|
||||
mal_uint32 mappedBufferFramesRemainingCapture;
|
||||
mal_uint32 mappedBufferFramesCapacityPlayback;
|
||||
mal_uint32 mappedBufferFramesCapacityCapture;
|
||||
mal_uint32 lastProcessedFrame; // This is circular.
|
||||
mal_bool32 breakFromMainLoop;
|
||||
mal_bool32 isStarted;
|
||||
mal_bool32 breakFromMainLoop; /* TODO: Delete me once the new main loop is finialized. */
|
||||
} dsound;
|
||||
#endif
|
||||
#ifdef MAL_SUPPORT_WINMM
|
||||
@@ -7050,7 +7058,7 @@ mal_result mal_device_reroute__wasapi(mal_device* pDevice)
|
||||
|
||||
mal_result mal_device_write__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount, const void* pPCMFrames, mal_uint32* pPCMFramesWritten)
|
||||
{
|
||||
mal_result result;
|
||||
mal_result result = MAL_SUCCESS;
|
||||
mal_bool32 wasStartedOnEntry;
|
||||
mal_uint32 totalPCMFramesWritten;
|
||||
HRESULT hr;
|
||||
@@ -7058,6 +7066,7 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCoun
|
||||
HANDLE hEvents[1];
|
||||
hEvents[0] = pDevice->wasapi.hEventPlayback;
|
||||
|
||||
*pPCMFramesWritten = 0;
|
||||
wasStartedOnEntry = pDevice->wasapi.isStarted;
|
||||
|
||||
/* Try to write every frame. */
|
||||
@@ -7080,13 +7089,8 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCoun
|
||||
totalPCMFramesWritten += framesToCopy;
|
||||
}
|
||||
|
||||
mal_assert(totalPCMFramesWritten <= pcmFrameCount);
|
||||
if (totalPCMFramesWritten == pcmFrameCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Getting here means we've consumed the device buffer and need to wait for more to become available. */
|
||||
if (pDevice->wasapi.deviceBufferFramesCapacityPlayback > 0) {
|
||||
if (pDevice->wasapi.deviceBufferFramesCapacityPlayback > 0 && pDevice->wasapi.deviceBufferFramesRemainingPlayback == 0) {
|
||||
hr = mal_IAudioRenderClient_ReleaseBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.deviceBufferFramesCapacityPlayback, 0);
|
||||
pDevice->wasapi.pDeviceBufferPlayback = NULL;
|
||||
pDevice->wasapi.deviceBufferFramesRemainingPlayback = 0;
|
||||
@@ -7115,6 +7119,11 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCoun
|
||||
}
|
||||
}
|
||||
|
||||
mal_assert(totalPCMFramesWritten <= pcmFrameCount);
|
||||
if (totalPCMFramesWritten == pcmFrameCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* Wait for data. */
|
||||
waitResult = WaitForMultipleObjects(mal_countof(hEvents), hEvents, FALSE, INFINITE);
|
||||
@@ -7153,12 +7162,12 @@ mal_result mal_device_write__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCoun
|
||||
}
|
||||
|
||||
*pPCMFramesWritten = totalPCMFramesWritten;
|
||||
return MAL_SUCCESS;
|
||||
return result;
|
||||
}
|
||||
|
||||
mal_result mal_device_read__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount, void* pPCMFrames, mal_uint32* pPCMFramesRead)
|
||||
{
|
||||
mal_result result;
|
||||
mal_result result = MAL_SUCCESS;
|
||||
mal_uint32 totalPCMFramesRead;
|
||||
HRESULT hr;
|
||||
DWORD waitResult;
|
||||
@@ -7194,13 +7203,8 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount
|
||||
totalPCMFramesRead += framesToCopy;
|
||||
}
|
||||
|
||||
mal_assert(totalPCMFramesRead <= pcmFrameCount);
|
||||
if (totalPCMFramesRead == pcmFrameCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Getting here means we've consumed the device buffer and need to wait for more to become available. */
|
||||
if (pDevice->wasapi.deviceBufferFramesCapacityCapture > 0) {
|
||||
if (pDevice->wasapi.deviceBufferFramesCapacityCapture > 0 && pDevice->wasapi.deviceBufferFramesRemainingCapture == 0) {
|
||||
hr = mal_IAudioCaptureClient_ReleaseBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.deviceBufferFramesCapacityCapture);
|
||||
pDevice->wasapi.pDeviceBufferCapture = NULL;
|
||||
pDevice->wasapi.deviceBufferFramesRemainingCapture = 0;
|
||||
@@ -7215,6 +7219,11 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount
|
||||
ResetEvent(pDevice->wasapi.hEventCapture);
|
||||
}
|
||||
|
||||
mal_assert(totalPCMFramesRead <= pcmFrameCount);
|
||||
if (totalPCMFramesRead == pcmFrameCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wait for data. */
|
||||
waitResult = WaitForMultipleObjects(mal_countof(hEvents), hEvents, FALSE, INFINITE);
|
||||
if (waitResult == WAIT_FAILED) {
|
||||
@@ -7252,7 +7261,7 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount
|
||||
}
|
||||
|
||||
*pPCMFramesRead = totalPCMFramesRead;
|
||||
return MAL_SUCCESS;
|
||||
return result;
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -8317,6 +8326,10 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
mal_assert(pDevice != NULL);
|
||||
mal_zero_object(&pDevice->dsound);
|
||||
|
||||
if (pDevice->periods > MAL_MAX_PERIODS_DSOUND) {
|
||||
pDevice->periods = MAL_MAX_PERIODS_DSOUND;
|
||||
}
|
||||
|
||||
// Check that we have a valid format.
|
||||
GUID subformat;
|
||||
switch (pConfig->format)
|
||||
@@ -8352,8 +8365,6 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
wf.dwChannelMask = mal_channel_map_to_channel_mask__win32(pConfig->channelMap, pConfig->channels);
|
||||
wf.SubFormat = subformat;
|
||||
|
||||
DWORD bufferSizeInBytes = 0;
|
||||
|
||||
// Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :(
|
||||
if (type == mal_device_type_playback) {
|
||||
mal_result result = mal_context_create_IDirectSound__dsound(pContext, pConfig->shareMode, pDeviceID, (mal_IDirectSound**)&pDevice->dsound.pPlayback);
|
||||
@@ -8443,9 +8454,8 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, pDevice->internalSampleRate);
|
||||
}
|
||||
|
||||
bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat);
|
||||
|
||||
|
||||
/* The size of the buffer must be a clean multiple of the period count. */
|
||||
pDevice->bufferSizeInFrames = (mal_uint32)(pDevice->bufferSizeInFrames/pDevice->periods) * pDevice->periods;
|
||||
|
||||
// Meaning of dwFlags (from MSDN):
|
||||
//
|
||||
@@ -8464,7 +8474,7 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
mal_zero_object(&descDS);
|
||||
descDS.dwSize = sizeof(descDS);
|
||||
descDS.dwFlags = MAL_DSBCAPS_CTRLPOSITIONNOTIFY | MAL_DSBCAPS_GLOBALFOCUS | MAL_DSBCAPS_GETCURRENTPOSITION2;
|
||||
descDS.dwBufferBytes = bufferSizeInBytes;
|
||||
descDS.dwBufferBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
|
||||
if (FAILED(mal_IDirectSound_CreateSoundBuffer((mal_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (mal_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL))) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
@@ -8479,9 +8489,9 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
} else {
|
||||
// The default buffer size is treated slightly differently for DirectSound which, for some reason, seems to
|
||||
// have worse latency with capture than playback (sometimes _much_ worse).
|
||||
if (pDevice->usingDefaultBufferSize) {
|
||||
pDevice->bufferSizeInFrames *= 2; // <-- Might need to fiddle with this to find a more ideal value. May even be able to just add a fixed amount rather than scaling.
|
||||
}
|
||||
//if (pDevice->usingDefaultBufferSize) {
|
||||
// pDevice->bufferSizeInFrames *= 2; // <-- Might need to fiddle with this to find a more ideal value. May even be able to just add a fixed amount rather than scaling.
|
||||
//}
|
||||
|
||||
mal_result result = mal_context_create_IDirectSoundCapture__dsound(pContext, pConfig->shareMode, pDeviceID, (mal_IDirectSoundCapture**)&pDevice->dsound.pCapture);
|
||||
if (result != MAL_SUCCESS) {
|
||||
@@ -8504,13 +8514,14 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
pDevice->bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, wf.Format.nSamplesPerSec);
|
||||
}
|
||||
|
||||
bufferSizeInBytes = pDevice->bufferSizeInFrames * wf.Format.nChannels * mal_get_bytes_per_sample(pDevice->format);
|
||||
/* The size of the buffer must be a clean multiple of the period count. */
|
||||
pDevice->bufferSizeInFrames = (mal_uint32)(pDevice->bufferSizeInFrames/pDevice->periods) * pDevice->periods;
|
||||
|
||||
MAL_DSCBUFFERDESC descDS;
|
||||
mal_zero_object(&descDS);
|
||||
descDS.dwSize = sizeof(descDS);
|
||||
descDS.dwFlags = 0;
|
||||
descDS.dwBufferBytes = bufferSizeInBytes;
|
||||
descDS.dwBufferBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, wf.Format.nChannels);
|
||||
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
|
||||
if (FAILED(mal_IDirectSoundCapture_CreateCaptureBuffer((mal_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (mal_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL))) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
@@ -8536,6 +8547,19 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap);
|
||||
}
|
||||
|
||||
/*
|
||||
After getting the actual format the size of the buffer in frames may have actually changed. However, we want this to be as close to what the
|
||||
user has asked for as possible, so let's go ahead and release the old capture buffer and create a new one in this case.
|
||||
*/
|
||||
if (pDevice->bufferSizeInFrames != (descDS.dwBufferBytes / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels))) {
|
||||
descDS.dwBufferBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, wf.Format.nChannels);
|
||||
mal_IDirectSoundCaptureBuffer_Release((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer);
|
||||
|
||||
if (FAILED(mal_IDirectSoundCapture_CreateCaptureBuffer((mal_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (mal_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL))) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
|
||||
}
|
||||
}
|
||||
|
||||
// Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer.
|
||||
if (FAILED(mal_IDirectSoundCaptureBuffer_QueryInterface((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) {
|
||||
@@ -8544,6 +8568,38 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
With the notifications, what we're essentially doing is telling DirectSound to signal an event when a portion of the buffer
|
||||
becomes available for reading or writing. The way DirectSound's notifiation system works is that you have events placed at
|
||||
specific points within the buffer. When the playback position (for playback) or read position (for capture) reaches a certain
|
||||
point in the buffer, the relevant event will be signaled.
|
||||
|
||||
We have one event for each period, with the event being placed at the end of the period. Thus, when the event is signaled,
|
||||
the portion of the buffer for that particular period will have just been processed which means mini_al can either read from
|
||||
it or write to it. A complication with this system is that
|
||||
*/
|
||||
{
|
||||
mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
|
||||
MAL_DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_PERIODS_DSOUND];
|
||||
|
||||
for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; ++iPeriod) {
|
||||
pDevice->dsound.pNotifyEvents[iPeriod] = CreateEventA(NULL, FALSE, FALSE, NULL);
|
||||
if (pDevice->dsound.pNotifyEvents[iPeriod] == NULL) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to create event for buffer notifications.", MAL_FAILED_TO_CREATE_EVENT);
|
||||
}
|
||||
|
||||
notifyPoints[iPeriod].dwOffset = (((iPeriod+1) * periodSizeInFrames) % pDevice->bufferSizeInFrames) * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
notifyPoints[iPeriod].hEventNotify = pDevice->dsound.pNotifyEvents[iPeriod];
|
||||
}
|
||||
|
||||
if (FAILED(mal_IDirectSoundNotify_SetNotificationPositions((mal_IDirectSoundNotify*)pDevice->dsound.pNotify, pDevice->periods, notifyPoints))) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_FAILED_TO_CREATE_EVENT);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// We need a notification for each period. The notification offset is slightly different depending on whether or not the
|
||||
// device is a playback or capture device. For a playback device we want to be notified when a period just starts playing,
|
||||
// whereas for a capture device we want to be notified when a period has just _finished_ capturing.
|
||||
@@ -8565,6 +8621,7 @@ mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type,
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_FAILED_TO_CREATE_EVENT);
|
||||
}
|
||||
#endif
|
||||
|
||||
// When the device is playing the worker thread will be waiting on a bunch of notification events. To return from
|
||||
// this wait state we need to signal a special event.
|
||||
@@ -8592,6 +8649,7 @@ mal_result mal_device_start__dsound(mal_device* pDevice)
|
||||
}
|
||||
}
|
||||
|
||||
mal_atomic_exchange_32(&pDevice->dsound.isStarted, MAL_TRUE);
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -8609,20 +8667,11 @@ mal_result mal_device_stop__dsound(mal_device* pDevice)
|
||||
}
|
||||
}
|
||||
|
||||
mal_atomic_exchange_32(&pDevice->dsound.isStarted, MAL_FALSE);
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
mal_result mal_device_break_main_loop__dsound(mal_device* pDevice)
|
||||
{
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
// The main loop will be waiting on a bunch of events via the WaitForMultipleObjects() API. One of those events
|
||||
// is a special event we use for forcing that function to return.
|
||||
pDevice->dsound.breakFromMainLoop = MAL_TRUE;
|
||||
SetEvent(pDevice->dsound.hStopEvent);
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
/* TODO: Change the return value to mal_result. Also get rid of the double underscore. */
|
||||
mal_bool32 mal_device__get_current_frame__dsound(mal_device* pDevice, mal_uint32* pCurrentPos)
|
||||
{
|
||||
mal_assert(pDevice != NULL);
|
||||
@@ -8640,10 +8689,294 @@ mal_bool32 mal_device__get_current_frame__dsound(mal_device* pDevice, mal_uint32
|
||||
}
|
||||
}
|
||||
|
||||
*pCurrentPos = (mal_uint32)dwCurrentPosition / mal_get_bytes_per_sample(pDevice->format) / pDevice->channels;
|
||||
*pCurrentPos = (mal_uint32)dwCurrentPosition / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
return MAL_TRUE;
|
||||
}
|
||||
|
||||
mal_result mal_device_map_next_playback_buffer__dsound(mal_device* pDevice)
|
||||
{
|
||||
DWORD periodSizeInBytes = (pDevice->bufferSizeInFrames / pDevice->periods) * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
DWORD lockOffset = (pDevice->dsound.iNextPeriod * periodSizeInBytes);
|
||||
DWORD mappedSizeInBytes;
|
||||
HRESULT hr = mal_IDirectSoundBuffer_Lock((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffset, periodSizeInBytes, &pDevice->dsound.pMappedBufferPlayback, &mappedSizeInBytes, NULL, NULL, 0);
|
||||
if (FAILED(hr)) {
|
||||
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", MAL_FAILED_TO_MAP_DEVICE_BUFFER);
|
||||
}
|
||||
|
||||
pDevice->dsound.mappedBufferFramesCapacityPlayback = (mal_uint32)mappedSizeInBytes / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
pDevice->dsound.mappedBufferFramesRemainingPlayback = (mal_uint32)mappedSizeInBytes / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
mal_result mal_device_write__dsound(mal_device* pDevice, mal_uint32 pcmFrameCount, const void* pPCMFrames, mal_uint32* pPCMFramesWritten)
|
||||
{
|
||||
mal_result result = MAL_SUCCESS;
|
||||
mal_bool32 wasStartedOnEntry;
|
||||
mal_uint32 totalPCMFramesWritten;
|
||||
HRESULT hr;
|
||||
DWORD waitResult;
|
||||
|
||||
mal_assert(pDevice != NULL);
|
||||
mal_assert(pPCMFrames != NULL);
|
||||
|
||||
*pPCMFramesWritten = 0;
|
||||
wasStartedOnEntry = pDevice->dsound.isStarted;
|
||||
|
||||
/* If the device is not started we do not have a mapped buffer, we'll need to map the first period so we can fill it. */
|
||||
if (!pDevice->dsound.isStarted && pDevice->dsound.pMappedBufferPlayback == NULL) {
|
||||
result = mal_device_map_next_playback_buffer__dsound(pDevice);
|
||||
if (result != MAL_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
totalPCMFramesWritten = 0;
|
||||
while (totalPCMFramesWritten < pcmFrameCount) {
|
||||
/* If a buffer is mapped we need to write to that first. Once it's consumed we reset the event and unmap it. */
|
||||
if (pDevice->dsound.pMappedBufferPlayback != NULL && pDevice->dsound.mappedBufferFramesRemainingPlayback > 0) {
|
||||
mal_uint32 bpf = mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
mal_uint32 mappedBufferFramesConsumed = pDevice->dsound.mappedBufferFramesCapacityPlayback - pDevice->dsound.mappedBufferFramesRemainingPlayback;
|
||||
|
||||
void* pDst = (mal_uint8*)pDevice->dsound.pMappedBufferPlayback + (mappedBufferFramesConsumed * bpf);
|
||||
const void* pSrc = (const mal_uint8*)pPCMFrames + (totalPCMFramesWritten * bpf);
|
||||
mal_uint32 framesToCopy = mal_min(pDevice->dsound.mappedBufferFramesRemainingPlayback, (pcmFrameCount - totalPCMFramesWritten));
|
||||
mal_copy_memory(pDst, pSrc, framesToCopy * bpf);
|
||||
|
||||
pDevice->dsound.mappedBufferFramesRemainingPlayback -= framesToCopy;
|
||||
totalPCMFramesWritten += framesToCopy;
|
||||
}
|
||||
|
||||
/* Getting here means we've consumed the device buffer and need to wait for more to become available. */
|
||||
if (pDevice->dsound.mappedBufferFramesCapacityPlayback > 0 && pDevice->dsound.mappedBufferFramesRemainingPlayback == 0) {
|
||||
hr = mal_IDirectSoundBuffer_Unlock((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pDevice->dsound.pMappedBufferPlayback, pDevice->dsound.mappedBufferFramesCapacityPlayback, NULL, 0);
|
||||
pDevice->dsound.pMappedBufferPlayback = NULL;
|
||||
pDevice->dsound.mappedBufferFramesRemainingPlayback = 0;
|
||||
pDevice->dsound.mappedBufferFramesCapacityPlayback = 0;
|
||||
pDevice->dsound.iNextPeriod = (pDevice->dsound.iNextPeriod + 1) % pDevice->periods;
|
||||
|
||||
if (FAILED(hr)) {
|
||||
result = MAL_FAILED_TO_UNMAP_DEVICE_BUFFER;
|
||||
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", result);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pDevice->dsound.isStarted && !wasStartedOnEntry) {
|
||||
result = mal_device_start__dsound(pDevice);
|
||||
if (result != MAL_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mal_assert(totalPCMFramesWritten <= pcmFrameCount);
|
||||
if (totalPCMFramesWritten == pcmFrameCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Relying exclusively on the notification events turns out to be horribly slow. It looks like by the time the event is signaled, the cursor
|
||||
has already progressed way beyond the notification point. It's better to instead poll the position, only returning when a whole period is
|
||||
available.
|
||||
|
||||
This loop just crudely sleeps for a bit and then queries the current position. If there's enough room for a whole period, we will it with
|
||||
data from the client. Otherwise we just wait a bit longer. It's crude, but it's simple and it works _much_ better than the notification
|
||||
system.
|
||||
*/
|
||||
for (;;) {
|
||||
DWORD timeoutInMilliseconds = 1;
|
||||
mal_sleep((mal_uint32)timeoutInMilliseconds);
|
||||
|
||||
/* We've woken up, so now we need to poll the current position. If there are enough frames for a whole period we can be done with the wait. */
|
||||
mal_uint32 currentPos;
|
||||
if (mal_device__get_current_frame__dsound(pDevice, ¤tPos) != MAL_TRUE) {
|
||||
result = MAL_ERROR;
|
||||
break; /* Failed to get the current frame. */
|
||||
}
|
||||
|
||||
mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames/pDevice->periods;
|
||||
if ((currentPos - (pDevice->dsound.iNextPeriod * periodSizeInFrames)) >= periodSizeInFrames) {
|
||||
break; /* There's enough room. */
|
||||
}
|
||||
|
||||
/* Don't keep waiting if the device has stopped. */
|
||||
if (!pDevice->dsound.isStarted && wasStartedOnEntry) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != MAL_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the device has been stopped don't continue. */
|
||||
if (!pDevice->dsound.isStarted && wasStartedOnEntry) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Getting here means we can map the next period. */
|
||||
result = mal_device_map_next_playback_buffer__dsound(pDevice);
|
||||
if (result != MAL_SUCCESS) {
|
||||
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*pPCMFramesWritten = totalPCMFramesWritten;
|
||||
return result;
|
||||
}
|
||||
|
||||
mal_result mal_device_map_next_capture_buffer__dsound(mal_device* pDevice)
|
||||
{
|
||||
#if 0
|
||||
DWORD dwCapturePosition, dwReadPosition;
|
||||
if (FAILED(mal_IDirectSoundCaptureBuffer_GetCurrentPosition((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &dwCapturePosition, &dwReadPosition))) {
|
||||
return MAL_ERROR;
|
||||
}
|
||||
printf("dwCapturePosition=%d, dwReadPosition=%d\n", dwCapturePosition, dwReadPosition);
|
||||
#endif
|
||||
|
||||
DWORD periodSizeInBytes = (pDevice->bufferSizeInFrames / pDevice->periods) * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
DWORD lockOffset = (pDevice->dsound.iNextPeriod * periodSizeInBytes);
|
||||
DWORD mappedSizeInBytes;
|
||||
HRESULT hr = mal_IDirectSoundCaptureBuffer_Lock((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffset, periodSizeInBytes, &pDevice->dsound.pMappedBufferCapture, &mappedSizeInBytes, NULL, NULL, 0);
|
||||
if (FAILED(hr)) {
|
||||
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", MAL_FAILED_TO_MAP_DEVICE_BUFFER);
|
||||
}
|
||||
|
||||
pDevice->dsound.mappedBufferFramesCapacityCapture = (mal_uint32)mappedSizeInBytes / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
pDevice->dsound.mappedBufferFramesRemainingCapture = (mal_uint32)mappedSizeInBytes / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
mal_result mal_device_read__dsound(mal_device* pDevice, mal_uint32 pcmFrameCount, void* pPCMFrames, mal_uint32* pPCMFramesRead)
|
||||
{
|
||||
mal_result result = MAL_SUCCESS;
|
||||
mal_uint32 totalPCMFramesRead;
|
||||
HRESULT hr;
|
||||
DWORD waitResult;
|
||||
|
||||
mal_assert(pDevice != NULL);
|
||||
mal_assert(pPCMFrames != NULL);
|
||||
|
||||
/* The device needs to be started immediately if it's not already. */
|
||||
if (!pDevice->dsound.isStarted) {
|
||||
result = mal_device_start__dsound(pDevice);
|
||||
if (result != MAL_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
totalPCMFramesRead = 0;
|
||||
while (totalPCMFramesRead < pcmFrameCount) {
|
||||
/* If a buffer is mapped we need to write to that first. Once it's consumed we reset the event and unmap it. */
|
||||
if (pDevice->dsound.pMappedBufferCapture != NULL && pDevice->dsound.mappedBufferFramesRemainingCapture > 0) {
|
||||
mal_uint32 bpf = mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
mal_uint32 mappedBufferFramesConsumed = pDevice->dsound.mappedBufferFramesCapacityCapture - pDevice->dsound.mappedBufferFramesRemainingCapture;
|
||||
|
||||
void* pDst = (mal_uint8*)pPCMFrames + (totalPCMFramesRead * bpf);
|
||||
const void* pSrc = (const mal_uint8*)pDevice->dsound.pMappedBufferCapture + (mappedBufferFramesConsumed * bpf);
|
||||
mal_uint32 framesToCopy = mal_min(pDevice->dsound.mappedBufferFramesRemainingCapture, (pcmFrameCount - totalPCMFramesRead));
|
||||
mal_copy_memory(pDst, pSrc, framesToCopy * bpf);
|
||||
|
||||
pDevice->dsound.mappedBufferFramesRemainingCapture -= framesToCopy;
|
||||
totalPCMFramesRead += framesToCopy;
|
||||
}
|
||||
|
||||
/* Getting here means we've consumed the device buffer and need to wait for more to become available. */
|
||||
if (pDevice->dsound.mappedBufferFramesCapacityCapture > 0 && pDevice->dsound.mappedBufferFramesRemainingCapture == 0) {
|
||||
hr = mal_IDirectSoundCaptureBuffer_Unlock((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pDevice->dsound.pMappedBufferCapture, pDevice->dsound.mappedBufferFramesCapacityCapture, NULL, 0);
|
||||
pDevice->dsound.pMappedBufferCapture = NULL;
|
||||
pDevice->dsound.mappedBufferFramesRemainingCapture = 0;
|
||||
pDevice->dsound.mappedBufferFramesCapacityCapture = 0;
|
||||
pDevice->dsound.iNextPeriod = (pDevice->dsound.iNextPeriod + 1) % pDevice->periods;
|
||||
|
||||
if (FAILED(hr)) {
|
||||
result = MAL_FAILED_TO_UNMAP_DEVICE_BUFFER;
|
||||
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", result);
|
||||
break;
|
||||
}
|
||||
|
||||
ResetEvent(pDevice->dsound.pNotifyEvents[pDevice->dsound.iNextPeriod]);
|
||||
}
|
||||
|
||||
mal_assert(totalPCMFramesRead <= pcmFrameCount);
|
||||
if (totalPCMFramesRead == pcmFrameCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Just like in the playback case we just use a simple wait-and-poll to know when a chunk of data is available. */
|
||||
for (;;) {
|
||||
DWORD timeoutInMilliseconds = 1;
|
||||
mal_sleep((mal_uint32)timeoutInMilliseconds);
|
||||
|
||||
/* We've woken up, so now we need to poll the current position. If there are enough frames for a whole period we can be done with the wait. */
|
||||
mal_uint32 currentPos;
|
||||
if (mal_device__get_current_frame__dsound(pDevice, ¤tPos) != MAL_TRUE) {
|
||||
result = MAL_ERROR;
|
||||
break; /* Failed to get the current frame. */
|
||||
}
|
||||
|
||||
mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames/pDevice->periods;
|
||||
if ((currentPos - (pDevice->dsound.iNextPeriod * periodSizeInFrames)) >= periodSizeInFrames) {
|
||||
break; /* There's enough room. */
|
||||
}
|
||||
|
||||
/* Don't keep waiting if the device has stopped. */
|
||||
if (!pDevice->dsound.isStarted) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != MAL_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Don't keep waiting if the device has stopped. */
|
||||
if (!pDevice->dsound.isStarted) {
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* Getting here means there's not enough room in the buffer and we need to wait. */
|
||||
waitResult = WaitForSingleObject(pDevice->dsound.pNotifyEvents[pDevice->dsound.iNextPeriod], INFINITE);
|
||||
if (waitResult == WAIT_FAILED) {
|
||||
result = MAL_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TEST: Reset every event. */
|
||||
//for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; ++iPeriod) {
|
||||
// ResetEvent(pDevice->dsound.pNotifyEvents[iPeriod]);
|
||||
//}
|
||||
#endif
|
||||
|
||||
result = mal_device_map_next_capture_buffer__dsound(pDevice);
|
||||
if (result != MAL_SUCCESS) {
|
||||
mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", result);
|
||||
break;
|
||||
}
|
||||
|
||||
//printf("MAPPED: period=%d, mappedBufferFramesCapacityCapture=%d\n", pDevice->dsound.iNextPeriod, pDevice->dsound.mappedBufferFramesCapacityCapture);
|
||||
}
|
||||
|
||||
*pPCMFramesRead = totalPCMFramesRead;
|
||||
return result;
|
||||
}
|
||||
|
||||
#if 0
|
||||
mal_result mal_device_break_main_loop__dsound(mal_device* pDevice)
|
||||
{
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
// The main loop will be waiting on a bunch of events via the WaitForMultipleObjects() API. One of those events
|
||||
// is a special event we use for forcing that function to return.
|
||||
pDevice->dsound.breakFromMainLoop = MAL_TRUE;
|
||||
SetEvent(pDevice->dsound.hStopEvent);
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
mal_uint32 mal_device__get_available_frames__dsound(mal_device* pDevice)
|
||||
{
|
||||
mal_assert(pDevice != NULL);
|
||||
@@ -8691,7 +9024,7 @@ mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice)
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
// The timeout to use for putting the thread to sleep is based on the size of the buffer and the period count.
|
||||
DWORD timeoutInMilliseconds = (pDevice->bufferSizeInFrames / (pDevice->sampleRate/1000)) / pDevice->periods;
|
||||
DWORD timeoutInMilliseconds = (pDevice->bufferSizeInFrames / (pDevice->internalSampleRate/1000)) / pDevice->periods;
|
||||
if (timeoutInMilliseconds < 1) {
|
||||
timeoutInMilliseconds = 1;
|
||||
}
|
||||
@@ -8770,6 +9103,7 @@ mal_result mal_device_main_loop__dsound(mal_device* pDevice)
|
||||
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
mal_result mal_context_uninit__dsound(mal_context* pContext)
|
||||
@@ -8804,8 +9138,11 @@ mal_result mal_context_init__dsound(mal_context* pContext)
|
||||
pContext->onDeviceUninit = mal_device_uninit__dsound;
|
||||
pContext->onDeviceStart = mal_device_start__dsound;
|
||||
pContext->onDeviceStop = mal_device_stop__dsound;
|
||||
pContext->onDeviceBreakMainLoop = mal_device_break_main_loop__dsound;
|
||||
pContext->onDeviceMainLoop = mal_device_main_loop__dsound;
|
||||
pContext->onDeviceWrite = mal_device_write__dsound;
|
||||
pContext->onDeviceRead = mal_device_read__dsound;
|
||||
|
||||
//pContext->onDeviceBreakMainLoop = mal_device_break_main_loop__dsound;
|
||||
//pContext->onDeviceMainLoop = mal_device_main_loop__dsound;
|
||||
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user