mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
alsa: rework timing
Use a DLL to track the hardware pointer and use this to set the timer. Handle XRUN and recover.
This commit is contained in:
parent
5444b850d2
commit
0343297257
4 changed files with 270 additions and 232 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <sys/timerfd.h>
|
||||
|
||||
#include <spa/pod/filter.h>
|
||||
#include <spa/control/control.h>
|
||||
|
||||
#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,64 +471,143 @@ 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,145 +791,86 @@ 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;
|
||||
|
||||
state->filled = state->buffer_frames - avail;
|
||||
nsec_now = SPA_TIMESPEC_TO_NSEC(&state->now);
|
||||
|
||||
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;
|
||||
|
||||
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 (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)
|
||||
clock_gettime(CLOCK_MONOTONIC, &state->now);
|
||||
if ((res = get_status(state, &delay)) < 0)
|
||||
return;
|
||||
}
|
||||
set_timeout(state, 0);
|
||||
} else {
|
||||
|
||||
spa_log_trace(state->log, "timeout %ld %d %ld", delay,
|
||||
state->threshold, state->sample_count);
|
||||
|
||||
nsec = SPA_TIMESPEC_TO_NSEC(&state->now);
|
||||
if ((res = update_time(state, nsec, delay, &elapsed, false)) < 0)
|
||||
return;
|
||||
|
||||
if (delay >= state->threshold * 2)
|
||||
goto next;
|
||||
|
||||
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);
|
||||
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 = avail * 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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
clock_gettime(CLOCK_MONOTONIC, &state->now);
|
||||
if ((res = get_status(state, &delay)) < 0)
|
||||
return;
|
||||
|
||||
spa_log_trace(state->log, "timeout %ld %d %ld",
|
||||
delay, state->threshold, state->sample_count);
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
snd_pcm_uframes_t to_read = SPA_MIN(avail, state->threshold);
|
||||
|
||||
while (total_read < to_read) {
|
||||
snd_pcm_uframes_t read, frames, offset;
|
||||
|
|
@ -875,15 +893,12 @@ static void alsa_on_capture_timeout_event(struct spa_source *source)
|
|||
total_read += read;
|
||||
}
|
||||
state->sample_count += total_read;
|
||||
}
|
||||
calc_timeout(state->threshold, avail - total_read, state->rate, &state->now, &ts.it_value);
|
||||
|
||||
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,13 +915,12 @@ 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);
|
||||
|
||||
if ((err = snd_pcm_prepare(state->hndl)) < 0) {
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,14 @@ 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;
|
||||
double w1, w2;
|
||||
double base, t0, dt;
|
||||
double bw;
|
||||
int count;
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue