From f2898fec7fa622670ee1d2836e825cdd3d613d98 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 29 Oct 2017 09:41:21 +1000 Subject: [PATCH] ALSA: Make format selection more robust. --- mini_al.h | 100 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/mini_al.h b/mini_al.h index 959aa815..512336e4 100644 --- a/mini_al.h +++ b/mini_al.h @@ -399,11 +399,12 @@ typedef enum { // I like to keep these explicitly defined because they're used as a key into a lookup table. When items are // added to this, make sure there are no gaps and that they're added to the lookup table in mal_get_sample_size_in_bytes(). - mal_format_u8 = 0, - mal_format_s16 = 1, // Seems to be the most widely supported format. - mal_format_s24 = 2, // Tightly packed. 3 bytes per sample. - mal_format_s32 = 3, - mal_format_f32 = 4, + mal_format_unknown = 0, // Mainly used for indicating an error. + mal_format_u8 = 1, + mal_format_s16 = 2, // Seems to be the most widely supported format. + mal_format_s24 = 3, // Tightly packed. 3 bytes per sample. + mal_format_s32 = 4, + mal_format_f32 = 5, } mal_format; typedef enum @@ -598,6 +599,8 @@ struct mal_context mal_proc snd_pcm_hw_params_sizeof; mal_proc snd_pcm_hw_params_any; mal_proc snd_pcm_hw_params_set_format; + mal_proc snd_pcm_hw_params_set_format_first; + mal_proc snd_pcm_hw_params_get_format_mask; mal_proc snd_pcm_hw_params_set_channels_near; mal_proc snd_pcm_hw_params_set_rate_near; mal_proc snd_pcm_hw_params_set_buffer_size_near; @@ -609,6 +612,8 @@ struct mal_context mal_proc snd_pcm_sw_params_set_avail_min; mal_proc snd_pcm_sw_params_set_start_threshold; mal_proc snd_pcm_sw_params; + mal_proc snd_pcm_format_mask_sizeof; + mal_proc snd_pcm_format_mask_test; mal_proc snd_pcm_get_chmap; mal_proc snd_pcm_prepare; mal_proc snd_pcm_start; @@ -4607,6 +4612,8 @@ typedef int (* mal_snd_pcm_close_proc) (sn typedef size_t (* mal_snd_pcm_hw_params_sizeof_proc) (void); typedef int (* mal_snd_pcm_hw_params_any_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params); typedef int (* mal_snd_pcm_hw_params_set_format_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val); +typedef int (* mal_snd_pcm_hw_params_set_format_first_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format); +typedef void (* mal_snd_pcm_hw_params_get_format_mask_proc) (snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask); typedef int (* mal_snd_pcm_hw_params_set_channels_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val); typedef int (* mal_snd_pcm_hw_params_set_rate_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir); typedef int (* mal_snd_pcm_hw_params_set_buffer_size_near_proc)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val); @@ -4618,6 +4625,8 @@ typedef int (* mal_snd_pcm_sw_params_current_proc) (sn typedef int (* mal_snd_pcm_sw_params_set_avail_min_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); typedef int (* mal_snd_pcm_sw_params_set_start_threshold_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); typedef int (* mal_snd_pcm_sw_params_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params); +typedef size_t (* mal_snd_pcm_format_mask_sizeof_proc) (void); +typedef int (* mal_snd_pcm_format_mask_test_proc) (const snd_pcm_format_mask_t *mask, snd_pcm_format_t val); typedef snd_pcm_chmap_t * (* mal_snd_pcm_get_chmap_proc) (snd_pcm_t *pcm); typedef int (* mal_snd_pcm_prepare_proc) (snd_pcm_t *pcm); typedef int (* mal_snd_pcm_start_proc) (snd_pcm_t *pcm); @@ -4634,6 +4643,32 @@ typedef snd_pcm_sframes_t (* mal_snd_pcm_writei_proc) (sn typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_proc) (snd_pcm_t *pcm); typedef int (* mal_snd_pcm_wait_proc) (snd_pcm_t *pcm, int timeout); +static snd_pcm_format_t g_mal_ALSAFormats[] = { + SND_PCM_FORMAT_UNKNOWN, // mal_format_unknown + SND_PCM_FORMAT_U8, // mal_format_u8 + SND_PCM_FORMAT_S16_LE, // mal_format_s16 + SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + SND_PCM_FORMAT_S32_LE, // mal_format_s32 + SND_PCM_FORMAT_FLOAT_LE // mal_format_f32 +}; + +snd_pcm_format_t mal_convert_mal_format_to_alsa_format(mal_format format) +{ + return g_mal_ALSAFormats[format]; +} + +mal_format mal_convert_alsa_format_to_mal_format(snd_pcm_format_t formatALSA) +{ + switch (formatALSA) + { + case SND_PCM_FORMAT_U8: return mal_format_u8; + case SND_PCM_FORMAT_S16_LE: return mal_format_s16; + case SND_PCM_FORMAT_S24_3LE: return mal_format_s24; + case SND_PCM_FORMAT_S32_LE: return mal_format_s32; + case SND_PCM_FORMAT_FLOAT_LE: return mal_format_f32; + default: return mal_format_unknown; + } +} mal_channel mal_convert_alsa_channel_position_to_mal_channel(unsigned int alsaChannelPos) { @@ -4684,6 +4719,8 @@ mal_result mal_context_init__alsa(mal_context* pContext) pContext->alsa.snd_pcm_hw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_sizeof"); pContext->alsa.snd_pcm_hw_params_any = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_any"); pContext->alsa.snd_pcm_hw_params_set_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format"); + pContext->alsa.snd_pcm_hw_params_set_format_first = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format_first"); + pContext->alsa.snd_pcm_hw_params_get_format_mask = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format_mask"); pContext->alsa.snd_pcm_hw_params_set_channels_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); pContext->alsa.snd_pcm_hw_params_set_rate_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_near"); pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); @@ -4695,6 +4732,8 @@ mal_result mal_context_init__alsa(mal_context* pContext) pContext->alsa.snd_pcm_sw_params_set_avail_min = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_avail_min"); pContext->alsa.snd_pcm_sw_params_set_start_threshold = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_start_threshold"); pContext->alsa.snd_pcm_sw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params"); + pContext->alsa.snd_pcm_format_mask_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_sizeof"); + pContext->alsa.snd_pcm_format_mask_test = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_test"); pContext->alsa.snd_pcm_get_chmap = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_get_chmap"); pContext->alsa.snd_pcm_prepare = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_prepare"); pContext->alsa.snd_pcm_start = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_start"); @@ -5117,16 +5156,8 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t mal_assert(pDevice != NULL); mal_zero_object(&pDevice->alsa); - snd_pcm_format_t formatALSA; - switch (pConfig->format) - { - case mal_format_u8: formatALSA = SND_PCM_FORMAT_U8; break; - case mal_format_s16: formatALSA = SND_PCM_FORMAT_S16_LE; break; - case mal_format_s24: formatALSA = SND_PCM_FORMAT_S24_3LE; break; - case mal_format_s32: formatALSA = SND_PCM_FORMAT_S32_LE; break; - case mal_format_f32: formatALSA = SND_PCM_FORMAT_FLOAT_LE; break; - return mal_post_error(pDevice, "[ALSA] Format not supported.", MAL_FORMAT_NOT_SUPPORTED); - } + snd_pcm_format_t formatALSA = mal_convert_mal_format_to_alsa_format(pConfig->format); + char deviceName[32]; if (pDeviceID == NULL) { @@ -5171,11 +5202,49 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // find any documentation for ALSA specifically, so I'm going to copy the recommendation for OSS. // Format. + // Try getting every supported format. + snd_pcm_format_mask_t* pFormatMask = (snd_pcm_format_mask_t*)alloca(((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + mal_zero_memory(pFormatMask, ((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + + ((mal_snd_pcm_hw_params_get_format_mask_proc)pContext->alsa.snd_pcm_hw_params_get_format_mask)(pHWParams, pFormatMask); + + // At this point we should have a list of supported formats, so now we need to find the best one. We first check if the requested format is + // supported, and if so, use that one. If it's not supported, we just run though a list of formats and try to find the best one. + if (!((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, formatALSA)) { + // The requested format is not supported so now try running through the list of formats and return the best one. + snd_pcm_format_t preferredFormatsALSA[] = { + SND_PCM_FORMAT_FLOAT_LE, // mal_format_f32 + SND_PCM_FORMAT_S32_LE, // mal_format_s32 + SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + SND_PCM_FORMAT_S16_LE, // mal_format_s16 + SND_PCM_FORMAT_U8 // mal_format_u8 + }; + + formatALSA = SND_PCM_FORMAT_UNKNOWN; + for (size_t i = 0; i < (sizeof(preferredFormatsALSA) / sizeof(preferredFormatsALSA[0])); ++i) { + if (((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, preferredFormatsALSA[i])) { + formatALSA = preferredFormatsALSA[i]; + break; + } + } + + if (formatALSA == SND_PCM_FORMAT_UNKNOWN) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Format not supported. The device does not support any mini_al formats.", MAL_FORMAT_NOT_SUPPORTED); + } + } + if (((mal_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", MAL_FORMAT_NOT_SUPPORTED); } + pDevice->internalFormat = mal_convert_alsa_format_to_mal_format(formatALSA); + if (pDevice->internalFormat == mal_format_unknown) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] The chosen format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + } + // Channels. if (((mal_snd_pcm_hw_params_set_channels_near_proc)pContext->alsa.snd_pcm_hw_params_set_channels_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &pConfig->channels) < 0) { mal_device_uninit__alsa(pDevice); @@ -8150,6 +8219,7 @@ mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice) mal_uint32 mal_get_sample_size_in_bytes(mal_format format) { mal_uint32 sizes[] = { + 0, // unknown 1, // u8 2, // s16 3, // s24