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
mal_event stopEvent;
mal_thread thread;
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 usingDefaultPeriods : 1;
mal_bool32 exclusiveMode : 1;
......@@ -1255,7 +1259,7 @@ struct mal_device
//
// 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
// 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
//
// 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
// 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
// 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.
// Look at pDevice->internalChannels, pDevice->internalSampleRate, etc. to determine the actual
// properties after initialization.
// The device's configuration is controlled with pConfig. This allows you to configure the sample
// format, channel count, sample rate, etc. Before calling mal_device_init(), you will most likely
// 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
// <periods> is set to 0 it will default to MAL_DEFAULT_PERIODS.
// Passing in 0 to any property in pConfig will force the use of a default value. In the case of
// 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
// 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.
//
// 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
// 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.
......@@ -1351,7 +1365,7 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma
//
// Thread Safety: UNSAFE
// 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().
//
// 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);
// Helper function for initializing a mal_context_config object.
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.
//
// 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)();
#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.
//
// 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)
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.
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.
for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) {
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
if (pDevice == NULL) {
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) {
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);
pDevice->pContext = pContext;
......@@ -12253,20 +12307,22 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
}
// Basic config validation.
if (config.channels == 0) {
return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count must be greater than 0.", MAL_INVALID_DEVICE_CONFIG);
// 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.
if (config.format == mal_format_unknown) {
config.format = MAL_DEFAULT_FORMAT;
pDevice->usingDefaultFormat = MAL_TRUE;
}
if (config.channels > MAL_MAX_CHANNELS) {
return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count cannot exceed 18.", MAL_INVALID_DEVICE_CONFIG);
if (config.channels == 0) {
config.channels = MAL_DEFAULT_CHANNELS;
pDevice->usingDefaultChannels = MAL_TRUE;
}
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 (!mal__is_channel_map_valid(pConfig->channelMap, pConfig->channels)) {
return mal_post_error(pDevice, "mal_device_init() called with invalid arguments. Channel map is invalid.", MAL_INVALID_DEVICE_CONFIG);
if (config.channelMap[0] == MAL_CHANNEL_NONE) {
pDevice->usingDefaultChannelMap = MAL_TRUE;
}
......@@ -12283,8 +12339,8 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
pDevice->type = type;
pDevice->format = config.format;
pDevice->channels = config.channels;
mal_copy_memory(pDevice->channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels);
pDevice->sampleRate = config.sampleRate;
mal_copy_memory(pDevice->channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels);
pDevice->bufferSizeInFrames = config.bufferSizeInFrames;
pDevice->periods = config.periods;
......@@ -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
// format required by the backend.
......@@ -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.
if (pContext->backend != mal_backend_jack && pContext->backend != mal_backend_opensl && pContext->backend != mal_backend_sdl) {
// The worker thread.
......@@ -12774,6 +12843,31 @@ mal_context_config mal_context_config_init(mal_log_proc onLog)
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])
{
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
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_zero_object(&config);
mal_device_config config = mal_device_config_init_default();
config.format = format;
config.channels = channels;
......@@ -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));
}
return config;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// SRC
......@@ -15607,11 +15701,16 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count)
// - Add support for JACK.
// - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no
// 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 some APIs more const-correct.
// - Fix errors with OpenAL detection.
// - Fix some memory leaks.
// - Miscellaneous bug fixes.
// - Documentation updates.
//
// v0.7 - 2018-02-25
// - 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