alsa: do the wakeup differently

Directly write from the process thread, reduces potential latency.
This commit is contained in:
Wim Taymans 2018-04-19 22:00:20 +02:00
parent 57cbc5e503
commit 64d1307684
3 changed files with 81 additions and 88 deletions

View file

@ -33,7 +33,7 @@
#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0)
static const char default_device[] = "hw:0";
static const uint32_t default_min_latency = 128;
static const uint32_t default_min_latency = 1024;
static const uint32_t default_max_latency = 1024;
static void reset_props(struct props *props)
@ -600,6 +600,8 @@ static int impl_node_process(struct spa_node *node)
spa_list_append(&this->ready, &b->link);
SPA_FLAG_UNSET(b->flags, BUFFER_FLAG_OUT);
spa_alsa_write(this, 0);
input->status = SPA_STATUS_OK;
}
return SPA_STATUS_HAVE_BUFFER;

View file

@ -358,42 +358,42 @@ static inline void calc_timeout(size_t target, size_t current,
}
}
static inline void try_pull(struct state *state, snd_pcm_uframes_t frames,
snd_pcm_uframes_t written, bool do_pull)
static int set_timeout(struct state *state, size_t extra)
{
struct spa_io_buffers *io = state->io;
struct itimerspec ts;
if (spa_list_is_empty(&state->ready) && do_pull) {
spa_log_trace(state->log, "alsa-util %p: %d %lu", state, io->status,
state->filled + written);
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 = frames * state->frame_size;
}
state->callbacks->process(state->callbacks_data, SPA_STATUS_NEED_BUFFER);
}
calc_timeout(state->filled + extra, state->threshold, 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);
return 0;
}
static inline snd_pcm_uframes_t
pull_frames(struct state *state,
const snd_pcm_channel_area_t *my_areas,
snd_pcm_uframes_t offset,
snd_pcm_uframes_t frames,
bool do_pull)
int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence)
{
snd_pcm_uframes_t total_frames = 0, to_write = SPA_MIN(frames, state->props.max_latency);
bool underrun = false;
snd_pcm_t *hndl = state->hndl;
const snd_pcm_channel_area_t *my_areas;
snd_pcm_uframes_t written, frames, offset, to_write;
int res;
try_pull(state, frames, 0, do_pull);
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 res;
}
spa_log_trace(state->log, "begin %ld %ld", offset, frames);
silence = SPA_MIN(silence, frames);
to_write = frames;
written = 0;
while (!spa_list_is_empty(&state->ready) && to_write > 0) {
uint8_t *dst, *src;
size_t n_bytes, n_frames;
struct buffer *b;
struct spa_data *d;
uint32_t index, offs, avail, l0, l1;
uint32_t index, offs, avail, size, maxsize, l0, l1;
b = spa_list_first(&state->ready, struct buffer, link);
d = b->outbuf->datas;
@ -401,15 +401,18 @@ pull_frames(struct state *state,
dst = SPA_MEMBER(my_areas[0].addr, offset * state->frame_size, uint8_t);
src = d[0].data;
size = d[0].chunk->size;
maxsize = d[0].maxsize;
index = d[0].chunk->offset + state->ready_offset;
avail = d[0].chunk->size - state->ready_offset;
avail = size - state->ready_offset;
avail /= state->frame_size;
n_frames = SPA_MIN(avail, to_write);
n_bytes = n_frames * state->frame_size;
offs = index % d[0].maxsize;
l0 = SPA_MIN(n_bytes, d[0].maxsize - offs);
offs = index % maxsize;
l0 = SPA_MIN(n_bytes, maxsize - offs);
l1 = n_bytes - l0;
memcpy(dst, src + offs, l0);
@ -418,38 +421,46 @@ pull_frames(struct state *state,
state->ready_offset += n_bytes;
if (state->ready_offset >= d[0].chunk->size) {
if (state->ready_offset >= size) {
spa_list_remove(&b->link);
SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT);
spa_log_trace(state->log, "alsa-util %p: reuse buffer %u", state, b->outbuf->id);
state->callbacks->reuse_buffer(state->callbacks_data, 0, b->outbuf->id);
state->ready_offset = 0;
try_pull(state, frames, total_frames, do_pull);
}
total_frames += n_frames;
written += n_frames;
to_write -= n_frames;
offset += n_frames;
spa_log_trace(state->log, "alsa-util %p: written %lu frames, left %ld",
state, total_frames, to_write);
if (silence > n_frames)
silence -= n_frames;
else
silence = 0;
}
if (total_frames == 0 && do_pull) {
total_frames = SPA_MIN(frames, state->threshold);
snd_pcm_areas_silence(my_areas, offset, state->channels, total_frames, state->format);
state->underrun += total_frames;
underrun = true;
if (silence > 0) {
snd_pcm_areas_silence(my_areas, offset, state->channels, silence, state->format);
written += silence;
}
if (state->underrun > 0) {
if (state->underrun >= state->rate || !underrun) {
spa_log_warn(state->log, "underrun, for %zd frames", state->underrun);
state->underrun = 0;
spa_log_trace(state->log, "commit %ld %ld", offset, 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 (!state->alsa_started && written > 0) {
spa_log_trace(state->log, "snd_pcm_start");
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;
}
return total_frames;
set_timeout(state, 0);
return 0;
}
static snd_pcm_uframes_t
@ -530,9 +541,6 @@ static void alsa_on_playback_timeout_event(struct spa_source *source)
struct state *state = source->data;
snd_pcm_t *hndl = state->hndl;
snd_pcm_sframes_t avail;
struct itimerspec ts;
snd_pcm_uframes_t total_written = 0;
const snd_pcm_channel_area_t *my_areas;
snd_pcm_status_t *status;
if (state->started && read(state->timerfd, &exp, sizeof(uint64_t)) != sizeof(uint64_t))
@ -565,50 +573,31 @@ static void alsa_on_playback_timeout_event(struct spa_source *source)
if ((res = alsa_try_resume(state)) < 0)
return;
}
set_timeout(state, 0);
} else {
snd_pcm_uframes_t to_write = avail;
bool do_pull = true;
if (spa_list_is_empty(&state->ready)) {
struct spa_io_buffers *io = state->io;
while (total_written < to_write) {
snd_pcm_uframes_t written, frames, offset;
if (state->filled < 128)
spa_alsa_write(state, state->threshold);
else
set_timeout(state, state->threshold - 128);
frames = to_write - total_written;
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;
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;
}
spa_log_trace(state->log, "begin %ld %ld", offset, frames);
written = pull_frames(state, my_areas, offset, frames, do_pull);
if (written < frames)
to_write = 0;
spa_log_trace(state->log, "commit %ld %ld", offset, 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;
}
total_written += written;
state->sample_count += written;
state->filled += written;
do_pull = false;
state->callbacks->process(state->callbacks_data, SPA_STATUS_NEED_BUFFER);
}
else {
spa_alsa_write(state, 0);
}
}
if (!state->alsa_started && total_written > 0) {
spa_log_trace(state->log, "snd_pcm_start");
if ((res = snd_pcm_start(state->hndl)) < 0) {
spa_log_error(state->log, "snd_pcm_start: %s", snd_strerror(res));
return;
}
state->alsa_started = true;
}
calc_timeout(state->filled, state->threshold, 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);
}

View file

@ -184,6 +184,8 @@ int spa_alsa_start(struct state *state, bool xrun_recover);
int spa_alsa_pause(struct state *state, bool xrun_recover);
int spa_alsa_close(struct state *state);
int spa_alsa_write(struct state *state, snd_pcm_uframes_t silence);
#ifdef __cplusplus
} /* extern "C" */
#endif