mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
ALSA: Changes to start/stop behaviour for playback devices.
* A start threshold is no longer used meaning an explicit call to snd_pcm_start() is required, which was happening already anyway. * The stop threshold is no longer used. * Stopping the playback side of device now drains it. * Starting a device will now pre-fill the playback side with silence before starting it which should hopefully avoid an immediate xrun.
This commit is contained in:
+77
-38
@@ -27975,6 +27975,7 @@ typedef struct
|
|||||||
typedef void (* ma_snd_lib_error_handler_t)(const char* file, int line, const char* function, int err, const char* fmt, ...);
|
typedef void (* ma_snd_lib_error_handler_t)(const char* file, int line, const char* function, int err, const char* fmt, ...);
|
||||||
|
|
||||||
typedef int (* ma_snd_lib_error_set_handler_proc) (ma_snd_lib_error_handler_t handler);
|
typedef int (* ma_snd_lib_error_set_handler_proc) (ma_snd_lib_error_handler_t handler);
|
||||||
|
typedef const char* (* ma_snd_strerror_proc) (int errnum);
|
||||||
typedef int (* ma_snd_pcm_open_proc) (ma_snd_pcm_t **pcm, const char *name, ma_snd_pcm_stream_t stream, int mode);
|
typedef int (* ma_snd_pcm_open_proc) (ma_snd_pcm_t **pcm, const char *name, ma_snd_pcm_stream_t stream, int mode);
|
||||||
typedef int (* ma_snd_pcm_close_proc) (ma_snd_pcm_t *pcm);
|
typedef int (* ma_snd_pcm_close_proc) (ma_snd_pcm_t *pcm);
|
||||||
typedef int (* ma_snd_pcm_link_proc) (ma_snd_pcm_t* pcm1, ma_snd_pcm_t* pcm2);
|
typedef int (* ma_snd_pcm_link_proc) (ma_snd_pcm_t* pcm1, ma_snd_pcm_t* pcm2);
|
||||||
@@ -28070,6 +28071,7 @@ typedef struct ma_context_state_alsa
|
|||||||
|
|
||||||
ma_handle asoundSO;
|
ma_handle asoundSO;
|
||||||
ma_snd_lib_error_set_handler_proc snd_lib_error_set_handler;
|
ma_snd_lib_error_set_handler_proc snd_lib_error_set_handler;
|
||||||
|
ma_snd_strerror_proc snd_strerror;
|
||||||
ma_snd_pcm_open_proc snd_pcm_open;
|
ma_snd_pcm_open_proc snd_pcm_open;
|
||||||
ma_snd_pcm_close_proc snd_pcm_close;
|
ma_snd_pcm_close_proc snd_pcm_close;
|
||||||
ma_snd_pcm_link_proc snd_pcm_link;
|
ma_snd_pcm_link_proc snd_pcm_link;
|
||||||
@@ -28501,6 +28503,7 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const void* pContex
|
|||||||
}
|
}
|
||||||
|
|
||||||
pContextStateALSA->snd_lib_error_set_handler = (ma_snd_lib_error_set_handler_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_lib_error_set_handler");
|
pContextStateALSA->snd_lib_error_set_handler = (ma_snd_lib_error_set_handler_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_lib_error_set_handler");
|
||||||
|
pContextStateALSA->snd_strerror = (ma_snd_strerror_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_strerror");
|
||||||
pContextStateALSA->snd_pcm_open = (ma_snd_pcm_open_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_open");
|
pContextStateALSA->snd_pcm_open = (ma_snd_pcm_open_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_open");
|
||||||
pContextStateALSA->snd_pcm_close = (ma_snd_pcm_close_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_close");
|
pContextStateALSA->snd_pcm_close = (ma_snd_pcm_close_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_close");
|
||||||
pContextStateALSA->snd_pcm_link = (ma_snd_pcm_link_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_link");
|
pContextStateALSA->snd_pcm_link = (ma_snd_pcm_link_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_link");
|
||||||
@@ -28575,6 +28578,7 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const void* pContex
|
|||||||
{
|
{
|
||||||
/* The system below is just for type safety. */
|
/* The system below is just for type safety. */
|
||||||
ma_snd_lib_error_set_handler_proc _snd_lib_error_set_handler = snd_lib_error_set_handler;
|
ma_snd_lib_error_set_handler_proc _snd_lib_error_set_handler = snd_lib_error_set_handler;
|
||||||
|
ma_snd_strerror_proc _snd_strerror = snd_strerror;
|
||||||
ma_snd_pcm_open_proc _snd_pcm_open = snd_pcm_open;
|
ma_snd_pcm_open_proc _snd_pcm_open = snd_pcm_open;
|
||||||
ma_snd_pcm_close_proc _snd_pcm_close = snd_pcm_close;
|
ma_snd_pcm_close_proc _snd_pcm_close = snd_pcm_close;
|
||||||
ma_snd_pcm_link_proc _snd_pcm_link = snd_pcm_link;
|
ma_snd_pcm_link_proc _snd_pcm_link = snd_pcm_link;
|
||||||
@@ -28646,6 +28650,7 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const void* pContex
|
|||||||
ma_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global;
|
ma_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global;
|
||||||
|
|
||||||
pContextStateALSA->snd_lib_error_set_handler = _snd_lib_error_set_handler;
|
pContextStateALSA->snd_lib_error_set_handler = _snd_lib_error_set_handler;
|
||||||
|
pContextStateALSA->snd_strerror = _snd_strerror;
|
||||||
pContextStateALSA->snd_pcm_open = _snd_pcm_open;
|
pContextStateALSA->snd_pcm_open = _snd_pcm_open;
|
||||||
pContextStateALSA->snd_pcm_close = _snd_pcm_close;
|
pContextStateALSA->snd_pcm_close = _snd_pcm_close;
|
||||||
pContextStateALSA->snd_pcm_link = _snd_pcm_link;
|
pContextStateALSA->snd_pcm_link = _snd_pcm_link;
|
||||||
@@ -28905,9 +28910,10 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu
|
|||||||
ma_snd_pcm_hw_params_t* pHWParams;
|
ma_snd_pcm_hw_params_t* pHWParams;
|
||||||
ma_uint32 iFormat;
|
ma_uint32 iFormat;
|
||||||
ma_uint32 iChannel;
|
ma_uint32 iChannel;
|
||||||
|
int openMode = 0; /*MA_SND_PCM_NO_AUTO_RESAMPLE | MA_SND_PCM_NO_AUTO_CHANNELS | MA_SND_PCM_NO_AUTO_FORMAT;*/
|
||||||
|
|
||||||
/* For detailed info we need to open the device. */
|
/* For detailed info we need to open the device. */
|
||||||
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) {
|
if (pContextStateALSA->snd_pcm_open(&pPCM, deviceInfo.id.alsa, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, openMode) != 0) {
|
||||||
goto next_device;
|
goto next_device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28922,7 +28928,7 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu
|
|||||||
if (resultALSA < 0) {
|
if (resultALSA < 0) {
|
||||||
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
|
ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext));
|
||||||
pContextStateALSA->snd_pcm_close(pPCM);
|
pContextStateALSA->snd_pcm_close(pPCM);
|
||||||
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.");
|
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. %s.", deviceInfo.id.alsa, pContextStateALSA->snd_strerror(resultALSA));
|
||||||
goto next_device;
|
goto next_device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29078,7 +29084,6 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
|
|||||||
void* pParamsMemory; /* One allocation for both hardware and software params. */
|
void* pParamsMemory; /* One allocation for both hardware and software params. */
|
||||||
ma_snd_pcm_hw_params_t* pHWParams;
|
ma_snd_pcm_hw_params_t* pHWParams;
|
||||||
ma_snd_pcm_sw_params_t* pSWParams;
|
ma_snd_pcm_sw_params_t* pSWParams;
|
||||||
ma_snd_pcm_uframes_t bufferBoundary;
|
|
||||||
const char* pDeviceNames[16];
|
const char* pDeviceNames[16];
|
||||||
size_t iDeviceName = 0;
|
size_t iDeviceName = 0;
|
||||||
size_t deviceNameCount;
|
size_t deviceNameCount;
|
||||||
@@ -29344,29 +29349,27 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_s
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
resultALSA = pContextStateALSA->snd_pcm_sw_params_get_boundary(pSWParams, &bufferBoundary);
|
/*
|
||||||
if (resultALSA < 0) {
|
miniaudio uses an explicit snd_pcm_start() to start the PCM. From the ALSA documentation:
|
||||||
bufferBoundary = internalPeriodSizeInFrames * internalPeriods;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deviceType == ma_device_type_playback && !isUsingMMap) { /* Only playback devices in writei/readi mode need a start threshold. */
|
If you want to use explicit start (snd_pcm_start), you can set this value greater than the ring buffer
|
||||||
/*
|
size (in frames). For that simply using a large constant such as LONG_MAX or the boundary value is not
|
||||||
Subtle detail here with the start threshold. When in playback-only mode (no full-duplex) we can set the start threshold to
|
a bad idea.
|
||||||
the size of a period. But for full-duplex we need to set it such that it is at least two periods.
|
|
||||||
*/
|
We'll just set the threshold to 0xFFFFFFFF.
|
||||||
resultALSA = pContextStateALSA->snd_pcm_sw_params_set_start_threshold(pPCM, pSWParams, internalPeriodSizeInFrames*2);
|
|
||||||
|
The reason we want explicitness when starting is mainly just duplex mode. We want to pre-fill the buffer
|
||||||
|
with silence, but we also want to link the playback PCM to capture PCM and use the capture side as the
|
||||||
|
master. The playback side should not auto-start during this pre-filling, but should instead be delayed to
|
||||||
|
the explicit snd_pcm_start().
|
||||||
|
*/
|
||||||
|
if (deviceType == ma_device_type_playback) {
|
||||||
|
resultALSA = pContextStateALSA->snd_pcm_sw_params_set_start_threshold(pPCM, pSWParams, 0xFFFFFFFF);
|
||||||
if (resultALSA < 0) {
|
if (resultALSA < 0) {
|
||||||
pContextStateALSA->snd_pcm_close(pPCM);
|
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);
|
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;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resultALSA = pContextStateALSA->snd_pcm_sw_params(pPCM, pSWParams);
|
resultALSA = pContextStateALSA->snd_pcm_sw_params(pPCM, pSWParams);
|
||||||
@@ -29636,6 +29639,32 @@ static ma_result ma_device_start__alsa(ma_device* pDevice)
|
|||||||
ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice));
|
ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice));
|
||||||
int resultALSA;
|
int resultALSA;
|
||||||
|
|
||||||
|
/*
|
||||||
|
For playback at this point we should have nothing in the internal buffers. We want to output a full
|
||||||
|
buffer of silence to the playback device, and since the buffer should be empty, this should be
|
||||||
|
effectively non-blocking.
|
||||||
|
*/
|
||||||
|
if (pDeviceStateALSA->pPCMPlayback != NULL) {
|
||||||
|
ma_uint8 buffer[4096];
|
||||||
|
ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
|
||||||
|
ma_uint32 framesToWrite = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods;
|
||||||
|
ma_uint32 framesWritten = 0;
|
||||||
|
|
||||||
|
MA_ZERO_MEMORY(buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
while (framesWritten < framesToWrite) {
|
||||||
|
ma_uint32 framesToWriteThisIteration = sizeof(buffer) / bpf;
|
||||||
|
|
||||||
|
/* Just a guard to ensure we don't get stuck in a loop. Should never happen in practice (would require a massive channel count). */
|
||||||
|
if (framesToWriteThisIteration == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, buffer, framesToWriteThisIteration);
|
||||||
|
framesWritten += framesToWriteThisIteration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (pDeviceStateALSA->pPCMCapture != NULL) {
|
if (pDeviceStateALSA->pPCMCapture != NULL) {
|
||||||
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMCapture);
|
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMCapture);
|
||||||
if (resultALSA < 0) {
|
if (resultALSA < 0) {
|
||||||
@@ -29646,16 +29675,6 @@ static ma_result ma_device_start__alsa(ma_device* pDevice)
|
|||||||
|
|
||||||
if (ma_device_get_type(pDevice) != ma_device_type_duplex || !pDeviceStateALSA->isLinked) { /* <-- In duplex mode the PCMs are linked which means the playback side will be started when the capture side starts. */
|
if (ma_device_get_type(pDevice) != ma_device_type_duplex || !pDeviceStateALSA->isLinked) { /* <-- In duplex mode the PCMs are linked which means the playback side will be started when the capture side starts. */
|
||||||
if (pDeviceStateALSA->pPCMPlayback != NULL) {
|
if (pDeviceStateALSA->pPCMPlayback != NULL) {
|
||||||
/*
|
|
||||||
When data is written to the device we wait for the device to get ready to receive data with poll(). In my testing
|
|
||||||
I have observed that poll() can sometimes block forever unless the device is started explicitly with snd_pcm_start()
|
|
||||||
or some data is written with snd_pcm_writei().
|
|
||||||
|
|
||||||
To resolve this I've decided to do an explicit start with snd_pcm_start(). The problem with this is that the device
|
|
||||||
is started without any data in the internal buffer which will result in an immediate underrun. If instead we were
|
|
||||||
to call into snd_pcm_writei() in an attempt to prevent the underrun, we would run the risk of a weird deadlock
|
|
||||||
issue as documented inside ma_device_write__alsa().
|
|
||||||
*/
|
|
||||||
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback);
|
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback);
|
||||||
if (resultALSA < 0) {
|
if (resultALSA < 0) {
|
||||||
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device.");
|
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device.");
|
||||||
@@ -29675,6 +29694,16 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice)
|
|||||||
int resultRead;
|
int resultRead;
|
||||||
|
|
||||||
if (pDeviceStateALSA->pPCMCapture != NULL) {
|
if (pDeviceStateALSA->pPCMCapture != NULL) {
|
||||||
|
/*
|
||||||
|
In the playback case we drain, but it's a bit different for capture. It is still true that the tail can
|
||||||
|
be trimmed, but I think it's less of an issue because the person doing the recording has more control
|
||||||
|
over when they actually stop recording. But a more practical concern I have is that when this function
|
||||||
|
is called, the device will be in a stopping status, and I don't like the idea of the miniaudio data
|
||||||
|
callback being fired when in a non-started state.
|
||||||
|
|
||||||
|
Maybe draining can be done at a higher level in the cross-platform section with a crude sleep equal to
|
||||||
|
the length of the internal buffer?
|
||||||
|
*/
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...");
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...");
|
||||||
pContextStateALSA->snd_pcm_drop(pDeviceStateALSA->pPCMCapture);
|
pContextStateALSA->snd_pcm_drop(pDeviceStateALSA->pPCMCapture);
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device successful.");
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device successful.");
|
||||||
@@ -29688,11 +29717,28 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ma_device_get_type(pDevice) != ma_device_type_duplex && pDeviceStateALSA->isLinked) { /* <-- In duplex mode the PCMs are linked which means the playback side will be stopped when the capture side starts. */
|
if (ma_device_get_type(pDevice) != ma_device_type_duplex || !pDeviceStateALSA->isLinked) { /* <-- In duplex mode the PCMs are linked which means the playback side will be stopped when the capture side starts. */
|
||||||
if (pDeviceStateALSA->pPCMPlayback != NULL) {
|
if (pDeviceStateALSA->pPCMPlayback != NULL) {
|
||||||
|
/*
|
||||||
|
When stopping we prefer to drain because if someone is playing a sound and then decides to stop the
|
||||||
|
device after the last part of the sound has been sent to the device, draining will ensure the tail
|
||||||
|
is actually played through the speakers. If we drop instead of drain, the tail will get trimmed.
|
||||||
|
|
||||||
|
However, there's a problem. `snd_pcm_drain()` is a blocking call, and I have not one single bit of
|
||||||
|
confidence that ALSA won't have a bug that results in `snd_pcm_drain()` get stuck in an infinite
|
||||||
|
hang. I'm going to bet $1 I'll have a bug report about the device hanging when stopping the device,
|
||||||
|
and it'll be right here in `snd_pcm_drain()`. If this happens we'll just do some kind of hack with
|
||||||
|
`snd_pcm_avail()` and a sleeping loop.
|
||||||
|
*/
|
||||||
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Draining playback device...");
|
||||||
|
pContextStateALSA->snd_pcm_drain(pDeviceStateALSA->pPCMPlayback);
|
||||||
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Draining playback device successful.");
|
||||||
|
|
||||||
|
#if 0
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device...");
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device...");
|
||||||
pContextStateALSA->snd_pcm_drop(pDeviceStateALSA->pPCMPlayback);
|
pContextStateALSA->snd_pcm_drop(pDeviceStateALSA->pPCMPlayback);
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device successful.");
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device successful.");
|
||||||
|
#endif
|
||||||
|
|
||||||
/* We need to prepare the device again, otherwise we won't be able to restart the device. */
|
/* We need to prepare the device again, otherwise we won't be able to restart the device. */
|
||||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device...");
|
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device...");
|
||||||
@@ -29853,13 +29899,6 @@ static ma_result ma_device_step__alsa(ma_device* pDevice, ma_blocking_mode block
|
|||||||
return ma_result_from_errno((int)-resultALSA);
|
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);
|
resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback);
|
||||||
if (resultALSA < 0) {
|
if (resultALSA < 0) {
|
||||||
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device after underrun.");
|
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device after underrun.");
|
||||||
|
|||||||
Reference in New Issue
Block a user