From 0343297257c32beaa1cd9dd515bb75d53a0aca07 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 16 Nov 2018 17:00:40 +0100 Subject: [PATCH] alsa: rework timing Use a DLL to track the hardware pointer and use this to set the timer. Handle XRUN and recover. --- spa/plugins/alsa/alsa-sink.c | 18 +- spa/plugins/alsa/alsa-source.c | 8 +- spa/plugins/alsa/alsa-utils.c | 421 +++++++++++++++++---------------- spa/plugins/alsa/alsa-utils.h | 55 +++-- 4 files changed, 270 insertions(+), 232 deletions(-) diff --git a/spa/plugins/alsa/alsa-sink.c b/spa/plugins/alsa/alsa-sink.c index 8d4e212dc..cd88d0157 100644 --- a/spa/plugins/alsa/alsa-sink.c +++ b/spa/plugins/alsa/alsa-sink.c @@ -229,11 +229,11 @@ static int impl_node_send_command(struct spa_node *node, const struct spa_comman if (this->n_buffers == 0) return -EIO; - if ((res = spa_alsa_start(this, false)) < 0) + if ((res = spa_alsa_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Pause: - if ((res = spa_alsa_pause(this, false)) < 0) + if ((res = spa_alsa_pause(this)) < 0) return res; break; default: @@ -431,6 +431,13 @@ impl_node_port_enum_params(struct spa_node *node, SPA_PARAM_IO_size, &SPA_POD_Int(sizeof(struct spa_io_clock)), 0); break; + case 2: + param = spa_pod_builder_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, &SPA_POD_Id(SPA_IO_Notify), + SPA_PARAM_IO_size, &SPA_POD_Int(sizeof(struct spa_io_sequence) + 1024), + 0); + break; default: return 0; } @@ -467,7 +474,7 @@ static int port_set_format(struct spa_node *node, if (format == NULL) { spa_log_debug(this->log, "clear format"); - spa_alsa_pause(this, false); + spa_alsa_pause(this); clear_buffers(this); spa_alsa_close(this); this->have_format = false; @@ -535,7 +542,7 @@ impl_node_port_use_buffers(struct spa_node *node, return -EIO; if (n_buffers == 0) { - spa_alsa_pause(this, false); + spa_alsa_pause(this); clear_buffers(this); return 0; } @@ -609,6 +616,9 @@ impl_node_port_set_io(struct spa_node *node, case SPA_IO_Clock: this->clock = data; break; + case SPA_IO_Notify: + this->notify = data; + break; default: return -ENOENT; } diff --git a/spa/plugins/alsa/alsa-source.c b/spa/plugins/alsa/alsa-source.c index f2bc9ea6a..9ea56061b 100644 --- a/spa/plugins/alsa/alsa-source.c +++ b/spa/plugins/alsa/alsa-source.c @@ -231,11 +231,11 @@ static int impl_node_send_command(struct spa_node *node, const struct spa_comman if (this->n_buffers == 0) return -EIO; - if ((res = spa_alsa_start(this, false)) < 0) + if ((res = spa_alsa_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Pause: - if ((res = spa_alsa_pause(this, false)) < 0) + if ((res = spa_alsa_pause(this)) < 0) return res; break; default: @@ -477,7 +477,7 @@ static int port_set_format(struct spa_node *node, int err; if (format == NULL) { - spa_alsa_pause(this, false); + spa_alsa_pause(this); clear_buffers(this); spa_alsa_close(this); this->have_format = false; @@ -544,7 +544,7 @@ impl_node_port_use_buffers(struct spa_node *node, return -EIO; if (this->n_buffers > 0) { - spa_alsa_pause(this, false); + spa_alsa_pause(this); if ((res = clear_buffers(this)) < 0) return res; } diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c index 977ae45bc..cda6b0606 100644 --- a/spa/plugins/alsa/alsa-utils.c +++ b/spa/plugins/alsa/alsa-utils.c @@ -10,6 +10,7 @@ #include #include +#include #include "alsa-utils.h" @@ -36,6 +37,7 @@ static int spa_alsa_open(struct state *state) state->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); state->opened = true; state->sample_count = 0; + state->sample_time = 0; return 0; } @@ -450,7 +452,6 @@ static int set_swparams(struct state *state) snd_pcm_t *hndl = state->hndl; int err = 0; snd_pcm_sw_params_t *params; - snd_pcm_uframes_t boundary; snd_pcm_sw_params_alloca(¶ms); @@ -461,9 +462,6 @@ static int set_swparams(struct state *state) /* start the transfer */ CHECK(snd_pcm_sw_params_set_start_threshold(hndl, params, LONG_MAX), "set_start_threshold"); - CHECK(snd_pcm_sw_params_get_boundary(params, &boundary), "get_boundary"); - - CHECK(snd_pcm_sw_params_set_stop_threshold(hndl, params, boundary), "set_stop_threshold"); CHECK(snd_pcm_sw_params_set_period_event(hndl, params, 0), "set_period_event"); @@ -473,65 +471,144 @@ static int set_swparams(struct state *state) return 0; } -static inline void calc_timeout(size_t target, size_t current, - size_t rate, snd_htimestamp_t *now, - struct timespec *ts) -{ - ts->tv_sec = now->tv_sec; - ts->tv_nsec = now->tv_nsec; - if (target > current) - ts->tv_nsec += ((target - current) * SPA_NSEC_PER_SEC) / rate; - - while (ts->tv_nsec >= SPA_NSEC_PER_SEC) { - ts->tv_sec++; - ts->tv_nsec -= SPA_NSEC_PER_SEC; - } -} - -static int set_timeout(struct state *state, size_t extra) +static int set_timeout(struct state *state, uint64_t time) { struct itimerspec ts; if (!state->slaved) { - calc_timeout(state->filled + extra, state->threshold, state->rate, &state->now, &ts.it_value); - + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = ((size_t)state->threshold * SPA_NSEC_PER_SEC) / state->rate; + ts.it_interval.tv_nsec = 0; timerfd_settime(state->timerfd, TFD_TIMER_ABSTIME, &ts, NULL); } return 0; } -static int get_status(struct state *state, snd_pcm_sframes_t *avail, snd_htimestamp_t *now) +static int get_status(struct state *state, snd_pcm_sframes_t *delay) { snd_pcm_status_t *status; - int res; + snd_pcm_sframes_t av; + int res, st; snd_pcm_status_alloca(&status); - +again: if ((res = snd_pcm_status(state->hndl, status)) < 0) { spa_log_error(state->log, "snd_pcm_status error: %s", snd_strerror(res)); return res; } + st = snd_pcm_status_get_state(status); + if (st == SND_PCM_STATE_XRUN) { + struct timeval now, trigger, diff; + uint64_t xrun, missing; - if (avail) { - *avail = snd_pcm_status_get_avail(status); - if (*avail > state->buffer_frames) - *avail = state->buffer_frames; - } - if (now) { + snd_pcm_status_get_tstamp (status, &now); + snd_pcm_status_get_trigger_tstamp (status, &trigger); + timersub(&now, &trigger, &diff); + + xrun = SPA_TIMEVAL_TO_USEC(&diff); + missing = xrun * state->rate / SPA_USEC_PER_SEC; + + state->sample_time = state->sample_count; + state->sample_count += missing; #if 0 - clock_gettime(CLOCK_MONOTONIC, now); -#else - snd_pcm_status_get_htstamp(status, now); - if (now->tv_sec == 0 && now->tv_nsec == 0) { - spa_log_warn(state->log, "0 from snd_pcm_status_get_htstamp %ld", *avail); - clock_gettime(CLOCK_MONOTONIC, now); - } + state->safety = SPA_MIN(state->safety + 0.000333, 0.0013333); + dll_bandwidth(&state->dll, DLL_BW_MAX); #endif + spa_log_error(state->log, "xrun of %"PRIu64" usec %"PRIu64" %f", + xrun, missing, state->safety); + + if ((res = snd_pcm_prepare(state->hndl)) < 0) { + spa_log_error(state->log, "snd_pcm_prepare error: %s", snd_strerror(res)); + } + + if (state->stream == SND_PCM_STREAM_CAPTURE) { + if ((res = snd_pcm_start(state->hndl)) < 0) { + spa_log_error(state->log, "snd_pcm_start: %s", snd_strerror(res)); + return res; + } + state->alsa_started = true; + } else { + state->alsa_started = false; + } + spa_alsa_write(state, state->threshold * 2); + goto again; } + av = snd_pcm_status_get_avail(status); + if (av > state->buffer_frames) + av = state->buffer_frames; + + if (delay) { + if (state->stream == SND_PCM_STREAM_PLAYBACK) + *delay = state->buffer_frames - av; + else + *delay = av; + } + return 0; +} + +static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, + uint64_t *period, bool slaved) +{ + uint64_t sample_time, elapsed; + double tw; + + if (!slaved) { + sample_time = state->sample_count; + elapsed = sample_time - state->sample_time; + state->sample_time = sample_time; + } else { + elapsed = state->threshold; + } + + /* if our buffers are too full, pause the dll */ + if (delay >= state->threshold * 2 || elapsed == 0) { + elapsed = state->threshold; + delay = state->threshold; + } + + /* we try to match the delay with the number of played samples */ + tw = nsec * 1e-9 + (double)delay / state->rate - state->safety; + tw = dll_update(&state->dll, tw, (double)elapsed / state->rate); + state->next_time = (tw - state->safety) * 1e9; + + if (state->dll.bw > DLL_BW_MIN && tw > state->dll.base + DLL_BW_PERIOD) + dll_bandwidth(&state->dll, DLL_BW_MIN); + + if (state->clock) { + state->clock->nsec = state->last_time; + state->clock->rate = SPA_FRACTION(1, state->rate); + state->clock->position = state->sample_count; + state->clock->delay = state->stream == SND_PCM_STREAM_CAPTURE ? delay : -delay; + state->clock->rate_diff = state->dll.dt; + } + + state->old_dt = SPA_CLAMP(state->dll.dt, 0.95, 1.05); + +#if 0 + if (slaved && state->notify) { + struct spa_pod_builder b = { 0 }; + spa_pod_builder_init(&b, state->notify, 1024); + spa_pod_builder_push_sequence(&b, 0); + spa_pod_builder_control_header(&b, 0, SPA_CONTROL_Properties); + spa_pod_builder_push_object(&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(&b, SPA_PROP_rate, 0); + spa_pod_builder_double(&b, state->old_dt); + spa_pod_builder_pop(&b); + spa_pod_builder_pop(&b); + } +#endif + + spa_log_trace(state->log, "%"PRIu64" %f %"PRIi64" %"PRIi64" %"PRIi64" %d %"PRIu64, nsec, + state->old_dt, delay, elapsed, (int64_t)(nsec - state->last_time), + state->threshold, state->next_time); + + state->last_time = nsec; + if (period) + *period = elapsed; + return 0; } @@ -539,34 +616,31 @@ int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence) { snd_pcm_t *hndl = state->hndl; const snd_pcm_channel_area_t *my_areas; - snd_pcm_uframes_t written, frames, offset, off, to_write; + snd_pcm_uframes_t written, frames, offset, off, to_write, total_written; int res; - if (state->position) + if (state->position && state->threshold != state->position->size) state->threshold = state->position->size; if (state->slaved) { - double dts, pts, rate_diff = 1.0; - struct timespec now; - snd_pcm_sframes_t avail; + uint64_t nsec, master; + snd_pcm_sframes_t delay; - if ((res = get_status(state, &avail, &now)) < 0) + master = state->position->clock.position + state->position->clock.delay; + nsec = master * SPA_NSEC_PER_SEC / state->rate; + + if ((res = get_status(state, &delay)) < 0) return res; - state->now = now; - state->filled = state->buffer_frames - avail; + if ((res = update_time(state, nsec, delay, NULL, true)) < 0) + return res; - dts = ((state->position->clock.position - state->filled) * 1000000ll / state->rate); - pts = dll_update(&state->dll, dts, state->threshold); - rate_diff = state->dll.T * state->rate / 1000000.f; - - if (state->bw != 0.05 && state->sample_count / state->rate > 4) { - state->bw = 0.05; - dll_bandwidth(&state->dll, state->threshold, state->rate, state->bw); - } - spa_log_trace(state->log, "slave %f %f %f", dts, pts, rate_diff); + spa_log_trace(state->log, "slave %f %"PRIi64" %"PRIu64" %d", + state->dll.dt, nsec, delay, state->rate); } + total_written = 0; +again: frames = state->buffer_frames; if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0) { spa_log_error(state->log, "snd_pcm_mmap_begin error: %s", snd_strerror(res)); @@ -628,8 +702,6 @@ int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence) else silence = 0; } - if (written == 0) - silence = SPA_MIN(to_write, state->threshold); if (silence > 0) { spa_log_trace(state->log, "silence %ld", silence); @@ -637,24 +709,29 @@ int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence) written += silence; } - spa_log_trace(state->log, "commit %ld %ld", offset, written); + spa_log_trace(state->log, "commit %ld %ld %"PRIi64, offset, written, state->sample_count); + total_written += written; + if ((res = snd_pcm_mmap_commit(hndl, offset, written)) < 0) { spa_log_error(state->log, "snd_pcm_mmap_commit error: %s", snd_strerror(res)); if (res != -EPIPE && res != -ESTRPIPE) return res; } - state->sample_count += written; - state->filled += written; + + if (!spa_list_is_empty(&state->ready) && total_written < state->threshold) + goto again; + + state->sample_count += total_written; if (!state->alsa_started && written > 0) { - spa_log_trace(state->log, "snd_pcm_start"); + spa_log_trace(state->log, "snd_pcm_start %lu", written); if ((res = snd_pcm_start(hndl)) < 0) { spa_log_error(state->log, "snd_pcm_start: %s", snd_strerror(res)); return res; } state->alsa_started = true; } - set_timeout(state, 0); + set_timeout(state, state->next_time); return 0; } @@ -714,176 +791,114 @@ push_frames(struct state *state, return total_frames; } -static int alsa_try_resume(struct state *state) -{ - int res; - - while ((res = snd_pcm_resume(state->hndl)) == -EAGAIN) - usleep(250000); - if (res < 0) { - spa_log_error(state->log, "suspended, failed to resume %s", snd_strerror(res)); - res = snd_pcm_prepare(state->hndl); - if (res < 0) - spa_log_error(state->log, "suspended, failed to prepare %s", snd_strerror(res)); - } - return res; -} - static void alsa_on_playback_timeout_event(struct spa_source *source) { - uint64_t exp, nsec_now; int res; struct state *state = source->data; - snd_pcm_t *hndl = state->hndl; - snd_pcm_sframes_t avail; - struct timespec now; - double pts, dts, rate_diff; + snd_pcm_sframes_t delay; + uint64_t nsec, expire, elapsed; - if (state->started && read(state->timerfd, &exp, sizeof(uint64_t)) != sizeof(uint64_t)) + if (state->started && read(state->timerfd, &expire, sizeof(uint64_t)) != sizeof(uint64_t)) spa_log_warn(state->log, "error reading timerfd: %s", strerror(errno)); - if ((res = get_status(state, &avail, &now)) < 0) - return; - - state->now = now; - - if (state->position) + if (state->position && state->threshold != state->position->size) state->threshold = state->position->size; - if (avail > state->buffer_frames) - avail = state->buffer_frames; + clock_gettime(CLOCK_MONOTONIC, &state->now); + if ((res = get_status(state, &delay)) < 0) + return; - state->filled = state->buffer_frames - avail; - nsec_now = SPA_TIMESPEC_TO_NSEC(&state->now); + spa_log_trace(state->log, "timeout %ld %d %ld", delay, + state->threshold, state->sample_count); - dts = nsec_now / 1000ll - (state->filled * 1000000ll / state->rate); - pts = dll_update(&state->dll, dts, state->threshold); - rate_diff = state->dll.T * state->rate / 1000000.f; + nsec = SPA_TIMESPEC_TO_NSEC(&state->now); + if ((res = update_time(state, nsec, delay, &elapsed, false)) < 0) + return; - if (state->clock) { - state->clock->nsec = nsec_now; - state->clock->rate = SPA_FRACTION(1, state->rate); - state->clock->position = state->sample_count; - state->clock->delay = -state->filled; - state->clock->rate_diff = rate_diff; + if (delay >= state->threshold * 2) + goto next; + + if (spa_list_is_empty(&state->ready)) { + struct spa_io_buffers *io = state->io; + + spa_log_trace(state->log, "alsa-util %p: %d", state, io->status); + + io->status = SPA_STATUS_NEED_BUFFER; + if (state->range) { + state->range->offset = state->sample_count * state->frame_size; + state->range->min_size = state->threshold * state->frame_size; + state->range->max_size = state->threshold * state->frame_size; + } + state->callbacks->process(state->callbacks_data, SPA_STATUS_NEED_BUFFER); + +next: + set_timeout(state, state->next_time); } - - if (state->bw != 0.05 && state->sample_count / state->rate > 4) { - state->bw = 0.05; - dll_bandwidth(&state->dll, state->threshold, state->rate, state->bw); - } - - spa_log_trace(state->log, "timeout %ld %d %ld %ld %f %f %f", state->filled, state->threshold, - state->sample_count, nsec_now, pts, dts, rate_diff); - - if (state->filled > state->threshold * 2) { - if (snd_pcm_state(hndl) == SND_PCM_STATE_SUSPENDED) { - spa_log_error(state->log, "suspended: try resume"); - if ((res = alsa_try_resume(state)) < 0) - return; - } - set_timeout(state, 0); - } else { - if (spa_list_is_empty(&state->ready)) { - struct spa_io_buffers *io = state->io; - - if (state->filled == 0) { - if (state->alsa_started) - spa_log_warn(state->log, - "alsa-util %p: underrun", state); - spa_alsa_write(state, state->threshold); - } - spa_log_trace(state->log, "alsa-util %p: %d %lu", state, io->status, - state->filled); - - io->status = SPA_STATUS_NEED_BUFFER; - if (state->range) { - state->range->offset = state->sample_count * state->frame_size; - state->range->min_size = state->threshold * state->frame_size; - state->range->max_size = avail * state->frame_size; - } - state->callbacks->process(state->callbacks_data, SPA_STATUS_NEED_BUFFER); - } - else { - spa_alsa_write(state, 0); - } + else { + spa_alsa_write(state, 0); } } static void alsa_on_capture_timeout_event(struct spa_source *source) { - uint64_t exp; + uint64_t expire, nsec; int res; struct state *state = source->data; snd_pcm_t *hndl = state->hndl; - snd_pcm_sframes_t avail; - snd_pcm_uframes_t total_read = 0; - struct itimerspec ts; + snd_pcm_sframes_t delay; + snd_pcm_uframes_t total_read = 0, to_read; const snd_pcm_channel_area_t *my_areas; - struct timespec now; - if (state->started && read(state->timerfd, &exp, sizeof(uint64_t)) != sizeof(uint64_t)) + if (state->started && read(state->timerfd, &expire, sizeof(uint64_t)) != sizeof(uint64_t)) spa_log_warn(state->log, "error reading timerfd: %s", strerror(errno)); - if ((res = get_status(state, &avail, &now)) < 0) - return; - - state->now = now; - if (state->position) state->threshold = state->position->size; - if (state->clock) { - state->clock->nsec = SPA_TIMESPEC_TO_NSEC(&state->now); - state->clock->rate = SPA_FRACTION(1, state->rate); - state->clock->position = state->sample_count; - state->clock->delay = avail; - } + clock_gettime(CLOCK_MONOTONIC, &state->now); + if ((res = get_status(state, &delay)) < 0) + return; - spa_log_trace(state->log, "timeout %ld %d %ld %ld %ld %ld %ld", avail, state->threshold, - state->sample_count, state->now.tv_sec, state->now.tv_nsec, - now.tv_sec, now.tv_nsec); + spa_log_trace(state->log, "timeout %ld %d %ld", + delay, state->threshold, state->sample_count); - if (avail < state->threshold) { - if (snd_pcm_state(hndl) == SND_PCM_STATE_SUSPENDED) { - spa_log_error(state->log, "suspended: try resume"); - if ((res = alsa_try_resume(state)) < 0) + if (delay < state->threshold) + goto next; + + to_read = SPA_MIN(delay, state->threshold); + + nsec = SPA_TIMESPEC_TO_NSEC(&state->now); + if ((res = update_time(state, nsec, delay, NULL, false)) < 0) + return; + + while (total_read < to_read) { + snd_pcm_uframes_t read, frames, offset; + + frames = to_read - total_read; + if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0) { + spa_log_error(state->log, "snd_pcm_mmap_begin error: %s", snd_strerror(res)); + return; + } + + read = push_frames(state, my_areas, offset, frames); + if (read < frames) + to_read = 0; + + if ((res = snd_pcm_mmap_commit(hndl, offset, read)) < 0) { + spa_log_error(state->log, "snd_pcm_mmap_commit error: %s", snd_strerror(res)); + if (res != -EPIPE && res != -ESTRPIPE) return; } - } else { - snd_pcm_uframes_t to_read = SPA_MIN(avail, state->threshold); - - while (total_read < to_read) { - snd_pcm_uframes_t read, frames, offset; - - frames = to_read - total_read; - if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0) { - spa_log_error(state->log, "snd_pcm_mmap_begin error: %s", snd_strerror(res)); - return; - } - - read = push_frames(state, my_areas, offset, frames); - if (read < frames) - to_read = 0; - - if ((res = snd_pcm_mmap_commit(hndl, offset, read)) < 0) { - spa_log_error(state->log, "snd_pcm_mmap_commit error: %s", snd_strerror(res)); - if (res != -EPIPE && res != -ESTRPIPE) - return; - } - total_read += read; - } - state->sample_count += total_read; + total_read += read; } - calc_timeout(state->threshold, avail - total_read, state->rate, &state->now, &ts.it_value); + state->sample_count += total_read; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - timerfd_settime(state->timerfd, TFD_TIMER_ABSTIME, &ts, NULL); +next: + set_timeout(state, state->next_time); } -int spa_alsa_start(struct state *state, bool xrun_recover) +int spa_alsa_start(struct state *state) { int err; struct itimerspec ts; @@ -900,14 +915,13 @@ int spa_alsa_start(struct state *state, bool xrun_recover) state->slaved = true; } - state->bw = 0.128; - dll_init(&state->dll, state->threshold, state->rate, state->bw); + dll_init(&state->dll, DLL_BW_MAX); + state->safety = 0.0; - spa_log_debug(state->log, "alsa %p: start %d", state, state->threshold); + spa_log_debug(state->log, "alsa %p: start %d %d", state, state->threshold, state->slaved); CHECK(set_swparams(state), "swparams"); - if (!xrun_recover) - snd_pcm_dump(state->hndl, state->output); + snd_pcm_dump(state->hndl, state->output); if ((err = snd_pcm_prepare(state->hndl)) < 0) { spa_log_error(state->log, "snd_pcm_prepare error: %s", snd_strerror(err)); @@ -945,6 +959,9 @@ int spa_alsa_start(struct state *state, bool xrun_recover) ts.it_interval.tv_nsec = 0; timerfd_settime(state->timerfd, 0, &ts, NULL); } + else { + spa_alsa_write(state, state->threshold * 2); + } state->io->status = SPA_STATUS_OK; state->io->buffer_id = SPA_ID_INVALID; @@ -976,7 +993,7 @@ static int do_remove_source(struct spa_loop *loop, return 0; } -int spa_alsa_pause(struct state *state, bool xrun_recover) +int spa_alsa_pause(struct state *state) { int err; diff --git a/spa/plugins/alsa/alsa-utils.h b/spa/plugins/alsa/alsa-utils.h index 6bb18be0a..12a2b93a1 100644 --- a/spa/plugins/alsa/alsa-utils.h +++ b/spa/plugins/alsa/alsa-utils.h @@ -64,11 +64,15 @@ struct buffer { struct spa_list link; }; +#define DLL_BW_MAX 0.256 +#define DLL_BW_MIN 0.05 +#define DLL_BW_PERIOD 4.0 + struct dll { - double T; - double b, c; - double n0; - int count; + double w1, w2; + double base, t0, dt; + double bw; + int count; }; struct state { @@ -95,7 +99,6 @@ struct state { bool have_format; struct spa_audio_info current_format; struct dll dll; - double bw; snd_pcm_uframes_t buffer_frames; snd_pcm_uframes_t period_frames; @@ -109,6 +112,7 @@ struct state { struct spa_io_range *range; struct spa_io_clock *clock; struct spa_io_position *position; + struct spa_io_sequence *notify; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; @@ -127,9 +131,14 @@ struct state { snd_htimestamp_t now; int64_t sample_count; - int64_t filled; + + int64_t sample_time; + uint64_t last_time; + uint64_t next_time; uint64_t underrun; + double old_dt; + double safety; }; int @@ -141,40 +150,42 @@ spa_alsa_enum_format(struct state *state, int spa_alsa_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags); -int spa_alsa_start(struct state *state, bool xrun_recover); -int spa_alsa_pause(struct state *state, bool xrun_recover); +int spa_alsa_start(struct state *state); +int spa_alsa_pause(struct state *state); int spa_alsa_close(struct state *state); int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence); -static inline void dll_bandwidth(struct dll *dll, double period, double rate, double bandwidth) +static inline void dll_bandwidth(struct dll *dll, double bandwidth) { - double w = 2 * M_PI * bandwidth * period / rate; - dll->b = 1.0 - exp(-M_SQRT2 * w); - dll->c = (1.0 - exp(-w * w)) / period; + double w = 2 * M_PI * bandwidth; + dll->w1 = w * M_SQRT2; + dll->w2 = w * w; + dll->bw = bandwidth; + dll->base = dll->t0; } -static inline void dll_init(struct dll *dll, double period, double rate, double bandwidth) +static inline void dll_init(struct dll *dll, double bandwidth) { - dll->T = 1000000.0 / rate; + dll->dt = 1.0; dll->count = 0; - dll_bandwidth(dll, period, rate, bandwidth); + dll_bandwidth(dll, bandwidth); } -static inline double dll_update(struct dll *dll, double system_time, double period) +static inline double dll_update(struct dll *dll, double tw, double period) { double e; if (dll->count++ == 0) { - dll->n0 = system_time; + dll->t0 = dll->base = tw; } else { - dll->n0 += period * dll->T; - e = system_time - dll->n0; - dll->n0 += SPA_MAX(dll->b, 1.0 / dll->count) * e; - dll->T += dll->c * e; + dll->t0 += dll->dt * period; + e = (tw - dll->t0) * period; + dll->t0 += dll->w1 * e; + dll->dt += dll->w2 * e; } - return dll->n0; + return dll->t0; } #ifdef __cplusplus