From 597654dcf67b8a3261a2d437dec4b9dda4325168 Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 8 Jan 2026 17:20:00 +1000 Subject: [PATCH] Refactoring to the ALSA backend. This is intended to address the following issues: - Stepping the device now correctly polls both the capture and playback side in one call in duplex mode. Prior to this commit it would wait separately for each side which was totally incorrect. - The initialization process has been simplified and made more robust when trying to initialize the default device. More work still to be done. --- miniaudio.h | 1265 ++++++++++++++++++++++----------------------------- 1 file changed, 533 insertions(+), 732 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 47cdb754..6f605a8f 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -28149,14 +28149,12 @@ 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; + void* pIntermediaryBuffer; /* Used as a scratch buffer for read() and write(). */ + int wakeupfd; /* eventfd for waking up from poll() when the device is stopped. */ + struct pollfd* pPollDescriptors; /* One array for wakeup, playback and capture. */ + int pollDescriptorCount; int pollDescriptorCountPlayback; int pollDescriptorCountCapture; - int wakeupfdPlayback; /* eventfd for waking up from poll() when the playback device is stopped. */ - int wakeupfdCapture; /* eventfd for waking up from poll() when the capture device is stopped. */ ma_bool8 isUsingMMapPlayback; ma_bool8 isUsingMMapCapture; } ma_device_state_alsa; @@ -28439,126 +28437,6 @@ static ma_bool32 ma_does_id_exist_in_list__alsa(ma_device_id* pUniqueIDs, ma_uin } -static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_context_state_alsa* pContextStateALSA, ma_share_mode shareMode, ma_device_type deviceType, const ma_device_id* pDeviceID, int openMode, ma_snd_pcm_t** ppPCM) -{ - ma_snd_pcm_t* pPCM; - ma_snd_pcm_stream_t stream; - - MA_ASSERT(pContextStateALSA != NULL); - MA_ASSERT(ppPCM != NULL); - - *ppPCM = NULL; - pPCM = NULL; - - stream = (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE; - - if (pDeviceID == NULL) { - ma_bool32 isDeviceOpen; - size_t i; - - /* - We're opening the default device. I don't know if trying anything other than "default" is necessary, but it makes - me feel better to try as hard as we can get to get _something_ working. - */ - const char* defaultDeviceNames[] = { - "default", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL - }; - - if (shareMode == ma_share_mode_exclusive) { - defaultDeviceNames[1] = "hw"; - defaultDeviceNames[2] = "hw:0"; - defaultDeviceNames[3] = "hw:0,0"; - } else { - if (deviceType == ma_device_type_playback) { - defaultDeviceNames[1] = "dmix"; - defaultDeviceNames[2] = "dmix:0"; - defaultDeviceNames[3] = "dmix:0,0"; - } else { - defaultDeviceNames[1] = "dsnoop"; - defaultDeviceNames[2] = "dsnoop:0"; - defaultDeviceNames[3] = "dsnoop:0,0"; - } - defaultDeviceNames[4] = "hw"; - defaultDeviceNames[5] = "hw:0"; - defaultDeviceNames[6] = "hw:0,0"; - } - - isDeviceOpen = MA_FALSE; - for (i = 0; i < ma_countof(defaultDeviceNames); ++i) { - if (defaultDeviceNames[i] != NULL && defaultDeviceNames[i][0] != '\0') { - if (pContextStateALSA->snd_pcm_open(&pPCM, defaultDeviceNames[i], stream, openMode) == 0) { - isDeviceOpen = MA_TRUE; - break; - } - } - } - - if (!isDeviceOpen) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } - } else { - /* - We're trying to open a specific device. There's a few things to consider here: - - miniaudio recognizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When - an ID of this format is specified, it indicates to miniaudio that it can try different combinations of plugins ("hw", "dmix", etc.) until it - finds an appropriate one that works. This comes in very handy when trying to open a device in shared mode ("dmix"), vs exclusive mode ("hw"). - */ - - /* May end up needing to make small adjustments to the ID, so make a copy. */ - ma_device_id deviceID = *pDeviceID; - int resultALSA = -ENODEV; - char hwid[256]; - - if (deviceID.alsa[0] != ':') { - /* The ID is not in ":0,0" format. Use the ID exactly as-is. */ - ma_strcpy_s(hwid, sizeof(hwid), deviceID.alsa); - resultALSA = pContextStateALSA->snd_pcm_open(&pPCM, hwid, stream, openMode); - } else { - /* The ID is in ":0,0" format. Try different plugins depending on the shared mode. */ - if (deviceID.alsa[1] == '\0') { - deviceID.alsa[0] = '\0'; /* An ID of ":" should be converted to "". */ - } - - if (shareMode == ma_share_mode_shared) { - if (deviceType == ma_device_type_playback) { - ma_strcpy_s(hwid, sizeof(hwid), "dmix"); - } else { - ma_strcpy_s(hwid, sizeof(hwid), "dsnoop"); - } - - if (ma_strcat_s(hwid, sizeof(hwid), deviceID.alsa) == 0) { - resultALSA = pContextStateALSA->snd_pcm_open(&pPCM, hwid, stream, openMode); - } - } - - /* If at this point we still don't have an open device it means we're either preferencing exclusive mode or opening with "dmix"/"dsnoop" failed. */ - if (resultALSA != 0) { - ma_strcpy_s(hwid, sizeof(hwid), "hw"); - if (ma_strcat_s(hwid, sizeof(hwid), deviceID.alsa) == 0) { - resultALSA = pContextStateALSA->snd_pcm_open(&pPCM, hwid, stream, openMode); - } - } - } - - if (resultALSA < 0) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open \"%s\".", hwid); - return ma_result_from_errno(-resultALSA); - } - } - - *ppPCM = pPCM; - return MA_SUCCESS; -} - - static ma_context_state_alsa* ma_context_get_backend_state__alsa(ma_context* pContext) { return (ma_context_state_alsa*)ma_context_get_backend_state(pContext); @@ -29077,15 +28955,13 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu Retrieving the data format requires us to open the device. */ { - ma_result result; ma_snd_pcm_t* pPCM; ma_snd_pcm_hw_params_t* pHWParams; ma_uint32 iFormat; ma_uint32 iChannel; /* For detailed info we need to open the device. */ - result = ma_context_open_pcm__alsa(pContext, pContextStateALSA, ma_share_mode_shared, deviceType, &deviceInfo.id, 0, &pPCM); - if (result != MA_SUCCESS) { + if (pContextStateALSA->snd_pcm_open(&pPCM, deviceInfo.id.alsa, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, 0) != 0) { goto next_device; } @@ -29238,9 +29114,9 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu } + static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_state_alsa* pContextStateALSA, ma_device_state_alsa* pDeviceStateALSA, const ma_device_config_alsa* pDeviceConfigALSA, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { - ma_result result; int resultALSA; ma_snd_pcm_t* pPCM; ma_bool32 isUsingMMap; @@ -29252,18 +29128,19 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; int openMode; + size_t paramsMemorySize; + void* pParamsMemory; /* One allocation for both hardware and software params. */ ma_snd_pcm_hw_params_t* pHWParams; ma_snd_pcm_sw_params_t* pSWParams; ma_snd_pcm_uframes_t bufferBoundary; - int pollDescriptorCount; - struct pollfd* pPollDescriptors; - int wakeupfd; - void* pIntermediaryBuffer; + const char* pDeviceNames[16]; + size_t iDeviceName = 0; + size_t deviceNameCount; MA_ASSERT(pContextStateALSA != NULL); - MA_ASSERT(pDeviceStateALSA != NULL); + MA_ASSERT(pDeviceStateALSA != NULL); MA_ASSERT(pDeviceConfigALSA != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should only be called for playback _or_ capture, never duplex. */ + MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should only be called for playback _or_ capture, never duplex. */ formatALSA = ma_convert_ma_format_to_alsa_format(pDescriptor->format); @@ -29278,436 +29155,373 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s openMode |= MA_SND_PCM_NO_AUTO_FORMAT; } - result = ma_context_open_pcm__alsa(pContext, pContextStateALSA, pDescriptor->shareMode, deviceType, pDescriptor->pDeviceID, openMode, &pPCM); - if (result != MA_SUCCESS) { - return result; + + /* When a device ID is specified we'll use that exact name, otherwise we'll use a list of default devices to try. */ + MA_ZERO_MEMORY(pDeviceNames, sizeof(pDeviceNames)); + iDeviceName = 0; + + if (pDescriptor->pDeviceID == NULL || pDescriptor->pDeviceID->alsa[0] == '\0') { + /* Using default device. */ + pDeviceNames[iDeviceName++] = "default"; + pDeviceNames[iDeviceName++] = "sysdefault"; + + if (pDescriptor->shareMode == ma_share_mode_exclusive) { + pDeviceNames[iDeviceName++] = "hw"; + } else { + if (deviceType == ma_device_type_playback) { + pDeviceNames[iDeviceName++] = "dmix"; + } else { + pDeviceNames[iDeviceName++] = "dsnoop"; + } + } + } else { + /* Using a specific device. */ + pDeviceNames[iDeviceName++] = pDescriptor->pDeviceID->alsa; } + deviceNameCount = iDeviceName; - /* Hardware parameters. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(pContextStateALSA->snd_pcm_hw_params_sizeof(), ma_context_get_allocation_callbacks(pContext)); - if (pHWParams == NULL) { - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for hardware parameters."); + + /* Allocate memory for our hardware and software params. We do this with a single allocation. */ + paramsMemorySize = 0; + paramsMemorySize += ma_align_64(pContextStateALSA->snd_pcm_hw_params_sizeof()); + paramsMemorySize += ma_align_64(pContextStateALSA->snd_pcm_sw_params_sizeof()); + + pParamsMemory = ma_calloc(paramsMemorySize, ma_context_get_allocation_callbacks(pContext)); + if (pParamsMemory == NULL) { return MA_OUT_OF_MEMORY; } - resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); - return ma_result_from_errno(-resultALSA); - } + pHWParams = (ma_snd_pcm_hw_params_t*)pParamsMemory; + pSWParams = (ma_snd_pcm_sw_params_t*)ma_offset_ptr(pParamsMemory, ma_align_64(pContextStateALSA->snd_pcm_hw_params_sizeof())); - /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ - isUsingMMap = MA_FALSE; -#if 0 /* NOTE: MMAP mode temporarily disabled. */ - if (deviceType != ma_device_type_capture) { /* <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it which means I can't test it... Contributions welcome. */ - if (!pConfig->alsa.noMMap) { - if (pContextStateALSA->snd_pcm_hw_params_set_access(pPCM, pHWParams, MA_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { - pDeviceStateALSA->isUsingMMap = MA_TRUE; + for (iDeviceName = 0; iDeviceName < deviceNameCount; iDeviceName += 1) { + const char* pDeviceName = pDeviceNames[iDeviceName]; + + resultALSA = pContextStateALSA->snd_pcm_open(&pPCM, pDeviceName, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, openMode); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed for device \"%s\".", pDeviceName); + continue; + } + + resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters for device \"%s\". snd_pcm_hw_params_any() failed.", pDeviceName); + continue; + } + + /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ + isUsingMMap = MA_FALSE; + #if 0 /* NOTE: MMAP mode temporarily disabled. */ + if (deviceType != ma_device_type_capture) { /* <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it which means I can't test it... Contributions welcome. */ + if (!pConfig->alsa.noMMap) { + if (pContextStateALSA->snd_pcm_hw_params_set_access(pPCM, pHWParams, MA_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { + pDeviceStateALSA->isUsingMMap = MA_TRUE; + } } } - } -#endif + #endif - if (!isUsingMMap) { - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_access(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[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."); - return ma_result_from_errno(-resultALSA); + if (!isUsingMMap) { + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_access(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED for device \"%s\". snd_pcm_hw_params_set_access() failed.", pDeviceName); + continue; + } } - } - /* - Most important properties first. The documentation for OSS (yes, I know this is ALSA!) recommends format, channels, then sample rate. I can't - find any documentation for ALSA specifically, so I'm going to copy the recommendation for OSS. - */ + /* The configuration order is the same as what aplay does: format, channels, sample rate, periods. */ - /* Format. */ - { - /* - 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 (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN || pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, formatALSA) != 0) { - /* We're either requesting the native format or the specified format is not supported. */ - size_t iFormat; + /* Format. */ + { + /* + 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 (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN || pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, formatALSA) != 0) { + /* We're either requesting the native format or the specified format is not supported. */ + size_t iFormat; - formatALSA = MA_SND_PCM_FORMAT_UNKNOWN; - for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); ++iFormat) { - if (pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat])) == 0) { - formatALSA = ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat]); - break; + formatALSA = MA_SND_PCM_FORMAT_UNKNOWN; + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); ++iFormat) { + if (pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat])) == 0) { + formatALSA = ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat]); + break; + } + } + + if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device \"%s\" does not support any miniaudio formats.", pDeviceName); + continue; } } - if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, formatALSA); + if (resultALSA < 0) { pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats."); - return MA_FORMAT_NOT_SUPPORTED; + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_hw_params_set_format() failed for device \"%s\".", pDeviceName); + continue; + } + + internalFormat = ma_format_from_alsa(formatALSA); + if (internalFormat == ma_format_unknown) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format used by device \"%s\" is not supported by miniaudio.", pDeviceName); + continue; } } - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, formatALSA); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed."); - return ma_result_from_errno(-resultALSA); - } + /* Channels. */ + { + unsigned int channels = pDescriptor->channels; - internalFormat = ma_format_from_alsa(formatALSA); - if (internalFormat == ma_format_unknown) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio."); - return MA_FORMAT_NOT_SUPPORTED; - } - } + /* If an explicit channel count was requested try using that directly. If this fails we'll fall back to set_channels_near(). */ + resultALSA = -1; + if (channels != 0 && pContextStateALSA->snd_pcm_hw_params_test_channels(pPCM, pHWParams, channels) == 0) { + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_channels(pPCM, pHWParams, channels); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[ALSA] Failed to set channel count for device \"%s\" to %u. Falling back to snd_pcm_hw_params_set_channels_near().", pDeviceName, channels); + } + } - /* Channels. */ - { - unsigned int channels = pDescriptor->channels; - - /* If an explicit channel count was requested try using that directly. If this fails we'll fall back to set_channels_near(). */ - resultALSA = -1; - if (channels != 0 && pContextStateALSA->snd_pcm_hw_params_test_channels(pPCM, pHWParams, channels) == 0) { - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_channels(pPCM, pHWParams, channels); + /* Fallback to set_channels_near() if we couldn't set the exact channel count. */ if (resultALSA < 0) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[ALSA] Failed to set channel count to %u. Falling back to snd_pcm_hw_params_set_channels_near().", channels); - } - } + if (channels == 0) { + channels = MA_DEFAULT_CHANNELS; + } - /* Fallback to set_channels_near() if we couldn't set the exact channel count. */ - if (resultALSA < 0) { - if (channels == 0) { - channels = MA_DEFAULT_CHANNELS; + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_channels_near(pPCM, pHWParams, &channels); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count for device \"%s\" with snd_pcm_hw_params_set_channels_near().", pDeviceName); + } } - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_channels_near(pPCM, pHWParams, &channels); + /* If we get here and we still haven't been able to find a channel count just bomb out. */ if (resultALSA < 0) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count with snd_pcm_hw_params_set_channels_near()."); + pContextStateALSA->snd_pcm_close(pPCM); + continue; + } + + internalChannels = (ma_uint32)channels; + } + + /* Sample Rate */ + { + unsigned int sampleRate; + + /* + It appears there's either a bug in ALSA, a bug in some drivers, or I'm doing something silly; but having resampling enabled causes + problems with some device configurations when used in conjunction with MMAP access mode. To fix this problem we need to disable + resampling. + + To reproduce this problem, open the "plug:dmix" device, and set the sample rate to 44100. Internally, it looks like dmix uses a + sample rate of 48000. The hardware parameters will get set correctly with no errors, but it looks like the 44100 -> 48000 resampling + doesn't work properly - but only with MMAP access mode. You will notice skipping/crackling in the audio, and it'll run at a slightly + faster rate. + + miniaudio has built-in support for sample rate conversion (albeit low quality at the moment), so disabling resampling should be fine + for us. The only problem is that it won't be taking advantage of any kind of hardware-accelerated resampling and it won't be very + good quality until I get a chance to improve the quality of miniaudio's software sample rate conversion. + + I don't currently know if the dmix plugin is the only one with this error. Indeed, this is the only one I've been able to reproduce + this error with. In the future, we may want to restrict the disabling of resampling to only known bad plugins. + */ + pContextStateALSA->snd_pcm_hw_params_set_rate_resample(pPCM, pHWParams, 0); + + sampleRate = pDescriptor->sampleRate; + if (sampleRate == 0) { + sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_rate_near(pPCM, pHWParams, &sampleRate, 0); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_hw_params_set_rate_near() failed for device \"%s\".", pDeviceName); + continue; + } + + internalSampleRate = (ma_uint32)sampleRate; + } + + /* Periods. */ + { + ma_uint32 periods = pDescriptor->periodCount; + + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_periods_near(pPCM, pHWParams, &periods, NULL); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count for device \"%s\". snd_pcm_hw_params_set_periods_near() failed.", pDeviceName); + continue; + } + + internalPeriods = periods; + } + + /* Buffer Size */ + { + ma_snd_pcm_uframes_t actualBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate) * internalPeriods; + + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_buffer_size_near(pPCM, pHWParams, &actualBufferSizeInFrames); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device \"%s\". snd_pcm_hw_params_set_buffer_size() failed.", pDeviceName); + continue; + } + + internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; + } + + /* Apply hardware parameters. */ + resultALSA = pContextStateALSA->snd_pcm_hw_params(pPCM, pHWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters for device \"%s\". snd_pcm_hw_params() failed.", pDeviceName); + continue; + } + + + /* Software parameters. */ + resultALSA = pContextStateALSA->snd_pcm_sw_params_current(pPCM, pSWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters for device \"%s\". snd_pcm_sw_params_current() failed.", pDeviceName); + continue; + } + + resultALSA = pContextStateALSA->snd_pcm_sw_params_set_avail_min(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed for device \"%s\".", pDeviceName); + continue; + } + + resultALSA = pContextStateALSA->snd_pcm_sw_params_get_boundary(pSWParams, &bufferBoundary); + if (resultALSA < 0) { + bufferBoundary = internalPeriodSizeInFrames * internalPeriods; + } + + if (deviceType == ma_device_type_playback && !isUsingMMap) { /* Only playback devices in writei/readi mode need a start threshold. */ + /* + Subtle detail here with the start threshold. When in playback-only mode (no full-duplex) we can set the start threshold to + the size of a period. But for full-duplex we need to set it such that it is at least two periods. + */ + resultALSA = pContextStateALSA->snd_pcm_sw_params_set_start_threshold(pPCM, pSWParams, internalPeriodSizeInFrames*2); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device \"%s\". snd_pcm_sw_params_set_start_threshold() failed.", pDeviceName); + continue; + } + + resultALSA = pContextStateALSA->snd_pcm_sw_params_set_stop_threshold(pPCM, pSWParams, bufferBoundary); + if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device \"%s\". snd_pcm_sw_params_set_stop_threshold() failed.", pDeviceName); + continue; } } - /* If we get here and we still haven't been able to find a channel count just bomb out. */ + resultALSA = pContextStateALSA->snd_pcm_sw_params(pPCM, pSWParams); if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); pContextStateALSA->snd_pcm_close(pPCM); - return ma_result_from_errno(-resultALSA); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters for device \"%s\". snd_pcm_sw_params() failed.", pDeviceName); + continue; } - internalChannels = (ma_uint32)channels; - } - /* Sample Rate */ - { - unsigned int sampleRate; + /* Grab the internal channel map. For now we're not going to bother trying to change the channel map and instead just do it ourselves. */ + { + if (pDeviceConfigALSA->assumeDefaultChannelLayout) { + ma_snd_pcm_chmap_t* pChmap = NULL; + if (pContextStateALSA->snd_pcm_get_chmap != NULL) { + pChmap = pContextStateALSA->snd_pcm_get_chmap(pPCM); + } - /* - It appears there's either a bug in ALSA, a bug in some drivers, or I'm doing something silly; but having resampling enabled causes - problems with some device configurations when used in conjunction with MMAP access mode. To fix this problem we need to disable - resampling. + if (pChmap != NULL) { + ma_uint32 iChannel; - To reproduce this problem, open the "plug:dmix" device, and set the sample rate to 44100. Internally, it looks like dmix uses a - sample rate of 48000. The hardware parameters will get set correctly with no errors, but it looks like the 44100 -> 48000 resampling - doesn't work properly - but only with MMAP access mode. You will notice skipping/crackling in the audio, and it'll run at a slightly - faster rate. + /* There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). */ + if (pChmap->channels >= internalChannels) { + /* Drop excess channels. */ + for (iChannel = 0; iChannel < internalChannels; ++iChannel) { + internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); + } + } else { + ma_uint32 i; - miniaudio has built-in support for sample rate conversion (albeit low quality at the moment), so disabling resampling should be fine - for us. The only problem is that it won't be taking advantage of any kind of hardware-accelerated resampling and it won't be very - good quality until I get a chance to improve the quality of miniaudio's software sample rate conversion. + /* + Excess channels use defaults. Do an initial fill with defaults, overwrite the first pChmap->channels, validate to ensure there are no duplicate + channels. If validation fails, fall back to defaults. + */ + ma_bool32 isValid = MA_TRUE; - I don't currently know if the dmix plugin is the only one with this error. Indeed, this is the only one I've been able to reproduce - this error with. In the future, we may want to restrict the disabling of resampling to only known bad plugins. - */ - pContextStateALSA->snd_pcm_hw_params_set_rate_resample(pPCM, pHWParams, 0); + /* Fill with defaults. */ + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); - sampleRate = pDescriptor->sampleRate; - if (sampleRate == 0) { - sampleRate = MA_DEFAULT_SAMPLE_RATE; - } + /* Overwrite first pChmap->channels channels. */ + for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { + internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); + } - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_rate_near(pPCM, pHWParams, &sampleRate, 0); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed."); - return ma_result_from_errno(-resultALSA); - } - - internalSampleRate = (ma_uint32)sampleRate; - } - - /* Periods. */ - { - ma_uint32 periods = pDescriptor->periodCount; - - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_periods_near(pPCM, pHWParams, &periods, NULL); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed."); - return ma_result_from_errno(-resultALSA); - } - - internalPeriods = periods; - } - - /* Buffer Size */ - { - ma_snd_pcm_uframes_t actualBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate) * internalPeriods; - - resultALSA = pContextStateALSA->snd_pcm_hw_params_set_buffer_size_near(pPCM, pHWParams, &actualBufferSizeInFrames); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed."); - return ma_result_from_errno(-resultALSA); - } - - internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; - } - - /* Apply hardware parameters. */ - resultALSA = pContextStateALSA->snd_pcm_hw_params(pPCM, pHWParams); - if (resultALSA < 0) { - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed."); - return ma_result_from_errno(-resultALSA); - } - - ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); - pHWParams = NULL; - - - /* Software parameters. */ - pSWParams = (ma_snd_pcm_sw_params_t*)ma_calloc(pContextStateALSA->snd_pcm_sw_params_sizeof(), ma_context_get_allocation_callbacks(pContext)); - if (pSWParams == NULL) { - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for software parameters."); - return MA_OUT_OF_MEMORY; - } - - resultALSA = pContextStateALSA->snd_pcm_sw_params_current(pPCM, pSWParams); - if (resultALSA < 0) { - ma_free(pSWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed."); - return ma_result_from_errno(-resultALSA); - } - - resultALSA = pContextStateALSA->snd_pcm_sw_params_set_avail_min(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); - if (resultALSA < 0) { - ma_free(pSWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed."); - return ma_result_from_errno(-resultALSA); - } - - resultALSA = pContextStateALSA->snd_pcm_sw_params_get_boundary(pSWParams, &bufferBoundary); - if (resultALSA < 0) { - bufferBoundary = internalPeriodSizeInFrames * internalPeriods; - } - - if (deviceType == ma_device_type_playback && !isUsingMMap) { /* Only playback devices in writei/readi mode need a start threshold. */ - /* - Subtle detail here with the start threshold. When in playback-only mode (no full-duplex) we can set the start threshold to - the size of a period. But for full-duplex we need to set it such that it is at least two periods. - */ - resultALSA = pContextStateALSA->snd_pcm_sw_params_set_start_threshold(pPCM, pSWParams, internalPeriodSizeInFrames*2); - if (resultALSA < 0) { - ma_free(pSWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed."); - return ma_result_from_errno(-resultALSA); - } - - resultALSA = pContextStateALSA->snd_pcm_sw_params_set_stop_threshold(pPCM, pSWParams, bufferBoundary); - if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ - ma_free(pSWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed."); - return ma_result_from_errno(-resultALSA); - } - } - - resultALSA = pContextStateALSA->snd_pcm_sw_params(pPCM, pSWParams); - if (resultALSA < 0) { - ma_free(pSWParams, ma_context_get_allocation_callbacks(pContext)); - pContextStateALSA->snd_pcm_close(pPCM); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed."); - return ma_result_from_errno(-resultALSA); - } - - ma_free(pSWParams, ma_context_get_allocation_callbacks(pContext)); - pSWParams = NULL; - - - /* Grab the internal channel map. For now we're not going to bother trying to change the channel map and instead just do it ourselves. */ - { - if (pDeviceConfigALSA->assumeDefaultChannelLayout) { - ma_snd_pcm_chmap_t* pChmap = NULL; - if (pContextStateALSA->snd_pcm_get_chmap != NULL) { - pChmap = pContextStateALSA->snd_pcm_get_chmap(pPCM); - } - - if (pChmap != NULL) { - ma_uint32 iChannel; - - /* There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). */ - if (pChmap->channels >= internalChannels) { - /* Drop excess channels. */ - for (iChannel = 0; iChannel < internalChannels; ++iChannel) { - internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); - } - } else { - ma_uint32 i; - - /* - Excess channels use defaults. Do an initial fill with defaults, overwrite the first pChmap->channels, validate to ensure there are no duplicate - channels. If validation fails, fall back to defaults. - */ - ma_bool32 isValid = MA_TRUE; - - /* Fill with defaults. */ - ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); - - /* Overwrite first pChmap->channels channels. */ - for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { - internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); - } - - /* Validate. */ - for (i = 0; i < internalChannels && isValid; ++i) { - ma_uint32 j; - for (j = i+1; j < internalChannels; ++j) { - if (internalChannelMap[i] == internalChannelMap[j]) { - isValid = MA_FALSE; - break; + /* Validate. */ + for (i = 0; i < internalChannels && isValid; ++i) { + ma_uint32 j; + for (j = i+1; j < internalChannels; ++j) { + if (internalChannelMap[i] == internalChannelMap[j]) { + isValid = MA_FALSE; + break; + } } } + + /* If our channel map is invalid, fall back to defaults. */ + if (!isValid) { + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); + } } - /* If our channel map is invalid, fall back to defaults. */ - if (!isValid) { - ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); - } + free(pChmap); + pChmap = NULL; + } else { + /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } - - free(pChmap); - pChmap = NULL; } else { - /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ + /* The caller has requested that we always use the default ALSA channel layout. */ ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } - } else { - /* The caller has requested that we always use the default ALSA channel layout. */ - ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } + + + /* If we've made it this far it means we successfully initialized the PCM object. */ + if (deviceType == ma_device_type_capture) { + pDeviceStateALSA->pPCMCapture = pPCM; + pDeviceStateALSA->isUsingMMapCapture = isUsingMMap; + } else { + pDeviceStateALSA->pPCMPlayback = pPCM; + pDeviceStateALSA->isUsingMMapPlayback = isUsingMMap; + } + + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_channel_map_copy(pDescriptor->channelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; + + ma_free(pParamsMemory, ma_context_get_allocation_callbacks(pContext)); + return MA_SUCCESS; } - - /* 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 - to allocate this on the heap. - */ - 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; - } - - 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; - } - - /* - We need an eventfd to wakeup from poll() and avoid a deadlock in situations where the driver - never returns from writei() and readi(). This has been observed with the "pulse" device. - */ - wakeupfd = eventfd(0, 0); - 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); - } - - /* We'll place the wakeup fd at the start of the buffer. */ - pPollDescriptors[0].fd = wakeupfd; - pPollDescriptors[0].events = POLLIN; /* We only care about waiting to read from the wakeup file descriptor. */ - pPollDescriptors[0].revents = 0; - - /* We can now extract the PCM poll descriptors which we place after the wakeup descriptor. */ - pollDescriptorCount = pContextStateALSA->snd_pcm_poll_descriptors(pPCM, pPollDescriptors + 1, pollDescriptorCount); /* +1 because we want to place these descriptors after the wakeup descriptor. */ - if (pollDescriptorCount <= 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 retrieve poll descriptors."); - return MA_ERROR; - } - - /* 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->pIntermediaryBufferCapture = pIntermediaryBuffer; - pDeviceStateALSA->isUsingMMapCapture = isUsingMMap; - pDeviceStateALSA->pollDescriptorCountCapture = pollDescriptorCount; - pDeviceStateALSA->pPollDescriptorsCapture = pPollDescriptors; - pDeviceStateALSA->wakeupfdCapture = wakeupfd; - } else { - pDeviceStateALSA->pPCMPlayback = pPCM; - pDeviceStateALSA->pIntermediaryBufferPlayback = pIntermediaryBuffer; - pDeviceStateALSA->isUsingMMapPlayback = isUsingMMap; - pDeviceStateALSA->pollDescriptorCountPlayback = pollDescriptorCount; - pDeviceStateALSA->pPollDescriptorsPlayback = pPollDescriptors; - pDeviceStateALSA->wakeupfdPlayback = wakeupfd; - } - - pDescriptor->format = internalFormat; - pDescriptor->channels = internalChannels; - pDescriptor->sampleRate = internalSampleRate; - ma_channel_map_copy(pDescriptor->channelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); - pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; - pDescriptor->periodCount = internalPeriods; - - return MA_SUCCESS; + /* Getting here means we failed to initialize the device. */ + ma_free(pParamsMemory, ma_context_get_allocation_callbacks(pContext)); + return MA_ERROR; } +static void ma_device_uninit__alsa(ma_device* pDevice); + static ma_result ma_device_init__alsa(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { ma_device_state_alsa* pDeviceStateALSA; @@ -29715,6 +29529,9 @@ static ma_result ma_device_init__alsa(ma_device* pDevice, const void* pDeviceBac ma_device_config_alsa defaultConfigALSA; ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); ma_device_type deviceType = ma_device_get_type(pDevice); + int pollDescriptorCountPlayback = 0; + int pollDescriptorCountCapture = 0; + int resultALSA; MA_ASSERT(pContextStateALSA != NULL); @@ -29735,19 +29552,102 @@ static ma_result ma_device_init__alsa(ma_device* pDevice, const void* pDeviceBac if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_by_type__alsa(ma_device_get_context(pDevice), pContextStateALSA, pDeviceStateALSA, pDeviceConfigALSA, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { - ma_free(pDeviceStateALSA, ma_device_get_allocation_callbacks(pDevice)); + ma_device_uninit__alsa(pDevice); return result; } + + pollDescriptorCountCapture = pContextStateALSA->snd_pcm_poll_descriptors_count(pDeviceStateALSA->pPCMCapture); + if (pollDescriptorCountCapture <= 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + ma_device_uninit__alsa(pDevice); + return MA_ERROR; + } } if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_by_type__alsa(ma_device_get_context(pDevice), pContextStateALSA, pDeviceStateALSA, pDeviceConfigALSA, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { - ma_free(pDeviceStateALSA, ma_device_get_allocation_callbacks(pDevice)); + ma_device_uninit__alsa(pDevice); return result; } + + pollDescriptorCountPlayback = pContextStateALSA->snd_pcm_poll_descriptors_count(pDeviceStateALSA->pPCMPlayback); + if (pollDescriptorCountPlayback <= 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + ma_device_uninit__alsa(pDevice); + return MA_ERROR; + } } + /* + We need an array of poll descriptors to check for when data is available. We query these from ALSA, but we also want to + include an extra one specifically for waking up for the purpose of our wakeup() callback. + */ + pDeviceStateALSA->pollDescriptorCount = 1 + pollDescriptorCountCapture + pollDescriptorCountPlayback; + + pDeviceStateALSA->pPollDescriptors = (struct pollfd*)ma_calloc(sizeof(struct pollfd) * pDeviceStateALSA->pollDescriptorCount, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateALSA->pPollDescriptors == NULL) { + ma_device_uninit__alsa(pDevice); + return MA_OUT_OF_MEMORY; + } + + pDeviceStateALSA->wakeupfd = eventfd(0, 0); + if (pDeviceStateALSA->wakeupfd < 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup."); + ma_device_uninit__alsa(pDevice); + return MA_ERROR; + } + + pDeviceStateALSA->pPollDescriptors[0].fd = pDeviceStateALSA->wakeupfd; + pDeviceStateALSA->pPollDescriptors[0].events = POLLIN; + pDeviceStateALSA->pPollDescriptors[0].revents = 0; + + if (pollDescriptorCountCapture > 0) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPollDescriptors + 1, pollDescriptorCountCapture); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors."); + ma_device_uninit__alsa(pDevice); + return MA_ERROR; + } + + pDeviceStateALSA->pollDescriptorCountCapture = pollDescriptorCountCapture; + } + + if (pollDescriptorCountPlayback > 0) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors(pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pPollDescriptors + 1 + pollDescriptorCountCapture, pollDescriptorCountPlayback); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors."); + ma_device_uninit__alsa(pDevice); + return MA_ERROR; + } + + pDeviceStateALSA->pollDescriptorCountPlayback = pollDescriptorCountPlayback; + } + + + /* Now we need a scratch buffer for snd_pcm_read() and snd_pcm_write(). */ + { + ma_uint32 intermediaryBufferSizeCapture = 0; + ma_uint32 intermediaryBufferSizePlayback = 0; + ma_uint32 intermediaryBufferSize; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + intermediaryBufferSizeCapture = ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodSizeInFrames; + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + intermediaryBufferSizePlayback = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodSizeInFrames; + } + + intermediaryBufferSize = ma_max(intermediaryBufferSizeCapture, intermediaryBufferSizePlayback); + + pDeviceStateALSA->pIntermediaryBuffer = ma_calloc(intermediaryBufferSize, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateALSA->pIntermediaryBuffer == NULL) { + ma_device_uninit__alsa(pDevice); + return MA_OUT_OF_MEMORY; + } + } + + *ppDeviceState = pDeviceStateALSA; return MA_SUCCESS; @@ -29760,16 +29660,16 @@ static void ma_device_uninit__alsa(ma_device* pDevice) if (pDeviceStateALSA->pPCMCapture) { 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->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateALSA->pPollDescriptors, ma_device_get_allocation_callbacks(pDevice)); + + if (pDeviceStateALSA->wakeupfd >= 0) { + close(pDeviceStateALSA->wakeupfd); } ma_free(pDeviceStateALSA, ma_device_get_allocation_callbacks(pDevice)); @@ -29834,16 +29734,6 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) } else { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful."); } - - /* Clear the wakeupfd. */ - resultPoll = poll(pDeviceStateALSA->pPollDescriptorsCapture, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - resultRead = read(pDeviceStateALSA->pPollDescriptorsCapture[0].fd, &t, sizeof(t)); - if (resultRead != sizeof(t)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from capture wakeupfd. read() = %d", resultRead); - } - } } if (pDeviceStateALSA->pPCMPlayback != NULL) { @@ -29858,197 +29748,154 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) } else { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device successful."); } + } - /* Clear the wakeupfd. */ - resultPoll = poll(pDeviceStateALSA->pPollDescriptorsPlayback, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - resultRead = read(pDeviceStateALSA->pPollDescriptorsPlayback[0].fd, &t, sizeof(t)); - if (resultRead != sizeof(t)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from playback wakeupfd. read() = %d", resultRead); - } + /* Clear the wakeupfd. */ + resultPoll = poll(pDeviceStateALSA->pPollDescriptors, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + resultRead = read(pDeviceStateALSA->pPollDescriptors[0].fd, &t, sizeof(t)); + if (resultRead != sizeof(t)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from wakeupfd. read() = %d", resultRead); } } 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, int timeout, ma_bool32* pIsDataAvailable) -{ - MA_ASSERT(pIsDataAvailable != NULL); - - *pIsDataAvailable = MA_FALSE; - - for (;;) { - unsigned short revents; - int resultALSA; - int resultPoll = poll(pPollDescriptors, pollDescriptorCount, timeout); - if (resultPoll < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] poll() failed."); - - /* - There have been reports that poll() is returning an error randomly and that instead of - returning an error, simply trying again will work. I'm experimenting with adopting this - advice. - */ - continue; - /*return ma_result_from_errno(errno);*/ - } - - /* - Before checking the ALSA poll descriptor flag we need to check if the wakeup descriptor - has had it's POLLIN flag set. If so, we need to actually read the data and then exit the - function. The wakeup descriptor will be the first item in the descriptors buffer. - */ - if ((pPollDescriptors[0].revents & POLLIN) != 0) { - ma_uint64 t; - int resultRead = read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ - if (resultRead < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed."); - return ma_result_from_errno(errno); - } - - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd"); - return MA_DEVICE_NOT_STARTED; - } - - /* - Getting here means that some data should be able to be read. We need to use ALSA to - translate the revents flags for us. - */ - resultALSA = pContextStateALSA->snd_pcm_poll_descriptors_revents(pPCM, pPollDescriptors + 1, pollDescriptorCount - 1, &revents); /* +1, -1 to ignore the wakeup descriptor. */ - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed."); - return ma_result_from_errno(-resultALSA); - } - - if ((revents & POLLERR) != 0) { - ma_snd_pcm_state_t state = pContextStateALSA->snd_pcm_state(pPCM); - if (state == MA_SND_PCM_STATE_XRUN) { - /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d", pContextStateALSA->snd_pcm_state(pPCM)); - } - } - - if ((revents & requiredEvent) == requiredEvent) { - *pIsDataAvailable = MA_TRUE; - break; /* We're done. Data available for reading or writing. */ - } - - /* In non-blocking mode we don't want to keep looping while we wait for data. */ - if (timeout == 0) { - break; - } - } - - return MA_SUCCESS; -} - -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, 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, int timeout, ma_bool32* pIsDataAvailable) -{ - 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, int timeout) +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_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); - ma_snd_pcm_sframes_t resultALSA = 0; - - MA_ASSERT(pFramesOut != NULL); - MA_ASSERT(pFramesRead != NULL); + ma_device_type deviceType = ma_device_get_type(pDevice); + int timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? -1 : 0; + int resultALSA; + int resultPoll; + unsigned short revents; - *pFramesRead = 0; + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } - for (;;) { - ma_result result; - ma_bool32 isDataAvailable; + /* + There have been reports that poll() is returning an error randomly and that instead of + returning an error, simply trying again will work. I'm experimenting with adopting this + advice. + */ + do { + resultPoll = poll(pDeviceStateALSA->pPollDescriptors, pDeviceStateALSA->pollDescriptorCount, timeout); + } while (resultPoll < 0 && errno == EINTR); - /* 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, timeout, &isDataAvailable); - if (result != MA_SUCCESS) { - return result; + if (resultPoll < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); + return MA_ERROR; + } + + if (resultPoll == 0) { + return MA_SUCCESS; /* Timeout. */ + } + + /* Check if the wakeup fd was triggered before checking anything else. */ + if ((pDeviceStateALSA->pPollDescriptors[0].revents & POLLIN) != 0) { + ma_uint64 t; + int resultRead = read(pDeviceStateALSA->pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + if (resultRead < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed."); + return ma_result_from_errno(errno); } - if (isDataAvailable) { - resultALSA = pContextStateALSA->snd_pcm_readi(pDeviceStateALSA->pPCMCapture, pFramesOut, frameCount); - if (resultALSA >= 0) { - break; /* Success. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd"); + return MA_SUCCESS; + } + + /* First check the capture side. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors_revents(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPollDescriptors + 1, pDeviceStateALSA->pollDescriptorCountCapture, &revents); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] snd_pcm_poll_descriptors_revents() failed for capture."); + return MA_ERROR; + } + + if ((revents & POLLERR) != 0) { + ma_snd_pcm_state_t state = pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMCapture); + if (state == MA_SND_PCM_STATE_XRUN) { + /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Capture xrun detected."); } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d", pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMCapture)); + } + } + + if ((revents & POLLIN) != 0) { + resultALSA = pContextStateALSA->snd_pcm_readi(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pIntermediaryBuffer, pDevice->capture.internalPeriodSizeInFrames); + if (resultALSA >= 0) { + /* Success. Process the data. */ + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateALSA->pIntermediaryBuffer, (ma_uint32)resultALSA); + } else { + /* Failed. No data processing will be done this iteration. What we do here depends on the type of error. */ if (resultALSA == -EAGAIN) { /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)");*/ - continue; /* Try again. */ + + /* Do nothing. */ } 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. */ + /* Overrun. Recover. 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."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover capture 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."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device after overrun."); return ma_result_from_errno((int)-resultALSA); } - - continue; /* Try reading again. */ + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Unexpected error when reading from capture device. errno = %d.", (int)-resultALSA); + return ma_result_from_errno((int)-resultALSA); } } } } - if (pFramesRead != NULL) { - *pFramesRead = resultALSA; - } - - return MA_SUCCESS; -} - -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)); - ma_snd_pcm_sframes_t resultALSA = 0; - - MA_ASSERT(pFramesIn != NULL); - MA_ASSERT(pFramesWritten != NULL); - - *pFramesWritten = 0; - - 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, timeout, &isDataAvailable); - if (result != MA_SUCCESS) { - return result; + /* Now check the playback side. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors_revents(pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pPollDescriptors + 1 + pDeviceStateALSA->pollDescriptorCountCapture, pDeviceStateALSA->pollDescriptorCountPlayback, &revents); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] snd_pcm_poll_descriptors_revents() failed for playback."); + return MA_ERROR; } - if (isDataAvailable) { - resultALSA = pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, pFramesIn, frameCount); - if (resultALSA >= 0) { - break; /* Success. */ + if ((revents & POLLERR) != 0) { + ma_snd_pcm_state_t state = pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMPlayback); + if (state == MA_SND_PCM_STATE_XRUN) { + /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Playback xrun detected"); } 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)"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d", pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMPlayback)); + } + } - /* 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 ((revents & POLLOUT) != 0) { + resultALSA = pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames); + if (resultALSA >= 0) { + /* Success. Process the data. */ + ma_device_handle_backend_data_callback(pDevice, pDeviceStateALSA->pIntermediaryBuffer, NULL, (ma_uint32)resultALSA); + } else { + /* Failed. No data processing will be done this iteration. What we do here depends on the type of error. */ + if (resultALSA == -EAGAIN) { + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)");*/ + + /* Do nothing. */ + } else if (resultALSA == -EPIPE) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)"); + + /* Underrun. Recover. If this fails we need to return an error. */ + resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMPlayback, resultALSA, MA_TRUE); if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover playback device after underrun."); return ma_result_from_errno((int)-resultALSA); } @@ -30061,56 +29908,17 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFramesIn */ 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."); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device after underrun."); return ma_result_from_errno((int)-resultALSA); } - - continue; /* Try writing again. */ + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Unexpected error when writing to playback device. errno = %d.", (int)-resultALSA); + return ma_result_from_errno((int)-resultALSA); } } } } - if (pFramesWritten != NULL) { - *pFramesWritten = resultALSA; - } - - 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; } @@ -30124,14 +29932,7 @@ static void ma_device_wakeup__alsa(ma_device* pDevice) ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up..."); - /* Write to an eventfd to trigger a wakeup from poll() and abort any reading or writing. */ - if (pDeviceStateALSA->pPollDescriptorsCapture != NULL) { - resultWrite = write(pDeviceStateALSA->wakeupfdCapture, &t, sizeof(t)); - } - if (pDeviceStateALSA->pPollDescriptorsPlayback != NULL) { - resultWrite = write(pDeviceStateALSA->wakeupfdPlayback, &t, sizeof(t)); - } - + resultWrite = write(pDeviceStateALSA->wakeupfd, &t, sizeof(t)); if (resultWrite < 0) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] write() failed."); return;