Commit 561e21fa authored by David Reid's avatar David Reid

Set up some infrastructure for transitioning to the new callbacks.

With the introduction of custom backends, it became necessary to
simplify the backend callbacks. For backwards compatibility, all
built-in backends are still using the old callbacks. Future commits are
going to be transitioning built-in backends to the new system. This
commit is designed to help with this migration so that we can migrate
backends one at a time while still keeping compatibility with this old
system, thereby allowing backends that haven't yet transitioned to keep
working.
parent 17769e88
......@@ -3494,6 +3494,7 @@ struct ma_context_config
struct ma_context
{
ma_backend_callbacks callbacks;
ma_backend backend; /* DirectSound, ALSA, etc. */
ma_log_proc logCallback;
ma_thread_priority threadPriority;
......@@ -3836,9 +3837,6 @@ struct ma_context
int _unused;
} webaudio;
#endif
#ifdef MA_SUPPORT_CUSTOM
ma_backend_callbacks custom;
#endif
#ifdef MA_SUPPORT_NULL
struct
{
......@@ -11122,276 +11120,6 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice,
}
/*******************************************************************************
Custom Backend
*******************************************************************************/
#ifdef MA_HAS_CUSTOM
static ma_result ma_context_enumerate_devices__custom(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData)
{
MA_ASSERT(pContext != NULL);
MA_ASSERT(callback != NULL);
if (pContext->custom.onContextEnumerateDevices == NULL) {
return MA_FALSE;
}
return pContext->custom.onContextEnumerateDevices(pContext, callback, pUserData);
}
static ma_result ma_context_get_device_info__custom(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo)
{
MA_ASSERT(pContext != NULL);
(void)shareMode; /* Not using the share mode. This is something I want to remove from device info queries. */
if (pContext->custom.onContextGetDeviceInfo == NULL) {
return MA_NOT_IMPLEMENTED;
}
/* Be as safe as possible and zero out the device info just in case the backend doesn't fill it out properly. */
MA_ZERO_OBJECT(pDeviceInfo);
return pContext->custom.onContextGetDeviceInfo(pContext, deviceType, pDeviceID, pDeviceInfo);
}
static ma_result ma_context_uninit__custom(ma_context* pContext)
{
MA_ASSERT(pContext != NULL);
if (pContext->custom.onContextUninit == NULL) {
return MA_NOT_IMPLEMENTED;
}
return pContext->custom.onContextUninit(pContext);
}
static void ma_device_uninit__custom(ma_device* pDevice)
{
MA_ASSERT(pDevice != NULL);
if (pDevice->pContext->custom.onDeviceUninit == NULL) {
return;
}
pDevice->pContext->custom.onDeviceUninit(pDevice);
}
static ma_result ma_device_init__custom(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice)
{
ma_result result;
ma_device_descriptor descriptorPlayback;
ma_device_descriptor descriptorCapture;
MA_ASSERT(pContext != NULL);
MA_ASSERT(pConfig != NULL);
MA_ASSERT(pDevice != NULL);
if (pContext->custom.onDeviceInit == NULL) {
return MA_NOT_IMPLEMENTED;
}
MA_ZERO_OBJECT(&descriptorPlayback);
descriptorPlayback.pDeviceID = pConfig->playback.pDeviceID;
descriptorPlayback.shareMode = pConfig->playback.shareMode;
descriptorPlayback.format = pConfig->playback.format;
descriptorPlayback.channels = pConfig->playback.channels;
descriptorPlayback.sampleRate = pConfig->sampleRate;
ma_channel_map_copy(descriptorPlayback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels);
descriptorPlayback.periodSizeInFrames = pConfig->periodSizeInFrames;
descriptorPlayback.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds;
descriptorPlayback.periodCount = pConfig->periods;
MA_ZERO_OBJECT(&descriptorCapture);
descriptorCapture.pDeviceID = pConfig->capture.pDeviceID;
descriptorCapture.shareMode = pConfig->capture.shareMode;
descriptorCapture.format = pConfig->capture.format;
descriptorCapture.channels = pConfig->capture.channels;
descriptorCapture.sampleRate = pConfig->sampleRate;
ma_channel_map_copy(descriptorCapture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels);
descriptorCapture.periodSizeInFrames = pConfig->periodSizeInFrames;
descriptorCapture.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds;
descriptorCapture.periodCount = pConfig->periods;
result = pContext->custom.onDeviceInit(pDevice, pConfig->deviceType, &descriptorPlayback, &descriptorCapture);
if (result != MA_SUCCESS) {
return result; /* Failed to initialize the device. */
}
/*
On output the descriptors will contain the *actual* data format of the device. We need this to know how to convert the data between
the requested format and the internal format.
*/
if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) {
ma_device_info deviceInfo; /* For retrieving the name. */
if (!ma_device_descriptor_is_valid(&descriptorCapture)) {
ma_device_uninit__custom(pDevice);
return MA_INVALID_ARGS;
}
pDevice->capture.internalFormat = descriptorCapture.format;
pDevice->capture.internalChannels = descriptorCapture.channels;
pDevice->capture.internalSampleRate = descriptorCapture.sampleRate;
ma_channel_map_copy(pDevice->capture.internalChannelMap, descriptorCapture.channelMap, descriptorCapture.channels);
pDevice->capture.internalPeriodSizeInFrames = descriptorCapture.periodSizeInFrames;
pDevice->capture.internalPeriods = descriptorCapture.periodCount;
if (pDevice->capture.internalPeriodSizeInFrames == 0) {
pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorCapture.periodSizeInMilliseconds, descriptorCapture.sampleRate);
}
/* The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info()` instead. */
result = ma_context_get_device_info(pContext, ma_device_type_capture, descriptorCapture.pDeviceID, descriptorCapture.shareMode, &deviceInfo);
if (result != MA_SUCCESS) {
/* We failed to retrieve the device info. Fall back to a default name. */
if (descriptorCapture.pDeviceID == NULL) {
ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1);
} else {
ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1);
}
}
}
if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) {
ma_device_info deviceInfo; /* For retrieving the name. */
if (!ma_device_descriptor_is_valid(&descriptorPlayback)) {
ma_device_uninit__custom(pDevice);
return MA_INVALID_ARGS;
}
pDevice->playback.internalFormat = descriptorPlayback.format;
pDevice->playback.internalChannels = descriptorPlayback.channels;
pDevice->playback.internalSampleRate = descriptorPlayback.sampleRate;
ma_channel_map_copy(pDevice->playback.internalChannelMap, descriptorPlayback.channelMap, descriptorPlayback.channels);
pDevice->playback.internalPeriodSizeInFrames = descriptorPlayback.periodSizeInFrames;
pDevice->playback.internalPeriods = descriptorPlayback.periodCount;
if (pDevice->playback.internalPeriodSizeInFrames == 0) {
pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorPlayback.periodSizeInMilliseconds, descriptorPlayback.sampleRate);
}
/* The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. */
result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, descriptorPlayback.shareMode, &deviceInfo);
if (result != MA_SUCCESS) {
/* We failed to retrieve the device info. Fall back to a default name. */
if (descriptorPlayback.pDeviceID == NULL) {
ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1);
} else {
ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1);
}
}
}
/* If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. */
if (pConfig->deviceType == ma_device_type_duplex) {
if (pContext->custom.onDeviceRead == NULL && pContext->custom.onDeviceWrite == NULL && pContext->custom.onDeviceAudioThread == NULL) {
result = ma_duplex_rb_init(pDevice->sampleRate, pDevice->capture.internalFormat, pDevice->capture.internalChannels, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB);
if (result != MA_SUCCESS) {
ma_device_uninit__custom(pDevice);
return result;
}
}
}
return MA_SUCCESS;
}
static ma_result ma_device_start__custom(ma_device* pDevice)
{
MA_ASSERT(pDevice != NULL);
if (pDevice->pContext->custom.onDeviceStart == NULL) {
return MA_NOT_IMPLEMENTED;
}
return pDevice->pContext->custom.onDeviceStart(pDevice);
}
static ma_result ma_device_stop__custom(ma_device* pDevice)
{
MA_ASSERT(pDevice != NULL);
if (pDevice->pContext->custom.onDeviceStop == NULL) {
return MA_NOT_IMPLEMENTED;
}
return pDevice->pContext->custom.onDeviceStop(pDevice);
}
static ma_result ma_device_audio_thread__custom(ma_device* pDevice)
{
MA_ASSERT(pDevice != NULL);
if (pDevice->pContext->custom.onDeviceAudioThread == NULL) {
return MA_NOT_IMPLEMENTED; /* Should never happen, but check anyway. */
}
return pDevice->pContext->custom.onDeviceAudioThread(pDevice);
}
static ma_result ma_device_audio_thread__read_write__custom(ma_device* pDevice)
{
return ma_device_audio_thread__default_read_write(pDevice, &pDevice->pContext->custom);
}
static ma_result ma_context_init__custom(const ma_context_config* pConfig, ma_context* pContext)
{
ma_result result;
MA_ASSERT(pContext != NULL);
MA_ASSERT(pConfig != NULL);
/* We need to ensure we have the necessary callbacks. */
if (pConfig->custom.onContextInit == NULL) {
return MA_NO_BACKEND; /* Need a context initialization callback. When set to NULL it means a custom backend is not defined. */
}
/* Set the custom callbacks before firing the backend's context initialization routine just in case the backend wants to reference them for whatever reason. */
pContext->custom = pConfig->custom;
/* Initialize the context first. If this fails we need to abort. */
result = pConfig->custom.onContextInit(pContext, &pContext->custom);
if (result != MA_SUCCESS) {
return result;
}
/* At this point the context should be initialized. We can now set up the callbacks for miniaudio's use internally. */
pContext->onUninit = ma_context_uninit__custom;
pContext->onEnumDevices = ma_context_enumerate_devices__custom;
pContext->onGetDeviceInfo = ma_context_get_device_info__custom;
pContext->onDeviceInit = ma_device_init__custom;
pContext->onDeviceUninit = ma_device_uninit__custom;
pContext->onDeviceStart = ma_device_start__custom;
pContext->onDeviceStop = ma_device_stop__custom;
/*
For now the context needs to be marked as asynchronous. This is required so that miniaudio knows how to handle data delivery and thread
management for the device, but the requirement for the backend itself to set this property will probably be removed in the future.
*/
if (pContext->custom.onDeviceRead == NULL && pContext->custom.onDeviceWrite == NULL) {
if (pContext->custom.onDeviceAudioThread == NULL) {
pContext->onDeviceMainLoop = NULL; /* Backend is asynchronous and expected to call ma_device_handle_backend_data_callback() from within their data callback. */
pContext->isBackendAsynchronous = MA_TRUE;
} else {
pContext->onDeviceMainLoop = ma_device_audio_thread__custom; /* Backend is doing a custom main loop. */
pContext->isBackendAsynchronous = MA_FALSE;
}
} else {
pContext->isBackendAsynchronous = MA_FALSE;
pContext->onDeviceMainLoop = ma_device_audio_thread__read_write__custom; /* Backend is using blocking read/write calls. */
}
return MA_SUCCESS;
}
#endif /* MA_HAS_CUSTOM */
/*******************************************************************************
......@@ -31814,6 +31542,15 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d
}
/* TEMP: Helper for determining whether or not a context is using the new callback system. Eventually all backends will be using the new callback system. */
static ma_bool32 ma_context__is_using_new_callbacks(ma_context* pContext)
{
MA_ASSERT(pContext != NULL);
return pContext->callbacks.onContextInit != NULL;
}
static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData)
{
ma_device* pDevice = (ma_device*)pData;
......@@ -31857,11 +31594,21 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData)
ma_device__set_state(pDevice, MA_STATE_STARTED);
ma_event_signal(&pDevice->startEvent);
if (pDevice->pContext->onDeviceMainLoop != NULL) {
pDevice->pContext->onDeviceMainLoop(pDevice);
if (ma_context__is_using_new_callbacks(pDevice->pContext)) {
if (pDevice->pContext->callbacks.onDeviceAudioThread != NULL) {
pDevice->pContext->callbacks.onDeviceAudioThread(pDevice);
} else {
/* The backend is not using a custom main loop implementation, so now fall back to the blocking read-write implementation. */
ma_device_audio_thread__default_read_write(pDevice, &pDevice->pContext->callbacks);
}
} else {
ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "No main loop implementation.", MA_API_NOT_FOUND);
if (pDevice->pContext->onDeviceMainLoop != NULL) {
pDevice->pContext->onDeviceMainLoop(pDevice);
} else {
ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "No main loop implementation.", MA_API_NOT_FOUND);
}
}
/*
Getting here means we have broken from the main loop which happens the application has requested that device be stopped. Note that this
......@@ -32065,7 +31812,21 @@ static ma_result ma_context_uninit_backend_apis(ma_context* pContext)
static ma_bool32 ma_context_is_backend_asynchronous(ma_context* pContext)
{
return pContext->isBackendAsynchronous;
MA_ASSERT(pContext != NULL);
if (ma_context__is_using_new_callbacks(pContext)) {
if (pContext->callbacks.onDeviceRead == NULL && pContext->callbacks.onDeviceWrite == NULL) {
if (pContext->callbacks.onDeviceAudioThread == NULL) {
return MA_TRUE;
} else {
return MA_FALSE;
}
} else {
return MA_FALSE;
}
} else {
return pContext->isBackendAsynchronous;
}
}
......@@ -32143,102 +31904,121 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC
*/
pContext->isBackendAsynchronous = MA_FALSE;
result = MA_NO_BACKEND;
/* These backends are using the new callback system. */
switch (backend) {
#ifdef MA_HAS_WASAPI
case ma_backend_wasapi:
{
result = ma_context_init__wasapi(&config, pContext);
} break;
#endif
#ifdef MA_HAS_DSOUND
case ma_backend_dsound:
{
result = ma_context_init__dsound(&config, pContext);
} break;
#endif
#ifdef MA_HAS_WINMM
case ma_backend_winmm:
{
result = ma_context_init__winmm(&config, pContext);
} break;
#endif
#ifdef MA_HAS_ALSA
case ma_backend_alsa:
{
result = ma_context_init__alsa(&config, pContext);
} break;
#endif
#ifdef MA_HAS_PULSEAUDIO
case ma_backend_pulseaudio:
{
result = ma_context_init__pulse(&config, pContext);
} break;
#endif
#ifdef MA_HAS_JACK
case ma_backend_jack:
{
result = ma_context_init__jack(&config, pContext);
} break;
#endif
#ifdef MA_HAS_COREAUDIO
case ma_backend_coreaudio:
{
result = ma_context_init__coreaudio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_SNDIO
case ma_backend_sndio:
{
result = ma_context_init__sndio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_AUDIO4
case ma_backend_audio4:
{
result = ma_context_init__audio4(&config, pContext);
} break;
#endif
#ifdef MA_HAS_OSS
case ma_backend_oss:
{
result = ma_context_init__oss(&config, pContext);
} break;
#endif
#ifdef MA_HAS_AAUDIO
case ma_backend_aaudio:
{
result = ma_context_init__aaudio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_OPENSL
case ma_backend_opensl:
{
result = ma_context_init__opensl(&config, pContext);
} break;
#endif
#ifdef MA_HAS_WEBAUDIO
case ma_backend_webaudio:
{
result = ma_context_init__webaudio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_CUSTOM
case ma_backend_custom:
{
result = ma_context_init__custom(&config, pContext);
} break;
#endif
#ifdef MA_HAS_NULL
case ma_backend_null:
{
result = ma_context_init__null(&config, pContext);
/* Slightly different logic for custom backends. Custom backends can optionally set all of their callbacks in the config. */
pContext->callbacks = config.custom;
} break;
#endif
default: break;
}
if (pContext->callbacks.onContextInit != NULL) {
result = pContext->callbacks.onContextInit(pContext, &pContext->callbacks);
} else {
result = MA_NO_BACKEND;
/* TEMP. Try falling back to the old callback system. Eventually this switch will be removed completely. */
switch (backend) {
#ifdef MA_HAS_WASAPI
case ma_backend_wasapi:
{
result = ma_context_init__wasapi(&config, pContext);
} break;
#endif
#ifdef MA_HAS_DSOUND
case ma_backend_dsound:
{
result = ma_context_init__dsound(&config, pContext);
} break;
#endif
#ifdef MA_HAS_WINMM
case ma_backend_winmm:
{
result = ma_context_init__winmm(&config, pContext);
} break;
#endif
#ifdef MA_HAS_ALSA
case ma_backend_alsa:
{
result = ma_context_init__alsa(&config, pContext);
} break;
#endif
#ifdef MA_HAS_PULSEAUDIO
case ma_backend_pulseaudio:
{
result = ma_context_init__pulse(&config, pContext);
} break;
#endif
#ifdef MA_HAS_JACK
case ma_backend_jack:
{
result = ma_context_init__jack(&config, pContext);
} break;
#endif
#ifdef MA_HAS_COREAUDIO
case ma_backend_coreaudio:
{
result = ma_context_init__coreaudio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_SNDIO
case ma_backend_sndio:
{
result = ma_context_init__sndio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_AUDIO4
case ma_backend_audio4:
{
result = ma_context_init__audio4(&config, pContext);
} break;
#endif
#ifdef MA_HAS_OSS
case ma_backend_oss:
{
result = ma_context_init__oss(&config, pContext);
} break;
#endif
#ifdef MA_HAS_AAUDIO
case ma_backend_aaudio:
{
result = ma_context_init__aaudio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_OPENSL
case ma_backend_opensl:
{
result = ma_context_init__opensl(&config, pContext);
} break;
#endif
#ifdef MA_HAS_WEBAUDIO
case ma_backend_webaudio:
{
result = ma_context_init__webaudio(&config, pContext);
} break;
#endif
#ifdef MA_HAS_CUSTOM
case ma_backend_custom:
{
/*result = ma_context_init__custom(&config, pContext);*/
} break;
#endif
#ifdef MA_HAS_NULL
case ma_backend_null:
{
result = ma_context_init__null(&config, pContext);
} break;
#endif
default: break;
}
}
/* If this iteration was successful, return. */
if (result == MA_SUCCESS) {
result = ma_mutex_init(&pContext->deviceEnumLock);
......@@ -32294,15 +32074,31 @@ MA_API ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devi
{
ma_result result;
if (pContext == NULL || pContext->onEnumDevices == NULL || callback == NULL) {
if (pContext == NULL || callback == NULL) {
return MA_INVALID_ARGS;
}
ma_mutex_lock(&pContext->deviceEnumLock);
{
result = pContext->onEnumDevices(pContext, callback, pUserData);
if (ma_context__is_using_new_callbacks(pContext)) {
if (pContext->callbacks.onContextEnumerateDevices == NULL) {
return MA_INVALID_OPERATION;
}
ma_mutex_lock(&pContext->deviceEnumLock);
{
result = pContext->callbacks.onContextEnumerateDevices(pContext, callback, pUserData);
}
ma_mutex_unlock(&pContext->deviceEnumLock);
} else {
if (pContext->onEnumDevices == NULL) {
return MA_INVALID_OPERATION;
}
ma_mutex_lock(&pContext->deviceEnumLock);
{
result = pContext->onEnumDevices(pContext, callback, pUserData);
}
ma_mutex_unlock(&pContext->deviceEnumLock);
}
ma_mutex_unlock(&pContext->deviceEnumLock);
return result;
}
......@@ -32371,6 +32167,16 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p
return MA_INVALID_ARGS;
}
if (ma_context__is_using_new_callbacks(pContext)) {
if (pContext->callbacks.onContextEnumerateDevices == NULL) {
return MA_INVALID_OPERATION;
}
} else {
if (pContext->onEnumDevices == NULL) {
return MA_INVALID_OPERATION;
}
}
/* Note that we don't use ma_context_enumerate_devices() here because we want to do locking at a higher level. */
ma_mutex_lock(&pContext->deviceEnumLock);
{
......@@ -32379,7 +32185,12 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p
pContext->captureDeviceInfoCount = 0;
/* Now enumerate over available devices. */
result = pContext->onEnumDevices(pContext, ma_context_get_devices__enum_callback, NULL);
if (ma_context__is_using_new_callbacks(pContext)) {
result = pContext->callbacks.onContextEnumerateDevices(pContext, ma_context_get_devices__enum_callback, NULL);
} else {
result = pContext->onEnumDevices(pContext, ma_context_get_devices__enum_callback, NULL);
}
if (result == MA_SUCCESS) {
/* Playback devices. */
if (ppPlaybackDeviceInfos != NULL) {
......@@ -32405,6 +32216,7 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p
MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo)
{
ma_result result;
ma_device_info deviceInfo;
/* NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. */
......@@ -32419,95 +32231,103 @@ MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type
MA_COPY_MEMORY(&deviceInfo.id, pDeviceID, sizeof(*pDeviceID));
}
if (pContext->onGetDeviceInfo != NULL) {
ma_result result;
ma_mutex_lock(&pContext->deviceInfoLock);
{
if (ma_context__is_using_new_callbacks(pContext)) {
if (pContext->callbacks.onContextGetDeviceInfo == NULL) {
return MA_INVALID_OPERATION;
}
} else {
if (pContext->onGetDeviceInfo == NULL) {
return MA_INVALID_OPERATION;
}
}
ma_mutex_lock(&pContext->deviceInfoLock);
{
if (ma_context__is_using_new_callbacks(pContext)) {
result = pContext->callbacks.onContextGetDeviceInfo(pContext, deviceType, pDeviceID, &deviceInfo);
} else {
result = pContext->onGetDeviceInfo(pContext, deviceType, pDeviceID, shareMode, &deviceInfo);
}
ma_mutex_unlock(&pContext->deviceInfoLock);
}
ma_mutex_unlock(&pContext->deviceInfoLock);
/*
If the backend is using the new device info system, do a pass to fill out the old settings for backwards compatibility. This will be removed in
the future when all backends have implemented the new device info system.
*/
if (deviceInfo.nativeDataFormatCount > 0) {
ma_uint32 iNativeFormat;
ma_uint32 iSampleFormat;
deviceInfo.minChannels = 0xFFFFFFFF;
deviceInfo.maxChannels = 0;
deviceInfo.minSampleRate = 0xFFFFFFFF;
deviceInfo.maxSampleRate = 0;
/*
If the backend is using the new device info system, do a pass to fill out the old settings for backwards compatibility. This will be removed in
the future when all backends have implemented the new device info system.
*/
if (deviceInfo.nativeDataFormatCount > 0) {
ma_uint32 iNativeFormat;
ma_uint32 iSampleFormat;
deviceInfo.minChannels = 0xFFFFFFFF;
deviceInfo.maxChannels = 0;
deviceInfo.minSampleRate = 0xFFFFFFFF;
deviceInfo.maxSampleRate = 0;
for (iNativeFormat = 0; iNativeFormat < deviceInfo.nativeDataFormatCount; iNativeFormat += 1) {
/* Formats. */
if (deviceInfo.nativeDataFormats[iNativeFormat].format == ma_format_unknown) {
/* All formats are supported. */
deviceInfo.formats[0] = ma_format_u8;
deviceInfo.formats[1] = ma_format_s16;
deviceInfo.formats[2] = ma_format_s24;
deviceInfo.formats[3] = ma_format_s32;
deviceInfo.formats[4] = ma_format_f32;
deviceInfo.formatCount = ma_format_count;
} else {
/* Make sure the format isn't already in the list. If so, skip. */
ma_bool32 alreadyExists = MA_FALSE;
for (iSampleFormat = 0; iSampleFormat < deviceInfo.formatCount; iSampleFormat += 1) {
if (deviceInfo.formats[iSampleFormat] == deviceInfo.nativeDataFormats[iNativeFormat].format) {
alreadyExists = MA_TRUE;
break;
}
for (iNativeFormat = 0; iNativeFormat < deviceInfo.nativeDataFormatCount; iNativeFormat += 1) {
/* Formats. */
if (deviceInfo.nativeDataFormats[iNativeFormat].format == ma_format_unknown) {
/* All formats are supported. */
deviceInfo.formats[0] = ma_format_u8;
deviceInfo.formats[1] = ma_format_s16;
deviceInfo.formats[2] = ma_format_s24;
deviceInfo.formats[3] = ma_format_s32;
deviceInfo.formats[4] = ma_format_f32;
deviceInfo.formatCount = ma_format_count;
} else {
/* Make sure the format isn't already in the list. If so, skip. */
ma_bool32 alreadyExists = MA_FALSE;
for (iSampleFormat = 0; iSampleFormat < deviceInfo.formatCount; iSampleFormat += 1) {
if (deviceInfo.formats[iSampleFormat] == deviceInfo.nativeDataFormats[iNativeFormat].format) {
alreadyExists = MA_TRUE;
break;
}
}
if (!alreadyExists) {
deviceInfo.formats[deviceInfo.formatCount++] = deviceInfo.nativeDataFormats[iNativeFormat].format;
}
if (!alreadyExists) {
deviceInfo.formats[deviceInfo.formatCount++] = deviceInfo.nativeDataFormats[iNativeFormat].format;
}
}
/* Channels. */
if (deviceInfo.nativeDataFormats[iNativeFormat].channels == 0) {
/* All channels supported. */
deviceInfo.minChannels = MA_MIN_CHANNELS;
deviceInfo.maxChannels = MA_MAX_CHANNELS;
} else {
if (deviceInfo.minChannels > deviceInfo.nativeDataFormats[iNativeFormat].channels) {
deviceInfo.minChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels;
}
if (deviceInfo.maxChannels < deviceInfo.nativeDataFormats[iNativeFormat].channels) {
deviceInfo.maxChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels;
}
/* Channels. */
if (deviceInfo.nativeDataFormats[iNativeFormat].channels == 0) {
/* All channels supported. */
deviceInfo.minChannels = MA_MIN_CHANNELS;
deviceInfo.maxChannels = MA_MAX_CHANNELS;
} else {
if (deviceInfo.minChannels > deviceInfo.nativeDataFormats[iNativeFormat].channels) {
deviceInfo.minChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels;
}
if (deviceInfo.maxChannels < deviceInfo.nativeDataFormats[iNativeFormat].channels) {
deviceInfo.maxChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels;
}
}
/* Sample rate. */
if (deviceInfo.nativeDataFormats[iNativeFormat].sampleRate == 0) {
/* All sample rates supported. */
deviceInfo.minSampleRate = MA_MIN_SAMPLE_RATE;
deviceInfo.maxSampleRate = MA_MAX_SAMPLE_RATE;
} else {
if (deviceInfo.minSampleRate > deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) {
deviceInfo.minSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate;
}
if (deviceInfo.maxSampleRate < deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) {
deviceInfo.maxSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate;
}
/* Sample rate. */
if (deviceInfo.nativeDataFormats[iNativeFormat].sampleRate == 0) {
/* All sample rates supported. */
deviceInfo.minSampleRate = MA_MIN_SAMPLE_RATE;
deviceInfo.maxSampleRate = MA_MAX_SAMPLE_RATE;
} else {
if (deviceInfo.minSampleRate > deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) {
deviceInfo.minSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate;
}
if (deviceInfo.maxSampleRate < deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) {
deviceInfo.maxSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate;
}
}
}
}
/* Clamp ranges. */
deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS);
deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS);
deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, MA_MIN_SAMPLE_RATE);
deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, MA_MAX_SAMPLE_RATE);
/* Clamp ranges. */
deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS);
deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS);
deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, MA_MIN_SAMPLE_RATE);
deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, MA_MAX_SAMPLE_RATE);
*pDeviceInfo = deviceInfo;
return result;
}
/* Getting here means onGetDeviceInfo has not been set. */
return MA_ERROR;
*pDeviceInfo = deviceInfo;
return result;
}
MA_API ma_bool32 ma_context_is_loopback_supported(ma_context* pContext)
......@@ -32539,16 +32359,34 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
ma_result result;
ma_device_config config;
/* The context can be null, in which case we self-manage it. */
if (pContext == NULL) {
return ma_device_init_ex(NULL, 0, NULL, pConfig, pDevice);
}
if (pDevice == NULL) {
return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS);
}
MA_ZERO_OBJECT(pDevice);
if (pConfig == NULL) {
return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pConfig == NULL).", MA_INVALID_ARGS);
}
/* Check that we have our callbacks defined. */
if (ma_context__is_using_new_callbacks(pContext)) {
if (pContext->callbacks.onDeviceInit == NULL) {
return MA_INVALID_OPERATION;
}
} else {
if (pContext->onDeviceInit == NULL) {
return MA_INVALID_OPERATION;
}
}
/* We need to make a copy of the config so we can set default values if they were left unset in the input config. */
config = *pConfig;
......@@ -32575,8 +32413,6 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
}
}
MA_ZERO_OBJECT(pDevice);
pDevice->pContext = pContext;
/* Set the user data and log callback ASAP to ensure it is available for the entire initialization process. */
......@@ -32593,6 +32429,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
if (config.playback.pDeviceID != NULL) {
MA_COPY_MEMORY(&pDevice->playback.id, config.playback.pDeviceID, sizeof(pDevice->playback.id));
}
if (config.capture.pDeviceID != NULL) {
MA_COPY_MEMORY(&pDevice->capture.id, config.capture.pDeviceID, sizeof(pDevice->capture.id));
}
......@@ -32658,8 +32495,8 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
MA_ASSERT(config.capture.channels <= MA_MAX_CHANNELS);
MA_ASSERT(config.playback.channels <= MA_MAX_CHANNELS);
pDevice->type = config.deviceType;
pDevice->sampleRate = config.sampleRate;
pDevice->type = config.deviceType;
pDevice->sampleRate = config.sampleRate;
pDevice->resampling.algorithm = config.resampling.algorithm;
pDevice->resampling.linear.lpfOrder = config.resampling.linear.lpfOrder;
pDevice->resampling.speex.quality = config.resampling.speex.quality;
......@@ -32720,10 +32557,130 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
}
result = pContext->onDeviceInit(pContext, &config, pDevice);
if (result != MA_SUCCESS) {
return result;
if (ma_context__is_using_new_callbacks(pContext)) {
ma_device_descriptor descriptorPlayback;
ma_device_descriptor descriptorCapture;
MA_ZERO_OBJECT(&descriptorPlayback);
descriptorPlayback.pDeviceID = pConfig->playback.pDeviceID;
descriptorPlayback.shareMode = pConfig->playback.shareMode;
descriptorPlayback.format = pConfig->playback.format;
descriptorPlayback.channels = pConfig->playback.channels;
descriptorPlayback.sampleRate = pConfig->sampleRate;
ma_channel_map_copy(descriptorPlayback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels);
descriptorPlayback.periodSizeInFrames = pConfig->periodSizeInFrames;
descriptorPlayback.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds;
descriptorPlayback.periodCount = pConfig->periods;
MA_ZERO_OBJECT(&descriptorCapture);
descriptorCapture.pDeviceID = pConfig->capture.pDeviceID;
descriptorCapture.shareMode = pConfig->capture.shareMode;
descriptorCapture.format = pConfig->capture.format;
descriptorCapture.channels = pConfig->capture.channels;
descriptorCapture.sampleRate = pConfig->sampleRate;
ma_channel_map_copy(descriptorCapture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels);
descriptorCapture.periodSizeInFrames = pConfig->periodSizeInFrames;
descriptorCapture.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds;
descriptorCapture.periodCount = pConfig->periods;
result = pContext->callbacks.onDeviceInit(pDevice, config.deviceType, &descriptorPlayback, &descriptorCapture);
if (result != MA_SUCCESS) {
ma_event_uninit(&pDevice->startEvent);
ma_event_uninit(&pDevice->wakeupEvent);
ma_mutex_uninit(&pDevice->lock);
return result;
}
/*
On output the descriptors will contain the *actual* data format of the device. We need this to know how to convert the data between
the requested format and the internal format.
*/
if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) {
ma_device_info deviceInfo; /* For retrieving the name. */
if (!ma_device_descriptor_is_valid(&descriptorCapture)) {
ma_device_uninit(pDevice);
return MA_INVALID_ARGS;
}
pDevice->capture.internalFormat = descriptorCapture.format;
pDevice->capture.internalChannels = descriptorCapture.channels;
pDevice->capture.internalSampleRate = descriptorCapture.sampleRate;
ma_channel_map_copy(pDevice->capture.internalChannelMap, descriptorCapture.channelMap, descriptorCapture.channels);
pDevice->capture.internalPeriodSizeInFrames = descriptorCapture.periodSizeInFrames;
pDevice->capture.internalPeriods = descriptorCapture.periodCount;
if (pDevice->capture.internalPeriodSizeInFrames == 0) {
pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorCapture.periodSizeInMilliseconds, descriptorCapture.sampleRate);
}
/* The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info()` instead. */
result = ma_context_get_device_info(pContext, ma_device_type_capture, descriptorCapture.pDeviceID, descriptorCapture.shareMode, &deviceInfo);
if (result == MA_SUCCESS) {
ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1);
} else {
/* We failed to retrieve the device info. Fall back to a default name. */
if (descriptorCapture.pDeviceID == NULL) {
ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1);
} else {
ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1);
}
}
}
if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) {
ma_device_info deviceInfo; /* For retrieving the name. */
if (!ma_device_descriptor_is_valid(&descriptorPlayback)) {
ma_device_uninit(pDevice);
return MA_INVALID_ARGS;
}
pDevice->playback.internalFormat = descriptorPlayback.format;
pDevice->playback.internalChannels = descriptorPlayback.channels;
pDevice->playback.internalSampleRate = descriptorPlayback.sampleRate;
ma_channel_map_copy(pDevice->playback.internalChannelMap, descriptorPlayback.channelMap, descriptorPlayback.channels);
pDevice->playback.internalPeriodSizeInFrames = descriptorPlayback.periodSizeInFrames;
pDevice->playback.internalPeriods = descriptorPlayback.periodCount;
if (pDevice->playback.internalPeriodSizeInFrames == 0) {
pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorPlayback.periodSizeInMilliseconds, descriptorPlayback.sampleRate);
}
/* The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. */
result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, descriptorPlayback.shareMode, &deviceInfo);
if (result == MA_SUCCESS) {
ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1);
} else {
/* We failed to retrieve the device info. Fall back to a default name. */
if (descriptorPlayback.pDeviceID == NULL) {
ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1);
} else {
ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1);
}
}
}
/* If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. */
if (ma_context_is_backend_asynchronous(pContext)) {
if (pConfig->deviceType == ma_device_type_duplex) {
result = ma_duplex_rb_init(pDevice->sampleRate, pDevice->capture.internalFormat, pDevice->capture.internalChannels, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB);
if (result != MA_SUCCESS) {
ma_device_uninit(pDevice);
return result;
}
}
}
} else {
result = pContext->onDeviceInit(pContext, &config, pDevice);
if (result != MA_SUCCESS) {
ma_event_uninit(&pDevice->startEvent);
ma_event_uninit(&pDevice->wakeupEvent);
ma_mutex_uninit(&pDevice->lock);
return result;
}
}
ma_device__post_init_setup(pDevice, pConfig->deviceType);
......@@ -32859,7 +32816,16 @@ MA_API void ma_device_uninit(ma_device* pDevice)
ma_thread_wait(&pDevice->thread);
}
pDevice->pContext->onDeviceUninit(pDevice);
if (ma_context__is_using_new_callbacks(pDevice->pContext)) {
if (pDevice->pContext->callbacks.onDeviceUninit != NULL) {
pDevice->pContext->callbacks.onDeviceUninit(pDevice);
}
} else {
if (pDevice->pContext->onDeviceUninit != NULL) {
pDevice->pContext->onDeviceUninit(pDevice);
}
}
ma_event_uninit(&pDevice->stopEvent);
ma_event_uninit(&pDevice->startEvent);
......@@ -32902,7 +32868,20 @@ MA_API ma_result ma_device_start(ma_device* pDevice)
/* Asynchronous backends need to be handled differently. */
if (ma_context_is_backend_asynchronous(pDevice->pContext)) {
result = pDevice->pContext->onDeviceStart(pDevice);
if (ma_context__is_using_new_callbacks(pDevice->pContext)) {
if (pDevice->pContext->callbacks.onDeviceStart != NULL) {
result = pDevice->pContext->callbacks.onDeviceStart(pDevice);
} else {
result = MA_INVALID_OPERATION;
}
} else {
if (pDevice->pContext->onDeviceStart != NULL) {
result = pDevice->pContext->onDeviceStart(pDevice);
} else {
result = MA_INVALID_OPERATION;
}
}
if (result == MA_SUCCESS) {
ma_device__set_state(pDevice, MA_STATE_STARTED);
}
......@@ -32920,6 +32899,11 @@ MA_API ma_result ma_device_start(ma_device* pDevice)
ma_event_wait(&pDevice->startEvent);
result = pDevice->workResult;
}
/* We changed the state from stopped to started, so if we failed, make sure we put the state back to stopped. */
if (result != MA_SUCCESS) {
ma_device__set_state(pDevice, MA_STATE_STOPPED);
}
}
ma_mutex_unlock(&pDevice->lock);
......@@ -32950,18 +32934,39 @@ MA_API ma_result ma_device_stop(ma_device* pDevice)
ma_device__set_state(pDevice, MA_STATE_STOPPING);
/* There's no need to wake up the thread like we do when starting. */
if (pDevice->pContext->onDeviceStop) {
result = pDevice->pContext->onDeviceStop(pDevice);
} else {
result = MA_SUCCESS;
}
/* Asynchronous backends need to be handled differently. */
if (ma_context_is_backend_asynchronous(pDevice->pContext)) {
/* Asynchronous backends must have a stop operation. */
if (ma_context__is_using_new_callbacks(pDevice->pContext)) {
if (pDevice->pContext->callbacks.onDeviceStop != NULL) {
result = pDevice->pContext->callbacks.onDeviceStop(pDevice);
} else {
result = MA_INVALID_OPERATION;
}
} else {
if (pDevice->pContext->onDeviceStop != NULL) {
result = pDevice->pContext->onDeviceStop(pDevice);
} else {
result = MA_INVALID_OPERATION;
}
}
ma_device__set_state(pDevice, MA_STATE_STOPPED);
} else {
/* Synchronous backends. */
/* Synchronous backends. Devices can optionally have a stop operation here. */
if (ma_context__is_using_new_callbacks(pDevice->pContext)) {
if (pDevice->pContext->callbacks.onDeviceStop != NULL) {
result = pDevice->pContext->callbacks.onDeviceStop(pDevice);
} else {
result = MA_SUCCESS;
}
} else {
if (pDevice->pContext->onDeviceStop != NULL) {
result = pDevice->pContext->onDeviceStop(pDevice);
} else {
result = MA_SUCCESS;
}
}
/*
We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be
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