Commit c4b6d404 authored by David Reid's avatar David Reid

Initial work on default device reinitialization.

parent b51cdd36
...@@ -1489,6 +1489,7 @@ struct mal_context ...@@ -1489,6 +1489,7 @@ struct mal_context
mal_result (* onGetDeviceInfo )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo); mal_result (* onGetDeviceInfo )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo);
mal_result (* onDeviceInit )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice); mal_result (* onDeviceInit )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice);
void (* onDeviceUninit )(mal_device* pDevice); void (* onDeviceUninit )(mal_device* pDevice);
mal_result (* onDeviceReinit )(mal_device* pDevice);
mal_result (* onDeviceStart )(mal_device* pDevice); mal_result (* onDeviceStart )(mal_device* pDevice);
mal_result (* onDeviceStop )(mal_device* pDevice); mal_result (* onDeviceStop )(mal_device* pDevice);
mal_result (* onDeviceBreakMainLoop)(mal_device* pDevice); mal_result (* onDeviceBreakMainLoop)(mal_device* pDevice);
...@@ -1904,14 +1905,15 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -1904,14 +1905,15 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_recv_proc onRecv; mal_recv_proc onRecv;
mal_send_proc onSend; mal_send_proc onSend;
mal_stop_proc onStop; mal_stop_proc onStop;
void* pUserData; // Application defined data. void* pUserData; // Application defined data.
char name[256]; char name[256];
mal_device_config initConfig; // The configuration passed in to mal_device_init(). Mainly used for reinitializing the backend device.
mal_mutex lock; mal_mutex lock;
mal_event wakeupEvent; mal_event wakeupEvent;
mal_event startEvent; mal_event startEvent;
mal_event stopEvent; mal_event stopEvent;
mal_thread thread; mal_thread thread;
mal_result workResult; // This is set by the worker thread after it's finished doing a job. mal_result workResult; // This is set by the worker thread after it's finished doing a job.
mal_bool32 usingDefaultFormat : 1; mal_bool32 usingDefaultFormat : 1;
mal_bool32 usingDefaultChannels : 1; mal_bool32 usingDefaultChannels : 1;
mal_bool32 usingDefaultSampleRate : 1; mal_bool32 usingDefaultSampleRate : 1;
...@@ -1920,6 +1922,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -1920,6 +1922,7 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_bool32 usingDefaultPeriods : 1; mal_bool32 usingDefaultPeriods : 1;
mal_bool32 exclusiveMode : 1; mal_bool32 exclusiveMode : 1;
mal_bool32 isOwnerOfContext : 1; // When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into mal_device_init(). mal_bool32 isOwnerOfContext : 1; // When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into mal_device_init().
mal_bool32 isDefaultDevice : 1; // Used to determine if the backend should try reinitializing if the default device is unplugged.
mal_format internalFormat; mal_format internalFormat;
mal_uint32 internalChannels; mal_uint32 internalChannels;
mal_uint32 internalSampleRate; mal_uint32 internalSampleRate;
...@@ -8431,6 +8434,8 @@ void mal_device_uninit__winmm(mal_device* pDevice) ...@@ -8431,6 +8434,8 @@ void mal_device_uninit__winmm(mal_device* pDevice)
mal_free(pDevice->winmm._pHeapData); mal_free(pDevice->winmm._pHeapData);
CloseHandle((HANDLE)pDevice->winmm.hEvent); CloseHandle((HANDLE)pDevice->winmm.hEvent);
mal_zero_object(&pDevice->winmm); // Safety.
} }
mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
...@@ -8615,6 +8620,10 @@ mal_result mal_device__start_backend__winmm(mal_device* pDevice) ...@@ -8615,6 +8620,10 @@ mal_result mal_device__start_backend__winmm(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (pDevice->winmm.hDevice == NULL) {
return MAL_INVALID_ARGS;
}
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
// Playback. The device is started when we call waveOutWrite() with a block of data. From MSDN: // Playback. The device is started when we call waveOutWrite() with a block of data. From MSDN:
// //
...@@ -8679,6 +8688,10 @@ mal_result mal_device__stop_backend__winmm(mal_device* pDevice) ...@@ -8679,6 +8688,10 @@ mal_result mal_device__stop_backend__winmm(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (pDevice->winmm.hDevice == NULL) {
return MAL_INVALID_ARGS;
}
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
MMRESULT resultMM = ((MAL_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevice); MMRESULT resultMM = ((MAL_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevice);
if (resultMM != MMSYSERR_NOERROR) { if (resultMM != MMSYSERR_NOERROR) {
...@@ -19063,6 +19076,65 @@ mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 c ...@@ -19063,6 +19076,65 @@ mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 c
} }
void mal_device__post_init_setup(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// Make sure the internal channel map was set correctly by the backend. If it's not valid, just fall back to defaults.
if (!mal_channel_map_valid(pDevice->internalChannels, pDevice->internalChannelMap)) {
mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
}
// If the format/channels/rate is using defaults we need to set these to be the same as the internal config.
if (pDevice->usingDefaultFormat) {
pDevice->format = pDevice->internalFormat;
}
if (pDevice->usingDefaultChannels) {
pDevice->channels = pDevice->internalChannels;
}
if (pDevice->usingDefaultSampleRate) {
pDevice->sampleRate = pDevice->internalSampleRate;
}
if (pDevice->usingDefaultChannelMap) {
mal_copy_memory(pDevice->channelMap, pDevice->internalChannelMap, sizeof(pDevice->channelMap));
}
// Buffer size. The backend will have set bufferSizeInFrames. We need to calculate bufferSizeInMilliseconds here.
pDevice->bufferSizeInMilliseconds = pDevice->bufferSizeInFrames / (pDevice->internalSampleRate/1000);
// We need a DSP object which is where samples are moved through in order to convert them to the
// format required by the backend.
mal_dsp_config dspConfig = mal_dsp_config_init_new();
dspConfig.neverConsumeEndOfInput = MAL_TRUE;
dspConfig.pUserData = pDevice;
if (pDevice->type == mal_device_type_playback) {
dspConfig.formatIn = pDevice->format;
dspConfig.channelsIn = pDevice->channels;
dspConfig.sampleRateIn = pDevice->sampleRate;
mal_copy_memory(dspConfig.channelMapIn, pDevice->channelMap, sizeof(dspConfig.channelMapIn));
dspConfig.formatOut = pDevice->internalFormat;
dspConfig.channelsOut = pDevice->internalChannels;
dspConfig.sampleRateOut = pDevice->internalSampleRate;
mal_copy_memory(dspConfig.channelMapOut, pDevice->internalChannelMap, sizeof(dspConfig.channelMapOut));
dspConfig.onRead = mal_device__on_read_from_client;
mal_dsp_init(&dspConfig, &pDevice->dsp);
} else {
dspConfig.formatIn = pDevice->internalFormat;
dspConfig.channelsIn = pDevice->internalChannels;
dspConfig.sampleRateIn = pDevice->internalSampleRate;
mal_copy_memory(dspConfig.channelMapIn, pDevice->internalChannelMap, sizeof(dspConfig.channelMapIn));
dspConfig.formatOut = pDevice->format;
dspConfig.channelsOut = pDevice->channels;
dspConfig.sampleRateOut = pDevice->sampleRate;
mal_copy_memory(dspConfig.channelMapOut, pDevice->channelMap, sizeof(dspConfig.channelMapOut));
dspConfig.onRead = mal_device__on_read_from_device;
mal_dsp_init(&dspConfig, &pDevice->dsp);
}
}
mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
{ {
mal_device* pDevice = (mal_device*)pData; mal_device* pDevice = (mal_device*)pData;
...@@ -19111,11 +19183,42 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) ...@@ -19111,11 +19183,42 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
// Now we just enter the main loop. When the main loop is terminated the device needs to be marked as stopped. This can // 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(). // be broken with mal_device__break_main_loop().
pDevice->pContext->onDeviceMainLoop(pDevice); mal_result mainLoopResult = pDevice->pContext->onDeviceMainLoop(pDevice);
if (mainLoopResult != MAL_SUCCESS && pDevice->isDefaultDevice && mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
// 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);
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.
pDevice->pContext->onDeviceStop(pDevice); // 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
// don't want to be doing this a second time.
if (mal_device__get_state(pDevice) != MAL_STATE_UNINITIALIZED) {
pDevice->pContext->onDeviceStop(pDevice);
}
// After the device has stopped, make sure an event is posted. // After the device has stopped, make sure an event is posted.
mal_stop_proc onStop = pDevice->onStop; mal_stop_proc onStop = pDevice->onStop;
...@@ -19123,9 +19226,13 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) ...@@ -19123,9 +19226,13 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
onStop(pDevice); onStop(pDevice);
} }
// A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. // A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. Note that
mal_device__set_state(pDevice, MAL_STATE_STOPPED); // it's possible that the device has been uninitialized which means we need to _not_ change the status to stopped. We cannot go from an
mal_event_signal(&pDevice->stopEvent); // uninitialized state to stopped state.
if (mal_device__get_state(pDevice) != MAL_STATE_UNINITIALIZED) {
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
mal_event_signal(&pDevice->stopEvent);
}
} }
#else #else
// This is only used to prevent posting onStop() when the device is first initialized. // This is only used to prevent posting onStop() when the device is first initialized.
...@@ -19690,6 +19797,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -19690,6 +19797,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
mal_zero_object(pDevice); mal_zero_object(pDevice);
pDevice->pContext = pContext; pDevice->pContext = pContext;
pDevice->initConfig = config;
// Set the user data and log callback ASAP to ensure it is available for the entire initialization process. // Set the user data and log callback ASAP to ensure it is available for the entire initialization process.
pDevice->pUserData = pUserData; pDevice->pUserData = pUserData;
...@@ -19703,6 +19811,10 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -19703,6 +19811,10 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
} }
} }
if (pDeviceID == NULL) {
pDevice->isDefaultDevice = MAL_TRUE;
}
// When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set // When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set
// to defaults it means the format conversion pipeline will run on a fast path where data transfer is just passed straight through to the backend. // to defaults it means the format conversion pipeline will run on a fast path where data transfer is just passed straight through to the backend.
...@@ -19781,28 +19893,10 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -19781,28 +19893,10 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
return MAL_NO_BACKEND; // The error message will have been posted with mal_post_error() by the source of the error so don't bother calling it here. return MAL_NO_BACKEND; // The error message will have been posted with mal_post_error() by the source of the error so don't bother calling it here.
} }
mal_device__post_init_setup(pDevice);
// If the backend did not fill out a name for the device, try a generic method.
if (pDevice->name[0] == '\0') {
if (mal_context__try_get_device_name_by_id(pContext, type, pDeviceID, pDevice->name, sizeof(pDevice->name)) != MAL_SUCCESS) {
// We failed to get the device name, so fall back to some generic names.
if (pDeviceID == NULL) {
if (type == mal_device_type_playback) {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1);
} else {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1);
}
} else {
if (type == mal_device_type_playback) {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "Playback Device", (size_t)-1);
} else {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "Capture Device", (size_t)-1);
}
}
}
}
#if 0
// Make sure the internal channel map was set correctly by the backend. If it's not valid, just fall back to defaults. // Make sure the internal channel map was set correctly by the backend. If it's not valid, just fall back to defaults.
if (!mal_channel_map_valid(pDevice->internalChannels, pDevice->internalChannelMap)) { if (!mal_channel_map_valid(pDevice->internalChannels, pDevice->internalChannelMap)) {
mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap); mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
...@@ -19855,8 +19949,29 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -19855,8 +19949,29 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
dspConfig.onRead = mal_device__on_read_from_device; dspConfig.onRead = mal_device__on_read_from_device;
mal_dsp_init(&dspConfig, &pDevice->dsp); mal_dsp_init(&dspConfig, &pDevice->dsp);
} }
#endif
// If the backend did not fill out a name for the device, try a generic method.
if (pDevice->name[0] == '\0') {
if (mal_context__try_get_device_name_by_id(pContext, type, pDeviceID, pDevice->name, sizeof(pDevice->name)) != MAL_SUCCESS) {
// We failed to get the device name, so fall back to some generic names.
if (pDeviceID == NULL) {
if (type == mal_device_type_playback) {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1);
} else {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1);
}
} else {
if (type == mal_device_type_playback) {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "Playback Device", (size_t)-1);
} else {
mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "Capture Device", (size_t)-1);
}
}
}
}
// Some backends don't require the worker thread. // Some backends don't require the worker thread.
if (!mal_context_is_backend_asynchronous(pContext)) { if (!mal_context_is_backend_asynchronous(pContext)) {
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