diff --git a/mini_al.h b/mini_al.h index 7fc0f762..9b27da4e 100644 --- a/mini_al.h +++ b/mini_al.h @@ -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; }