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.
This commit is contained in:
Wim Taymans 2023-06-05 16:33:12 +02:00
parent 04e17a8b1c
commit 61ce16b19f

View file

@ -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);