From 61ce16b19fcb819b094a48e62d99e142dd88b183 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 Jun 2023 16:33:12 +0200 Subject: [PATCH] alsa: decouple delay from avail Use separate values for the number of available samples in the ringbuffer and the delay in the device. When using htimestamp we can use the tstamp to get a more accurate delay value against the graph start time. --- spa/plugins/alsa/alsa-pcm.c | 109 ++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 606002a05..e63d4249a 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -1856,7 +1856,8 @@ recover: return do_start(state); } -static int get_avail(struct state *state, uint64_t current_time) +#if 1 +static int get_avail(struct state *state, uint64_t current_time, snd_pcm_uframes_t *delay) { int res, missed; snd_pcm_sframes_t avail; @@ -1874,11 +1875,12 @@ static int get_avail(struct state *state, uint64_t current_time) } else { state->alsa_recovering = false; } + *delay = avail; return avail; } -#if 0 -static int get_avail_htimestamp(struct state *state, uint64_t current_time) +#else +static int get_avail(struct state *state, uint64_t current_time, snd_pcm_uframes_t *delay) { int res, missed; snd_pcm_uframes_t avail; @@ -1886,7 +1888,7 @@ static int get_avail_htimestamp(struct state *state, uint64_t current_time) uint64_t then; if ((res = snd_pcm_htimestamp(state->hndl, &avail, &tstamp)) < 0) { - if ((res = alsa_recover(state, avail)) < 0) + if ((res = alsa_recover(state, res)) < 0) return res; if ((res = snd_pcm_htimestamp(state->hndl, &avail, &tstamp)) < 0) { if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { @@ -1898,28 +1900,34 @@ static int get_avail_htimestamp(struct state *state, uint64_t current_time) } else { state->alsa_recovering = false; } + *delay = avail; if ((then = SPA_TIMESPEC_TO_NSEC(&tstamp)) != 0) { + int64_t diff; + if (then < current_time) - avail += (current_time - then) * state->rate / SPA_NSEC_PER_SEC; + diff = ((int64_t)(current_time - then)) * state->rate / SPA_NSEC_PER_SEC; else - avail -= (then - current_time) * state->rate / SPA_NSEC_PER_SEC; + diff = -((int64_t)(then - current_time)) * state->rate / SPA_NSEC_PER_SEC; + + spa_log_trace_fp(state->log, "%"PRIu64" %"PRIu64" %"PRIi64, current_time, then, diff); + + *delay += diff; } return SPA_MIN(avail, state->buffer_frames); } #endif -static int get_status(struct state *state, uint64_t current_time, +static int get_status(struct state *state, uint64_t current_time, snd_pcm_uframes_t *avail, snd_pcm_uframes_t *delay, snd_pcm_uframes_t *target) { - int avail; + int res; + snd_pcm_uframes_t a, d; - if ((avail = get_avail(state, current_time)) < 0) - return avail; + if ((res = get_avail(state, current_time, &d)) < 0) + return res; - avail = SPA_MIN(avail, (int)state->buffer_frames); - - *target = state->threshold + state->headroom; + a = SPA_MIN(res, (int)state->buffer_frames); if (state->resample && state->rate_match) { state->delay = state->rate_match->delay; @@ -1928,12 +1936,14 @@ static int get_status(struct state *state, uint64_t current_time, state->delay = 0; state->read_size = state->threshold; } - if (state->stream == SND_PCM_STREAM_PLAYBACK) { - *delay = state->buffer_frames - avail; + *avail = state->buffer_frames - a; + *delay = state->buffer_frames - SPA_MIN(d, state->buffer_frames); + *target = state->threshold + state->headroom; } else { - *delay = avail; - *target = SPA_MAX(*target, state->read_size + state->headroom); + *avail = a; + *delay = d; + *target = SPA_MAX(state->threshold, state->read_size) + state->headroom; } *target = SPA_CLAMP(*target, state->min_delay, state->max_delay); return 0; @@ -2105,11 +2115,11 @@ int spa_alsa_write(struct state *state) if (state->following && state->alsa_started) { uint64_t current_time; - snd_pcm_uframes_t delay, target; + snd_pcm_uframes_t avail, delay, target; current_time = state->position->clock.nsec; - if (SPA_UNLIKELY((res = get_status(state, current_time, &delay, &target)) < 0)) + if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) return res; if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, true)) < 0)) @@ -2124,16 +2134,17 @@ int spa_alsa_write(struct state *state) lev = SPA_LOG_LEVEL_INFO; if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { - spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u, " - "resync (%d missed)", state->props.device, delay, + spa_log_lev(state->log, lev, "%s: follower avail:%lu delay:%ld " + "target:%ld thr:%u, resync (%d missed)", + state->props.device, avail, delay, target, state->threshold, missed); } - if (delay > target) - snd_pcm_rewind(state->hndl, delay - target); - else if (delay < target) - spa_alsa_silence(state, target - delay); - delay = target; + if (avail > target) + snd_pcm_rewind(state->hndl, avail - target); + else if (avail < target) + spa_alsa_silence(state, target - avail); + avail = target; state->alsa_sync = false; } else state->alsa_sync_warning = true; @@ -2344,11 +2355,9 @@ int spa_alsa_read(struct state *state) current_time = state->position->clock.nsec; - if ((res = get_status(state, current_time, &delay, &target)) < 0) + if ((res = get_status(state, current_time, &avail, &delay, &target)) < 0) return res; - avail = delay; - if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, true)) < 0)) return res; @@ -2366,11 +2375,11 @@ int spa_alsa_read(struct state *state) target, state->threshold, missed); } - if (delay < target) - max_read = target - delay; - else if (delay > target) - snd_pcm_forward(state->hndl, delay - target); - delay = target; + if (avail < target) + max_read = target - avail; + else if (avail > target) + snd_pcm_forward(state->hndl, avail - target); + avail = target; state->alsa_sync = false; } else state->alsa_sync_warning = true; @@ -2458,13 +2467,14 @@ int spa_alsa_skip(struct state *state) } -static int handle_play(struct state *state, uint64_t current_time, +static int handle_play(struct state *state, uint64_t current_time, snd_pcm_uframes_t avail, snd_pcm_uframes_t delay, snd_pcm_uframes_t target) { int res; if (state->alsa_started && SPA_UNLIKELY(delay > target + state->max_error)) { - spa_log_trace(state->log, "%p: early wakeup %lu %lu", state, delay, target); + spa_log_trace(state->log, "%p: early wakeup %ld %lu %lu", state, + avail, delay, target); if (delay > target * 3) delay = target * 3; state->next_time = current_time + (delay - target) * SPA_NSEC_PER_SEC / state->rate; @@ -2490,15 +2500,15 @@ static int handle_play(struct state *state, uint64_t current_time, return res; } -static int handle_capture(struct state *state, uint64_t current_time, +static int handle_capture(struct state *state, uint64_t current_time, snd_pcm_uframes_t avail, snd_pcm_uframes_t delay, snd_pcm_uframes_t target) { int res; struct spa_io_buffers *io; - if (SPA_UNLIKELY(delay < state->read_size)) { - spa_log_trace(state->log, "%p: early wakeup %ld %ld %d", state, delay, target, - state->read_size); + if (SPA_UNLIKELY(avail < state->read_size)) { + spa_log_trace(state->log, "%p: early wakeup %ld %ld %ld %d", state, + delay, avail, target, state->read_size); state->next_time = current_time + (target - delay) * SPA_NSEC_PER_SEC / state->rate; return -EAGAIN; @@ -2544,7 +2554,7 @@ static uint64_t get_time_ns(struct state *state) static void alsa_wakeup_event(struct spa_source *source) { struct state *state = source->data; - snd_pcm_uframes_t delay, target; + snd_pcm_uframes_t avail, delay, target; uint64_t expire, current_time; int res; @@ -2594,7 +2604,7 @@ static void alsa_wakeup_event(struct spa_source *source) return; } - if (SPA_UNLIKELY(get_status(state, current_time, &delay, &target) < 0)) { + if (SPA_UNLIKELY(get_status(state, current_time, &avail, &delay, &target) < 0)) { spa_log_error(state->log, "get_status error"); state->next_time += state->threshold * 1e9 / state->rate; goto done; @@ -2603,24 +2613,25 @@ static void alsa_wakeup_event(struct spa_source *source) #ifndef FASTPATH if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { uint64_t nsec = get_time_ns(state); - spa_log_trace_fp(state->log, "%p: wakeup %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 - " %d %"PRIi64, state, delay, target, nsec, nsec, + spa_log_trace_fp(state->log, "%p: wakeup %lu %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 + " %d %"PRIi64, state, avail, delay, target, nsec, nsec, nsec - current_time, state->threshold, state->sample_count); } #endif if (state->stream == SND_PCM_STREAM_PLAYBACK) - handle_play(state, current_time, delay, target); + handle_play(state, current_time, avail, delay, target); else - handle_capture(state, current_time, delay, target); + handle_capture(state, current_time, avail, delay, target); done: if (!state->disable_tsched && (state->next_time > current_time + SPA_NSEC_PER_SEC || current_time > state->next_time + SPA_NSEC_PER_SEC)) { - spa_log_error(state->log, "%s: impossible timeout %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 - " %d %"PRIi64, state->props.device, delay, target, current_time, state->next_time, - state->next_time - current_time, state->threshold, state->sample_count); + spa_log_error(state->log, "%s: impossible timeout %lu %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 + " %d %"PRIi64, state->props.device, avail, delay, target, + current_time, state->next_time, state->next_time - current_time, + state->threshold, state->sample_count); state->next_time = current_time + state->threshold * 1e9 / state->rate; } set_timeout(state, state->next_time);