Commit ccd9e736 authored by David Reid's avatar David Reid

ALSA: Miscellaneous improvements:

  * Generally improve latency.
  * Get MMAP mode working with playback devices.
    * This can be disabled with the alsa.noMMap config.
  * No longer default to using "plughw" devices and instead just use "hw"
    devices where appropriate.
    * This can be disabled with the alsa.preferPlugHW config.
parent be65f148
...@@ -547,6 +547,12 @@ typedef struct ...@@ -547,6 +547,12 @@ typedef struct
mal_recv_proc onRecvCallback; mal_recv_proc onRecvCallback;
mal_send_proc onSendCallback; mal_send_proc onSendCallback;
mal_stop_proc onStopCallback; mal_stop_proc onStopCallback;
struct
{
mal_bool32 preferPlugHW;
mal_bool32 noMMap; // Disables MMap mode.
} alsa;
} mal_device_config; } mal_device_config;
struct mal_context struct mal_context
...@@ -629,6 +635,7 @@ struct mal_context ...@@ -629,6 +635,7 @@ struct mal_context
mal_proc snd_pcm_readi; mal_proc snd_pcm_readi;
mal_proc snd_pcm_writei; mal_proc snd_pcm_writei;
mal_proc snd_pcm_avail; mal_proc snd_pcm_avail;
mal_proc snd_pcm_avail_update;
mal_proc snd_pcm_wait; mal_proc snd_pcm_wait;
} alsa; } alsa;
#endif #endif
...@@ -4828,6 +4835,7 @@ typedef int (* mal_snd_pcm_recover_proc) (sn ...@@ -4828,6 +4835,7 @@ typedef int (* mal_snd_pcm_recover_proc) (sn
typedef snd_pcm_sframes_t (* mal_snd_pcm_readi_proc) (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size); typedef snd_pcm_sframes_t (* mal_snd_pcm_readi_proc) (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size);
typedef snd_pcm_sframes_t (* mal_snd_pcm_writei_proc) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); typedef snd_pcm_sframes_t (* mal_snd_pcm_writei_proc) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_proc) (snd_pcm_t *pcm); typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_proc) (snd_pcm_t *pcm);
typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_update_proc) (snd_pcm_t *pcm);
typedef int (* mal_snd_pcm_wait_proc) (snd_pcm_t *pcm, int timeout); typedef int (* mal_snd_pcm_wait_proc) (snd_pcm_t *pcm, int timeout);
static snd_pcm_format_t g_mal_ALSAFormats[] = { static snd_pcm_format_t g_mal_ALSAFormats[] = {
...@@ -4935,6 +4943,7 @@ mal_result mal_context_init__alsa(mal_context* pContext) ...@@ -4935,6 +4943,7 @@ mal_result mal_context_init__alsa(mal_context* pContext)
pContext->alsa.snd_pcm_readi = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_readi"); pContext->alsa.snd_pcm_readi = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_readi");
pContext->alsa.snd_pcm_writei = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_writei"); pContext->alsa.snd_pcm_writei = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_writei");
pContext->alsa.snd_pcm_avail = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail"); pContext->alsa.snd_pcm_avail = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail");
pContext->alsa.snd_pcm_avail_update = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail_update");
pContext->alsa.snd_pcm_wait = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_wait"); pContext->alsa.snd_pcm_wait = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_wait");
return MAL_SUCCESS; return MAL_SUCCESS;
...@@ -4976,14 +4985,20 @@ static const char* mal_find_char(const char* str, char c, int* index) ...@@ -4976,14 +4985,20 @@ static const char* mal_find_char(const char* str, char c, int* index)
// value is the number of frames available. // value is the number of frames available.
// //
// This will return early if the main loop is broken with mal_device__break_main_loop(). // This will return early if the main loop is broken with mal_device__break_main_loop().
static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice) static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pRequiresRestart)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (pRequiresRestart) *pRequiresRestart = MAL_FALSE;
mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_proc)pDevice->pContext->alsa.snd_pcm_avail)((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM);
if (framesAvailable > 0) {
return framesAvailable; // Keep the returned number of samples consistent and based on the period size.
if (framesAvailable >= periodSizeInFrames) {
return periodSizeInFrames;
} }
if (framesAvailable < 0) { if (framesAvailable < 0) {
...@@ -4992,22 +5007,34 @@ static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice) ...@@ -4992,22 +5007,34 @@ static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice)
return 0; return 0;
} }
framesAvailable = ((mal_snd_pcm_avail_proc)pDevice->pContext->alsa.snd_pcm_avail)((snd_pcm_t*)pDevice->alsa.pPCM); framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM);
if (framesAvailable < 0) { if (framesAvailable < 0) {
return 0; return 0;
} }
} }
} }
const int timeoutInMilliseconds = 20; // <-- The larger this value, the longer it'll take to stop the device! const int timeoutInMilliseconds = 1; // <-- The larger this value, the longer it'll take to stop the device!
int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((snd_pcm_t*)pDevice->alsa.pPCM, timeoutInMilliseconds); int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((snd_pcm_t*)pDevice->alsa.pPCM, timeoutInMilliseconds);
if (waitResult < 0) { if (waitResult < 0) {
((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE); if (waitResult == -EPIPE) {
if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE) < 0) {
return 0;
}
if (pRequiresRestart) {
*pRequiresRestart = MAL_TRUE;
}
return pDevice->bufferSizeInFrames;
} else {
return 0;
}
} }
} }
// We'll get here if the loop was terminated. Just return whatever's available. // We'll get here if the loop was terminated. Just return whatever's available.
snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_proc)pDevice->pContext->alsa.snd_pcm_avail)((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM);
if (framesAvailable < 0) { if (framesAvailable < 0) {
return 0; return 0;
} }
...@@ -5028,7 +5055,8 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice) ...@@ -5028,7 +5055,8 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice)
if (pDevice->alsa.pIntermediaryBuffer == NULL) { if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap. // mmap.
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice); mal_bool32 requiresRestart;
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart);
if (framesAvailable == 0) { if (framesAvailable == 0) {
return MAL_FALSE; return MAL_FALSE;
} }
...@@ -5056,12 +5084,18 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice) ...@@ -5056,12 +5084,18 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
if (requiresRestart) {
if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) {
return MAL_FALSE;
}
}
framesAvailable -= mappedFrames; framesAvailable -= mappedFrames;
} }
} else { } else {
// readi/writei. // readi/writei.
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice); mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL);
if (framesAvailable == 0) { if (framesAvailable == 0) {
continue; continue;
} }
...@@ -5118,7 +5152,8 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -5118,7 +5152,8 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
void* pBuffer = NULL; void* pBuffer = NULL;
if (pDevice->alsa.pIntermediaryBuffer == NULL) { if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap. // mmap.
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice); mal_bool32 requiresRestart;
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart);
if (framesAvailable == 0) { if (framesAvailable == 0) {
return MAL_FALSE; return MAL_FALSE;
} }
...@@ -5141,13 +5176,19 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -5141,13 +5176,19 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
if (requiresRestart) {
if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) {
return MAL_FALSE;
}
}
framesAvailable -= mappedFrames; framesAvailable -= mappedFrames;
} }
} else { } else {
// readi/writei. // readi/writei.
snd_pcm_sframes_t framesRead = 0; snd_pcm_sframes_t framesRead = 0;
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice); mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL);
if (framesAvailable == 0) { if (framesAvailable == 0) {
continue; continue;
} }
...@@ -5186,13 +5227,6 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -5186,13 +5227,6 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
mal_device__send_frames_to_client(pDevice, framesToSend, pBuffer); mal_device__send_frames_to_client(pDevice, framesToSend, pBuffer);
} }
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap.
} else {
// readi/writei.
}
return MAL_TRUE; return MAL_TRUE;
} }
...@@ -5350,21 +5384,24 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t ...@@ -5350,21 +5384,24 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t
if (pDeviceID == NULL) { if (pDeviceID == NULL) {
mal_strncpy_s(deviceName, sizeof(deviceName), "default", (size_t)-1); mal_strncpy_s(deviceName, sizeof(deviceName), "default", (size_t)-1);
} else { } else {
// For now, convert "hw" devices to "plughw". The reason for this is that mini_al has not been thoroughly tested // Is preferred, convert "hw" devices to "plughw".
// with non "plughw" devices. if (pConfig->alsa.preferPlugHW && pDeviceID->alsa[0] == 'h' && pDeviceID->alsa[1] == 'w' && pDeviceID->alsa[2] == ':') {
if (pDeviceID->alsa[0] == 'h' && pDeviceID->alsa[1] == 'w' && pDeviceID->alsa[2] == ':') { deviceName[0] = 'p'; deviceName[1] = 'l'; deviceName[2] = 'u'; deviceName[3] = 'g';
deviceName[0] = 'p'; deviceName[1] = 'l'; deviceName[2] = 'u'; deviceName[3] = 'g';
mal_strncpy_s(deviceName+4, sizeof(deviceName-4), pDeviceID->alsa, (size_t)-1); mal_strncpy_s(deviceName+4, sizeof(deviceName-4), pDeviceID->alsa, (size_t)-1);
} else { } else {
mal_strncpy_s(deviceName, sizeof(deviceName), pDeviceID->alsa, (size_t)-1); mal_strncpy_s(deviceName, sizeof(deviceName), pDeviceID->alsa, (size_t)-1);
} }
} }
if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, deviceName, (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, 0) < 0) { if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, deviceName, (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, 0) < 0) {
if (mal_strcmp(deviceName, "default") == 0 || mal_strcmp(deviceName, "pulse") == 0) { if (mal_strcmp(deviceName, "default") == 0 || mal_strcmp(deviceName, "pulse") == 0) {
// We may have failed to open the "default" or "pulse" device, in which case try falling back to "plughw:0,0". // We may have failed to open the "default" or "pulse" device, in which case try falling back to "hw:0,0".
mal_strncpy_s(deviceName, sizeof(deviceName), "plughw:0,0", (size_t)-1); if (pConfig->alsa.preferPlugHW) {
mal_strncpy_s(deviceName, sizeof(deviceName), "plughw:0,0", (size_t)-1);
} else {
mal_strncpy_s(deviceName, sizeof(deviceName), "hw:0,0", (size_t)-1);
}
if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, deviceName, (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, 0) < 0) { if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, deviceName, (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, 0) < 0) {
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE);
...@@ -5470,17 +5507,15 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t ...@@ -5470,17 +5507,15 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t
pDevice->periods = periods; pDevice->periods = periods;
// MMAP Mode // MMAP Mode
// //
// Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. // Try using interleaved MMAP access. If this fails, fall back to standard readi/writei.
pDevice->alsa.isUsingMMap = MAL_FALSE; pDevice->alsa.isUsingMMap = MAL_FALSE;
#ifdef MAL_ENABLE_EXPERIMENTAL_ALSA_MMAP if (!pConfig->alsa.noMMap && pDevice->type != mal_device_type_capture) { // <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it so I can test it... Contributions welcome.
if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) {
pDevice->alsa.isUsingMMap = MAL_TRUE; pDevice->alsa.isUsingMMap = MAL_TRUE;
mal_log(pDevice, "USING MMAP\n"); }
} }
#endif
if (!pDevice->alsa.isUsingMMap) { if (!pDevice->alsa.isUsingMMap) {
if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {; if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {;
...@@ -5571,6 +5606,13 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice) ...@@ -5571,6 +5606,13 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice)
if (!mal_device_write__alsa(pDevice)) { if (!mal_device_write__alsa(pDevice)) {
return mal_post_error(pDevice, "[ALSA] Failed to write initial chunk of data to the playback device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); return mal_post_error(pDevice, "[ALSA] Failed to write initial chunk of data to the playback device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
} }
// mmap mode requires an explicit start.
if (pDevice->alsa.isUsingMMap) {
if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) {
return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE);
}
}
} else { } else {
if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) {
return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE);
...@@ -9605,7 +9647,7 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) ...@@ -9605,7 +9647,7 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count)
for (unsigned int i = 0; i < count; ++i) { for (unsigned int i = 0; i < count; ++i) {
float x = pIn[i]; float x = pIn[i];
float c; float c;
int s; long long s;
c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); c = ((x < -1) ? -1 : ((x > 1) ? 1 : x));
s = ((*((int*)&x)) & 0x80000000) >> 31; s = ((*((int*)&x)) & 0x80000000) >> 31;
s = s + 2147483647; s = s + 2147483647;
...@@ -9631,8 +9673,13 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) ...@@ -9631,8 +9673,13 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count)
// - Added support for UWP (Universal Windows Platform) applications. Currently C++ only. // - Added support for UWP (Universal Windows Platform) applications. Currently C++ only.
// - Added support for exclusive mode for selected backends. Currently supported on WASAPI. // - Added support for exclusive mode for selected backends. Currently supported on WASAPI.
// - POSIX builds no longer require explicit linking to libpthread (-lpthread). // - POSIX builds no longer require explicit linking to libpthread (-lpthread).
// - The ALSA backend no longer requires explicit linking to libasound (-lasound). // - ALSA: Explicit linking to libasound (-lasound) is no longer required.
// - ALSA: Latency improvements.
// - ALSA: Use MMAP mode where available. This can be disabled with the alsa.noMMap config.
// - ALSA: Use "hw" devices instead of "plughw" devices by default. This can be disabled with the
// alsa.preferPlugHW config.
// - WASAPI is now the highest priority backend on Windows platforms. // - WASAPI is now the highest priority backend on Windows platforms.
// - Fixed an error with sample rate conversion which was causing crackling when capturing.
// - Improved error handling. // - Improved error handling.
// //
// v0.3 - 2017-06-19 // v0.3 - 2017-06-19
......
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