Commit 2cb9a8c6 authored by David Reid's avatar David Reid

Experimental work on MMAP mode for ALSA.

parent fd85d5ac
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
// //
// NOTES // NOTES
// ===== // =====
// - This library uses an asynchronous API delivering and requesting audio data. Each device will have // - This library uses an asynchronous API for delivering and requesting audio data. Each device will have
// it's own worker thread which is managed by the library. // it's own worker thread which is managed by the library.
// - If mal_device_init() is called with a device that's not aligned to the platform's natural alignment // - If mal_device_init() is called with a device that's not aligned to the platform's natural alignment
// boundary (4 bytes on 32-bit, 8 bytes on 64-bit), it will _not_ be thread-safe. The reason for this // boundary (4 bytes on 32-bit, 8 bytes on 64-bit), it will _not_ be thread-safe. The reason for this
...@@ -1623,7 +1623,7 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice) ...@@ -1623,7 +1623,7 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice)
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
snd_pcm_sframes_t framesAvailable = snd_pcm_avail_update(pDevice->alsa.pPCM); snd_pcm_sframes_t framesAvailable = snd_pcm_avail(pDevice->alsa.pPCM);
if (framesAvailable >= pDevice->fragmentSizeInFrames) { if (framesAvailable >= pDevice->fragmentSizeInFrames) {
return pDevice->fragmentSizeInFrames; return pDevice->fragmentSizeInFrames;
} }
...@@ -1631,30 +1631,33 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice) ...@@ -1631,30 +1631,33 @@ mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice)
if (framesAvailable < 0) { if (framesAvailable < 0) {
if (framesAvailable == -EPIPE) { if (framesAvailable == -EPIPE) {
if (snd_pcm_recover(pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) { if (snd_pcm_recover(pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) {
return MAL_FALSE; return 0;
} }
framesAvailable = snd_pcm_avail_update(pDevice->alsa.pPCM); framesAvailable = snd_pcm_avail(pDevice->alsa.pPCM);
if (framesAvailable < 0) { if (framesAvailable < 0) {
return MAL_FALSE; return 0;
} }
} }
} }
const int timeoutInMilliseconds = 20; // <-- The larger this value, the longer it'll take to stop the device! const int timeoutInMilliseconds = 20; // <-- The larger this value, the longer it'll take to stop the device!
int waitResult = snd_pcm_wait(pDevice->alsa.pPCM, timeoutInMilliseconds); int waitResult = snd_pcm_wait(pDevice->alsa.pPCM, timeoutInMilliseconds);
if (waitResult < 0) { if (waitResult < 0) {
snd_pcm_recover(pDevice->alsa.pPCM, waitResult, MAL_TRUE); snd_pcm_recover(pDevice->alsa.pPCM, waitResult, MAL_TRUE);
} }
} }
// 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, making sure it's never
// more than the size of a fragment.
snd_pcm_sframes_t framesAvailable = snd_pcm_avail(pDevice->alsa.pPCM); snd_pcm_sframes_t framesAvailable = snd_pcm_avail(pDevice->alsa.pPCM);
if (framesAvailable < 0) { if (framesAvailable < 0) {
return 0; return 0;
} }
return framesAvailable; // There's a small chance we'll have more frames available than the size of a fragment. These frames
// will be lost to cyberspace :(
return (framesAvailable < pDevice->fragmentSizeInFrames) ? framesAvailable : pDevice->fragmentSizeInFrames;
} }
mal_bool32 mal_device_write__alsa(mal_device* pDevice) mal_bool32 mal_device_write__alsa(mal_device* pDevice)
...@@ -1667,17 +1670,39 @@ mal_bool32 mal_device_write__alsa(mal_device* pDevice) ...@@ -1667,17 +1670,39 @@ mal_bool32 mal_device_write__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
void* pBuffer = NULL;
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap.
return MAL_FALSE;
} else {
// readi/writei.
pBuffer = pDevice->alsa.pIntermediaryBuffer;
}
if (pDevice->alsa.pIntermediaryBuffer == NULL) { if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap. // mmap.
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice);
if (framesAvailable == 0) {
return MAL_FALSE;
}
// Don't bother asking the client for more audio data if we're just stopping the device anyway.
if (pDevice->alsa.breakFromMainLoop) {
return MAL_FALSE;
}
const snd_pcm_channel_area_t* pAreas;
snd_pcm_uframes_t mappedOffset;
snd_pcm_uframes_t mappedFrames = framesAvailable;
while (framesAvailable > 0) {
int result = snd_pcm_mmap_begin(pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames);
if (result < 0) {
return MAL_FALSE;
}
void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8);
mal_device__read_samples_from_client(pDevice, mappedFrames * pDevice->channels, pBuffer);
result = snd_pcm_mmap_commit(pDevice->alsa.pPCM, mappedOffset, mappedFrames);
if (result < 0 || result != mappedFrames) {
snd_pcm_recover(pDevice->alsa.pPCM, result, MAL_TRUE);
return MAL_FALSE;
}
framesAvailable -= mappedFrames;
}
} else { } else {
// readi/writei. // readi/writei.
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
...@@ -1691,7 +1716,7 @@ mal_bool32 mal_device_write__alsa(mal_device* pDevice) ...@@ -1691,7 +1716,7 @@ mal_bool32 mal_device_write__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
mal_device__read_samples_from_client(pDevice, framesAvailable * pDevice->channels, pBuffer); mal_device__read_samples_from_client(pDevice, framesAvailable * pDevice->channels, pDevice->alsa.pIntermediaryBuffer);
snd_pcm_sframes_t framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); snd_pcm_sframes_t framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
if (framesWritten < 0) { if (framesWritten < 0) {
...@@ -1735,7 +1760,31 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -1735,7 +1760,31 @@ 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.
return MAL_FALSE; mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice);
if (framesAvailable == 0) {
return MAL_FALSE;
}
const snd_pcm_channel_area_t* pAreas;
snd_pcm_uframes_t mappedOffset;
snd_pcm_uframes_t mappedFrames = framesAvailable;
while (framesAvailable > 0) {
int result = snd_pcm_mmap_begin(pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames);
if (result < 0) {
return MAL_FALSE;
}
void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8);
mal_device__send_samples_to_client(pDevice, mappedFrames * pDevice->channels, pBuffer);
result = snd_pcm_mmap_commit(pDevice->alsa.pPCM, mappedOffset, mappedFrames);
if (result < 0 || result != mappedFrames) {
snd_pcm_recover(pDevice->alsa.pPCM, result, MAL_TRUE);
return MAL_FALSE;
}
framesAvailable -= mappedFrames;
}
} else { } else {
// readi/writei. // readi/writei.
snd_pcm_sframes_t framesRead = 0; snd_pcm_sframes_t framesRead = 0;
...@@ -1744,7 +1793,7 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -1744,7 +1793,7 @@ mal_bool32 mal_device_read__alsa(mal_device* pDevice)
if (framesAvailable == 0) { if (framesAvailable == 0) {
return MAL_FALSE; return MAL_FALSE;
} }
framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
if (framesRead < 0) { if (framesRead < 0) {
if (framesRead == -EAGAIN) { if (framesRead == -EAGAIN) {
...@@ -1841,7 +1890,7 @@ void mal_device_uninit__alsa(mal_device* pDevice) ...@@ -1841,7 +1890,7 @@ void mal_device_uninit__alsa(mal_device* pDevice)
if (pDevice->alsa.pPCM) { if (pDevice->alsa.pPCM) {
snd_pcm_close((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_close((snd_pcm_t*)pDevice->alsa.pPCM);
if (pDevice->alsa.pIntermediaryBuffer == NULL) { if (pDevice->alsa.pIntermediaryBuffer != NULL) {
mal_free(pDevice->alsa.pIntermediaryBuffer); mal_free(pDevice->alsa.pIntermediaryBuffer);
} }
} }
...@@ -1883,38 +1932,44 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_ ...@@ -1883,38 +1932,44 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_
snd_pcm_hw_params_alloca(&pHWParams); snd_pcm_hw_params_alloca(&pHWParams);
if (snd_pcm_hw_params_any((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { if (snd_pcm_hw_params_any((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", MAL_ALSA_FAILED_TO_SET_HW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", MAL_ALSA_FAILED_TO_SET_HW_PARAMS);
} }
if (snd_pcm_hw_params_set_access((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { // Try using interleaved MMAP access. If this fails, fall back to standard readi/writei.
snd_pcm_hw_params_free(pHWParams); pDevice->alsa.isUsingMMap = MAL_FALSE;
mal_device_uninit__alsa(pDevice); #ifdef MAL_ENABLE_EXPERIMENTAL_ALSA_MMAP
return mal_post_error(pDevice, "[ALSA] Failed to set access mode to SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", MAL_FORMAT_NOT_SUPPORTED); if (snd_pcm_hw_params_set_access((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) {
} pDevice->alsa.isUsingMMap = MAL_TRUE;
mal_log(pDevice, "USING MMAP\n");
}
#endif
if (!pDevice->alsa.isUsingMMap) {
if (snd_pcm_hw_params_set_access((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {;
mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", MAL_FORMAT_NOT_SUPPORTED);
}
}
if (snd_pcm_hw_params_set_format((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) { if (snd_pcm_hw_params_set_format((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); 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); return mal_post_error(pDevice, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
if (snd_pcm_hw_params_set_rate_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &sampleRate, 0) < 0) { if (snd_pcm_hw_params_set_rate_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &sampleRate, 0) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
if (snd_pcm_hw_params_set_channels_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &channels) < 0) { if (snd_pcm_hw_params_set_channels_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &channels) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
int dir = 0; int dir = 1;
if (snd_pcm_hw_params_set_periods_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &fragmentCount, &dir) < 0) { if (snd_pcm_hw_params_set_periods_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &fragmentCount, &dir) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set fragment count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Failed to set fragment count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
...@@ -1931,13 +1986,11 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_ ...@@ -1931,13 +1986,11 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_
pDevice->fragmentSizeInFrames = mal_next_power_of_2(fragmentSizeInFrames); pDevice->fragmentSizeInFrames = mal_next_power_of_2(fragmentSizeInFrames);
if (snd_pcm_hw_params_set_buffer_size((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, pDevice->fragmentSizeInFrames * pDevice->fragmentCount) < 0) { if (snd_pcm_hw_params_set_buffer_size((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, pDevice->fragmentSizeInFrames * pDevice->fragmentCount) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
if (snd_pcm_hw_params((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { if (snd_pcm_hw_params((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS);
} }
...@@ -1948,32 +2001,29 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_ ...@@ -1948,32 +2001,29 @@ mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_
snd_pcm_sw_params_alloca(&pSWParams); snd_pcm_sw_params_alloca(&pSWParams);
if (snd_pcm_sw_params_current((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { if (snd_pcm_sw_params_current((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS);
} }
if (snd_pcm_sw_params_set_avail_min((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, pDevice->fragmentSizeInFrames) != 0) { if (snd_pcm_sw_params_set_avail_min((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, pDevice->fragmentSizeInFrames) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set fragment size. snd_pcm_sw_params_set_avail_min() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Failed to set fragment size. snd_pcm_sw_params_set_avail_min() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
if (type == mal_device_type_playback) { if (type == mal_device_type_playback) {
if (snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, pDevice->fragmentSizeInFrames) != 0) { if (snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, pDevice->fragmentSizeInFrames) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS);
} }
} }
if (snd_pcm_sw_params((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { if (snd_pcm_sw_params((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS);
} }
// If we're _not_ using mmap we need to use an intermediary buffer. // If we're _not_ using mmap we need to use an intermediary buffer.
if (!pDevice->alsa.isUsingMMap) { if (!pDevice->alsa.isUsingMMap) {
...@@ -2456,14 +2506,20 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format) ...@@ -2456,14 +2506,20 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
// TODO // TODO
// ==== // ====
// - Profiling. Need to measure mal_device_start() and mal_device_stop() in particular. One of the two seems to be taking a bit
// longer than it should.
// - Initial test for start/stop times show that it's _not_ tied to the fragment size...
// - Implement mmap mode for ALSA.
// - Make device initialization more robust for ALSA
// - Clamp period sizes to their min/max.
// - Support rewinding. This will enable applications to employ better anti-latency. // - Support rewinding. This will enable applications to employ better anti-latency.
// - Implement the null device. // - Implement the null device.
// - Pass the log callback to mal_device_init().
// - Consider having some core formats which are guaranteed to work. Perhaps u8, s16 and
// f32 to cover the 8-, 16 and 32-bit ranges.
// - The rationale for this is to make it easier for
//
//
// ALSA
// ----
// - Fix device iteration.
// - Make device initialization more robust.
// - Need to have my test hardware working with plughw at a minimum.
// - Finish mmap mode for ALSA.
// DEVELOPMENT NOTES // DEVELOPMENT NOTES
......
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