mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
alsa: improve rate control
Try to match the delay with the target delay. Use the rate to adjust the timeout period for master or the resampler rate for slaves.
This commit is contained in:
parent
df4cb30c7a
commit
b7cc9ea102
2 changed files with 88 additions and 114 deletions
|
|
@ -501,6 +501,21 @@ static int set_timeout(struct state *state, uint64_t time)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void init_loop(struct state *state)
|
||||
{
|
||||
state->bw = 0.0;
|
||||
state->z1 = state->z2 = state->z3 = 0.0;
|
||||
}
|
||||
|
||||
static void set_loop(struct state *state, double bw)
|
||||
{
|
||||
double w = 2 * M_PI * bw * state->threshold / state->rate;
|
||||
state->w0 = 1.0 - exp (-20.0 * w);
|
||||
state->w1 = w * 1.5 / state->threshold;
|
||||
state->w2 = w / 1.5;
|
||||
state->bw = bw;
|
||||
}
|
||||
|
||||
static int alsa_recover(struct state *state, int err)
|
||||
{
|
||||
int res, st;
|
||||
|
|
@ -539,7 +554,7 @@ static int alsa_recover(struct state *state, int err)
|
|||
spa_log_error(state->log, "snd_pcm_recover error: %s", snd_strerror(res));
|
||||
return res;
|
||||
}
|
||||
dll_init(&state->dll, DLL_BW_MAX);
|
||||
init_loop(state);
|
||||
|
||||
if (state->stream == SND_PCM_STREAM_CAPTURE) {
|
||||
if ((res = snd_pcm_start(state->hndl)) < 0) {
|
||||
|
|
@ -575,70 +590,60 @@ static int get_status(struct state *state, snd_pcm_sframes_t *delay)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, bool slaved)
|
||||
static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, bool slave)
|
||||
{
|
||||
uint64_t sample_time, elapsed;
|
||||
double tw = 0.0, extra = 0.0;
|
||||
int64_t sdelay;
|
||||
double err, corr, elapsed;
|
||||
|
||||
sample_time = state->sample_count;
|
||||
if (!slaved) {
|
||||
elapsed = sample_time - state->sample_time;
|
||||
} else {
|
||||
elapsed = state->threshold;
|
||||
if (state->bw == 0.0) {
|
||||
set_loop(state, BW_MAX);
|
||||
state->next_time = nsec;
|
||||
state->base_time = nsec;
|
||||
}
|
||||
|
||||
if (state->stream == SND_PCM_STREAM_CAPTURE) {
|
||||
elapsed = state->threshold;
|
||||
extra = (double) elapsed / state->rate;
|
||||
sdelay = (int64_t)(delay - elapsed);
|
||||
} else {
|
||||
if (elapsed == 0) {
|
||||
elapsed = state->threshold / 2;
|
||||
delay = state->threshold / 2;
|
||||
}
|
||||
state->sample_time = sample_time;
|
||||
sdelay = -delay;
|
||||
err = delay - state->threshold;
|
||||
|
||||
state->z1 += state->w0 * (state->w1 * err - state->z1);
|
||||
state->z2 += state->w0 * (state->z1 - state->z2);
|
||||
state->z3 += state->w2 * state->z2;
|
||||
|
||||
corr = 1.0 - (state->z2 + state->z3);
|
||||
|
||||
elapsed = (state->next_time - state->base_time) * 1e-9;
|
||||
if (elapsed > BW_PERIOD) {
|
||||
state->base_time = state->next_time;
|
||||
if (state->bw == BW_MAX)
|
||||
set_loop(state, BW_MED);
|
||||
else if (state->bw == BW_MED)
|
||||
set_loop(state, BW_MIN);
|
||||
|
||||
spa_log_debug(state->log, "slave:%d rate:%f bw:%f", slave, corr, state->bw);
|
||||
}
|
||||
|
||||
|
||||
/* we try to match the delay with the number of delayed samples */
|
||||
tw = nsec * 1e-9 - (double)sdelay / state->rate - state->safety;
|
||||
tw = dll_update(&state->dll, tw, (double)elapsed / state->rate);
|
||||
state->next_time = (tw + extra - 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;
|
||||
if (slave && state->notify) {
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[2];
|
||||
spa_pod_builder_init(&b, state->notify, 1024);
|
||||
spa_pod_builder_push_sequence(&b, &f[0], 0);
|
||||
spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties);
|
||||
spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
|
||||
spa_pod_builder_prop(&b, SPA_PROP_rate, 0);
|
||||
spa_pod_builder_double(&b, SPA_CLAMP(corr, 0.95, 1.05));
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
spa_pod_builder_pop(&b, &f[0]);
|
||||
}
|
||||
if (!slave && state->clock) {
|
||||
state->clock->nsec = state->next_time;
|
||||
state->clock->rate = SPA_FRACTION(1, state->rate);
|
||||
state->clock->position = state->sample_count;
|
||||
state->clock->delay = sdelay;
|
||||
state->clock->rate_diff = state->dll.dt;
|
||||
state->clock->delay = state->threshold * corr;
|
||||
state->clock->rate_diff = corr;
|
||||
}
|
||||
|
||||
state->old_dt = SPA_CLAMP(state->dll.dt, 0.95, 1.05);
|
||||
spa_log_trace_fp(state->log, "%"PRIu64" %f %ld %f %f %d", nsec,
|
||||
corr, delay, err, state->threshold * corr,
|
||||
state->threshold);
|
||||
|
||||
#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(&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_fp(state->log, "%"PRIu64" %f %"PRIi64" %"PRIi64" %"PRIi64" %d %"PRIu64" %f %f", nsec,
|
||||
state->old_dt, delay, elapsed, (int64_t)(nsec - state->last_time),
|
||||
state->threshold, state->next_time, tw, extra);
|
||||
|
||||
state->last_time = nsec;
|
||||
state->next_time += state->threshold / corr * 1e9 / state->rate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -653,22 +658,28 @@ int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence)
|
|||
if (state->position && state->threshold != state->position->size)
|
||||
state->threshold = state->position->size;
|
||||
|
||||
if (state->slaved) {
|
||||
uint64_t nsec, master;
|
||||
if (state->slaved && state->alsa_started) {
|
||||
uint64_t nsec;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
master = state->position->clock.position + state->position->clock.delay;
|
||||
nsec = master * SPA_NSEC_PER_SEC / state->rate;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &state->now);
|
||||
if ((res = get_status(state, &delay)) < 0)
|
||||
return res;
|
||||
|
||||
if (state->alsa_sync) {
|
||||
if (delay > state->threshold)
|
||||
snd_pcm_rewind(state->hndl, delay - state->threshold);
|
||||
else
|
||||
snd_pcm_forward(state->hndl, state->threshold - delay);
|
||||
|
||||
delay = state->threshold;
|
||||
state->alsa_sync = false;
|
||||
}
|
||||
|
||||
nsec = SPA_TIMESPEC_TO_NSEC(&state->now);
|
||||
if ((res = update_time(state, nsec, delay, true)) < 0)
|
||||
return res;
|
||||
|
||||
spa_log_trace_fp(state->log, "slave %f %"PRIi64" %"PRIu64" %d",
|
||||
state->dll.dt, nsec, delay, state->rate);
|
||||
|
||||
if (delay > state->threshold * 2) {
|
||||
spa_log_warn(state->log, "slave: skip period");
|
||||
snd_pcm_rewind(state->hndl, state->threshold);
|
||||
|
|
@ -850,9 +861,6 @@ int spa_alsa_read(struct state *state, snd_pcm_uframes_t silence)
|
|||
if ((res = update_time(state, nsec, delay, true)) < 0)
|
||||
return res;
|
||||
|
||||
spa_log_trace_fp(state->log, "slave %f %"PRIi64" %"PRIu64" %d",
|
||||
state->dll.dt, nsec, delay, state->rate);
|
||||
|
||||
if (delay > state->threshold * 2) {
|
||||
spa_log_trace_fp(state->log, "slave: skip period");
|
||||
snd_pcm_forward(state->hndl, state->threshold);
|
||||
|
|
@ -895,7 +903,7 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
|
|||
|
||||
if (delay >= state->threshold * 2) {
|
||||
spa_log_trace(state->log, "early wakeup %ld %d", delay, state->threshold);
|
||||
state->next_time = nsec + (state->threshold / 2) * SPA_NSEC_PER_SEC / state->rate;
|
||||
state->next_time = nsec + state->threshold * SPA_NSEC_PER_SEC / state->rate;
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
|
|
@ -1020,8 +1028,7 @@ int spa_alsa_start(struct state *state)
|
|||
state->slaved = true;
|
||||
}
|
||||
|
||||
dll_init(&state->dll, DLL_BW_MAX);
|
||||
state->old_dt = 1.0;
|
||||
init_loop(state);
|
||||
state->safety = 0.0;
|
||||
|
||||
spa_log_debug(state->log, "alsa %p: start %d slave:%d", state, state->threshold, state->slaved);
|
||||
|
|
@ -1044,6 +1051,7 @@ int spa_alsa_start(struct state *state)
|
|||
}
|
||||
|
||||
reset_buffers(state);
|
||||
state->alsa_sync = true;
|
||||
|
||||
if (state->stream == SND_PCM_STREAM_PLAYBACK) {
|
||||
state->alsa_started = false;
|
||||
|
|
|
|||
|
|
@ -65,16 +65,10 @@ 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 w1, w2;
|
||||
double base, t0, dt;
|
||||
double bw;
|
||||
int count;
|
||||
};
|
||||
#define BW_MAX 0.256
|
||||
#define BW_MED 0.064
|
||||
#define BW_MIN 0.016
|
||||
#define BW_PERIOD 3.0
|
||||
|
||||
struct state {
|
||||
struct spa_handle handle;
|
||||
|
|
@ -101,7 +95,6 @@ struct state {
|
|||
|
||||
bool have_format;
|
||||
struct spa_audio_info current_format;
|
||||
struct dll dll;
|
||||
|
||||
snd_pcm_uframes_t buffer_frames;
|
||||
snd_pcm_uframes_t period_frames;
|
||||
|
|
@ -130,20 +123,24 @@ struct state {
|
|||
bool started;
|
||||
struct spa_source source;
|
||||
int timerfd;
|
||||
bool alsa_started;
|
||||
bool slaved;
|
||||
uint32_t threshold;
|
||||
unsigned int alsa_started:1;
|
||||
unsigned int alsa_sync:1;
|
||||
unsigned int slaved:1;
|
||||
|
||||
snd_htimestamp_t now;
|
||||
int64_t sample_count;
|
||||
|
||||
int64_t sample_time;
|
||||
uint64_t last_time;
|
||||
uint64_t next_time;
|
||||
uint64_t base_time;
|
||||
|
||||
uint64_t underrun;
|
||||
double old_dt;
|
||||
double safety;
|
||||
|
||||
double bw;
|
||||
double z1, z2, z3;
|
||||
double w0, w1, w2;
|
||||
};
|
||||
|
||||
int
|
||||
|
|
@ -160,37 +157,6 @@ int spa_alsa_close(struct state *state);
|
|||
int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence);
|
||||
int spa_alsa_read(struct state *state, snd_pcm_uframes_t silence);
|
||||
|
||||
static inline void dll_bandwidth(struct dll *dll, double bandwidth)
|
||||
{
|
||||
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 bandwidth)
|
||||
{
|
||||
dll->dt = 1.0;
|
||||
dll->count = 0;
|
||||
dll_bandwidth(dll, bandwidth);
|
||||
}
|
||||
|
||||
static inline double dll_update(struct dll *dll, double tw, double period)
|
||||
{
|
||||
double e;
|
||||
|
||||
if (dll->count++ == 0) {
|
||||
dll->t0 = dll->base = tw;
|
||||
} else {
|
||||
dll->t0 += dll->dt * period;
|
||||
e = (tw - dll->t0) * period;
|
||||
dll->t0 += dll->w1 * e;
|
||||
dll->dt += dll->w2 * e;
|
||||
}
|
||||
return dll->t0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue