Commit 7a3b0085 authored by David Reid's avatar David Reid

More work on the audio thread's new main loop.

Automatic device switching has not yet been tested with this.
parent a08a3764
...@@ -1941,8 +1941,10 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -1941,8 +1941,10 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_IMMNotificationClient notificationClient; mal_IMMNotificationClient notificationClient;
/*HANDLE*/ mal_handle hEventPlayback; /* Used with the blocking API. Manual reset. Initialized to signaled. */ /*HANDLE*/ mal_handle hEventPlayback; /* Used with the blocking API. Manual reset. Initialized to signaled. */
/*HANDLE*/ mal_handle hEventCapture; /* Used with the blocking API. Manual reset. Initialized to unsignaled. */ /*HANDLE*/ mal_handle hEventCapture; /* Used with the blocking API. Manual reset. Initialized to unsignaled. */
#if 0
/*HANDLE*/ mal_handle hEvent; /*HANDLE*/ mal_handle hEvent;
/*HANDLE*/ mal_handle hBreakEvent; /* <-- Used to break from WaitForMultipleObjects() in the main loop. */ /*HANDLE*/ mal_handle hBreakEvent; /* <-- Used to break from WaitForMultipleObjects() in the main loop. */
#endif
void* pDeviceBufferPlayback; void* pDeviceBufferPlayback;
void* pDeviceBufferCapture; void* pDeviceBufferCapture;
mal_uint32 deviceBufferFramesRemainingPlayback; mal_uint32 deviceBufferFramesRemainingPlayback;
...@@ -2294,14 +2296,6 @@ mal_result mal_device_init_ex(const mal_backend backends[], mal_uint32 backendCo ...@@ -2294,14 +2296,6 @@ mal_result mal_device_init_ex(const mal_backend backends[], mal_uint32 backendCo
// try using the device at the same time as uninitializing it. // try using the device at the same time as uninitializing it.
void mal_device_uninit(mal_device* pDevice); void mal_device_uninit(mal_device* pDevice);
// Writes PCM frames to the device.
mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const void* pPCMFrames, mal_uint32* pPCMFramesWritten);
// Reads PCM frames from the device.
mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void* pPCMFrames, mal_uint32* pPCMFramesRead);
// Sets the callback to use when the device has stopped, either explicitly or as a result of an error. // Sets the callback to use when the device has stopped, either explicitly or as a result of an error.
// //
// Thread Safety: SAFE // Thread Safety: SAFE
...@@ -4576,6 +4570,12 @@ static MAL_INLINE mal_uint32 mal_device__get_state(mal_device* pDevice) ...@@ -4576,6 +4570,12 @@ static MAL_INLINE mal_uint32 mal_device__get_state(mal_device* pDevice)
return pDevice->state; return pDevice->state;
} }
/* A helper for determining whether or not the device is running in async mode. */
static MAL_INLINE mal_bool32 mal_device__is_async(mal_device* pDevice)
{
return pDevice->onRecv != NULL || pDevice->onSend != NULL;
}
#ifdef MAL_WIN32 #ifdef MAL_WIN32
GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
...@@ -5928,8 +5928,10 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I ...@@ -5928,8 +5928,10 @@ HRESULT STDMETHODCALLTYPE mal_IMMNotificationClient_OnDefaultDeviceChanged(mal_I
// We don't change the device here - we change it in the worker thread to keep synchronization simple. To this I'm just setting a flag to // We don't change the device here - we change it in the worker thread to keep synchronization simple. To this I'm just setting a flag to
// indicate that the default device has changed. // indicate that the default device has changed.
mal_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultDeviceChanged, MAL_TRUE); mal_atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultDeviceChanged, MAL_TRUE);
SetEvent(pThis->pDevice->wasapi.hBreakEvent); // <-- The main loop will be waiting on some events. We want to break from this wait ASAP so we can change the device as quickly as possible.
#if 0
SetEvent(pThis->pDevice->wasapi.hBreakEvent); // <-- The main loop will be waiting on some events. We want to break from this wait ASAP so we can change the device as quickly as possible.
#endif
(void)pDefaultDeviceID; (void)pDefaultDeviceID;
return S_OK; return S_OK;
...@@ -6419,12 +6421,14 @@ void mal_device_uninit__wasapi(mal_device* pDevice) ...@@ -6419,12 +6421,14 @@ void mal_device_uninit__wasapi(mal_device* pDevice)
CloseHandle(pDevice->wasapi.hEventCapture); CloseHandle(pDevice->wasapi.hEventCapture);
} }
#if 0
if (pDevice->wasapi.hEvent) { if (pDevice->wasapi.hEvent) {
CloseHandle(pDevice->wasapi.hEvent); CloseHandle(pDevice->wasapi.hEvent);
} }
if (pDevice->wasapi.hBreakEvent) { if (pDevice->wasapi.hBreakEvent) {
CloseHandle(pDevice->wasapi.hBreakEvent); CloseHandle(pDevice->wasapi.hBreakEvent);
} }
#endif
} }
typedef struct typedef struct
...@@ -6813,7 +6817,9 @@ mal_result mal_device_reinit__wasapi(mal_device* pDevice) ...@@ -6813,7 +6817,9 @@ mal_result mal_device_reinit__wasapi(mal_device* pDevice)
pDevice->periods = data.periodsOut; pDevice->periods = data.periodsOut;
mal_strcpy_s(pDevice->name, sizeof(pDevice->name), data.deviceName); mal_strcpy_s(pDevice->name, sizeof(pDevice->name), data.deviceName);
#if 0
mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent); mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent);
#endif
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -6884,54 +6890,48 @@ mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type, ...@@ -6884,54 +6890,48 @@ mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type,
#endif #endif
/* Events. */ /* Events. */
mal_bool32 isSynchronous = (pConfig->onRecvCallback == NULL && pConfig->onSendCallback == NULL);
if (isSynchronous) {
/*
The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled
only after the whole available space has been filled, never before.
The playback event also needs to be initially set to a signaled state so that the first call to mal_device_write() is able /*
to get passed WaitForMultipleObjects(). The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled
*/ only after the whole available space has been filled, never before.
if (type == mal_device_type_playback) {
pDevice->wasapi.hEventPlayback = CreateEventA(NULL, TRUE, TRUE, NULL); /* Manual reset, signaled by default. */
if (pDevice->wasapi.hEventPlayback == NULL) {
errorMsg = "[WASAPI] Failed to create event for playback."; result = MAL_FAILED_TO_CREATE_EVENT;
goto done;
}
mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventPlayback); The playback event also needs to be initially set to a signaled state so that the first call to mal_device_write() is able
to get passed WaitForMultipleObjects().
*/
if (type == mal_device_type_playback) {
pDevice->wasapi.hEventPlayback = CreateEventA(NULL, TRUE, TRUE, NULL); /* Manual reset, signaled by default. */
if (pDevice->wasapi.hEventPlayback == NULL) {
errorMsg = "[WASAPI] Failed to create event for playback."; result = MAL_FAILED_TO_CREATE_EVENT;
goto done;
} }
/* mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventPlayback);
The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled, }
however, because we want to block until we actually have something for the first call to mal_device_read().
*/
if (type == mal_device_type_capture) {
pDevice->wasapi.hEventCapture = CreateEventA(NULL, TRUE, FALSE, NULL); /* Manual reset, unsignaled by default. */
if (pDevice->wasapi.hEventCapture == NULL) {
errorMsg = "[WASAPI] Failed to create event for capture."; result = MAL_FAILED_TO_CREATE_EVENT;
goto done;
}
mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventCapture); /*
} The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled,
} else { however, because we want to block until we actually have something for the first call to mal_device_read().
// We need to create and set the event for event-driven mode. This event is signaled whenever a new chunk of audio */
// data needs to be written or read from the device. if (type == mal_device_type_capture) {
pDevice->wasapi.hEvent = CreateEventA(NULL, FALSE, TRUE, NULL); pDevice->wasapi.hEventCapture = CreateEventA(NULL, TRUE, FALSE, NULL); /* Manual reset, unsignaled by default. */
if (pDevice->wasapi.hEvent == NULL) { if (pDevice->wasapi.hEventCapture == NULL) {
errorMsg = "[WASAPI] Failed to create main event for main loop.", result = MAL_FAILED_TO_CREATE_EVENT; errorMsg = "[WASAPI] Failed to create event for capture."; result = MAL_FAILED_TO_CREATE_EVENT;
goto done; goto done;
} }
mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent); mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEventCapture);
} }
#if 0
// We need to create and set the event for event-driven mode. This event is signaled whenever a new chunk of audio
// data needs to be written or read from the device.
pDevice->wasapi.hEvent = CreateEventA(NULL, FALSE, TRUE, NULL);
if (pDevice->wasapi.hEvent == NULL) {
errorMsg = "[WASAPI] Failed to create main event for main loop.", result = MAL_FAILED_TO_CREATE_EVENT;
goto done;
}
mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent);
// When the device is playing the worker thread will be waiting on a bunch of notification events. To return from // 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. // this wait state we need to signal a special event.
...@@ -6940,6 +6940,7 @@ mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type, ...@@ -6940,6 +6940,7 @@ mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type,
errorMsg = "[WASAPI] Failed to create break event for main loop break notification.", result = MAL_FAILED_TO_CREATE_EVENT; errorMsg = "[WASAPI] Failed to create break event for main loop break notification.", result = MAL_FAILED_TO_CREATE_EVENT;
goto done; goto done;
} }
#endif
result = MAL_SUCCESS; result = MAL_SUCCESS;
...@@ -7203,7 +7204,7 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount ...@@ -7203,7 +7204,7 @@ mal_result mal_device_read__wasapi(mal_device* pDevice, mal_uint32 pcmFrameCount
return MAL_SUCCESS; return MAL_SUCCESS;
} }
#if 0
mal_result mal_device_break_main_loop__wasapi(mal_device* pDevice) mal_result mal_device_break_main_loop__wasapi(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
...@@ -7367,6 +7368,7 @@ mal_result mal_device_main_loop__wasapi(mal_device* pDevice) ...@@ -7367,6 +7368,7 @@ mal_result mal_device_main_loop__wasapi(mal_device* pDevice)
return MAL_SUCCESS; return MAL_SUCCESS;
} }
#endif
mal_result mal_context_uninit__wasapi(mal_context* pContext) mal_result mal_context_uninit__wasapi(mal_context* pContext)
{ {
...@@ -7416,8 +7418,10 @@ mal_result mal_context_init__wasapi(mal_context* pContext) ...@@ -7416,8 +7418,10 @@ mal_result mal_context_init__wasapi(mal_context* pContext)
pContext->onDeviceWrite = mal_device_write__wasapi; pContext->onDeviceWrite = mal_device_write__wasapi;
pContext->onDeviceRead = mal_device_read__wasapi; pContext->onDeviceRead = mal_device_read__wasapi;
#if 0
pContext->onDeviceBreakMainLoop = mal_device_break_main_loop__wasapi; pContext->onDeviceBreakMainLoop = mal_device_break_main_loop__wasapi;
pContext->onDeviceMainLoop = mal_device_main_loop__wasapi; pContext->onDeviceMainLoop = mal_device_main_loop__wasapi;
#endif
return result; return result;
} }
...@@ -19895,7 +19899,7 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) ...@@ -19895,7 +19899,7 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
mal_device__set_state(pDevice, MAL_STATE_STOPPED); mal_device__set_state(pDevice, MAL_STATE_STOPPED);
mal_event_signal(&pDevice->stopEvent); mal_event_signal(&pDevice->stopEvent);
for (;;) { for (;;) { /* <-- This loop just keeps the thread alive. The main audio loop is inside. */
// We wait on an event to know when something has requested that the device be started and the main loop entered. // We wait on an event to know when something has requested that the device be started and the main loop entered.
mal_event_wait(&pDevice->wakeupEvent); mal_event_wait(&pDevice->wakeupEvent);
...@@ -19912,51 +19916,132 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) ...@@ -19912,51 +19916,132 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
// in both the success and error case. It's important that the state of the device is set _before_ signaling the event. // in both the success and error case. It's important that the state of the device is set _before_ signaling the event.
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTING); mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTING);
pDevice->workResult = pDevice->pContext->onDeviceStart(pDevice); /*
if (pDevice->workResult != MAL_SUCCESS) { The old main loop is getting replaced with an improved implementation that's based on the blocking read/write API.
mal_device__set_state(pDevice, MAL_STATE_STOPPED); */
if (pDevice->pContext->onDeviceRead || pDevice->pContext->onDeviceWrite) { /* <-- TODO: Get rid of this check once the old implementation has been entirely replaced. */
mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
/*
With the blocking API, the device is started automatically in read()/write(). All we need to do is enter the loop and just keep reading
or writing based on the period size.
*/
mal_assert(pDevice->periods >= 2);
mal_assert(pDevice->bufferSizeInFrames >= pDevice->periods);
/* Make sure the state is set appropriately. */
mal_device__set_state(pDevice, MAL_STATE_STARTED);
mal_event_signal(&pDevice->startEvent); mal_event_signal(&pDevice->startEvent);
continue;
} /* Main Loop */
mal_assert(periodSizeInFrames >= 1);
while (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
mal_result result = MAL_SUCCESS;
mal_uint32 totalFramesProcessed = 0;
mal_uint8 buffer[4096];
mal_uint32 bufferSizeInFrames = sizeof(buffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
while (totalFramesProcessed < periodSizeInFrames) {
mal_uint32 framesProcessed;
mal_uint32 framesRemaining = periodSizeInFrames - totalFramesProcessed;
mal_uint32 framesToProcess = framesRemaining;
if (framesToProcess > bufferSizeInFrames) {
framesToProcess = bufferSizeInFrames;
}
// At this point the device should be started. framesProcessed = 0;
mal_device__set_state(pDevice, MAL_STATE_STARTED); if (pDevice->type == mal_device_type_playback) {
mal_event_signal(&pDevice->startEvent); mal_device__read_frames_from_client(pDevice, framesToProcess, buffer);
result = pDevice->pContext->onDeviceWrite(pDevice, framesToProcess, buffer, &framesProcessed);
} else {
result = pDevice->pContext->onDeviceRead(pDevice, framesToProcess, buffer, &framesProcessed);
mal_device__send_frames_to_client(pDevice, framesProcessed, buffer);
}
totalFramesProcessed += framesProcessed;
}
// Now we just enter the main loop. When the main loop is terminated the device needs to be marked as stopped. This can /* If something has gone wrong while writing or reading, we can try switching devices. */
// be broken with mal_device__break_main_loop(). if (result != MAL_SUCCESS && pDevice->isDefaultDevice && mal_device__get_state(pDevice) == MAL_STATE_STARTED && pDevice->initConfig.shareMode != mal_share_mode_exclusive) {
mal_result mainLoopResult = pDevice->pContext->onDeviceMainLoop(pDevice); /* Here is where we try switching to the new default device. */
if (mainLoopResult != MAL_SUCCESS && pDevice->isDefaultDevice && mal_device__get_state(pDevice) == MAL_STATE_STARTED && pDevice->initConfig.shareMode != mal_share_mode_exclusive) { mal_result reinitResult = MAL_ERROR;
// Something has failed during the main loop. It could be that the device has been lost. If it's the default device, if (pDevice->pContext->onDeviceReinit) {
// we can try switching over to the new default device by uninitializing and reinitializing. reinitResult = pDevice->pContext->onDeviceReinit(pDevice);
mal_result reinitResult = MAL_ERROR; } else {
if (pDevice->pContext->onDeviceReinit) { pDevice->pContext->onDeviceStop(pDevice);
reinitResult = pDevice->pContext->onDeviceReinit(pDevice); mal_device__set_state(pDevice, MAL_STATE_STOPPED);
} else {
pDevice->pContext->onDeviceStop(pDevice);
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
pDevice->pContext->onDeviceUninit(pDevice); pDevice->pContext->onDeviceUninit(pDevice);
mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED); mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED);
reinitResult = pDevice->pContext->onDeviceInit(pDevice->pContext, pDevice->type, NULL, &pDevice->initConfig, pDevice); reinitResult = pDevice->pContext->onDeviceInit(pDevice->pContext, pDevice->type, NULL, &pDevice->initConfig, pDevice);
} }
// Perform the post initialization setup just in case the data conversion pipeline needs to be reinitialized. /* Perform the post initialization setup just in case the data conversion pipeline needs to be reinitialized. */
if (reinitResult == MAL_SUCCESS) { if (reinitResult == MAL_SUCCESS) {
mal_device__post_init_setup(pDevice); mal_device__post_init_setup(pDevice);
} }
// If reinitialization was successful, loop back to the start. /* If reinitialization was successful, loop back to the start. */
if (reinitResult == MAL_SUCCESS) { if (reinitResult == MAL_SUCCESS) {
mal_device__set_state(pDevice, MAL_STATE_STARTING); // <-- The device is restarting. mal_device__set_state(pDevice, MAL_STATE_STARTING); /* <-- The device is restarting. */
mal_event_signal(&pDevice->wakeupEvent); mal_event_signal(&pDevice->wakeupEvent);
continue;
}
}
/* Get out of the loop if read()/write() returned an error. It probably means the device has been stopped. */
if (result != MAL_SUCCESS) {
break;
}
}
} else {
/* Old loop. This will be removed later. */
pDevice->workResult = pDevice->pContext->onDeviceStart(pDevice);
if (pDevice->workResult != MAL_SUCCESS) {
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
mal_event_signal(&pDevice->startEvent);
continue; continue;
} }
}
// At this point the device should be started.
mal_device__set_state(pDevice, MAL_STATE_STARTED);
mal_event_signal(&pDevice->startEvent);
// Now we just enter the main loop. When the main loop is terminated the device needs to be marked as stopped. This can
// be broken with mal_device__break_main_loop().
mal_result mainLoopResult = pDevice->pContext->onDeviceMainLoop(pDevice);
if (mainLoopResult != MAL_SUCCESS && pDevice->isDefaultDevice && mal_device__get_state(pDevice) == MAL_STATE_STARTED && pDevice->initConfig.shareMode != mal_share_mode_exclusive) {
// Something has failed during the main loop. It could be that the device has been lost. If it's the default device,
// we can try switching over to the new default device by uninitializing and reinitializing.
mal_result reinitResult = MAL_ERROR;
if (pDevice->pContext->onDeviceReinit) {
reinitResult = pDevice->pContext->onDeviceReinit(pDevice);
} else {
pDevice->pContext->onDeviceStop(pDevice);
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
pDevice->pContext->onDeviceUninit(pDevice);
mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED);
reinitResult = pDevice->pContext->onDeviceInit(pDevice->pContext, pDevice->type, NULL, &pDevice->initConfig, pDevice);
}
// Perform the post initialization setup just in case the data conversion pipeline needs to be reinitialized.
if (reinitResult == MAL_SUCCESS) {
mal_device__post_init_setup(pDevice);
}
// If reinitialization was successful, loop back to the start.
if (reinitResult == MAL_SUCCESS) {
mal_device__set_state(pDevice, MAL_STATE_STARTING); // <-- The device is restarting.
mal_event_signal(&pDevice->wakeupEvent);
continue;
}
}
}
// Getting here means we have broken from the main loop which happens the application has requested that device be stopped. Note that this // Getting here means we have broken from the main loop which happens the application has requested that device be stopped. Note that this
// may have actually already happened above if the device was lost and mini_al has attempted to re-initialize the device. In this case we // may have actually already happened above if the device was lost and mini_al has attempted to re-initialize the device. In this case we
// don't want to be doing this a second time. // don't want to be doing this a second time.
...@@ -20732,6 +20817,9 @@ void mal_device_uninit(mal_device* pDevice) ...@@ -20732,6 +20817,9 @@ void mal_device_uninit(mal_device* pDevice)
mal_zero_object(pDevice); mal_zero_object(pDevice);
} }
/*
Writes PCM frames to the device.
*/
mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const void* pPCMFrames, mal_uint32* pPCMFramesWritten) mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const void* pPCMFrames, mal_uint32* pPCMFramesWritten)
{ {
mal_result result; mal_result result;
...@@ -20741,7 +20829,7 @@ mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const ...@@ -20741,7 +20829,7 @@ mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const
*pPCMFramesWritten = 0; /* Safety. */ *pPCMFramesWritten = 0; /* Safety. */
} }
if (pDevice == NULL || pPCMFrames == NULL) { if (mal_device__is_async(pDevice)) {
return MAL_INVALID_ARGS; return MAL_INVALID_ARGS;
} }
...@@ -20761,28 +20849,9 @@ mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const ...@@ -20761,28 +20849,9 @@ mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const
result = pDevice->pContext->onDeviceWrite(pDevice, pcmFrameCount, pPCMFrames, &totalPCMFramesWritten); result = pDevice->pContext->onDeviceWrite(pDevice, pcmFrameCount, pPCMFrames, &totalPCMFramesWritten);
} else { } else {
/* Slow path. Perform a data conversion. */ /* Slow path. Perform a data conversion. */
#if 0
mal_uint8 buffer[4096];
while (totalPCMFramesWritten < pcmFrameCount) {
mal_uint32 framesJustWritten = 0;
mal_uint32 framesRemaining = (pcmFrameCount - totalPCMFramesWritten);
mal_uint32 framesToWrite = framesRemaining;
if (framesToWrite > (sizeof(buffer)/mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels))) {
framesToWrite = (sizeof(buffer)/mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
}
/* TODO: Convert the pPCMFrames to the device's internal format. */
result = pDevice->pContext->onDeviceWrite(pDevice, framesToWrite, buffer, &framesJustWritten);
totalPCMFramesWritten += framesJustWritten;
if (result != MAL_SUCCESS) {
break;
}
}
#endif
/* TODO: Implement me. */
result = MAL_INVALID_OPERATION; result = MAL_INVALID_OPERATION;
/*result = MAL_SUCCESS;*/
} }
...@@ -20793,6 +20862,9 @@ mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const ...@@ -20793,6 +20862,9 @@ mal_result mal_device_write(mal_device* pDevice, mal_uint32 pcmFrameCount, const
return result; return result;
} }
/*
Reads PCM frames from the device.
*/
mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void* pPCMFrames, mal_uint32* pPCMFramesRead) mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void* pPCMFrames, mal_uint32* pPCMFramesRead)
{ {
mal_result result; mal_result result;
...@@ -20807,7 +20879,7 @@ mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void* ...@@ -20807,7 +20879,7 @@ mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void*
} }
/* Not allowed to call this in asynchronous mode. */ /* Not allowed to call this in asynchronous mode. */
if (pDevice->onRecv != NULL || pDevice->onSend != NULL) { if (mal_device__is_async(pDevice)) {
return MAL_INVALID_OPERATION; return MAL_INVALID_OPERATION;
} }
...@@ -20816,7 +20888,6 @@ mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void* ...@@ -20816,7 +20888,6 @@ mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void*
return MAL_INVALID_OPERATION; return MAL_INVALID_OPERATION;
} }
/* If it's a passthrough we can call the backend directly, otherwise we need a data conversion into an intermediary buffer. */ /* If it's a passthrough we can call the backend directly, otherwise we need a data conversion into an intermediary buffer. */
if (pDevice->dsp.isPassthrough) { if (pDevice->dsp.isPassthrough) {
/* Fast path. Write directly to the device. */ /* Fast path. Write directly to the device. */
...@@ -20824,7 +20895,6 @@ mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void* ...@@ -20824,7 +20895,6 @@ mal_result mal_device_read(mal_device* pDevice, mal_uint32 pcmFrameCount, void*
} else { } else {
/* Slow path. Perform a data conversion. */ /* Slow path. Perform a data conversion. */
/* TODO: Implement me. */ /* TODO: Implement me. */
result = MAL_INVALID_OPERATION; result = MAL_INVALID_OPERATION;
} }
...@@ -20853,11 +20923,20 @@ mal_result mal_device_start(mal_device* pDevice) ...@@ -20853,11 +20923,20 @@ mal_result mal_device_start(mal_device* pDevice)
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "mal_device_start() called for an uninitialized device.", MAL_DEVICE_NOT_INITIALIZED); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "mal_device_start() called for an uninitialized device.", MAL_DEVICE_NOT_INITIALIZED);
} }
/*
Starting the device doesn't do anything in synchronous mode because in that case it's started automatically with
mal_device_write() and mal_device_read(). It's best to return an error so that the application can be aware that
it's not doing it right.
*/
if (!mal_device__is_async(pDevice)) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "mal_device_start() called in synchronous mode. This should only be used in asynchronous/callback mode.", MAL_DEVICE_NOT_INITIALIZED);
}
mal_result result = MAL_ERROR; mal_result result = MAL_ERROR;
mal_mutex_lock(&pDevice->lock); mal_mutex_lock(&pDevice->lock);
{ {
// Starting, stopping and pausing are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. // Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state.
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STOPPED /*|| mal_device__get_state(pDevice) == MAL_STATE_PAUSED*/); mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STOPPED);
mal_device__set_state(pDevice, MAL_STATE_STARTING); mal_device__set_state(pDevice, MAL_STATE_STARTING);
...@@ -20893,11 +20972,19 @@ mal_result mal_device_stop(mal_device* pDevice) ...@@ -20893,11 +20972,19 @@ mal_result mal_device_stop(mal_device* pDevice)
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "mal_device_stop() called for an uninitialized device.", MAL_DEVICE_NOT_INITIALIZED); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "mal_device_stop() called for an uninitialized device.", MAL_DEVICE_NOT_INITIALIZED);
} }
/*
Stopping is slightly different for synchronous mode. In this case it just tells the driver to stop the internal processing of the device. Also,
stopping in synchronous mode does not require state checking.
*/
if (!mal_device__is_async(pDevice)) {
return pDevice->pContext->onDeviceStop(pDevice);
}
mal_result result = MAL_ERROR; mal_result result = MAL_ERROR;
mal_mutex_lock(&pDevice->lock); mal_mutex_lock(&pDevice->lock);
{ {
// Starting, stopping and pausing are wrapped in a mutex which means we can assert that the device is in a started or paused state. // Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state.
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTED /*|| mal_device__get_state(pDevice) == MAL_STATE_PAUSED*/); mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTED);
mal_device__set_state(pDevice, MAL_STATE_STOPPING); mal_device__set_state(pDevice, MAL_STATE_STOPPING);
...@@ -20912,7 +20999,9 @@ mal_result mal_device_stop(mal_device* pDevice) ...@@ -20912,7 +20999,9 @@ mal_result mal_device_stop(mal_device* pDevice)
// When we get here the worker thread is likely in a wait state while waiting for the backend device to deliver or request // When we get here the worker thread is likely in a wait state while waiting for the backend device to deliver or request
// audio data. We need to force these to return as quickly as possible. // audio data. We need to force these to return as quickly as possible.
pDevice->pContext->onDeviceBreakMainLoop(pDevice); if (pDevice->pContext->onDeviceBreakMainLoop) {
pDevice->pContext->onDeviceBreakMainLoop(pDevice);
}
// We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be // We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be
// the one who puts the device into the stopped state. Don't call mal_device__set_state() here. // the one who puts the device into the stopped state. Don't call mal_device__set_state() here.
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