ALSA: Refactoring for the new backend architecture.

This commit is contained in:
David Reid
2025-12-24 14:35:06 +10:00
parent 61fd14955a
commit a2c7e697d1
+150 -81
View File
@@ -28566,6 +28566,8 @@ typedef struct ma_device_state_alsa
{
ma_snd_pcm_t* pPCMPlayback;
ma_snd_pcm_t* pPCMCapture;
void* pIntermediaryBufferPlayback;
void* pIntermediaryBufferCapture;
struct pollfd* pPollDescriptorsPlayback;
struct pollfd* pPollDescriptorsCapture;
int pollDescriptorCountPlayback;
@@ -29655,6 +29657,7 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
int pollDescriptorCount;
struct pollfd* pPollDescriptors;
int wakeupfd;
void* pIntermediaryBuffer;
MA_ASSERT(pContextStateALSA != NULL);
MA_ASSERT(pDeviceStateALSA != NULL);
@@ -29993,6 +29996,13 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
}
/* Now that we know our internal data format and period size we can allocate an intermediary buffer. */
pIntermediaryBuffer = ma_malloc(ma_get_bytes_per_frame(internalFormat, internalChannels) * internalPeriodSizeInFrames, ma_context_get_allocation_callbacks(pContext));
if (pIntermediaryBuffer == NULL) {
pContextStateALSA->snd_pcm_close(pPCM);
}
/*
We need to retrieve the poll descriptors so we can use poll() to wait for data to become
available for reading or writing. There's no well defined maximum for this so we're just going
@@ -30001,6 +30011,7 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
pollDescriptorCount = pContextStateALSA->snd_pcm_poll_descriptors_count(pPCM);
if (pollDescriptorCount <= 0) {
pContextStateALSA->snd_pcm_close(pPCM);
ma_free(pIntermediaryBuffer, ma_context_get_allocation_callbacks(pContext));
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count.");
return MA_ERROR;
}
@@ -30008,6 +30019,7 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), ma_context_get_allocation_callbacks(pContext)); /* +1 because we want room for the wakeup descriptor. */
if (pPollDescriptors == NULL) {
pContextStateALSA->snd_pcm_close(pPCM);
ma_free(pIntermediaryBuffer, ma_context_get_allocation_callbacks(pContext));
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors.");
return MA_OUT_OF_MEMORY;
}
@@ -30020,6 +30032,7 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
if (wakeupfd < 0) {
ma_free(pPollDescriptors, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
ma_free(pIntermediaryBuffer, ma_context_get_allocation_callbacks(pContext));
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup.");
return ma_result_from_errno(errno);
}
@@ -30035,38 +30048,36 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
close(wakeupfd);
ma_free(pPollDescriptors, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
ma_free(pIntermediaryBuffer, ma_context_get_allocation_callbacks(pContext));
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors.");
return MA_ERROR;
}
if (deviceType == ma_device_type_capture) {
pDeviceStateALSA->pollDescriptorCountCapture = pollDescriptorCount;
pDeviceStateALSA->pPollDescriptorsCapture = pPollDescriptors;
pDeviceStateALSA->wakeupfdCapture = wakeupfd;
} else {
pDeviceStateALSA->pollDescriptorCountPlayback = pollDescriptorCount;
pDeviceStateALSA->pPollDescriptorsPlayback = pPollDescriptors;
pDeviceStateALSA->wakeupfdPlayback = wakeupfd;
}
/* We're done. Prepare the device. */
resultALSA = pContextStateALSA->snd_pcm_prepare(pPCM);
if (resultALSA < 0) {
close(wakeupfd);
ma_free(pPollDescriptors, ma_context_get_allocation_callbacks(pContext));
pContextStateALSA->snd_pcm_close(pPCM);
ma_free(pIntermediaryBuffer, ma_context_get_allocation_callbacks(pContext));
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device.");
return ma_result_from_errno(-resultALSA);
}
if (deviceType == ma_device_type_capture) {
pDeviceStateALSA->pPCMCapture = pPCM;
pDeviceStateALSA->isUsingMMapCapture = isUsingMMap;
pDeviceStateALSA->pPCMCapture = pPCM;
pDeviceStateALSA->pIntermediaryBufferCapture = pIntermediaryBuffer;
pDeviceStateALSA->isUsingMMapCapture = isUsingMMap;
pDeviceStateALSA->pollDescriptorCountCapture = pollDescriptorCount;
pDeviceStateALSA->pPollDescriptorsCapture = pPollDescriptors;
pDeviceStateALSA->wakeupfdCapture = wakeupfd;
} else {
pDeviceStateALSA->pPCMPlayback = pPCM;
pDeviceStateALSA->isUsingMMapPlayback = isUsingMMap;
pDeviceStateALSA->pPCMPlayback = pPCM;
pDeviceStateALSA->pIntermediaryBufferPlayback = pIntermediaryBuffer;
pDeviceStateALSA->isUsingMMapPlayback = isUsingMMap;
pDeviceStateALSA->pollDescriptorCountPlayback = pollDescriptorCount;
pDeviceStateALSA->pPollDescriptorsPlayback = pPollDescriptors;
pDeviceStateALSA->wakeupfdPlayback = wakeupfd;
}
pDescriptor->format = internalFormat;
@@ -30133,12 +30144,14 @@ static void ma_device_uninit__alsa(ma_device* pDevice)
pContextStateALSA->snd_pcm_close(pDeviceStateALSA->pPCMCapture);
close(pDeviceStateALSA->wakeupfdCapture);
ma_free(pDeviceStateALSA->pPollDescriptorsCapture, ma_device_get_allocation_callbacks(pDevice));
ma_free(pDeviceStateALSA->pIntermediaryBufferCapture, ma_device_get_allocation_callbacks(pDevice));
}
if (pDeviceStateALSA->pPCMPlayback) {
pContextStateALSA->snd_pcm_close(pDeviceStateALSA->pPCMPlayback);
close(pDeviceStateALSA->wakeupfdPlayback);
ma_free(pDeviceStateALSA->pPollDescriptorsPlayback, ma_device_get_allocation_callbacks(pDevice));
ma_free(pDeviceStateALSA->pIntermediaryBufferPlayback, ma_device_get_allocation_callbacks(pDevice));
}
ma_free(pDeviceStateALSA, ma_device_get_allocation_callbacks(pDevice));
@@ -30242,12 +30255,16 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice)
return MA_SUCCESS;
}
static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_context_state_alsa* pContextStateALSA, ma_snd_pcm_t* pPCM, struct pollfd* pPollDescriptors, int pollDescriptorCount, short requiredEvent)
static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_context_state_alsa* pContextStateALSA, ma_snd_pcm_t* pPCM, struct pollfd* pPollDescriptors, int pollDescriptorCount, short requiredEvent, int timeout, ma_bool32* pIsDataAvailable)
{
MA_ASSERT(pIsDataAvailable != NULL);
*pIsDataAvailable = MA_FALSE;
for (;;) {
unsigned short revents;
int resultALSA;
int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1);
int resultPoll = poll(pPollDescriptors, pollDescriptorCount, timeout);
if (resultPoll < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] poll() failed.");
@@ -30297,6 +30314,7 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_context_state_alsa*
}
if ((revents & requiredEvent) == requiredEvent) {
*pIsDataAvailable = MA_TRUE;
break; /* We're done. Data available for reading or writing. */
}
}
@@ -30304,17 +30322,17 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_context_state_alsa*
return MA_SUCCESS;
}
static ma_result ma_device_wait_read__alsa(ma_device* pDevice, ma_context_state_alsa* pContextStateALSA, ma_device_state_alsa* pDeviceStateALSA)
static ma_result ma_device_wait_read__alsa(ma_device* pDevice, ma_context_state_alsa* pContextStateALSA, ma_device_state_alsa* pDeviceStateALSA, int timeout, ma_bool32* pIsDataAvailable)
{
return ma_device_wait__alsa(pDevice, pContextStateALSA, pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPollDescriptorsCapture, pDeviceStateALSA->pollDescriptorCountCapture + 1, POLLIN); /* +1 to account for the wakeup descriptor. */
return ma_device_wait__alsa(pDevice, pContextStateALSA, pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPollDescriptorsCapture, pDeviceStateALSA->pollDescriptorCountCapture + 1, POLLIN, timeout, pIsDataAvailable); /* +1 to account for the wakeup descriptor. */
}
static ma_result ma_device_wait_write__alsa(ma_device* pDevice, ma_context_state_alsa* pContextStateALSA, ma_device_state_alsa* pDeviceStateALSA)
static ma_result ma_device_wait_write__alsa(ma_device* pDevice, ma_context_state_alsa* pContextStateALSA, ma_device_state_alsa* pDeviceStateALSA, int timeout, ma_bool32* pIsDataAvailable)
{
return ma_device_wait__alsa(pDevice, pContextStateALSA, pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pPollDescriptorsPlayback, pDeviceStateALSA->pollDescriptorCountPlayback + 1, POLLOUT); /* +1 to account for the wakeup descriptor. */
return ma_device_wait__alsa(pDevice, pContextStateALSA, pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pPollDescriptorsPlayback, pDeviceStateALSA->pollDescriptorCountPlayback + 1, POLLOUT, timeout, pIsDataAvailable); /* +1 to account for the wakeup descriptor. */
}
static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead)
static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, int timeout)
{
ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice);
ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice));
@@ -30327,38 +30345,40 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u
for (;;) {
ma_result result;
ma_bool32 isDataAvailable;
/* The first thing to do is wait for data to become available for reading. This will return an error code if the device has been stopped. */
result = ma_device_wait_read__alsa(pDevice, pContextStateALSA, pDeviceStateALSA);
result = ma_device_wait_read__alsa(pDevice, pContextStateALSA, pDeviceStateALSA, timeout, &isDataAvailable);
if (result != MA_SUCCESS) {
return result;
}
/* Getting here means we should have data available. */
resultALSA = pContextStateALSA->snd_pcm_readi(pDeviceStateALSA->pPCMCapture, pFramesOut, frameCount);
if (resultALSA >= 0) {
break; /* Success. */
} else {
if (resultALSA == -EAGAIN) {
/*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)");*/
continue; /* Try again. */
} else if (resultALSA == -EPIPE) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)");
if (isDataAvailable) {
resultALSA = pContextStateALSA->snd_pcm_readi(pDeviceStateALSA->pPCMCapture, pFramesOut, frameCount);
if (resultALSA >= 0) {
break; /* Success. */
} else {
if (resultALSA == -EAGAIN) {
/*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)");*/
continue; /* Try again. */
} else if (resultALSA == -EPIPE) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)");
/* Overrun. Recover and try again. If this fails we need to return an error. */
resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMCapture, resultALSA, MA_TRUE);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.");
return ma_result_from_errno((int)-resultALSA);
/* Overrun. Recover and try again. If this fails we need to return an error. */
resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMCapture, resultALSA, MA_TRUE);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.");
return ma_result_from_errno((int)-resultALSA);
}
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMCapture);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.");
return ma_result_from_errno((int)-resultALSA);
}
continue; /* Try reading again. */
}
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMCapture);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.");
return ma_result_from_errno((int)-resultALSA);
}
continue; /* Try reading again. */
}
}
}
@@ -30370,7 +30390,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u
return MA_SUCCESS;
}
static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFramesIn, ma_uint32 frameCount, ma_uint32* pFramesWritten)
static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFramesIn, ma_uint32 frameCount, ma_uint32* pFramesWritten, int timeout)
{
ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice);
ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice));
@@ -30383,44 +30403,47 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFramesIn
for (;;) {
ma_result result;
ma_bool32 isDataAvailable;
/* The first thing to do is wait for space to become available for writing. This will return an error code if the device has been stopped. */
result = ma_device_wait_write__alsa(pDevice, pContextStateALSA, pDeviceStateALSA);
result = ma_device_wait_write__alsa(pDevice, pContextStateALSA, pDeviceStateALSA, timeout, &isDataAvailable);
if (result != MA_SUCCESS) {
return result;
}
resultALSA = pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, pFramesIn, frameCount);
if (resultALSA >= 0) {
break; /* Success. */
} else {
if (resultALSA == -EAGAIN) {
/*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)");*/
continue; /* Try again. */
} else if (resultALSA == -EPIPE) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)");
if (isDataAvailable) {
resultALSA = pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, pFramesIn, frameCount);
if (resultALSA >= 0) {
break; /* Success. */
} else {
if (resultALSA == -EAGAIN) {
/*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)");*/
continue; /* Try again. */
} else if (resultALSA == -EPIPE) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)");
/* Underrun. Recover and try again. If this fails we need to return an error. */
resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.");
return ma_result_from_errno((int)-resultALSA);
/* Underrun. Recover and try again. If this fails we need to return an error. */
resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.");
return ma_result_from_errno((int)-resultALSA);
}
/*
In my testing I have had a situation where writei() does not automatically restart the device even though I've set it
up as such in the software parameters. What will happen is writei() will block indefinitely even though the number of
frames is well beyond the auto-start threshold. To work around this I've needed to add an explicit start here. Not sure
if this is me just being stupid and not recovering the device properly, but this definitely feels like something isn't
quite right here.
*/
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.");
return ma_result_from_errno((int)-resultALSA);
}
continue; /* Try writing again. */
}
/*
In my testing I have had a situation where writei() does not automatically restart the device even though I've set it
up as such in the software parameters. What will happen is writei() will block indefinitely even though the number of
frames is well beyond the auto-start threshold. To work around this I've needed to add an explicit start here. Not sure
if this is me just being stupid and not recovering the device properly, but this definitely feels like something isn't
quite right here.
*/
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.");
return ma_result_from_errno((int)-resultALSA);
}
continue; /* Try writing again. */
}
}
}
@@ -30432,6 +30455,52 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFramesIn
return MA_SUCCESS;
}
static ma_result ma_device_step__alsa(ma_device* pDevice, ma_blocking_mode blockingMode)
{
ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice);
ma_device_type deviceType = ma_device_get_type(pDevice);
ma_result result;
int timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? -1 : 0;
if (!ma_device_is_started(pDevice)) {
return MA_DEVICE_NOT_STARTED;
}
if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) {
ma_uint32 framesRead;
result = ma_device_read__alsa(pDevice, pDeviceStateALSA->pIntermediaryBufferCapture, pDevice->capture.internalPeriodSizeInFrames, &framesRead, timeout);
if (result != MA_SUCCESS) {
return result;
}
ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateALSA->pIntermediaryBufferCapture, framesRead);
}
if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) {
ma_uint32 framesWritten;
result = ma_device_write__alsa(pDevice, pDeviceStateALSA->pIntermediaryBufferPlayback, pDevice->playback.internalPeriodSizeInFrames, &framesWritten, timeout);
if (result != MA_SUCCESS) {
return result;
}
ma_device_handle_backend_data_callback(pDevice, pDeviceStateALSA->pIntermediaryBufferPlayback, NULL, framesWritten);
}
return MA_SUCCESS;
}
static void ma_device_loop__alsa(ma_device* pDevice)
{
while (ma_device_is_started(pDevice)) {
ma_result result = ma_device_step__alsa(pDevice, MA_BLOCKING_MODE_BLOCKING);
if (result != MA_SUCCESS) {
break;
}
}
}
static void ma_device_wakeup__alsa(ma_device* pDevice)
{
ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice);
@@ -30468,9 +30537,9 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_ALSA =
ma_device_uninit__alsa,
ma_device_start__alsa,
ma_device_stop__alsa,
ma_device_read__alsa,
ma_device_write__alsa,
NULL, /* onDeviceLoop */
NULL,
NULL,
ma_device_loop__alsa,
ma_device_wakeup__alsa
};