From 459dd51a31fa088774cbbdb3466147d72c37d32e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:50:14 +0000 Subject: [PATCH] Use snd_pcm_recover only _after_ an XRUN or suspend has happened. Don't use it to remove one -- it doesn't work. --- audio_alsa.c | 191 ++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 102 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0528c270..20c30bd1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1020,82 +1020,51 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (ret == 0) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); - ret = snd_pcm_recover(alsa_handle, ret, 1); + if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (*state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + ret = sps_extra_code_output_stalled; + } + } else { + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = *delay; + } + if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if (*state != SND_PCM_STATE_PREPARED) { - debug(1, "alsa: snd_pcm_recover failed -- have to use snd_pcm_prepare."); - ret = snd_pcm_prepare(alsa_handle); - if (ret == 0) - ret = snd_pcm_recover(alsa_handle, ret, 1); - if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - } - } + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *delay = *delay - frames_played_since_last_interrupt_sized; } - } + } else { // not running, thus no delay information, thus can't check for stall + *delay = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - // here, the device must be either SND_PCM_STATE_RUNNING - // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING - - if (ret == 0) { - - if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { - - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - - if (*state == SND_PCM_STATE_DRAINING) - debug(1, "alsa: draining with a delay of %d.", delay); - - clock_gettime(CLOCK_MONOTONIC, &tn); - uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t update_timestamp_ns = - update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - - // see if it's stalled - - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { - // hasn't outputted anything since the last call to delay() - - if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || - ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - ret = sps_extra_code_output_stalled; - } - } else { - stall_monitor_start_time = update_timestamp_ns; - stall_monitor_frame_count = *delay; - } - - if (ret == 0) { - uint64_t delta = time_now_ns - update_timestamp_ns; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = - frames_played_since_last_interrupt; - - *delay = *delay - frames_played_since_last_interrupt_sized; - } - } else { // not running, thus no delay information, thus can't check for stall - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - - // not running, thus no delay information, thus can't check for frame rates - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if ((*state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.", *state); - ret = sps_extra_code_output_state_cannot_make_ready; - } - } + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; } } return ret; @@ -1180,41 +1149,59 @@ int untimed_play(void *buf, int samples) { ret = delay_prep_and_status(&state, &my_delay); if (ret == 0) { // will be non-zero if an error or a stall - if ((state == SND_PCM_STATE_PREPARED) || (state == SND_PCM_STATE_RUNNING)) { - if ((samples != 0) && (buf != NULL)) { + if ((samples != 0) && (buf != NULL)) { - // debug(3, "write %d frames.", samples); - ret = alsa_pcm_write(alsa_handle, buf, samples); - if (ret == samples) { - stall_monitor_frame_count += samples; + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * 44100) / 352; // two seconds of frames… + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; } - - const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… - - frame_index++; - - if ((frame_index == start_measurement_from_this_frame) || - ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; - - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; + } + } else { + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + if (ret == -EPIPE) { /* underrun */ + ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); + if (ret < 0) { + ret = snd_pcm_prepare(alsa_handle); + if (ret < 0) + warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " + "snd_pcm_prepare() failed: %s.", + snd_strerror(ret)); + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) + sleep(1); /* wait until the suspend flag is released */ + if (ret < 0) { + ret = snd_pcm_prepare(alsa_handle); + if (ret < 0) + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); } } - } else { - debug(1, "alsa: error writing to alsa device."); } } } else {