mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-23 16:54:03 +02:00
Initial work on default device reinitialization.
This commit is contained in:
@@ -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
|
|||||||
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
|
|||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
{
|
{
|
||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
|||||||
|
|
||||||
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Getting here means we have broken from the main loop which happens the application has requested that device be stopped.
|
// If reinitialization was successful, loop back to the start.
|
||||||
pDevice->pContext->onDeviceStop(pDevice);
|
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
|
||||||
|
// 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)
|
|||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
|||||||
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
|
|||||||
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)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user