Commit 81ca0ccb authored by David Reid's avatar David Reid

Introduce the notion of default device configurations.

parent 165c92ec
...@@ -1102,6 +1102,10 @@ struct mal_device ...@@ -1102,6 +1102,10 @@ struct mal_device
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 usingDefaultChannels : 1;
mal_bool32 usingDefaultSampleRate : 1;
mal_bool32 usingDefaultChannelMap : 1;
mal_bool32 usingDefaultBufferSize : 1; mal_bool32 usingDefaultBufferSize : 1;
mal_bool32 usingDefaultPeriods : 1; mal_bool32 usingDefaultPeriods : 1;
mal_bool32 exclusiveMode : 1; mal_bool32 exclusiveMode : 1;
...@@ -1255,7 +1259,7 @@ struct mal_device ...@@ -1255,7 +1259,7 @@ struct mal_device
// //
// The context is used for selecting and initializing the relevant backends. // The context is used for selecting and initializing the relevant backends.
// //
// Note that the location of the device cannot change throughout it's lifetime. Consider allocating // Note that the location of the context cannot change throughout it's lifetime. Consider allocating
// the mal_context object with malloc() if this is an issue. The reason for this is that a pointer // the mal_context object with malloc() if this is an issue. The reason for this is that a pointer
// to the context is stored in the mal_device structure. // to the context is stored in the mal_device structure.
// //
...@@ -1321,27 +1325,37 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma ...@@ -1321,27 +1325,37 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma
// //
// mal_context_init(NULL, 0, NULL, &context); // mal_context_init(NULL, 0, NULL, &context);
// //
// Do not pass in null for the context if you are needing to open multiple devices. // Do not pass in null for the context if you are needing to open multiple devices. You can,
// however, use null when initializing the first device, and then use device.pContext for the
// initialization of other devices.
// //
// The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you // The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you
// can retrieve the ID by calling mal_enumerate_devices() and using the ID from the returned data. // can retrieve the ID by calling mal_enumerate_devices() and using the ID from the returned data.
// Set pDeviceID to NULL to use the default device. Do _not_ rely on the first device ID returned // Set pDeviceID to NULL to use the default device. Do _not_ rely on the first device ID returned
// by mal_enumerate_devices() to be the default device. // by mal_enumerate_devices() to be the default device.
// //
// This will try it's hardest to create a valid device, even if it means adjusting input arguments. // The device's configuration is controlled with pConfig. This allows you to configure the sample
// Look at pDevice->internalChannels, pDevice->internalSampleRate, etc. to determine the actual // format, channel count, sample rate, etc. Before calling mal_device_init(), you will most likely
// properties after initialization. // want to initialize a mal_device_config object using mal_device_config_init(),
// mal_device_config_init_playback(), etc. You can also pass in NULL for the device config in
// which case it will use defaults, but will require you to call mal_device_set_recv_callback() or
// mal_device_set_send_callback() before starting the device.
// //
// If <bufferSizeInFrames> is 0, it will default to MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If // Passing in 0 to any property in pConfig will force the use of a default value. In the case of
// <periods> is set to 0 it will default to MAL_DEFAULT_PERIODS. // sample format, channel count, sample rate and channel map it will default to the values used by
// the backend's internal device. If <bufferSizeInFrames> is 0, it will default to
// MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If <periods> is set to 0 it will default to
// MAL_DEFAULT_PERIODS.
//
// When sending or receiving data to/from a device, mini_al will internally perform a format
// conversion to convert between the format specified by pConfig and the format used internally by
// the backend. If you pass in NULL for pConfig or 0 for the sample format, channel count,
// sample rate _and_ channel map, data transmission will run on an optimized pass-through fast path.
// //
// The <periods> property controls how frequently the background thread is woken to check for more // The <periods> property controls how frequently the background thread is woken to check for more
// data. It's tied to the buffer size, so as an example, if your buffer size is equivalent to 10 // data. It's tied to the buffer size, so as an example, if your buffer size is equivalent to 10
// milliseconds and you have 2 periods, the CPU will wake up approximately every 5 milliseconds. // milliseconds and you have 2 periods, the CPU will wake up approximately every 5 milliseconds.
// //
// Use mal_device_config_init(), mal_device_config_init_playback(), etc. to initialize a
// mal_device_config object.
//
// When compiling for UWP you must ensure you call this function on the main UI thread because the // When compiling for UWP you must ensure you call this function on the main UI thread because the
// operating system may need to present the user with a message asking for permissions. Please refer // operating system may need to present the user with a message asking for permissions. Please refer
// to the official documentation for ActivateAudioInterfaceAsync() for more information. // to the official documentation for ActivateAudioInterfaceAsync() for more information.
...@@ -1351,7 +1365,7 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma ...@@ -1351,7 +1365,7 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma
// //
// Thread Safety: UNSAFE // Thread Safety: UNSAFE
// It is not safe to call this function simultaneously for different devices because some backends // It is not safe to call this function simultaneously for different devices because some backends
// depend on and mutate global state (such as OpenSL|ES). The same applies to calling this as the // depend on and mutate global state (such as OpenSL|ES). The same applies to calling this at the
// same time as mal_device_uninit(). // same time as mal_device_uninit().
// //
// Results are undefined if you try using a device before this function has returned. // Results are undefined if you try using a device before this function has returned.
...@@ -1515,6 +1529,19 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format); ...@@ -1515,6 +1529,19 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format);
// Helper function for initializing a mal_context_config object. // Helper function for initializing a mal_context_config object.
mal_context_config mal_context_config_init(mal_log_proc onLog); mal_context_config mal_context_config_init(mal_log_proc onLog);
// Initializes a default device config.
//
// A default configuration will configure the device such that the format, channel count, sample rate and channel map are
// the same as the backend's internal configuration. This means the application loses explicit control of these properties,
// but in return gets an optimized fast path for data transmission since mini_al will be releived of all format conversion
// duties. You will not typically want to use default configurations unless you have some specific low-latency requirements.
//
// mal_device_config_init(), mal_device_config_init_playback(), etc. will allow you to explicitly set the sample format,
// channel count, etc.
mal_device_config mal_device_config_init_default();
mal_device_config mal_device_config_init_default_capture(mal_recv_proc onRecvCallback);
mal_device_config mal_device_config_init_default_playback(mal_send_proc onSendCallback);
// Helper function for initializing a mal_device_config object. // Helper function for initializing a mal_device_config object.
// //
// This is just a helper API, and as such the returned object can be safely modified as needed. // This is just a helper API, and as such the returned object can be safely modified as needed.
...@@ -1999,6 +2026,21 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); ...@@ -1999,6 +2026,21 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)();
#define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped. #define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped.
// The default format when mal_format_unknown (0) is requested when initializing a device.
#ifndef MAL_DEFAULT_FORMAT
#define MAL_DEFAULT_FORMAT mal_format_f32
#endif
// The default channel count to use when 0 is used when initializing a device.
#ifndef MAL_DEFAULT_CHANNELS
#define MAL_DEFAULT_CHANNELS 2
#endif
// The default sample rate to use when 0 is used when initializing a device.
#ifndef MAL_DEFAULT_SAMPLE_RATE
#define MAL_DEFAULT_SAMPLE_RATE 48000
#endif
// The default size of the device's buffer in milliseconds. // The default size of the device's buffer in milliseconds.
// //
// If this is too small you may get underruns and overruns in which case you'll need to either increase // If this is too small you may get underruns and overruns in which case you'll need to either increase
...@@ -11514,10 +11556,12 @@ static mal_result mal_device__stop_backend__sdl(mal_device* pDevice) ...@@ -11514,10 +11556,12 @@ static mal_result mal_device__stop_backend__sdl(mal_device* pDevice)
mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 channels) mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 channels)
{ {
mal_assert(channels > 0);
// A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context. // A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context.
if (channelMap[0] != MAL_CHANNEL_NONE) { if (channelMap[0] != MAL_CHANNEL_NONE) {
if (channels == 0) {
return MAL_FALSE; // No channels.
}
// A channel cannot be present in the channel map more than once. // A channel cannot be present in the channel map more than once.
for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) {
for (mal_uint32 jChannel = iChannel + 1; jChannel < channels; ++jChannel) { for (mal_uint32 jChannel = iChannel + 1; jChannel < channels; ++jChannel) {
...@@ -12229,13 +12273,23 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -12229,13 +12273,23 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
if (pDevice == NULL) { if (pDevice == NULL) {
return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pDevice == NULL).", MAL_INVALID_ARGS); return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pDevice == NULL).", MAL_INVALID_ARGS);
} }
// The config is allowed to be NULL, in which case we default to mal_device_config_init_default().
mal_device_config config;
if (pConfig == NULL) { if (pConfig == NULL) {
return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pConfig == NULL).", MAL_INVALID_ARGS); config = mal_device_config_init_default();
} else {
config = *pConfig;
} }
// Basic config validation.
if (config.channels > MAL_MAX_CHANNELS) {
return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count cannot exceed 32.", MAL_INVALID_DEVICE_CONFIG);
}
if (!mal__is_channel_map_valid(config.channelMap, config.channels)) {
return mal_post_error(pDevice, "mal_device_init() called with invalid config. Channel map is invalid.", MAL_INVALID_DEVICE_CONFIG);
}
// Make a copy of the config to ensure we don't override the caller's object.
mal_device_config config = *pConfig;
mal_zero_object(pDevice); mal_zero_object(pDevice);
pDevice->pContext = pContext; pDevice->pContext = pContext;
...@@ -12253,20 +12307,22 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -12253,20 +12307,22 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
} }
// Basic config validation. // 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
if (config.channels == 0) { // 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.
return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count must be greater than 0.", MAL_INVALID_DEVICE_CONFIG); if (config.format == mal_format_unknown) {
config.format = MAL_DEFAULT_FORMAT;
pDevice->usingDefaultFormat = MAL_TRUE;
} }
if (config.channels > MAL_MAX_CHANNELS) { if (config.channels == 0) {
return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count cannot exceed 18.", MAL_INVALID_DEVICE_CONFIG); config.channels = MAL_DEFAULT_CHANNELS;
pDevice->usingDefaultChannels = MAL_TRUE;
} }
if (config.sampleRate == 0) { if (config.sampleRate == 0) {
return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Sample rate must be greater than 0.", MAL_INVALID_DEVICE_CONFIG); config.sampleRate = MAL_DEFAULT_SAMPLE_RATE;
pDevice->usingDefaultSampleRate = MAL_TRUE;
} }
if (config.channelMap[0] == MAL_CHANNEL_NONE) {
if (!mal__is_channel_map_valid(pConfig->channelMap, pConfig->channels)) { pDevice->usingDefaultChannelMap = MAL_TRUE;
return mal_post_error(pDevice, "mal_device_init() called with invalid arguments. Channel map is invalid.", MAL_INVALID_DEVICE_CONFIG);
} }
...@@ -12283,8 +12339,8 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -12283,8 +12339,8 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
pDevice->type = type; pDevice->type = type;
pDevice->format = config.format; pDevice->format = config.format;
pDevice->channels = config.channels; pDevice->channels = config.channels;
mal_copy_memory(pDevice->channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels);
pDevice->sampleRate = config.sampleRate; pDevice->sampleRate = config.sampleRate;
mal_copy_memory(pDevice->channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels);
pDevice->bufferSizeInFrames = config.bufferSizeInFrames; pDevice->bufferSizeInFrames = config.bufferSizeInFrames;
pDevice->periods = config.periods; pDevice->periods = config.periods;
...@@ -12418,6 +12474,20 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -12418,6 +12474,20 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
} }
} }
// 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));
}
// We need a DSP object which is where samples are moved through in order to convert them to the // We need a DSP object which is where samples are moved through in order to convert them to the
// format required by the backend. // format required by the backend.
...@@ -12447,7 +12517,6 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi ...@@ -12447,7 +12517,6 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
// Some backends don't require the worker thread. // Some backends don't require the worker thread.
if (pContext->backend != mal_backend_jack && pContext->backend != mal_backend_opensl && pContext->backend != mal_backend_sdl) { if (pContext->backend != mal_backend_jack && pContext->backend != mal_backend_opensl && pContext->backend != mal_backend_sdl) {
// The worker thread. // The worker thread.
...@@ -12774,6 +12843,31 @@ mal_context_config mal_context_config_init(mal_log_proc onLog) ...@@ -12774,6 +12843,31 @@ mal_context_config mal_context_config_init(mal_log_proc onLog)
return config; return config;
} }
mal_device_config mal_device_config_init_default()
{
mal_device_config config;
mal_zero_object(&config);
return config;
}
mal_device_config mal_device_config_init_default_capture(mal_recv_proc onRecvCallback)
{
mal_device_config config = mal_device_config_init_default();
config.onRecvCallback = onRecvCallback;
return config;
}
mal_device_config mal_device_config_init_default_playback(mal_send_proc onSendCallback)
{
mal_device_config config = mal_device_config_init_default();
config.onSendCallback = onSendCallback;
return config;
}
static void mal_get_default_device_config_channel_map(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) static void mal_get_default_device_config_channel_map(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS])
{ {
mal_zero_memory(channelMap, sizeof(mal_channel)*MAL_MAX_CHANNELS); mal_zero_memory(channelMap, sizeof(mal_channel)*MAL_MAX_CHANNELS);
...@@ -12857,8 +12951,7 @@ static void mal_get_default_device_config_channel_map(mal_uint32 channels, mal_c ...@@ -12857,8 +12951,7 @@ static void mal_get_default_device_config_channel_map(mal_uint32 channels, mal_c
mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback, mal_send_proc onSendCallback)
{ {
mal_device_config config; mal_device_config config = mal_device_config_init_default();
mal_zero_object(&config);
config.format = format; config.format = format;
config.channels = channels; config.channels = channels;
...@@ -12872,11 +12965,12 @@ mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channe ...@@ -12872,11 +12965,12 @@ mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channe
mal_copy_memory(config.channelMap, channelMap, sizeof(config.channelMap)); mal_copy_memory(config.channelMap, channelMap, sizeof(config.channelMap));
} }
return config; return config;
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// SRC // SRC
...@@ -15607,11 +15701,16 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) ...@@ -15607,11 +15701,16 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count)
// - Add support for JACK. // - Add support for JACK.
// - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no // - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no
// longer required to build mini_al. // longer required to build mini_al.
// - Introduce the notion of default device configurations. A default config uses the same configuration
// as the backend's internal device, and as such results in a pass-through data transmission pipeline.
// - Add support for passing in NULL for the device config in mal_device_init(), which uses a default
// config. This requires manually calling mal_device_set_send/recv_callback().
// - Make mal_device_init_ex() more robust. // - Make mal_device_init_ex() more robust.
// - Make some APIs more const-correct. // - Make some APIs more const-correct.
// - Fix errors with OpenAL detection. // - Fix errors with OpenAL detection.
// - Fix some memory leaks. // - Fix some memory leaks.
// - Miscellaneous bug fixes. // - Miscellaneous bug fixes.
// - Documentation updates.
// //
// v0.7 - 2018-02-25 // v0.7 - 2018-02-25
// - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts. // - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts.
......
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