audiomixer: improve mixing

Remove PortIO flags, we can use the status
Move PortIO to ports
Move transport to client-node
Improve scheduling
This commit is contained in:
Wim Taymans 2017-04-12 10:40:17 +02:00
parent 9bd92b781c
commit 4c7b56020a
14 changed files with 398 additions and 300 deletions

View file

@ -61,10 +61,8 @@ typedef struct {
/**
* SpaPortIO:
* @state: the port state
* @flags: extra flags
* @buffer_id: a buffer id
* @status: the status
* @buffer_id: a buffer id
* @range: requested range
* @event: event
*
@ -72,10 +70,8 @@ typedef struct {
* by the host and configured on all ports for which IO is requested.
*/
typedef struct {
#define SPA_PORT_IO_FLAG_RANGE (1 << 0) /* a range is present */
uint32_t flags;
uint32_t buffer_id;
uint32_t status;
uint32_t buffer_id;
SpaRange range;
} SpaPortIO;

View file

@ -337,8 +337,7 @@ pull_frames_queue (SpaALSAState *state,
if (spa_list_is_empty (&state->ready)) {
SpaEvent event = SPA_EVENT_INIT (state->type.event_node.NeedInput);
io->flags = SPA_PORT_IO_FLAG_RANGE;
io->status = SPA_RESULT_OK;
io->status = SPA_RESULT_NEED_INPUT;
io->range.offset = state->sample_count * state->frame_size;
io->range.min_size = state->threshold * state->frame_size;
io->range.max_size = frames * state->frame_size;
@ -370,7 +369,7 @@ pull_frames_queue (SpaALSAState *state,
spa_list_remove (&b->link);
b->outstanding = true;
state->io->buffer_id = b->outbuf->id;
spa_log_trace (state->log, "alsa-util %p: reuse buffer %u", state, b->outbuf->id);
state->event_cb (&state->node, (SpaEvent *)&rb, state->user_data);
state->ready_offset = 0;

View file

@ -327,7 +327,7 @@ static SpaResult
clear_buffers (SpaAudioMixer *this, SpaAudioMixerPort *port)
{
if (port->n_buffers > 0) {
spa_log_info (this->log, "audio-mixer %p: clear buffers", this);
spa_log_info (this->log, "audio-mixer %p: clear buffers %p", this, port);
port->n_buffers = 0;
spa_list_init (&port->queue);
}
@ -593,11 +593,15 @@ add_port_data (SpaAudioMixer *this, MixerBuffer *out, SpaAudioMixerPort *port, i
int i;
int16_t *op, *ip;
size_t os, is, chunk;
MixerBuffer *b = spa_list_first (&port->queue, MixerBuffer, link);
MixerBuffer *b;
op = SPA_MEMBER (out->outbuf->datas[0].data, out->outbuf->datas[0].chunk->offset, void);
os = out->outbuf->datas[0].chunk->size;
ip = SPA_MEMBER (b->outbuf->datas[0].data, port->queued_offset + b->outbuf->datas[0].chunk->offset, void);
b = spa_list_first (&port->queue, MixerBuffer, link);
ip = SPA_MEMBER (b->outbuf->datas[0].data,
port->queued_offset + b->outbuf->datas[0].chunk->offset, void);
is = b->outbuf->datas[0].chunk->size - port->queued_offset;
chunk = SPA_MIN (os, is);
@ -611,36 +615,98 @@ add_port_data (SpaAudioMixer *this, MixerBuffer *out, SpaAudioMixerPort *port, i
op[i] = SPA_CLAMP (op[i] + ip[i], INT16_MIN, INT16_MAX);
}
op += chunk / 2;
os -= chunk;
port->queued_offset += chunk;
port->queued_bytes -= chunk;
if (chunk == is) {
spa_log_trace (this->log, "audiomixer %p: return buffer %d on port %p", this, b->outbuf->id, port);
spa_log_trace (this->log, "audiomixer %p: return buffer %d on port %p %zd",
this, b->outbuf->id, port, chunk);
port->io->buffer_id = b->outbuf->id;
spa_list_remove (&b->link);
b->outstanding = true;
port->queued_offset = 0;
} else {
spa_log_trace (this->log, "audiomixer %p: keeping buffer %d on port %p %zd %zd",
this, b->outbuf->id, port, port->queued_bytes, chunk);
}
}
static SpaResult
mix_output (SpaAudioMixer *this, size_t n_bytes)
{
MixerBuffer *outbuf;
int i, layer;
SpaAudioMixerPort *outport;
SpaPortIO *output;
outport = &this->out_ports[0];
output = outport->io;
if (spa_list_is_empty (&outport->queue))
return SPA_RESULT_OUT_OF_BUFFERS;
outbuf = spa_list_first (&outport->queue, MixerBuffer, link);
spa_list_remove (&outbuf->link);
n_bytes = SPA_MIN (n_bytes, outbuf->outbuf->datas[0].maxsize);
spa_log_trace (this->log, "audiomixer %p: dequeue output buffer %d %zd", this, outbuf->outbuf->id, n_bytes);
outbuf->outstanding = true;
outbuf->outbuf->datas[0].chunk->offset = 0;
outbuf->outbuf->datas[0].chunk->size = n_bytes;
outbuf->outbuf->datas[0].chunk->stride = 0;
for (layer = 0, i = 0; i < MAX_PORTS; i++) {
SpaAudioMixerPort *port = &this->in_ports[i];
if (port->io == NULL || port->n_buffers == 0)
continue;
if (spa_list_is_empty (&port->queue)) {
spa_log_warn (this->log, "audiomixer %p: underrun stream %d", this, i);
port->queued_bytes = 0;
port->queued_offset = 0;
continue;
}
add_port_data (this, outbuf, port, layer++);
}
if (layer == 0) {
this->state = STATE_IN;
return SPA_RESULT_NEED_INPUT;
}
output->buffer_id = outbuf->outbuf->id;
output->status = SPA_RESULT_HAVE_OUTPUT;
this->state = STATE_OUT;
return SPA_RESULT_HAVE_OUTPUT;
}
static SpaResult
spa_audiomixer_node_process_input (SpaNode *node)
{
SpaResult res;
SpaAudioMixer *this;
uint32_t i;
SpaAudioMixerPort *outport;
size_t min_queued = -1;
size_t min_queued = SIZE_MAX;
SpaPortIO *output;
spa_return_val_if_fail (node != NULL, SPA_RESULT_INVALID_ARGUMENTS);
this = SPA_CONTAINER_OF (node, SpaAudioMixer, node);
outport = &this->out_ports[0];
output = outport->io;
spa_return_val_if_fail (output != NULL, SPA_RESULT_ERROR);
if (this->state == STATE_OUT)
return SPA_RESULT_HAVE_OUTPUT;
outport = &this->out_ports[0];
spa_return_val_if_fail (outport->io != NULL, SPA_RESULT_ERROR);
for (i = 0; i < MAX_PORTS; i++) {
SpaAudioMixerPort *port = &this->in_ports[i];
SpaPortIO *input;
@ -648,7 +714,9 @@ spa_audiomixer_node_process_input (SpaNode *node)
if ((input = port->io) == NULL || port->n_buffers == 0)
continue;
if (input->buffer_id != SPA_ID_INVALID) {
if (port->queued_bytes == 0 &&
input->status == SPA_RESULT_HAVE_OUTPUT &&
input->buffer_id != SPA_ID_INVALID) {
MixerBuffer *b = &port->buffers[input->buffer_id];
if (!b->outstanding) {
@ -656,66 +724,31 @@ spa_audiomixer_node_process_input (SpaNode *node)
continue;
}
if (spa_list_is_empty (&port->queue)) {
port->queued_bytes = 0;
port->queued_offset = 0;
}
spa_list_insert (port->queue.prev, &b->link);
b->outstanding = false;
port->queued_bytes += b->outbuf->datas[0].chunk->size;
spa_log_trace (this->log, "audiomixer %p: queue buffer %d on port %p %zd %zd",
this, b->outbuf->id, port, port->queued_bytes, min_queued);
input->buffer_id = SPA_ID_INVALID;
spa_list_insert (port->queue.prev, &b->link);
port->queued_bytes += b->outbuf->datas[0].chunk->size;
spa_log_trace (this->log, "audiomixer %p: queue buffer %d on port %d %zd %zd",
this, b->outbuf->id, i, port->queued_bytes, min_queued);
}
if (min_queued == -1 || (port->queued_bytes > 0 && port->queued_bytes < min_queued))
if (min_queued == SIZE_MAX || port->queued_bytes < min_queued)
min_queued = port->queued_bytes;
input->status = SPA_RESULT_OK;
}
if (min_queued > 0) {
MixerBuffer *outbuf;
SpaPortIO *output;
int layer;
if (spa_list_is_empty (&outport->queue))
return SPA_RESULT_OUT_OF_BUFFERS;
outbuf = spa_list_first (&outport->queue, MixerBuffer, link);
spa_list_remove (&outbuf->link);
spa_log_trace (this->log, "audiomixer %p: dequeue output buffer %d %zd", this, outbuf->outbuf->id, min_queued);
outbuf->outstanding = true;
outbuf->outbuf->datas[0].chunk->offset = 0;
outbuf->outbuf->datas[0].chunk->size = min_queued;
outbuf->outbuf->datas[0].chunk->stride = 0;
for (layer = 0, i = 0; i < MAX_PORTS; i++) {
SpaAudioMixerPort *port = &this->in_ports[i];
if (port->io == NULL)
continue;
if (spa_list_is_empty (&port->queue)) {
spa_log_warn (this->log, "audiomixer %p: underrun stream %d", this, i);
continue;
}
add_port_data (this, outbuf, port, layer++);
}
if (layer == 0)
clear_buffer (this, outbuf);
output = outport->io;
output->buffer_id = outbuf->outbuf->id;
output->status = SPA_RESULT_OK;
this->state = STATE_OUT;
if (min_queued != SIZE_MAX && min_queued > 0) {
res = mix_output (this, min_queued);
} else {
res = SPA_RESULT_NEED_INPUT;
}
return this->state == STATE_IN ? SPA_RESULT_NEED_INPUT : SPA_RESULT_HAVE_OUTPUT;
return res;
}
static SpaResult
spa_audiomixer_node_process_output (SpaNode *node)
{
SpaResult res;
SpaAudioMixer *this;
SpaAudioMixerPort *port;
SpaPortIO *output;
@ -729,24 +762,54 @@ spa_audiomixer_node_process_output (SpaNode *node)
spa_return_val_if_fail (port->io != NULL, SPA_RESULT_ERROR);
output = port->io;
spa_return_val_if_fail (port->have_format, SPA_RESULT_NO_FORMAT);
spa_return_val_if_fail (port->n_buffers > 0, SPA_RESULT_NO_BUFFERS);
for (i = 0; i < MAX_PORTS; i++) {
SpaPortIO *input;
if ((input = this->in_ports[i].io) == NULL)
continue;
input->flags = output->flags;
input->range = output->range;
}
/* recycle */
if (output->buffer_id != SPA_ID_INVALID) {
recycle_buffer (this, output->buffer_id);
output->buffer_id = SPA_ID_INVALID;
}
this->state = STATE_IN;
res = SPA_RESULT_NEED_INPUT;
/* produce more output if possible */
if (this->state == STATE_OUT) {
size_t min_queued = -1;
return SPA_RESULT_NEED_INPUT;
for (i = 0; i < MAX_PORTS; i++) {
SpaAudioMixerPort *port = &this->in_ports[i];
if (port->io == NULL || port->n_buffers == 0)
continue;
if (min_queued == -1 || port->queued_bytes < min_queued)
min_queued = port->queued_bytes;
}
if (min_queued != -1 && min_queued > 0) {
res = mix_output (this, min_queued);
} else {
this->state = STATE_IN;
}
}
/* take requested output range and apply to input */
if (this->state == STATE_IN) {
for (i = 0; i < MAX_PORTS; i++) {
SpaAudioMixerPort *port = &this->in_ports[i];
SpaPortIO *input;
if ((input = port->io) == NULL || port->n_buffers == 0)
continue;
if (port->queued_bytes == 0) {
input->range = output->range;
input->status = SPA_RESULT_NEED_INPUT;
}
else {
input->status = SPA_RESULT_OK;
}
spa_log_trace (this->log, "audiomixer %p: port %d %d queued %zd, res %d", this,
i, output->range.min_size, port->queued_bytes, input->status);
}
}
return res;
}
static const SpaNode audiomixer_node = {
@ -832,6 +895,7 @@ spa_audiomixer_init (const SpaHandleFactory *factory,
init_type (&this->type, this->map);
this->node = audiomixer_node;
this->state = STATE_IN;
this->out_ports[0].io = NULL;
this->out_ports[0].info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS |

View file

@ -295,10 +295,13 @@ audiotestsrc_make_buffer (SpaAudioTestSrc *this)
b->outstanding = true;
n_bytes = b->outbuf->datas[0].maxsize;
if (io->flags & SPA_PORT_IO_FLAG_RANGE)
n_bytes = SPA_CLAMP (n_bytes, io->range.min_size, io->range.max_size);
if (io->range.min_size != 0) {
if (io->range.max_size < n_bytes)
n_bytes = io->range.max_size;
}
spa_log_trace (this->log, "audiotestsrc %p: dequeue buffer %d %d", this, b->outbuf->id, n_bytes);
spa_log_trace (this->log, "audiotestsrc %p: dequeue buffer %d %d %d", this, b->outbuf->id,
b->outbuf->datas[0].maxsize, n_bytes);
n_samples = n_bytes / this->bpf;
this->render_func (this, b->outbuf->datas[0].data, n_samples);
@ -317,9 +320,8 @@ audiotestsrc_make_buffer (SpaAudioTestSrc *this)
this->elapsed_time = SAMPLES_TO_TIME (this, this->sample_count);
set_timer (this, true);
io->flags = 0;
io->buffer_id = b->outbuf->id;
io->status = SPA_RESULT_OK;
io->status = SPA_RESULT_HAVE_OUTPUT;
return SPA_RESULT_HAVE_OUTPUT;
}
@ -817,17 +819,23 @@ static SpaResult
spa_audiotestsrc_node_process_output (SpaNode *node)
{
SpaAudioTestSrc *this;
SpaPortIO *io;
spa_return_val_if_fail (node != NULL, SPA_RESULT_INVALID_ARGUMENTS);
this = SPA_CONTAINER_OF (node, SpaAudioTestSrc, node);
io = this->io;
spa_return_val_if_fail (io != NULL, SPA_RESULT_WRONG_STATE);
if (this->io && this->io->buffer_id != SPA_ID_INVALID) {
if (io->status == SPA_RESULT_HAVE_OUTPUT)
return SPA_RESULT_HAVE_OUTPUT;
if (io->buffer_id != SPA_ID_INVALID) {
reuse_buffer (this, this->io->buffer_id);
this->io->buffer_id = SPA_ID_INVALID;
}
if (!this->async)
if (!this->async && (io->status == SPA_RESULT_NEED_INPUT))
return audiotestsrc_make_buffer (this);
else
return SPA_RESULT_OK;

View file

@ -305,9 +305,8 @@ videotestsrc_make_buffer (SpaVideoTestSrc *this)
this->elapsed_time = FRAMES_TO_TIME (this, this->frame_count);
set_timer (this, true);
io->flags = 0;
io->buffer_id = b->outbuf->id;
io->status = SPA_RESULT_OK;
io->status = SPA_RESULT_HAVE_OUTPUT;
return SPA_RESULT_HAVE_OUTPUT;
}

View file

@ -100,13 +100,13 @@ typedef struct {
SpaNode *source1;
SpaPortIO source1_mix_io[1];
SpaBuffer *source1_buffers[1];
Buffer source1_buffer[1];
SpaBuffer *source1_buffers[2];
Buffer source1_buffer[2];
SpaNode *source2;
SpaPortIO source2_mix_io[1];
SpaBuffer *source2_buffers[1];
Buffer source2_buffer[1];
SpaBuffer *source2_buffers[2];
Buffer source2_buffer[2];
bool running;
pthread_t thread;
@ -119,35 +119,43 @@ typedef struct {
unsigned int n_fds;
} AppData;
#define BUFFER_SIZE 4096
#define BUFFER_SIZE1 4092
#define BUFFER_SIZE2 4096
static void
init_buffer (AppData *data, Buffer *b, void *ptr, size_t size)
init_buffer (AppData *data, SpaBuffer **bufs, Buffer *ba, int n_buffers, size_t size)
{
b->buffer.id = 0;
b->buffer.n_metas = 1;
b->buffer.metas = b->metas;
b->buffer.n_datas = 1;
b->buffer.datas = b->datas;
int i;
b->header.flags = 0;
b->header.seq = 0;
b->header.pts = 0;
b->header.dts_offset = 0;
b->metas[0].type = SPA_META_TYPE_HEADER;
b->metas[0].data = &b->header;
b->metas[0].size = sizeof (b->header);
for (i = 0; i < n_buffers; i++) {
Buffer *b = &ba[i];
bufs[i] = &b->buffer;
b->datas[0].type = SPA_DATA_TYPE_MEMPTR;
b->datas[0].flags = 0;
b->datas[0].fd = -1;
b->datas[0].mapoffset = 0;
b->datas[0].maxsize = size;
b->datas[0].data = ptr;
b->datas[0].chunk = &b->chunks[0];
b->datas[0].chunk->offset = 0;
b->datas[0].chunk->size = size;
b->datas[0].chunk->stride = 0;
b->buffer.id = i;
b->buffer.n_metas = 1;
b->buffer.metas = b->metas;
b->buffer.n_datas = 1;
b->buffer.datas = b->datas;
b->header.flags = 0;
b->header.seq = 0;
b->header.pts = 0;
b->header.dts_offset = 0;
b->metas[0].type = SPA_META_TYPE_HEADER;
b->metas[0].data = &b->header;
b->metas[0].size = sizeof (b->header);
b->datas[0].type = SPA_DATA_TYPE_MEMPTR;
b->datas[0].flags = 0;
b->datas[0].fd = -1;
b->datas[0].mapoffset = 0;
b->datas[0].maxsize = size;
b->datas[0].data = malloc (size);
b->datas[0].chunk = &b->chunks[0];
b->datas[0].chunk->offset = 0;
b->datas[0].chunk->size = size;
b->datas[0].chunk->stride = 0;
}
}
static SpaResult
@ -213,15 +221,17 @@ on_sink_event (SpaNode *node, SpaEvent *event, void *user_data)
if (res == SPA_RESULT_NEED_INPUT) {
res = spa_node_process_output (data->source1);
if (data->source1_mix_io[0].status == SPA_RESULT_NEED_INPUT) {
res = spa_node_process_output (data->source1);
if (res != SPA_RESULT_HAVE_OUTPUT)
printf ("got process_output error from source1 %d\n", res);
}
if (res != SPA_RESULT_HAVE_OUTPUT)
printf ("got process_output error from source1 %d\n", res);
res = spa_node_process_output (data->source2);
if (res != SPA_RESULT_HAVE_OUTPUT)
printf ("got process_output error from source2 %d\n", res);
if (data->source2_mix_io[0].status == SPA_RESULT_NEED_INPUT) {
res = spa_node_process_output (data->source2);
if (res != SPA_RESULT_HAVE_OUTPUT)
printf ("got process_output error from source2 %d\n", res);
}
res = spa_node_process_input (data->mix);
if (res == SPA_RESULT_HAVE_OUTPUT)
@ -301,7 +311,7 @@ make_nodes (AppData *data)
spa_pod_builder_init (&b, buffer, sizeof (buffer));
spa_pod_builder_props (&b, &f[0], data->type.props,
SPA_POD_PROP (&f[1], data->type.props_device, 0, SPA_POD_TYPE_STRING, 1, "hw:1"),
SPA_POD_PROP (&f[1], data->type.props_device, 0, SPA_POD_TYPE_STRING, 1, "hw:0"),
SPA_POD_PROP (&f[1], data->type.props_min_latency, 0, SPA_POD_TYPE_INT, 1, 256),
SPA_POD_PROP (&f[1], data->type.props_live, 0, SPA_POD_TYPE_BOOL, 1, false));
props = SPA_POD_BUILDER_DEREF (&b, f[0].ref, SpaProps);
@ -392,8 +402,7 @@ negotiate_formats (AppData *data)
if ((res = spa_node_port_set_format (data->mix, SPA_DIRECTION_OUTPUT, 0, 0, format)) < 0)
return res;
init_buffer (data, &data->mix_buffer[0], malloc (BUFFER_SIZE), BUFFER_SIZE);
data->mix_buffers[0] = &data->mix_buffer[0].buffer;
init_buffer (data, data->mix_buffers, data->mix_buffer, 1, BUFFER_SIZE2);
if ((res = spa_node_port_use_buffers (data->sink, SPA_DIRECTION_INPUT, 0, data->mix_buffers, 1)) < 0)
return res;
if ((res = spa_node_port_use_buffers (data->mix, SPA_DIRECTION_OUTPUT, 0, data->mix_buffers, 1)) < 0)
@ -412,11 +421,10 @@ negotiate_formats (AppData *data)
if ((res = spa_node_port_set_format (data->source1, SPA_DIRECTION_OUTPUT, 0, 0, format)) < 0)
return res;
init_buffer (data, &data->source1_buffer[0], malloc (BUFFER_SIZE), BUFFER_SIZE);
data->source1_buffers[0] = &data->source1_buffer[0].buffer;
if ((res = spa_node_port_use_buffers (data->mix, SPA_DIRECTION_INPUT, data->mix_ports[0], data->source1_buffers, 1)) < 0)
init_buffer (data, data->source1_buffers, data->source1_buffer, 2, BUFFER_SIZE1);
if ((res = spa_node_port_use_buffers (data->mix, SPA_DIRECTION_INPUT, data->mix_ports[0], data->source1_buffers, 2)) < 0)
return res;
if ((res = spa_node_port_use_buffers (data->source1, SPA_DIRECTION_OUTPUT, 0, data->source1_buffers, 1)) < 0)
if ((res = spa_node_port_use_buffers (data->source1, SPA_DIRECTION_OUTPUT, 0, data->source1_buffers, 2)) < 0)
return res;
data->mix_ports[1] = 1;
@ -432,11 +440,10 @@ negotiate_formats (AppData *data)
if ((res = spa_node_port_set_format (data->source2, SPA_DIRECTION_OUTPUT, 0, 0, format)) < 0)
return res;
init_buffer (data, &data->source2_buffer[0], malloc (BUFFER_SIZE), BUFFER_SIZE);
data->source2_buffers[0] = &data->source2_buffer[0].buffer;
if ((res = spa_node_port_use_buffers (data->mix, SPA_DIRECTION_INPUT, data->mix_ports[1], data->source2_buffers, 1)) < 0)
init_buffer (data, data->source2_buffers, data->source2_buffer, 2, BUFFER_SIZE2);
if ((res = spa_node_port_use_buffers (data->mix, SPA_DIRECTION_INPUT, data->mix_ports[1], data->source2_buffers, 2)) < 0)
return res;
if ((res = spa_node_port_use_buffers (data->source2, SPA_DIRECTION_OUTPUT, 0, data->source2_buffers, 1)) < 0)
if ((res = spa_node_port_use_buffers (data->source2, SPA_DIRECTION_OUTPUT, 0, data->source2_buffers, 2)) < 0)
return res;
return SPA_RESULT_OK;
@ -547,6 +554,7 @@ main (int argc, char *argv[])
{
AppData data = { NULL };
SpaResult res;
const char *str;
data.map = spa_type_map_get_default();
data.log = spa_log_get_default();
@ -556,7 +564,8 @@ main (int argc, char *argv[])
data.data_loop.remove_source = do_remove_source;
data.data_loop.invoke = do_invoke;
// data.log->level = SPA_LOG_LEVEL_TRACE;
if ((str = getenv ("PINOS_DEBUG")))
data.log->level = atoi (str);
data.support[0].type = SPA_TYPE__TypeMap;
data.support[0].data = data.map;