alsa: improve drain

This commit is contained in:
Wim Taymans 2020-04-06 17:50:28 +02:00
parent 7927a66fdd
commit c658574c01

View file

@ -60,13 +60,15 @@ typedef struct {
uint32_t target; uint32_t target;
int fd; int fd;
bool activated; /* PipeWire is activated? */ unsigned int activated:1; /* PipeWire is activated? */
bool error; unsigned int error:1;
unsigned int drained:1;
unsigned int draining:1;
unsigned int num_ports; snd_pcm_uframes_t hw_ptr;
unsigned int hw_ptr; snd_pcm_uframes_t boundary;
unsigned int sample_bits;
snd_pcm_uframes_t min_avail; snd_pcm_uframes_t min_avail;
unsigned int sample_bits;
struct spa_system *system; struct spa_system *system;
struct pw_thread_loop *main_loop; struct pw_thread_loop *main_loop;
@ -91,7 +93,10 @@ static int pcm_poll_block_check(snd_pcm_ioplug_t *io)
snd_pcm_sframes_t avail; snd_pcm_sframes_t avail;
snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_pipewire_t *pw = io->private_data;
if (io->state == SND_PCM_STATE_RUNNING || if (io->state == SND_PCM_STATE_DRAINING) {
spa_system_eventfd_read(pw->system, io->poll_fd, &val);
return 0;
} else if (io->state == SND_PCM_STATE_RUNNING ||
(io->state == SND_PCM_STATE_PREPARED && io->stream == SND_PCM_STREAM_CAPTURE)) { (io->state == SND_PCM_STATE_PREPARED && io->stream == SND_PCM_STREAM_CAPTURE)) {
avail = snd_pcm_avail_update(io->pcm); avail = snd_pcm_avail_update(io->pcm);
if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) {
@ -99,7 +104,6 @@ static int pcm_poll_block_check(snd_pcm_ioplug_t *io)
return 1; return 1;
} }
} }
return 0; return 0;
} }
@ -112,22 +116,25 @@ static inline int pcm_poll_unblock_check(snd_pcm_ioplug_t *io)
static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw)
{ {
if (pw) { if (pw == NULL)
if (pw->main_loop) return;
pw_thread_loop_stop(pw->main_loop);
if (pw->context) pw_log_debug(NAME" %p:", pw);
pw_context_destroy(pw->context); if (pw->main_loop)
if (pw->fd >= 0) pw_thread_loop_stop(pw->main_loop);
spa_system_close(pw->system, pw->fd); if (pw->context)
if (pw->main_loop) pw_context_destroy(pw->context);
pw_thread_loop_destroy(pw->main_loop); if (pw->fd >= 0)
free(pw); spa_system_close(pw->system, pw->fd);
} if (pw->main_loop)
pw_thread_loop_destroy(pw->main_loop);
free(pw);
} }
static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io)
{ {
snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_pipewire_t *pw = io->private_data;
pw_log_debug(NAME" %p:", pw);
snd_pcm_pipewire_free(pw); snd_pcm_pipewire_free(pw);
return 0; return 0;
} }
@ -153,15 +160,19 @@ static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io,
static snd_pcm_sframes_t snd_pcm_pipewire_pointer(snd_pcm_ioplug_t *io) static snd_pcm_sframes_t snd_pcm_pipewire_pointer(snd_pcm_ioplug_t *io)
{ {
snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_pipewire_t *pw = io->private_data;
snd_pcm_sframes_t hw_ptr = pw->hw_ptr;
if (pw->error) if (pw->error)
return -EBADFD; return -EBADFD;
return pw->hw_ptr; if (pw->draining && !pw->drained)
hw_ptr = hw_ptr > 1 ? hw_ptr - 1 : (snd_pcm_sframes_t)(pw->boundary - 1);
return hw_ptr;
} }
static int static int
snd_pcm_pipewire_process_playback(snd_pcm_pipewire_t *pw, struct pw_buffer *b) snd_pcm_pipewire_process_playback(snd_pcm_pipewire_t *pw, struct pw_buffer *b, snd_pcm_uframes_t *hw_avail)
{ {
snd_pcm_ioplug_t *io = &pw->io; snd_pcm_ioplug_t *io = &pw->io;
const snd_pcm_channel_area_t *areas; const snd_pcm_channel_area_t *areas;
@ -187,6 +198,7 @@ snd_pcm_pipewire_process_playback(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
index = 0; index = 0;
avail = maxsize - filled; avail = maxsize - filled;
avail = SPA_MIN(avail, pw->min_avail * bpf); avail = SPA_MIN(avail, pw->min_avail * bpf);
avail = SPA_MIN(avail, *hw_avail * bpf);
do { do {
offset = index % maxsize; offset = index % maxsize;
@ -215,7 +227,7 @@ snd_pcm_pipewire_process_playback(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
xfer = 0; xfer = 0;
while (xfer < nframes) { while (xfer < nframes) {
snd_pcm_uframes_t frames = nframes - xfer; snd_pcm_uframes_t frames = nframes - xfer;
snd_pcm_uframes_t offset = pw->hw_ptr; snd_pcm_uframes_t offset = pw->hw_ptr % io->buffer_size;
snd_pcm_uframes_t cont = io->buffer_size - offset; snd_pcm_uframes_t cont = io->buffer_size - offset;
if (cont < frames) if (cont < frames)
@ -226,11 +238,11 @@ snd_pcm_pipewire_process_playback(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
io->channels, frames, io->format); io->channels, frames, io->format);
pw->hw_ptr += frames; pw->hw_ptr += frames;
pw->hw_ptr %= io->buffer_size; if (pw->hw_ptr > pw->boundary)
pw->hw_ptr -= pw->boundary;
xfer += frames; xfer += frames;
} }
*hw_avail -= xfer;
pcm_poll_unblock_check(io); /* unblock socket for polling if needed */
done: done:
index += nbytes; index += nbytes;
@ -245,7 +257,7 @@ snd_pcm_pipewire_process_playback(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
} }
static int static int
snd_pcm_pipewire_process_record(snd_pcm_pipewire_t *pw, struct pw_buffer *b) snd_pcm_pipewire_process_record(snd_pcm_pipewire_t *pw, struct pw_buffer *b, snd_pcm_uframes_t *hw_avail)
{ {
snd_pcm_ioplug_t *io = &pw->io; snd_pcm_ioplug_t *io = &pw->io;
const snd_pcm_channel_area_t *areas; const snd_pcm_channel_area_t *areas;
@ -265,7 +277,7 @@ snd_pcm_pipewire_process_record(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
d = b->buffer->datas; d = b->buffer->datas;
maxsize = d[0].chunk->size; maxsize = d[0].chunk->size;
avail = maxsize; avail = SPA_MIN(maxsize, *hw_avail * bpf);
index = d[0].chunk->offset; index = d[0].chunk->offset;
do { do {
@ -288,7 +300,7 @@ snd_pcm_pipewire_process_record(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
xfer = 0; xfer = 0;
while (xfer < nframes) { while (xfer < nframes) {
snd_pcm_uframes_t frames = nframes - xfer; snd_pcm_uframes_t frames = nframes - xfer;
snd_pcm_uframes_t offset = pw->hw_ptr; snd_pcm_uframes_t offset = pw->hw_ptr % io->buffer_size;
snd_pcm_uframes_t cont = io->buffer_size - offset; snd_pcm_uframes_t cont = io->buffer_size - offset;
if (cont < frames) if (cont < frames)
@ -299,12 +311,11 @@ snd_pcm_pipewire_process_record(snd_pcm_pipewire_t *pw, struct pw_buffer *b)
io->channels, frames, io->format); io->channels, frames, io->format);
pw->hw_ptr += frames; pw->hw_ptr += frames;
pw->hw_ptr %= io->buffer_size; if (pw->hw_ptr > pw->boundary)
pw->hw_ptr -= pw->boundary;
xfer += frames; xfer += frames;
} }
*hw_avail -= xfer;
pcm_poll_unblock_check(io); /* unblock socket for polling if needed */
avail -= nbytes; avail -= nbytes;
index += nbytes; index += nbytes;
} while (avail > 0); } while (avail > 0);
@ -350,23 +361,47 @@ static void on_stream_process(void *data)
snd_pcm_pipewire_t *pw = data; snd_pcm_pipewire_t *pw = data;
snd_pcm_ioplug_t *io = &pw->io; snd_pcm_ioplug_t *io = &pw->io;
struct pw_buffer *b; struct pw_buffer *b;
snd_pcm_uframes_t hw_avail;
hw_avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr);
if (pw->drained) {
pcm_poll_unblock_check(io); /* unblock socket for polling if needed */
return;
}
b = pw_stream_dequeue_buffer(pw->stream); b = pw_stream_dequeue_buffer(pw->stream);
if (b == NULL) if (b == NULL)
return; return;
if (io->stream == SND_PCM_STREAM_PLAYBACK) if (io->stream == SND_PCM_STREAM_PLAYBACK)
snd_pcm_pipewire_process_playback(pw, b); snd_pcm_pipewire_process_playback(pw, b, &hw_avail);
else else
snd_pcm_pipewire_process_record(pw, b); snd_pcm_pipewire_process_record(pw, b, &hw_avail);
pw_stream_queue_buffer(pw->stream, b); pw_stream_queue_buffer(pw->stream, b);
if (io->state == SND_PCM_STATE_DRAINING && !pw->draining && hw_avail == 0) {
pw_stream_flush(pw->stream, true);
pw->draining = true;
pw->drained = false;
}
pcm_poll_unblock_check(io); /* unblock socket for polling if needed */
}
static void on_stream_drained(void *data)
{
snd_pcm_pipewire_t *pw = data;
pw->drained = true;
pw_log_debug(NAME" %p: drained", pw);
pw_thread_loop_signal(pw->main_loop, false);
} }
static const struct pw_stream_events stream_events = { static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS, PW_VERSION_STREAM_EVENTS,
.param_changed = on_stream_param_changed, .param_changed = on_stream_param_changed,
.process = on_stream_process, .process = on_stream_process,
.drained = on_stream_drained,
}; };
static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io)
@ -383,10 +418,13 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io)
pw_thread_loop_lock(pw->main_loop); pw_thread_loop_lock(pw->main_loop);
snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_alloca(&swparams);
if ((res = snd_pcm_sw_params_current(io->pcm, swparams)) == 0) if ((res = snd_pcm_sw_params_current(io->pcm, swparams)) == 0) {
snd_pcm_sw_params_get_avail_min(swparams, &pw->min_avail); snd_pcm_sw_params_get_avail_min(swparams, &pw->min_avail);
else snd_pcm_sw_params_get_boundary(swparams, &pw->boundary);
} else {
pw->min_avail = io->period_size; pw->min_avail = io->period_size;
pw->boundary = io->buffer_size;
}
min_period = (MIN_PERIOD * io->rate / 48000); min_period = (MIN_PERIOD * io->rate / 48000);
pw->min_avail = SPA_MAX(pw->min_avail, min_period); pw->min_avail = SPA_MAX(pw->min_avail, min_period);
@ -447,6 +485,7 @@ static int snd_pcm_pipewire_start(snd_pcm_ioplug_t *io)
snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_pipewire_t *pw = io->private_data;
pw_thread_loop_lock(pw->main_loop); pw_thread_loop_lock(pw->main_loop);
pw_log_debug(NAME" %p:", pw);
if (!pw->activated && pw->stream != NULL) { if (!pw->activated && pw->stream != NULL) {
pw_stream_set_active(pw->stream, true); pw_stream_set_active(pw->stream, true);
pw->activated = true; pw->activated = true;
@ -459,6 +498,9 @@ static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io)
{ {
snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_pipewire_t *pw = io->private_data;
pw_log_debug(NAME" %p:", pw);
pcm_poll_unblock_check(io);
pw_thread_loop_lock(pw->main_loop); pw_thread_loop_lock(pw->main_loop);
if (pw->activated && pw->stream != NULL) { if (pw->activated && pw->stream != NULL) {
pw_stream_set_active(pw->stream, false); pw_stream_set_active(pw->stream, false);