diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c new file mode 100644 index 000000000..bdd8f75c8 --- /dev/null +++ b/spa/plugins/audioconvert/audioconvert.c @@ -0,0 +1,980 @@ +/* Spa + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NAME "audioconvert" + +#define MAX_BUFFERS 32 + +#define PROP_DEFAULT_TRUNCATE false +#define PROP_DEFAULT_DITHER 0 + +struct impl; + +struct props { + bool truncate; + uint32_t dither; +}; + +static void props_reset(struct props *props) +{ + props->truncate = PROP_DEFAULT_TRUNCATE; + props->dither = PROP_DEFAULT_DITHER; +} + +struct buffer { + struct spa_list link; +#define BUFFER_FLAG_OUT (1 << 0) + uint32_t flags; + struct spa_buffer *outbuf; + struct spa_meta_header *h; +}; + +struct port { + uint32_t id; + + struct spa_io_buffers *io; + struct spa_port_info info; + + bool have_format; + struct spa_audio_info format; + int stride; + int blocks; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct spa_list queue; +}; + +struct type { + uint32_t node; + uint32_t format; + uint32_t prop_truncate; + uint32_t prop_dither; + struct spa_type_io io; + struct spa_type_param param; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_audio format_audio; + struct spa_type_audio_format audio_format; + struct spa_type_command_node command_node; + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_param_buffers param_buffers; + struct spa_type_param_meta param_meta; + struct spa_type_param_io param_io; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + type->node = spa_type_map_get_id(map, SPA_TYPE__Node); + type->format = spa_type_map_get_id(map, SPA_TYPE__Format); + type->prop_truncate = spa_type_map_get_id(map, SPA_TYPE_PROPS__truncate); + type->prop_dither = spa_type_map_get_id(map, SPA_TYPE_PROPS__ditherType); + spa_type_io_map(map, &type->io); + spa_type_param_map(map, &type->param); + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_audio_map(map, &type->format_audio); + spa_type_audio_format_map(map, &type->audio_format); + spa_type_command_node_map(map, &type->command_node); + spa_type_meta_map(map, &type->meta); + spa_type_data_map(map, &type->data); + spa_type_param_buffers_map(map, &type->param_buffers); + spa_type_param_meta_map(map, &type->param_meta); + spa_type_param_io_map(map, &type->param_io); +} + + +#include "fmt-ops.c" +#include "channelmix-ops.c" + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct type type; + struct spa_type_map *map; + struct spa_log *log; + + struct props props; + + const struct spa_node_callbacks *callbacks; + void *user_data; + + struct port in_port; + struct port out_port; + + bool started; + + const struct buffer *src; + + int n_conv; + const struct conv_info *conv[5]; + + convert_func_t convert; + + uint8_t temp[8192]; +}; + +#define CHECK_PORT(this,d,id) (id == 0) +#define GET_IN_PORT(this,id) (&this->in_port) +#define GET_OUT_PORT(this,id) (&this->out_port) +#define GET_PORT(this,d,id) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,id) : GET_OUT_PORT(this,id)) + +static void convert_generic (void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], int n_bytes) +{ +#if 0 + struct port *inport, *outport; + + inport = GET_PORT(this, SPA_DIRECTION_INPUT, 0); + outport = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); + + if (inport->format.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) { + } + else { + } +#endif +} + +static int setup_convert(struct impl *this) +{ + struct port *inport, *outport; + const struct conv_info *conv; + uint32_t src_fmt, dst_fmt; + struct type *t = &this->type; + + inport = GET_PORT(this, SPA_DIRECTION_INPUT, 0); + outport = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); + + src_fmt = inport->format.info.raw.format; + dst_fmt = outport->format.info.raw.format; + + spa_log_info(this->log, NAME " %p: %d/%d@%d.%d->%d/%d@%d.%d", this, + src_fmt, + inport->format.info.raw.channels, + inport->format.info.raw.rate, + inport->format.info.raw.layout, + dst_fmt, + outport->format.info.raw.channels, + outport->format.info.raw.rate, + outport->format.info.raw.layout); + + /* find fast path */ + if (inport->format.info.raw.channels == outport->format.info.raw.channels && + inport->format.info.raw.rate == outport->format.info.raw.rate) { + this->conv[0] = find_conv_info(&t->audio_format, src_fmt, dst_fmt); + if (this->conv[0] != NULL) { + if (inport->format.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) { + if (outport->format.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) + this->convert = this->conv[0]->i2i; + else + this->convert = this->conv[0]->i2d; + } + else { + if (outport->format.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) + this->convert = this->conv[0]->d2i; + else + this->convert = this->conv[0]->i2i; + } + return 0; + } + } + + this->n_conv = 0; + + /* unpack */ + if ((conv = find_conv_info(&t->audio_format, src_fmt, t->audio_format.F32)) == NULL) + return -ENOTSUP; + + this->conv[this->n_conv++] = conv; + + /* down mix */ + if (inport->format.info.raw.channels > outport->format.info.raw.channels) { + this->conv[this->n_conv++] = conv; + } + + /* resample */ + if (inport->format.info.raw.rate != outport->format.info.raw.rate) { + this->conv[this->n_conv++] = conv; + } + + /* up mix */ + if (inport->format.info.raw.channels < outport->format.info.raw.channels) { + this->conv[this->n_conv++] = conv; + } + + /* pack */ + if ((conv = find_conv_info(&t->audio_format, t->audio_format.F32, dst_fmt)) == NULL) + return -ENOTSUP; + + this->conv[this->n_conv++] = conv; + + this->convert = convert_generic; + + return 0; +} + +static int impl_node_enum_params(struct spa_node *node, + uint32_t id, uint32_t *index, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + return -ENOTSUP; +} + +static int impl_node_set_param(struct spa_node *node, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return -ENOTSUP; +} + +static int impl_node_send_command(struct spa_node *node, const struct spa_command *command) +{ + struct impl *this; + + spa_return_val_if_fail(node != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + if (SPA_COMMAND_TYPE(command) == this->type.command_node.Start) { + this->started = true; + } else if (SPA_COMMAND_TYPE(command) == this->type.command_node.Pause) { + this->started = false; + } else + return -ENOTSUP; + + return 0; +} + +static int +impl_node_set_callbacks(struct spa_node *node, + const struct spa_node_callbacks *callbacks, + void *user_data) +{ + struct impl *this; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + this->callbacks = callbacks; + this->user_data = user_data; + + return 0; +} + +static int +impl_node_get_n_ports(struct spa_node *node, + uint32_t *n_input_ports, + uint32_t *max_input_ports, + uint32_t *n_output_ports, + uint32_t *max_output_ports) +{ + spa_return_val_if_fail(node != NULL, -EINVAL); + + if (n_input_ports) + *n_input_ports = 1; + if (max_input_ports) + *max_input_ports = 1; + if (n_output_ports) + *n_output_ports = 1; + if (max_output_ports) + *max_output_ports = 1; + + return 0; +} + +static int +impl_node_get_port_ids(struct spa_node *node, + uint32_t *input_ids, + uint32_t n_input_ids, + uint32_t *output_ids, + uint32_t n_output_ids) +{ + spa_return_val_if_fail(node != NULL, -EINVAL); + + if (n_input_ids && input_ids) + input_ids[0] = 0; + if (n_output_ids > 0 && output_ids) + output_ids[0] = 0; + + return 0; +} + +static int impl_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_port_get_info(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + const struct spa_port_info **info) +{ + struct impl *this; + struct port *port; + + spa_return_val_if_fail(node != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + *info = &port->info; + + return 0; +} + +static int port_enum_formats(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t *index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = SPA_CONTAINER_OF(node, struct impl, node); + struct type *t = &this->type; + + switch (*index) { + case 0: + *param = spa_pod_builder_object(builder, + t->param.idEnumFormat, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "Ieu", t->audio_format.S16, + SPA_POD_PROP_ENUM(11, t->audio_format.U8, + t->audio_format.S16, + t->audio_format.S16_OE, + t->audio_format.F32, + t->audio_format.F32_OE, + t->audio_format.S32, + t->audio_format.S32_OE, + t->audio_format.S24, + t->audio_format.S24_OE, + t->audio_format.S24_32, + t->audio_format.S24_32_OE), + ":", t->format_audio.layout, "ieu", SPA_AUDIO_LAYOUT_INTERLEAVED, + SPA_POD_PROP_ENUM(2, SPA_AUDIO_LAYOUT_INTERLEAVED, + SPA_AUDIO_LAYOUT_NON_INTERLEAVED), + ":", t->format_audio.rate, "iru", 44100, + SPA_POD_PROP_MIN_MAX(1, INT32_MAX), + ":", t->format_audio.channels, "iru", 2, + SPA_POD_PROP_MIN_MAX(1, INT32_MAX)); + break; + default: + return 0; + } + return 1; +} + +static int port_get_format(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t *index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = SPA_CONTAINER_OF(node, struct impl, node); + struct type *t = &this->type; + struct port *port = GET_PORT(this, direction, port_id); + + if (!port->have_format) + return -EIO; + if (*index > 0) + return 0; + + *param = spa_pod_builder_object(builder, + t->param.idFormat, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "I", port->format.info.raw.format, + ":", t->format_audio.layout, "i", port->format.info.raw.layout, + ":", t->format_audio.rate, "i", port->format.info.raw.rate, + ":", t->format_audio.channels, "i", port->format.info.raw.channels); + + return 1; +} + +static int +impl_node_port_enum_params(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t *index, + const struct spa_pod *filter, + struct spa_pod **result, + struct spa_pod_builder *builder) +{ + struct impl *this; + struct type *t; + struct port *port; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + int res; + + spa_return_val_if_fail(node != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + spa_return_val_if_fail(builder != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + next: + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + if (id == t->param.idList) { + uint32_t list[] = { t->param.idEnumFormat, + t->param.idFormat, + t->param.idBuffers, + t->param.idMeta, + t->param_io.idBuffers }; + + if (*index < SPA_N_ELEMENTS(list)) + param = spa_pod_builder_object(&b, id, t->param.List, + ":", t->param.listId, "I", list[*index]); + else + return 0; + } + else if (id == t->param.idEnumFormat) { + if ((res = port_enum_formats(node, direction, port_id, index, ¶m, &b)) <= 0) + return res; + } + else if (id == t->param.idFormat) { + if ((res = port_get_format(node, direction, port_id, index, ¶m, &b)) <= 0) + return res; + } + else if (id == t->param.idBuffers) { + if (!port->have_format) + return -EIO; + if (*index > 0) + return 0; + + param = spa_pod_builder_object(&b, + id, t->param_buffers.Buffers, + ":", t->param_buffers.buffers, "iru", 1, + SPA_POD_PROP_MIN_MAX(1, MAX_BUFFERS), + ":", t->param_buffers.blocks, "i", port->blocks, + ":", t->param_buffers.size, "iru", 1024 * port->stride, + SPA_POD_PROP_MIN_MAX(16 * port->stride, INT32_MAX / port->stride), + ":", t->param_buffers.stride, "i", port->stride, + ":", t->param_buffers.align, "i", 16); + } + else if (id == t->param.idMeta) { + if (!port->have_format) + return -EIO; + + switch (*index) { + case 0: + param = spa_pod_builder_object(&b, + id, t->param_meta.Meta, + ":", t->param_meta.type, "I", t->meta.Header, + ":", t->param_meta.size, "i", sizeof(struct spa_meta_header)); + break; + default: + return 0; + } + } + else if (id == t->param_io.idBuffers) { + switch (*index) { + case 0: + param = spa_pod_builder_object(&b, + id, t->param_io.Buffers, + ":", t->param_io.id, "I", t->io.Buffers, + ":", t->param_io.size, "i", sizeof(struct spa_io_buffers)); + break; + default: + return 0; + } + } + else + return -ENOENT; + + (*index)++; + + if (spa_pod_filter(builder, result, param, filter) < 0) + goto next; + + return 1; +} + +static int calc_width(struct spa_audio_info *info, struct type *t) +{ + if (info->info.raw.format == t->audio_format.U8) + return 1; + else if (info->info.raw.format == t->audio_format.S16 || + info->info.raw.format == t->audio_format.S16_OE) + return 2; + else if (info->info.raw.format == t->audio_format.S24 || + info->info.raw.format == t->audio_format.S24_OE) + return 3; + else + return 4; +} + +static int clear_buffers(struct impl *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_log_info(this->log, NAME " %p: clear buffers %p", this, port); + port->n_buffers = 0; + spa_list_init(&port->queue); + } + return 0; +} + +static int port_set_format(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + struct impl *this = SPA_CONTAINER_OF(node, struct impl, node); + struct port *port, *other; + struct type *t = &this->type; + int res = 0; + + port = GET_PORT(this, direction, port_id); + other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); + + if (format == NULL) { + if (port->have_format) { + port->have_format = false; + clear_buffers(this, port); + } + this->convert = NULL; + } else { + struct spa_audio_info info = { 0 }; + + spa_pod_object_parse(format, + "I", &info.media_type, + "I", &info.media_subtype); + + if (info.media_type != t->media_type.audio || + info.media_subtype != t->media_subtype.raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw, &t->format_audio) < 0) + return -EINVAL; + + port->have_format = true; + port->format = info; + + port->stride = calc_width(&info, t); + + if (info.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) { + port->stride *= info.info.raw.channels; + port->blocks = 1; + } + else { + port->blocks = info.info.raw.channels; + } + + if (other->have_format) + res = setup_convert(this); + + spa_log_info(this->log, NAME " %p: set format on port %d %d", this, port_id, res); + } + return res; +} + +static int +impl_node_port_set_param(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this; + struct type *t; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + if (id == t->param.idFormat) { + return port_set_format(node, direction, port_id, flags, param); + } + else + return -ENOENT; +} + +static int +impl_node_port_use_buffers(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct impl *this; + struct port *port; + uint32_t i; + struct type *t; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_return_val_if_fail(port->have_format, -EIO); + + spa_log_info(this->log, NAME " %p: use buffers %d on port %d", this, n_buffers, port_id); + + clear_buffers(this, port); + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + struct spa_data *d = buffers[i]->datas; + + b = &port->buffers[i]; + b->flags = 0; + b->outbuf = buffers[i]; + b->h = spa_buffer_find_meta(buffers[i], t->meta.Header); + + if (!((d[0].type == t->data.MemPtr || + d[0].type == t->data.MemFd || + d[0].type == t->data.DmaBuf) && d[0].data != NULL)) { + spa_log_error(this->log, NAME " %p: invalid memory on buffer %p", this, + buffers[i]); + return -EINVAL; + } + if (direction == SPA_DIRECTION_OUTPUT) + spa_list_append(&port->queue, &b->link); + else + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_alloc_buffers(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_pod **params, + uint32_t n_params, + struct spa_buffer **buffers, + uint32_t *n_buffers) +{ + return -ENOTSUP; +} + +static int +impl_node_port_set_io(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + struct impl *this; + struct port *port; + struct type *t; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + if (id == t->io.Buffers) + port->io = data; + else + return -ENOENT; + + return 0; +} + +static void recycle_buffer(struct impl *this, uint32_t id) +{ + struct port *port = GET_OUT_PORT(this, 0); + struct buffer *b = &port->buffers[id]; + + if (SPA_FLAG_CHECK(b->flags, BUFFER_FLAG_OUT)) { + spa_list_append(&port->queue, &b->link); + SPA_FLAG_UNSET(b->flags, BUFFER_FLAG_OUT); + spa_log_trace(this->log, NAME " %p: recycle buffer %d", this, id); + } +} + +static struct buffer *dequeue_buffer(struct impl *this, struct port *port) +{ + struct buffer *b; + + if (spa_list_is_empty(&port->queue)) + return NULL; + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + return b; +} + + +static int impl_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *this; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); + + recycle_buffer(this, buffer_id); + + return 0; +} + +static int +impl_node_port_send_command(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + const struct spa_command *command) +{ + return -ENOTSUP; +} + +static int impl_node_process(struct spa_node *node) +{ + struct impl *this; + struct port *outport, *inport; + struct spa_io_buffers *outio, *inio; + struct buffer *sbuf, *dbuf; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + outport = GET_OUT_PORT(this, 0); + inport = GET_IN_PORT(this, 0); + + outio = outport->io; + inio = inport->io; + + spa_return_val_if_fail(outio != NULL, -EIO); + spa_return_val_if_fail(inio != NULL, -EIO); + + spa_log_trace(this->log, NAME " %p: status %d", this, outio->status); + + if (outio->status == SPA_STATUS_HAVE_BUFFER) + return outio->status; + + if (inio->status != SPA_STATUS_HAVE_BUFFER) + return SPA_STATUS_NEED_BUFFER; + + /* recycle */ + if (outio->buffer_id < outport->n_buffers) { + recycle_buffer(this, outio->buffer_id); + outio->buffer_id = SPA_ID_INVALID; + } + + if (inio->buffer_id >= inport->n_buffers) + return inio->status = -EINVAL; + + if ((dbuf = dequeue_buffer(this, outport)) == NULL) + return outio->status = -EPIPE; + + sbuf = &inport->buffers[inio->buffer_id]; + + { + int i, n_bytes; + uint32_t n_src_datas = sbuf->outbuf->n_datas; + uint32_t n_dst_datas = dbuf->outbuf->n_datas; + const void *src_datas[n_src_datas]; + void *dst_datas[n_dst_datas]; + + n_bytes = sbuf->outbuf->datas[0].chunk->size; + + for (i = 0; i < n_src_datas; i++) + src_datas[i] = sbuf->outbuf->datas[i].data; + for (i = 0; i < n_dst_datas; i++) + dst_datas[i] = dbuf->outbuf->datas[i].data; + + this->convert(this, n_dst_datas, dst_datas, n_src_datas, src_datas, n_bytes); + + dbuf->outbuf->datas[0].chunk->size = n_bytes / 2; + } + + outio->status = SPA_STATUS_HAVE_BUFFER; + outio->buffer_id = dbuf->outbuf->id; + + return outio->status; +} + +static const struct spa_node impl_node = { + SPA_VERSION_NODE, + NULL, + impl_node_enum_params, + impl_node_set_param, + impl_node_send_command, + impl_node_set_callbacks, + impl_node_get_n_ports, + impl_node_get_port_ids, + impl_node_add_port, + impl_node_remove_port, + impl_node_port_get_info, + impl_node_port_enum_params, + impl_node_port_set_param, + impl_node_port_use_buffers, + impl_node_port_alloc_buffers, + impl_node_port_set_io, + impl_node_port_reuse_buffer, + impl_node_port_send_command, + impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, uint32_t interface_id, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (interface_id == this->type.node) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + return 0; +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + for (i = 0; i < n_support; i++) { + if (strcmp(support[i].type, SPA_TYPE__TypeMap) == 0) + this->map = support[i].data; + else if (strcmp(support[i].type, SPA_TYPE__Log) == 0) + this->log = support[i].data; + } + if (this->map == NULL) { + spa_log_error(this->log, "an id-map is needed"); + return -EINVAL; + } + init_type(&this->type, this->map); + + this->node = impl_node; + + port = GET_OUT_PORT(this, 0); + port->id = 0; + port->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + spa_list_init(&port->queue); + + port = GET_IN_PORT(this, 0); + port->id = 0; + port->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + spa_list_init(&port->queue); + + props_reset(&this->props); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE__Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +const struct spa_handle_factory spa_audioconvert_factory = { + SPA_VERSION_HANDLE_FACTORY, + NAME, + NULL, + sizeof(struct impl), + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c new file mode 100644 index 000000000..49150c72c --- /dev/null +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -0,0 +1,115 @@ +/* Spa + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include + +static void +channelmix_copy(void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], void *matrix, int n_bytes) +{ + int i; + for (i = 0; i < n_src; i++) + memcpy(dst[i], src[i], n_bytes); +} + +static void +channelmix_f32_n_m(void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], void *matrix, int n_bytes) +{ + int i, j, n, n_samples; + float **d = (float **) dst; + float **s = (float **) src; + float *m = matrix; + + n_samples = n_bytes / sizeof(float); + for (n = 0; n < n_samples; n++) { + for (i = 0; i < n_dst; i++) { + float sum = 0.0f; + + for (j = 0; j < n_src; j++) + sum += s[j][n] * m[i * n_src + j]; + + d[i][n] = sum; + } + } +} + +static void +channelmix_f32_1_2(void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], void *matrix, int n_bytes) +{ + int n, n_samples; + float **d = (float **) dst; + float **s = (float **) src; + + n_samples = n_bytes / sizeof(float); + for (n = 0; n < n_samples; n++) { + d[0][n] = s[0][n]; + d[1][n] = s[0][n]; + } +} + +static void +channelmix_f32_2_1(void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], void *matrix, int n_bytes) +{ + int n, n_samples; + float **d = (float **) dst; + float **s = (float **) src; + + n_samples = n_bytes / sizeof(float); + for (n = 0; n < n_samples; n++) + d[0][n] = (s[0][n] + s[1][n]) * 0.5f; +} + +typedef void (*channelmix_func_t) (void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], + void *matrix, int n_bytes); + + +static const struct channelmix_info { + uint32_t src_chan; + uint32_t dst_chan; + + channelmix_func_t func; +#define CHANNELMIX_INFO_FLAG_NO_MATRIX (1 << 0) + uint32_t flags; +} channelmix_table[] = +{ + { 1, 2, channelmix_f32_1_2, CHANNELMIX_INFO_FLAG_NO_MATRIX }, + { 2, 1, channelmix_f32_2_1, CHANNELMIX_INFO_FLAG_NO_MATRIX }, + { -1, -1, channelmix_f32_n_m, 0 }, +}; + +#define MATCH_CHAN(a,b) ((a) == -1 || (a) == (b)) + +static const struct channelmix_info *find_channelmix_info(uint32_t src_chan, uint32_t dst_chan) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS(channelmix_table); i++) { + if (MATCH_CHAN(channelmix_table[i].src_chan, src_chan) && + MATCH_CHAN(channelmix_table[i].dst_chan, dst_chan)) + return &channelmix_table[i]; + } + return NULL; +} diff --git a/spa/plugins/audioconvert/channelmix.c b/spa/plugins/audioconvert/channelmix.c new file mode 100644 index 000000000..720868967 --- /dev/null +++ b/spa/plugins/audioconvert/channelmix.c @@ -0,0 +1,953 @@ +/* Spa + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NAME "channelmix" + +#define MAX_BUFFERS 32 + +struct impl; + +struct props { + int dummy; +}; + +static void props_reset(struct props *props) +{ +} + +struct buffer { + struct spa_list link; +#define BUFFER_FLAG_OUT (1 << 0) + uint32_t flags; + struct spa_buffer *outbuf; + struct spa_meta_header *h; +}; + +struct port { + uint32_t id; + + struct spa_io_buffers *io; + struct spa_port_info info; + + bool have_format; + struct spa_audio_info format; + uint32_t stride; + uint32_t blocks; + uint32_t size; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct spa_list queue; +}; + +struct type { + uint32_t node; + uint32_t format; + uint32_t prop_truncate; + uint32_t prop_dither; + struct spa_type_io io; + struct spa_type_param param; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_audio format_audio; + struct spa_type_audio_format audio_format; + struct spa_type_command_node command_node; + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_param_buffers param_buffers; + struct spa_type_param_meta param_meta; + struct spa_type_param_io param_io; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + type->node = spa_type_map_get_id(map, SPA_TYPE__Node); + type->format = spa_type_map_get_id(map, SPA_TYPE__Format); + type->prop_truncate = spa_type_map_get_id(map, SPA_TYPE_PROPS__truncate); + type->prop_dither = spa_type_map_get_id(map, SPA_TYPE_PROPS__ditherType); + spa_type_io_map(map, &type->io); + spa_type_param_map(map, &type->param); + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_audio_map(map, &type->format_audio); + spa_type_audio_format_map(map, &type->audio_format); + spa_type_command_node_map(map, &type->command_node); + spa_type_meta_map(map, &type->meta); + spa_type_data_map(map, &type->data); + spa_type_param_buffers_map(map, &type->param_buffers); + spa_type_param_meta_map(map, &type->param_meta); + spa_type_param_io_map(map, &type->param_io); +} + + +#include "channelmix-ops.c" + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct type type; + struct spa_type_map *map; + struct spa_log *log; + + struct props props; + + const struct spa_node_callbacks *callbacks; + void *user_data; + + struct port in_port; + struct port out_port; + + bool started; + + const struct buffer *src; + + channelmix_func_t convert; + float matrix[4096]; +}; + +#define CHECK_PORT(this,d,id) (id == 0) +#define GET_IN_PORT(this,id) (&this->in_port) +#define GET_OUT_PORT(this,id) (&this->out_port) +#define GET_PORT(this,d,id) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,id) : GET_OUT_PORT(this,id)) + +static void setup_matrix(struct impl *this, + const struct spa_audio_info *src_info, + const struct spa_audio_info *dst_info) +{ + uint32_t src_chan, dst_chan; + int i, j; + + src_chan = src_info->info.raw.channels; + dst_chan = dst_info->info.raw.channels; + + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + if (i == j) + this->matrix[i * src_chan + j] = 1.0f; + else + this->matrix[i * src_chan + j] = 0.0f; + } + } +} + +static int setup_convert(struct impl *this, + enum spa_direction direction, + const struct spa_audio_info *info) +{ + const struct spa_audio_info *src_info, *dst_info; + uint32_t src_chan, dst_chan; + const struct channelmix_info *chanmix_info; + + if (direction == SPA_DIRECTION_INPUT) { + src_info = info; + dst_info = &GET_OUT_PORT(this, 0)->format; + } else { + src_info = &GET_IN_PORT(this, 0)->format; + dst_info = info; + } + + src_chan = src_info->info.raw.channels; + dst_chan = dst_info->info.raw.channels; + + spa_log_info(this->log, NAME " %p: %d/%d@%d.%d->%d/%d@%d.%d", this, + src_info->info.raw.format, + src_chan, + src_info->info.raw.rate, + src_info->info.raw.layout, + dst_info->info.raw.format, + dst_chan, + dst_info->info.raw.rate, + dst_info->info.raw.layout); + + if (src_info->info.raw.rate != dst_info->info.raw.rate) + return -EINVAL; + + /* find convert function */ + if ((chanmix_info = find_channelmix_info(src_chan, dst_chan)) == NULL) + return -ENOTSUP; + + this->convert = chanmix_info->func; + + /* set up the matrix if needed */ + if (!SPA_FLAG_CHECK(chanmix_info->flags, CHANNELMIX_INFO_FLAG_NO_MATRIX)) { + setup_matrix(this, src_info, dst_info); + } + + return 0; +} + +static int impl_node_enum_params(struct spa_node *node, + uint32_t id, uint32_t *index, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + return -ENOTSUP; +} + +static int impl_node_set_param(struct spa_node *node, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return -ENOTSUP; +} + +static int impl_node_send_command(struct spa_node *node, const struct spa_command *command) +{ + struct impl *this; + + spa_return_val_if_fail(node != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + if (SPA_COMMAND_TYPE(command) == this->type.command_node.Start) { + this->started = true; + } else if (SPA_COMMAND_TYPE(command) == this->type.command_node.Pause) { + this->started = false; + } else + return -ENOTSUP; + + return 0; +} + +static int +impl_node_set_callbacks(struct spa_node *node, + const struct spa_node_callbacks *callbacks, + void *user_data) +{ + struct impl *this; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + this->callbacks = callbacks; + this->user_data = user_data; + + return 0; +} + +static int +impl_node_get_n_ports(struct spa_node *node, + uint32_t *n_input_ports, + uint32_t *max_input_ports, + uint32_t *n_output_ports, + uint32_t *max_output_ports) +{ + spa_return_val_if_fail(node != NULL, -EINVAL); + + if (n_input_ports) + *n_input_ports = 1; + if (max_input_ports) + *max_input_ports = 1; + if (n_output_ports) + *n_output_ports = 1; + if (max_output_ports) + *max_output_ports = 1; + + return 0; +} + +static int +impl_node_get_port_ids(struct spa_node *node, + uint32_t *input_ids, + uint32_t n_input_ids, + uint32_t *output_ids, + uint32_t n_output_ids) +{ + spa_return_val_if_fail(node != NULL, -EINVAL); + + if (n_input_ids && input_ids) + input_ids[0] = 0; + if (n_output_ids > 0 && output_ids) + output_ids[0] = 0; + + return 0; +} + +static int impl_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_port_get_info(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + const struct spa_port_info **info) +{ + struct impl *this; + struct port *port; + + spa_return_val_if_fail(node != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + *info = &port->info; + + return 0; +} + +static int port_enum_formats(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t *index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = SPA_CONTAINER_OF(node, struct impl, node); + struct type *t = &this->type; + struct port *other; + + other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), 0); + + switch (*index) { + case 0: + if (other->have_format) { + *param = spa_pod_builder_object(builder, + t->param.idEnumFormat, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "I", t->audio_format.F32, + ":", t->format_audio.layout, "i", SPA_AUDIO_LAYOUT_NON_INTERLEAVED, + ":", t->format_audio.rate, "i", other->format.info.raw.rate, + ":", t->format_audio.channels, "iru", 2, + SPA_POD_PROP_MIN_MAX(1, INT32_MAX)); + } else { + *param = spa_pod_builder_object(builder, + t->param.idEnumFormat, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "I", t->audio_format.F32, + ":", t->format_audio.layout, "i", SPA_AUDIO_LAYOUT_NON_INTERLEAVED, + ":", t->format_audio.rate, "iru", 44100, + SPA_POD_PROP_MIN_MAX(1, INT32_MAX), + ":", t->format_audio.channels, "iru", 2, + SPA_POD_PROP_MIN_MAX(1, INT32_MAX)); + } + break; + default: + return 0; + } + return 1; +} + +static int port_get_format(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t *index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = SPA_CONTAINER_OF(node, struct impl, node); + struct type *t = &this->type; + struct port *port = GET_PORT(this, direction, port_id); + + if (!port->have_format) + return -EIO; + if (*index > 0) + return 0; + + *param = spa_pod_builder_object(builder, + t->param.idFormat, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "I", port->format.info.raw.format, + ":", t->format_audio.layout, "i", port->format.info.raw.layout, + ":", t->format_audio.rate, "i", port->format.info.raw.rate, + ":", t->format_audio.channels, "i", port->format.info.raw.channels); + + return 1; +} + +static int +impl_node_port_enum_params(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t *index, + const struct spa_pod *filter, + struct spa_pod **result, + struct spa_pod_builder *builder) +{ + struct impl *this; + struct type *t; + struct port *port, *other; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + int res; + + spa_return_val_if_fail(node != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + spa_return_val_if_fail(builder != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); + + next: + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + if (id == t->param.idList) { + uint32_t list[] = { t->param.idEnumFormat, + t->param.idFormat, + t->param.idBuffers, + t->param.idMeta, + t->param_io.idBuffers }; + + if (*index < SPA_N_ELEMENTS(list)) + param = spa_pod_builder_object(&b, id, t->param.List, + ":", t->param.listId, "I", list[*index]); + else + return 0; + } + else if (id == t->param.idEnumFormat) { + if ((res = port_enum_formats(node, direction, port_id, index, ¶m, &b)) <= 0) + return res; + } + else if (id == t->param.idFormat) { + if ((res = port_get_format(node, direction, port_id, index, ¶m, &b)) <= 0) + return res; + } + else if (id == t->param.idBuffers) { + if (!port->have_format) + return -EIO; + if (*index > 0) + return 0; + + if (other->n_buffers > 0) { + param = spa_pod_builder_object(&b, + id, t->param_buffers.Buffers, + ":", t->param_buffers.buffers, "iru", other->n_buffers, + SPA_POD_PROP_MIN_MAX(1, MAX_BUFFERS), + ":", t->param_buffers.blocks, "i", port->blocks, + ":", t->param_buffers.size, "i", (other->size / other->stride) * + port->stride, + ":", t->param_buffers.stride, "i", port->stride, + ":", t->param_buffers.align, "i", 16); + } else { + param = spa_pod_builder_object(&b, + id, t->param_buffers.Buffers, + ":", t->param_buffers.buffers, "iru", 1, + SPA_POD_PROP_MIN_MAX(1, MAX_BUFFERS), + ":", t->param_buffers.blocks, "i", port->blocks, + ":", t->param_buffers.size, "iru", 1024 * port->stride, + SPA_POD_PROP_MIN_MAX(16 * port->stride, INT32_MAX / port->stride), + ":", t->param_buffers.stride, "i", port->stride, + ":", t->param_buffers.align, "i", 16); + } + } + else if (id == t->param.idMeta) { + if (!port->have_format) + return -EIO; + + switch (*index) { + case 0: + param = spa_pod_builder_object(&b, + id, t->param_meta.Meta, + ":", t->param_meta.type, "I", t->meta.Header, + ":", t->param_meta.size, "i", sizeof(struct spa_meta_header)); + break; + default: + return 0; + } + } + else if (id == t->param_io.idBuffers) { + switch (*index) { + case 0: + param = spa_pod_builder_object(&b, + id, t->param_io.Buffers, + ":", t->param_io.id, "I", t->io.Buffers, + ":", t->param_io.size, "i", sizeof(struct spa_io_buffers)); + break; + default: + return 0; + } + } + else + return -ENOENT; + + (*index)++; + + if (spa_pod_filter(builder, result, param, filter) < 0) + goto next; + + return 1; +} + +static int clear_buffers(struct impl *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_log_info(this->log, NAME " %p: clear buffers %p", this, port); + port->n_buffers = 0; + spa_list_init(&port->queue); + } + return 0; +} + +static int port_set_format(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + struct impl *this = SPA_CONTAINER_OF(node, struct impl, node); + struct port *port, *other; + struct type *t = &this->type; + int res = 0; + + port = GET_PORT(this, direction, port_id); + other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); + + if (format == NULL) { + if (port->have_format) { + port->have_format = false; + clear_buffers(this, port); + } + this->convert = NULL; + } else { + struct spa_audio_info info = { 0 }; + + spa_pod_object_parse(format, + "I", &info.media_type, + "I", &info.media_subtype); + + if (info.media_type != t->media_type.audio || + info.media_subtype != t->media_subtype.raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw, &t->format_audio) < 0) + return -EINVAL; + + if (info.info.raw.format != t->audio_format.F32) + return -EINVAL; + if (info.info.raw.layout != SPA_AUDIO_LAYOUT_NON_INTERLEAVED) + return -EINVAL; + + port->stride = sizeof(float); + port->blocks = info.info.raw.channels; + + if (other->have_format) { + if ((res = setup_convert(this, direction, &info)) < 0) + return res; + } + port->format = info; + port->have_format = true; + + spa_log_info(this->log, NAME " %p: set format on port %d %d", this, port_id, res); + } + return res; +} + +static int +impl_node_port_set_param(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this; + struct type *t; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + if (id == t->param.idFormat) { + return port_set_format(node, direction, port_id, flags, param); + } + else + return -ENOENT; +} + +static int +impl_node_port_use_buffers(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct impl *this; + struct port *port; + uint32_t i, size = SPA_ID_INVALID; + struct type *t; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_return_val_if_fail(port->have_format, -EIO); + + spa_log_info(this->log, NAME " %p: use buffers %d on port %d", this, n_buffers, port_id); + + clear_buffers(this, port); + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + struct spa_data *d = buffers[i]->datas; + + b = &port->buffers[i]; + b->flags = 0; + b->outbuf = buffers[i]; + b->h = spa_buffer_find_meta(buffers[i], t->meta.Header); + + if (size == SPA_ID_INVALID) + size = d[0].maxsize; + else + if (size != d[0].maxsize) + return -EINVAL; + + if (!((d[0].type == t->data.MemPtr || + d[0].type == t->data.MemFd || + d[0].type == t->data.DmaBuf) && d[0].data != NULL)) { + spa_log_error(this->log, NAME " %p: invalid memory on buffer %p", this, + buffers[i]); + return -EINVAL; + } + if (direction == SPA_DIRECTION_OUTPUT) + spa_list_append(&port->queue, &b->link); + else + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + } + port->n_buffers = n_buffers; + port->size = size; + + return 0; +} + +static int +impl_node_port_alloc_buffers(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_pod **params, + uint32_t n_params, + struct spa_buffer **buffers, + uint32_t *n_buffers) +{ + return -ENOTSUP; +} + +static int +impl_node_port_set_io(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + struct impl *this; + struct port *port; + struct type *t; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + t = &this->type; + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + if (id == t->io.Buffers) + port->io = data; + else + return -ENOENT; + + return 0; +} + +static void recycle_buffer(struct impl *this, uint32_t id) +{ + struct port *port = GET_OUT_PORT(this, 0); + struct buffer *b = &port->buffers[id]; + + if (SPA_FLAG_CHECK(b->flags, BUFFER_FLAG_OUT)) { + spa_list_append(&port->queue, &b->link); + SPA_FLAG_UNSET(b->flags, BUFFER_FLAG_OUT); + spa_log_trace(this->log, NAME " %p: recycle buffer %d", this, id); + } +} + +static struct buffer *dequeue_buffer(struct impl *this, struct port *port) +{ + struct buffer *b; + + if (spa_list_is_empty(&port->queue)) + return NULL; + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + return b; +} + + +static int impl_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *this; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); + + recycle_buffer(this, buffer_id); + + return 0; +} + +static int +impl_node_port_send_command(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + const struct spa_command *command) +{ + return -ENOTSUP; +} + +static int impl_node_process(struct spa_node *node) +{ + struct impl *this; + struct port *outport, *inport; + struct spa_io_buffers *outio, *inio; + struct buffer *sbuf, *dbuf; + + spa_return_val_if_fail(node != NULL, -EINVAL); + + this = SPA_CONTAINER_OF(node, struct impl, node); + + outport = GET_OUT_PORT(this, 0); + inport = GET_IN_PORT(this, 0); + + outio = outport->io; + inio = inport->io; + + spa_return_val_if_fail(outio != NULL, -EIO); + spa_return_val_if_fail(inio != NULL, -EIO); + + spa_log_trace(this->log, NAME " %p: status %d", this, outio->status); + + if (outio->status == SPA_STATUS_HAVE_BUFFER) + return outio->status; + + if (inio->status != SPA_STATUS_HAVE_BUFFER) + return SPA_STATUS_NEED_BUFFER; + + /* recycle */ + if (outio->buffer_id < outport->n_buffers) { + recycle_buffer(this, outio->buffer_id); + outio->buffer_id = SPA_ID_INVALID; + } + + if (inio->buffer_id >= inport->n_buffers) + return inio->status = -EINVAL; + + if ((dbuf = dequeue_buffer(this, outport)) == NULL) + return outio->status = -EPIPE; + + sbuf = &inport->buffers[inio->buffer_id]; + + { + int i, n_bytes; + uint32_t n_src_datas = sbuf->outbuf->n_datas; + uint32_t n_dst_datas = dbuf->outbuf->n_datas; + const void *src_datas[n_src_datas]; + void *dst_datas[n_dst_datas]; + + n_bytes = sbuf->outbuf->datas[0].chunk->size; + + for (i = 0; i < n_src_datas; i++) + src_datas[i] = sbuf->outbuf->datas[i].data; + for (i = 0; i < n_dst_datas; i++) + dst_datas[i] = dbuf->outbuf->datas[i].data; + + this->convert(this, n_dst_datas, dst_datas, + n_src_datas, src_datas, + this->matrix, n_bytes); + + for (i = 0; i < n_dst_datas; i++) + dbuf->outbuf->datas[i].chunk->size = (n_bytes / inport->stride) * + outport->stride; + } + + outio->status = SPA_STATUS_HAVE_BUFFER; + outio->buffer_id = dbuf->outbuf->id; + + return outio->status; +} + +static const struct spa_node impl_node = { + SPA_VERSION_NODE, + NULL, + impl_node_enum_params, + impl_node_set_param, + impl_node_send_command, + impl_node_set_callbacks, + impl_node_get_n_ports, + impl_node_get_port_ids, + impl_node_add_port, + impl_node_remove_port, + impl_node_port_get_info, + impl_node_port_enum_params, + impl_node_port_set_param, + impl_node_port_use_buffers, + impl_node_port_alloc_buffers, + impl_node_port_set_io, + impl_node_port_reuse_buffer, + impl_node_port_send_command, + impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, uint32_t interface_id, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (interface_id == this->type.node) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + return 0; +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + for (i = 0; i < n_support; i++) { + if (strcmp(support[i].type, SPA_TYPE__TypeMap) == 0) + this->map = support[i].data; + else if (strcmp(support[i].type, SPA_TYPE__Log) == 0) + this->log = support[i].data; + } + if (this->map == NULL) { + spa_log_error(this->log, "an id-map is needed"); + return -EINVAL; + } + init_type(&this->type, this->map); + + this->node = impl_node; + + port = GET_OUT_PORT(this, 0); + port->id = 0; + port->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + spa_list_init(&port->queue); + + port = GET_IN_PORT(this, 0); + port->id = 0; + port->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + spa_list_init(&port->queue); + + props_reset(&this->props); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE__Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +const struct spa_handle_factory spa_channelmix_factory = { + SPA_VERSION_HANDLE_FACTORY, + NAME, + NULL, + sizeof(struct impl), + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 3a0aee58b..78590bb58 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -545,7 +545,7 @@ deinterleave_8(void *data, int n_dst, void *dst[n_dst], int n_src, const void *s uint8_t **d = (uint8_t **) dst; int i, j; - n_bytes /= n_dst; + n_bytes /= (sizeof(uint8_t) * n_dst); for (j = 0; j < n_bytes; j++) { for (i = 0; i < n_dst; i++) d[i][j] = *s++; @@ -604,7 +604,7 @@ interleave_8(void *data, int n_dst, void *dst[n_dst], int n_src, const void *src uint8_t *d = dst[0]; int i, j; - n_bytes /= (sizeof(uint8_t) * n_src); + n_bytes /= sizeof(uint8_t); for (j = 0; j < n_bytes; j++) { for (i = 0; i < n_src; i++) *d++ = s[i][j]; @@ -618,7 +618,7 @@ interleave_16(void *data, int n_dst, void *dst[n_dst], int n_src, const void *sr uint16_t *d = dst[0]; int i, j; - n_bytes /= (sizeof(uint16_t) * n_src); + n_bytes /= sizeof(uint16_t); for (j = 0; j < n_bytes; j++) { for (i = 0; i < n_src; i++) *d++ = s[i][j]; @@ -632,7 +632,7 @@ interleave_24(void *data, int n_dst, void *dst[n_dst], int n_src, const void *sr uint8_t *d = dst[0]; int i, j; - n_bytes /= (3 * n_src); + n_bytes /= 3; for (j = 0; j < n_bytes; j++) { for (i = 0; i < n_src; i++) { WRITE24(d, READ24(s[i])); @@ -649,9 +649,97 @@ interleave_32(void *data, int n_dst, void *dst[n_dst], int n_src, const void *sr uint32_t *d = dst[0]; int i, j; - n_bytes /= (sizeof(uint32_t) * n_src); + n_bytes /= sizeof(uint32_t); for (j = 0; j < n_bytes; j++) { for (i = 0; i < n_src; i++) *d++ = s[i][j]; } } + +typedef void (*convert_func_t) (void *data, int n_dst, void *dst[n_dst], + int n_src, const void *src[n_src], int n_bytes); + +static const struct conv_info { + off_t src_fmt; + off_t dst_fmt; + + convert_func_t i2i; + convert_func_t i2d; + convert_func_t d2i; +} conv_table[] = +{ + /* to f32 */ + { offsetof(struct spa_type_audio_format, U8), + offsetof(struct spa_type_audio_format, F32), + conv_u8_to_f32, conv_u8_to_f32d, conv_u8d_to_f32 }, + { offsetof(struct spa_type_audio_format, S16), + offsetof(struct spa_type_audio_format, F32), + conv_s16_to_f32, conv_s16_to_f32d, conv_s16d_to_f32 }, + { offsetof(struct spa_type_audio_format, F32), + offsetof(struct spa_type_audio_format, F32), + conv_copy, deinterleave_32, interleave_32 }, + { offsetof(struct spa_type_audio_format, S32), + offsetof(struct spa_type_audio_format, F32), + conv_s32_to_f32, conv_s32_to_f32d, conv_s32d_to_f32 }, + { offsetof(struct spa_type_audio_format, S24), + offsetof(struct spa_type_audio_format, F32), + conv_s24_to_f32, conv_s24_to_f32d, conv_s24d_to_f32 }, + { offsetof(struct spa_type_audio_format, S24_32), + offsetof(struct spa_type_audio_format, F32), + conv_s24_32_to_f32, conv_s24_32_to_f32d, conv_s24_32d_to_f32 }, + + /* from f32 */ + { offsetof(struct spa_type_audio_format, F32), + offsetof(struct spa_type_audio_format, U8), + conv_f32_to_u8, conv_f32_to_u8d, conv_f32d_to_u8 }, + { offsetof(struct spa_type_audio_format, F32), + offsetof(struct spa_type_audio_format, S16), + conv_f32_to_s16, conv_f32_to_s16d, conv_f32d_to_s16 }, + { offsetof(struct spa_type_audio_format, F32), + offsetof(struct spa_type_audio_format, S32), + conv_f32_to_s32, conv_f32_to_s32d, conv_f32d_to_s32 }, + { offsetof(struct spa_type_audio_format, F32), + offsetof(struct spa_type_audio_format, S24), + conv_f32_to_s24, conv_f32_to_s24d, conv_f32d_to_s24 }, + { offsetof(struct spa_type_audio_format, F32), + offsetof(struct spa_type_audio_format, S24_32), + conv_f32_to_s24_32, conv_f32_to_s24_32d, conv_f32d_to_s24_32 }, + + /* u8 */ + { offsetof(struct spa_type_audio_format, U8), + offsetof(struct spa_type_audio_format, U8), + conv_copy, deinterleave_8, interleave_8 }, + + /* s16 */ + { offsetof(struct spa_type_audio_format, S16), + offsetof(struct spa_type_audio_format, S16), + conv_copy, deinterleave_16, interleave_16 }, + + /* s32 */ + { offsetof(struct spa_type_audio_format, S32), + offsetof(struct spa_type_audio_format, S32), + conv_copy, deinterleave_32, interleave_32 }, + + /* s24 */ + { offsetof(struct spa_type_audio_format, S24), + offsetof(struct spa_type_audio_format, S24), + conv_copy, deinterleave_24, interleave_24 }, + + /* s24_32 */ + { offsetof(struct spa_type_audio_format, S24_32), + offsetof(struct spa_type_audio_format, S24_32), + conv_copy, deinterleave_32, interleave_32 }, +}; + +static const struct conv_info *find_conv_info(struct spa_type_audio_format *audio_format, + uint32_t src_fmt, uint32_t dst_fmt) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS(conv_table); i++) { + if (*SPA_MEMBER(audio_format, conv_table[i].src_fmt, uint32_t) == src_fmt && + *SPA_MEMBER(audio_format, conv_table[i].dst_fmt, uint32_t) == dst_fmt) + return &conv_table[i]; + } + return NULL; +} diff --git a/spa/plugins/audioconvert/fmtconvert.c b/spa/plugins/audioconvert/fmtconvert.c index 5c63e0913..36ed0fe69 100644 --- a/spa/plugins/audioconvert/fmtconvert.c +++ b/spa/plugins/audioconvert/fmtconvert.c @@ -69,8 +69,9 @@ struct port { bool have_format; struct spa_audio_info format; - int stride; - int blocks; + uint32_t stride; + uint32_t blocks; + uint32_t size; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; @@ -120,81 +121,6 @@ static inline void init_type(struct type *type, struct spa_type_map *map) #include "fmt-ops.c" -typedef void (*convert_func_t) (void *data, int n_dst, void *dst[n_dst], - int n_src, const void *src[n_src], int n_bytes); - -static const struct conv_info { - off_t src_fmt; - off_t dst_fmt; - - convert_func_t i2i; - convert_func_t i2d; - convert_func_t d2i; -} conv_table[] = -{ - /* to f32 */ - { offsetof(struct spa_type_audio_format, U8), - offsetof(struct spa_type_audio_format, F32), - conv_u8_to_f32, conv_u8_to_f32d, conv_u8d_to_f32 }, - { offsetof(struct spa_type_audio_format, S16), - offsetof(struct spa_type_audio_format, F32), - conv_s16_to_f32, conv_s16_to_f32d, conv_s16d_to_f32 }, - { offsetof(struct spa_type_audio_format, F32), - offsetof(struct spa_type_audio_format, F32), - conv_copy, deinterleave_32, interleave_32 }, - { offsetof(struct spa_type_audio_format, S32), - offsetof(struct spa_type_audio_format, F32), - conv_s32_to_f32, conv_s32_to_f32d, conv_s32d_to_f32 }, - { offsetof(struct spa_type_audio_format, S24), - offsetof(struct spa_type_audio_format, F32), - conv_s24_to_f32, conv_s24_to_f32d, conv_s24d_to_f32 }, - { offsetof(struct spa_type_audio_format, S24_32), - offsetof(struct spa_type_audio_format, F32), - conv_s24_32_to_f32, conv_s24_32_to_f32d, conv_s24_32d_to_f32 }, - - /* from f32 */ - { offsetof(struct spa_type_audio_format, F32), - offsetof(struct spa_type_audio_format, U8), - conv_f32_to_u8, conv_f32_to_u8d, conv_f32d_to_u8 }, - { offsetof(struct spa_type_audio_format, F32), - offsetof(struct spa_type_audio_format, S16), - conv_f32_to_s16, conv_f32_to_s16d, conv_f32d_to_s16 }, - { offsetof(struct spa_type_audio_format, F32), - offsetof(struct spa_type_audio_format, S32), - conv_f32_to_s32, conv_f32_to_s32d, conv_f32d_to_s32 }, - { offsetof(struct spa_type_audio_format, F32), - offsetof(struct spa_type_audio_format, S24), - conv_f32_to_s24, conv_f32_to_s24d, conv_f32d_to_s24 }, - { offsetof(struct spa_type_audio_format, F32), - offsetof(struct spa_type_audio_format, S24_32), - conv_f32_to_s24_32, conv_f32_to_s24_32d, conv_f32d_to_s24_32 }, - - /* u8 */ - { offsetof(struct spa_type_audio_format, U8), - offsetof(struct spa_type_audio_format, U8), - conv_copy, deinterleave_8, interleave_8 }, - - /* s16 */ - { offsetof(struct spa_type_audio_format, S16), - offsetof(struct spa_type_audio_format, S16), - conv_copy, deinterleave_16, interleave_16 }, - - /* s32 */ - { offsetof(struct spa_type_audio_format, S32), - offsetof(struct spa_type_audio_format, S32), - conv_copy, deinterleave_32, interleave_32 }, - - /* s24 */ - { offsetof(struct spa_type_audio_format, S24), - offsetof(struct spa_type_audio_format, S24), - conv_copy, deinterleave_24, interleave_24 }, - - /* s24_32 */ - { offsetof(struct spa_type_audio_format, S24_32), - offsetof(struct spa_type_audio_format, S24_32), - conv_copy, deinterleave_32, interleave_32 }, -}; - struct impl { struct spa_handle handle; struct spa_node node; @@ -227,19 +153,6 @@ struct impl { #define GET_OUT_PORT(this,id) (&this->out_port) #define GET_PORT(this,d,id) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,id) : GET_OUT_PORT(this,id)) -static const struct conv_info *find_conv_info(struct impl *this, uint32_t src_fmt, uint32_t dst_fmt) -{ - struct type *t = &this->type; - int i; - - for (i = 0; i < SPA_N_ELEMENTS(conv_table); i++) { - if (*SPA_MEMBER(&t->audio_format, conv_table[i].src_fmt, uint32_t) == src_fmt && - *SPA_MEMBER(&t->audio_format, conv_table[i].dst_fmt, uint32_t) == dst_fmt) - return &conv_table[i]; - } - return NULL; -} - static void convert_generic (void *data, int n_dst, void *dst[n_dst], int n_src, const void *src[n_src], int n_bytes) { @@ -285,7 +198,7 @@ static int setup_convert(struct impl *this) return -EINVAL; /* find fast path */ - this->conv[0] = find_conv_info(this, src_fmt, dst_fmt); + this->conv[0] = find_conv_info(&t->audio_format, src_fmt, dst_fmt); if (this->conv[0] != NULL) { if (inport->format.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) { if (outport->format.info.raw.layout == SPA_AUDIO_LAYOUT_INTERLEAVED) @@ -303,8 +216,8 @@ static int setup_convert(struct impl *this) } /* go through intermediate format */ - this->conv[0] = find_conv_info(this, src_fmt, t->audio_format.F32); - this->conv[1] = find_conv_info(this, t->audio_format.F32, dst_fmt); + this->conv[0] = find_conv_info(&t->audio_format, src_fmt, t->audio_format.F32); + this->conv[1] = find_conv_info(&t->audio_format, t->audio_format.F32, dst_fmt); if (this->conv[0] == NULL || this->conv[1] == NULL) return -ENOTSUP; @@ -454,7 +367,7 @@ static int port_enum_formats(struct spa_node *node, t->param.idEnumFormat, t->format, "I", t->media_type.audio, "I", t->media_subtype.raw, - ":", t->format_audio.format, "Ieu", t->audio_format.S16, + ":", t->format_audio.format, "Ieu", other->format.info.raw.format, SPA_POD_PROP_ENUM(11, t->audio_format.U8, t->audio_format.S16, t->audio_format.S16_OE, @@ -466,7 +379,7 @@ static int port_enum_formats(struct spa_node *node, t->audio_format.S24_OE, t->audio_format.S24_32, t->audio_format.S24_32_OE), - ":", t->format_audio.layout, "ieu", SPA_AUDIO_LAYOUT_INTERLEAVED, + ":", t->format_audio.layout, "ieu", other->format.info.raw.layout, SPA_POD_PROP_ENUM(2, SPA_AUDIO_LAYOUT_INTERLEAVED, SPA_AUDIO_LAYOUT_NON_INTERLEAVED), ":", t->format_audio.rate, "i", other->format.info.raw.rate, @@ -540,7 +453,8 @@ impl_node_port_enum_params(struct spa_node *node, { struct impl *this; struct type *t; - struct port *port; + struct port *port, *other; + struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; @@ -556,6 +470,7 @@ impl_node_port_enum_params(struct spa_node *node, spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); + other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); next: spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -587,15 +502,28 @@ impl_node_port_enum_params(struct spa_node *node, if (*index > 0) return 0; - param = spa_pod_builder_object(&b, - id, t->param_buffers.Buffers, - ":", t->param_buffers.buffers, "iru", 1, - SPA_POD_PROP_MIN_MAX(1, MAX_BUFFERS), - ":", t->param_buffers.blocks, "i", port->blocks, - ":", t->param_buffers.size, "iru", 1024 * port->stride, - SPA_POD_PROP_MIN_MAX(16 * port->stride, INT32_MAX / port->stride), - ":", t->param_buffers.stride, "i", port->stride, - ":", t->param_buffers.align, "i", 16); + if (other->n_buffers > 0) { + param = spa_pod_builder_object(&b, + id, t->param_buffers.Buffers, + ":", t->param_buffers.buffers, "iru", other->n_buffers, + SPA_POD_PROP_MIN_MAX(1, MAX_BUFFERS), + ":", t->param_buffers.blocks, "i", port->blocks, + ":", t->param_buffers.size, "i", (other->size / other->stride) * + port->stride, + ":", t->param_buffers.stride, "i", port->stride, + ":", t->param_buffers.align, "i", 16); + } + else { + param = spa_pod_builder_object(&b, + id, t->param_buffers.Buffers, + ":", t->param_buffers.buffers, "iru", 1, + SPA_POD_PROP_MIN_MAX(1, MAX_BUFFERS), + ":", t->param_buffers.blocks, "i", port->blocks, + ":", t->param_buffers.size, "iru", 1024 * port->stride, + SPA_POD_PROP_MIN_MAX(16 * port->stride, INT32_MAX / port->stride), + ":", t->param_buffers.stride, "i", port->stride, + ":", t->param_buffers.align, "i", 16); + } } else if (id == t->param.idMeta) { if (!port->have_format) @@ -746,7 +674,7 @@ impl_node_port_use_buffers(struct spa_node *node, { struct impl *this; struct port *port; - uint32_t i; + uint32_t i, size = SPA_ID_INVALID; struct type *t; spa_return_val_if_fail(node != NULL, -EINVAL); @@ -773,6 +701,12 @@ impl_node_port_use_buffers(struct spa_node *node, b->outbuf = buffers[i]; b->h = spa_buffer_find_meta(buffers[i], t->meta.Header); + if (size == SPA_ID_INVALID) + size = d[0].maxsize; + else + if (size != d[0].maxsize) + return -EINVAL; + if (!((d[0].type == t->data.MemPtr || d[0].type == t->data.MemFd || d[0].type == t->data.DmaBuf) && d[0].data != NULL)) { @@ -786,6 +720,7 @@ impl_node_port_use_buffers(struct spa_node *node, SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); } port->n_buffers = n_buffers; + port->size = size; return 0; } @@ -937,7 +872,8 @@ static int impl_node_process(struct spa_node *node) this->convert(this, n_dst_datas, dst_datas, n_src_datas, src_datas, n_bytes); - dbuf->outbuf->datas[0].chunk->size = n_bytes / 2; + for (i = 0; i < n_dst_datas; i++) + dbuf->outbuf->datas[i].chunk->size = (n_bytes / inport->stride) * outport->stride; } outio->status = SPA_STATUS_HAVE_BUFFER; @@ -1062,7 +998,7 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, return 1; } -const struct spa_handle_factory spa_audioconvert_factory = { +const struct spa_handle_factory spa_fmtconvert_factory = { SPA_VERSION_HANDLE_FACTORY, NAME, NULL, diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index 67efa12a6..5849d5f87 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -1,4 +1,4 @@ -audioconvert_sources = ['fmtconvert.c', 'plugin.c'] +audioconvert_sources = ['fmtconvert.c', 'audioconvert.c', 'channelmix.c', 'plugin.c'] audioconvertlib = shared_library('spa-audioconvert', audioconvert_sources, diff --git a/spa/plugins/audioconvert/plugin.c b/spa/plugins/audioconvert/plugin.c index 504d9e459..9e59f33af 100644 --- a/spa/plugins/audioconvert/plugin.c +++ b/spa/plugins/audioconvert/plugin.c @@ -22,6 +22,8 @@ #include extern const struct spa_handle_factory spa_audioconvert_factory; +extern const struct spa_handle_factory spa_fmtconvert_factory; +extern const struct spa_handle_factory spa_channelmix_factory; int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) @@ -33,6 +35,12 @@ spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *ind case 0: *factory = &spa_audioconvert_factory; break; + case 1: + *factory = &spa_fmtconvert_factory; + break; + case 2: + *factory = &spa_channelmix_factory; + break; default: return 0; } diff --git a/spa/tests/meson.build b/spa/tests/meson.build index a1bdb6d67..fe9924f0f 100644 --- a/spa/tests/meson.build +++ b/spa/tests/meson.build @@ -8,6 +8,11 @@ executable('test-convert', 'test-convert.c', dependencies : [dl_lib, pthread_lib, mathlib], link_with : spalib, install : false) +executable('test-convert2', 'test-convert2.c', + include_directories : [spa_inc, spa_libinc ], + dependencies : [dl_lib, pthread_lib, mathlib], + link_with : spalib, + install : false) executable('test-bluez5', 'test-bluez5.c', include_directories : [spa_inc, spa_libinc ], dependencies : [dl_lib, pthread_lib, mathlib, dbus_dep], diff --git a/spa/tests/test-convert2.c b/spa/tests/test-convert2.c new file mode 100644 index 000000000..49614a221 --- /dev/null +++ b/spa/tests/test-convert2.c @@ -0,0 +1,514 @@ +/* Spa + * Copyright (C) 2017 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static SPA_TYPE_MAP_IMPL(default_map, 4096); +static SPA_LOG_IMPL(default_log); + +struct type { + uint32_t node; + uint32_t props; + uint32_t format; + struct spa_type_io io; + struct spa_type_param param; + struct spa_type_param_buffers param_buffers; + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_audio format_audio; + struct spa_type_audio_format audio_format; + struct spa_type_event_node event_node; + struct spa_type_command_node command_node; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + type->node = spa_type_map_get_id(map, SPA_TYPE__Node); + type->props = spa_type_map_get_id(map, SPA_TYPE__Props); + type->format = spa_type_map_get_id(map, SPA_TYPE__Format); + spa_type_io_map(map, &type->io); + spa_type_param_map(map, &type->param); + spa_type_param_buffers_map(map, &type->param_buffers); + spa_type_meta_map(map, &type->meta); + spa_type_data_map(map, &type->data); + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_audio_map(map, &type->format_audio); + spa_type_audio_format_map(map, &type->audio_format); + spa_type_event_node_map(map, &type->event_node); + spa_type_command_node_map(map, &type->command_node); +} + +struct buffer { + struct spa_buffer buffer; + struct spa_meta metas[1]; + struct spa_meta_header header; + struct spa_data datas[8]; + struct spa_chunk chunks[8]; +}; + +struct node { + struct spa_node *node; +}; + +struct link { + struct node *out_node; + uint32_t out_port; + struct node *in_node; + uint32_t in_port; + struct spa_io_buffers io; + uint32_t n_buffers; + struct spa_buffer **buffers; +}; + +struct data { + struct spa_type_map *map; + struct spa_log *log; + struct type type; + + struct spa_support support[4]; + uint32_t n_support; + + struct node nodes[4]; + struct link links[4]; +}; + +static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) +{ + struct type *t = &data->type; + struct spa_handle *handle; + int res; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + uint32_t i; + + if ((hnd = dlopen(lib, RTLD_NOW)) == NULL) { + printf("can't load %s: %s\n", lib, dlerror()); + return -errno; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + printf("can't find enum function\n"); + return -errno; + } + + for (i = 0;;) { + const struct spa_handle_factory *factory; + void *iface; + + if ((res = enum_func(&factory, &i)) <= 0) { + if (res != 0) + printf("can't enumerate factories: %s\n", spa_strerror(res)); + break; + } + if (strcmp(factory->name, name)) + continue; + + handle = calloc(1, factory->size); + if ((res = + spa_handle_factory_init(factory, handle, NULL, data->support, + data->n_support)) < 0) { + printf("can't make factory instance: %d\n", res); + return res; + } + if ((res = spa_handle_get_interface(handle, t->node, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + *node = iface; + return 0; + } + return -EBADF; +} + +static int make_nodes(struct data *data, const char *device) +{ + int res; + + if ((res = make_node(data, &data->nodes[0].node, + "build/spa/plugins/audioconvert/libspa-audioconvert.so", + "fmtconvert")) < 0) { + printf("can't create fmtconvert: %d\n", res); + return res; + } + if ((res = make_node(data, &data->nodes[1].node, + "build/spa/plugins/audioconvert/libspa-audioconvert.so", + "channelmix")) < 0) { + printf("can't create channelmix: %d\n", res); + return res; + } + if ((res = make_node(data, &data->nodes[2].node, + "build/spa/plugins/audioconvert/libspa-audioconvert.so", + "fmtconvert")) < 0) { + printf("can't create fmtconvert: %d\n", res); + return res; + } + + return res; +} + +static int make_link(struct data *data, struct link *link, + struct node *out_node, uint32_t out_port, + struct node *in_node, uint32_t in_port) +{ + struct type *t = &data->type; + + link->out_node = out_node; + link->out_port = out_port; + link->in_node = in_node; + link->in_port = in_port; + link->io = SPA_IO_BUFFERS_INIT; + link->n_buffers = 0; + link->buffers = NULL; + + if (out_node != NULL) { + spa_node_port_set_io(out_node->node, + SPA_DIRECTION_OUTPUT, out_port, + t->io.Buffers, + &link->io, sizeof(link->io)); + } + if (in_node != NULL) { + spa_node_port_set_io(in_node->node, + SPA_DIRECTION_INPUT, in_port, + t->io.Buffers, + &link->io, sizeof(link->io)); + } + return 0; +} + +static int link_nodes(struct data *data) +{ + make_link(data, &data->links[0], NULL, 0, &data->nodes[0], 0); + make_link(data, &data->links[1], &data->nodes[0], 0, &data->nodes[1], 0); + make_link(data, &data->links[2], &data->nodes[1], 0, &data->nodes[2], 0); + make_link(data, &data->links[3], &data->nodes[2], 0, NULL, 0); + return 0; +} + +static int negotiate_link_format(struct data *data, struct link *link, struct spa_pod *filter) +{ + struct type *t = &data->type; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + uint32_t state; + struct spa_pod *format; + int res; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + if (link->out_node != NULL) { + state = 0; + if ((res = spa_node_port_enum_params(link->out_node->node, + SPA_DIRECTION_OUTPUT, link->out_port, + t->param.idEnumFormat, &state, + filter, &format, &b)) <= 0) + return -ENOTSUP; + + filter = format; + } + if (link->in_node != NULL) { + state = 0; + if ((res = spa_node_port_enum_params(link->in_node->node, + SPA_DIRECTION_INPUT, link->in_port, + t->param.idEnumFormat, &state, + filter, &format, &b)) <= 0) + return -ENOTSUP; + + filter = format; + } + + spa_pod_fixate(filter); + spa_debug_pod(filter, SPA_DEBUG_FLAG_FORMAT); + + if (link->out_node != NULL) { + if ((res = spa_node_port_set_param(link->out_node->node, + SPA_DIRECTION_OUTPUT, link->out_port, + t->param.idFormat, 0, + filter)) < 0) + return res; + } + if (link->in_node != NULL) { + if ((res = spa_node_port_set_param(link->in_node->node, + SPA_DIRECTION_INPUT, link->in_port, + t->param.idFormat, 0, + filter)) < 0) + return res; + } + return 0; +} + +static int negotiate_formats(struct data *data) +{ + int res; + struct spa_pod *format; + struct spa_pod_builder b = { 0 }; + struct type *t = &data->type; + uint8_t buffer[4096]; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + format = spa_pod_builder_object(&b, + 0, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "I", t->audio_format.S16, + ":", t->format_audio.layout, "i", SPA_AUDIO_LAYOUT_INTERLEAVED, + ":", t->format_audio.rate, "i", 44100, + ":", t->format_audio.channels, "i", 2); + + if ((res = negotiate_link_format(data, &data->links[0], format)) < 0) + return res; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + format = spa_pod_builder_object(&b, + 0, t->format, + "I", t->media_type.audio, + "I", t->media_subtype.raw, + ":", t->format_audio.format, "I", t->audio_format.F32, + ":", t->format_audio.layout, "i", SPA_AUDIO_LAYOUT_NON_INTERLEAVED, + ":", t->format_audio.rate, "i", 44100, + ":", t->format_audio.channels, "i", 1); + + if ((res = negotiate_link_format(data, &data->links[3], format)) < 0) + return res; + + if ((res = negotiate_link_format(data, &data->links[1], NULL)) < 0) + return res; + + if ((res = negotiate_link_format(data, &data->links[2], NULL)) < 0) + return res; + + return 0; +} + +static int negotiate_link_buffers(struct data *data, struct link *link) +{ + struct type *t = &data->type; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t state; + struct spa_pod *param = NULL; + int res, i; + int32_t size, stride, buffers, blocks, align; + uint32_t *sizes, *aligns; + + if (link->out_node != NULL) { + state = 0; + if ((res = spa_node_port_enum_params(link->out_node->node, + SPA_DIRECTION_OUTPUT, link->out_port, + t->param.idBuffers, &state, + param, ¶m, &b)) <= 0) + return -ENOTSUP; + } + if (link->in_node != NULL) { + state = 0; + if ((res = spa_node_port_enum_params(link->in_node->node, + SPA_DIRECTION_INPUT, link->in_port, + t->param.idBuffers, &state, + param, ¶m, &b)) <= 0) + return -ENOTSUP; + } + + spa_pod_fixate(param); + spa_debug_pod(param, 0); + + if (spa_pod_object_parse(param, + ":", t->param_buffers.buffers, "i", &buffers, + ":", t->param_buffers.blocks, "i", &blocks, + ":", t->param_buffers.size, "i", &size, + ":", t->param_buffers.stride, "i", &stride, + ":", t->param_buffers.align, "i", &align, + NULL) < 0) + return -EINVAL; + + sizes = alloca(sizeof(uint32_t) * blocks); + aligns = alloca(sizeof(uint32_t) * blocks); + for (i = 0; i < blocks; i++) { + sizes[i] = size; + aligns[i] = align; + } + + link->buffers = spa_buffer_alloc_array(buffers, t->data.MemPtr, + 0, NULL, blocks, sizes, aligns); + if (link->buffers == NULL) + return -ENOMEM; + + link->n_buffers = buffers; + + if (link->out_node != NULL) { + if ((res = spa_node_port_use_buffers(link->out_node->node, + SPA_DIRECTION_OUTPUT, link->out_port, + link->buffers, link->n_buffers)) < 0) + return res; + } + if (link->in_node != NULL) { + if ((res = spa_node_port_use_buffers(link->in_node->node, + SPA_DIRECTION_INPUT, link->in_port, + link->buffers, link->n_buffers)) < 0) + return res; + } + + return 0; +} + +static int negotiate_buffers(struct data *data) +{ + int res; + + if ((res = negotiate_link_buffers(data, &data->links[0])) < 0) + return res; + if ((res = negotiate_link_buffers(data, &data->links[1])) < 0) + return res; + if ((res = negotiate_link_buffers(data, &data->links[2])) < 0) + return res; + if ((res = negotiate_link_buffers(data, &data->links[3])) < 0) + return res; + + return 0; +} + +static void fill_buffer(struct data *data, struct spa_buffer *buffers[], int id) +{ + int i; + struct spa_buffer *b = buffers[id]; + + for (i = 0; i < b->datas[0].maxsize; i++) { + *SPA_MEMBER(b->datas[0].data, i, uint8_t) = i; + } + b->datas[0].chunk->size = b->datas[0].maxsize; +} + +static void run_convert(struct data *data) +{ + struct type *t = &data->type; + struct spa_buffer *b; + int res, i; + + { + struct spa_command cmd = SPA_COMMAND_INIT(t->command_node.Start); + + if ((res = spa_node_send_command(data->nodes[0].node, &cmd)) < 0) + printf("got command error %d\n", res); + if ((res = spa_node_send_command(data->nodes[1].node, &cmd)) < 0) + printf("got command error %d\n", res); + if ((res = spa_node_send_command(data->nodes[2].node, &cmd)) < 0) + printf("got command error %d\n", res); + } + + fill_buffer(data, data->links[0].buffers, 0); + + data->links[0].io.status = SPA_STATUS_HAVE_BUFFER; + data->links[0].io.buffer_id = 0; + + data->links[1].io.status = SPA_STATUS_NEED_BUFFER; + data->links[1].io.buffer_id = SPA_ID_INVALID; + data->links[2].io.status = SPA_STATUS_NEED_BUFFER; + data->links[2].io.buffer_id = SPA_ID_INVALID; + data->links[3].io.status = SPA_STATUS_NEED_BUFFER; + data->links[3].io.buffer_id = SPA_ID_INVALID; + + b = data->links[0].buffers[0]; + for (i = 0; i < b->n_datas; i++) + spa_debug_dump_mem(b->datas[i].data, b->datas[i].maxsize); + + res = spa_node_process(data->nodes[0].node); + printf("called process %d\n", res); + res = spa_node_process(data->nodes[1].node); + printf("called process %d\n", res); + res = spa_node_process(data->nodes[2].node); + printf("called process %d\n", res); + + b = data->links[3].buffers[0]; + for (i = 0; i < b->n_datas; i++) + spa_debug_dump_mem(b->datas[i].data, b->datas[i].maxsize); + + + { + struct spa_command cmd = SPA_COMMAND_INIT(t->command_node.Pause); + + if ((res = spa_node_send_command(data->nodes[0].node, &cmd)) < 0) + printf("got command error %d\n", res); + if ((res = spa_node_send_command(data->nodes[1].node, &cmd)) < 0) + printf("got command error %d\n", res); + if ((res = spa_node_send_command(data->nodes[2].node, &cmd)) < 0) + printf("got command error %d\n", res); + } +} + +int main(int argc, char *argv[]) +{ + struct data data = { NULL }; + int res; + const char *str; + + data.map = &default_map.map; + data.log = &default_log.log; + + if ((str = getenv("SPA_DEBUG"))) + data.log->level = atoi(str); + + data.support[0].type = SPA_TYPE__TypeMap; + data.support[0].data = data.map; + data.support[1].type = SPA_TYPE__Log; + data.support[1].data = data.log; + data.n_support = 2; + + init_type(&data.type, data.map); + spa_debug_set_type_map(data.map); + + if ((res = make_nodes(&data, argc > 1 ? argv[1] : NULL)) < 0) { + printf("can't make nodes: %d\n", res); + return -1; + } + if ((res = link_nodes(&data)) < 0) { + printf("can't link nodes: %d\n", res); + return -1; + } + if ((res = negotiate_formats(&data)) < 0) { + printf("can't negotiate nodes: %d\n", res); + return -1; + } + if ((res = negotiate_buffers(&data)) < 0) { + printf("can't negotiate buffers: %d\n", res); + return -1; + } + + run_convert(&data); +} diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 69969babc..58112821b 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -119,9 +120,7 @@ struct stream { struct data data; bool use_converter; - struct spa_node *fmtconvert; - struct spa_node *resample; - struct spa_node *remix; + struct spa_node *convert; struct spa_io_buffers conv_io; }; @@ -182,8 +181,11 @@ static int configure_converter(struct stream *impl) } } + if (impl->convert == NULL) + return -ENOTSUP; + /* configure the converter */ - if ((res = spa_node_port_set_param(impl->fmtconvert, + if ((res = spa_node_port_set_param(impl->convert, impl->direction, 0, t->param.idFormat, 0, impl->format)) < 0) @@ -195,7 +197,7 @@ static int configure_converter(struct stream *impl) param = impl->init_params[i]; if (spa_pod_is_object_type(param, t->spa_format)) { - if ((res = spa_node_port_set_param(impl->fmtconvert, + if ((res = spa_node_port_set_param(impl->convert, SPA_DIRECTION_REVERSE(impl->direction), 0, t->param.idFormat, SPA_NODE_PARAM_FLAG_FIXATE, @@ -211,12 +213,12 @@ static int configure_converter(struct stream *impl) if (!impl->use_converter) return -ENOTSUP; - res = spa_node_port_set_io(impl->fmtconvert, + res = spa_node_port_set_io(impl->convert, impl->direction, 0, t->io.Buffers, impl->io, sizeof(struct spa_io_buffers)); impl->io = &impl->conv_io; - res = spa_node_port_set_io(impl->fmtconvert, + res = spa_node_port_set_io(impl->convert, SPA_DIRECTION_REVERSE(impl->direction), 0, t->io.Buffers, impl->io, sizeof(struct spa_io_buffers)); @@ -367,9 +369,9 @@ static int impl_port_set_io(struct spa_node *node, enum spa_direction direction, if (impl->use_converter) { impl->io = &impl->conv_io; - res = spa_node_port_set_io(impl->fmtconvert, + res = spa_node_port_set_io(impl->convert, direction, 0, id, data, size); - res = spa_node_port_set_io(impl->fmtconvert, + res = spa_node_port_set_io(impl->convert, SPA_DIRECTION_REVERSE(direction), 0, id, impl->io, size); } @@ -564,81 +566,6 @@ static void clear_buffers(struct pw_stream *stream) spa_ringbuffer_init(&impl->queued.ring); } -static struct spa_buffer ** alloc_buffers(struct stream *impl, - uint32_t n_buffers, - uint32_t n_metas, - uint32_t meta_sizes[n_metas], - uint32_t n_datas, - uint32_t data_sizes[n_datas]) -{ - struct spa_buffer **buffers; - size_t skel_size, data_size = 0; - struct spa_buffer *bp, *b; - void *dp, *d, *ddp; - struct spa_chunk *cdp; - int i, j; - struct pw_type *t = impl->t; - - skel_size = sizeof(struct spa_buffer *); - skel_size += sizeof(struct spa_buffer); - skel_size += n_metas * sizeof(struct spa_meta); - for (i = 0; i < n_metas; i++) - data_size += meta_sizes[i]; - skel_size += n_datas * sizeof(struct spa_data); - data_size += n_datas * sizeof(struct spa_chunk); - for (i = 0; i < n_datas; i++) - data_size += data_sizes[i]; - - buffers = malloc((skel_size + data_size) * n_buffers); - - bp = SPA_MEMBER(buffers, n_buffers * sizeof(struct spa_buffer *), struct spa_buffer); - dp = SPA_MEMBER(bp, n_buffers * skel_size, void); - - for (i = 0; i < n_buffers; i++) { - b = SPA_MEMBER(bp, skel_size * i, struct spa_buffer); - d = SPA_MEMBER(dp, data_size * i, void); - - buffers[i] = b; - b->id = i; - b->n_metas = n_metas; - b->metas = SPA_MEMBER(b, sizeof(struct spa_buffer), struct spa_meta); - for (j = 0; j < n_metas; j++) { - struct spa_meta *m = &b->metas[j]; - m->size = meta_sizes[j]; - m->data = d; - d += m->size; - } - b->n_datas = n_datas; - b->datas = SPA_MEMBER(b->metas, n_metas * sizeof(struct spa_meta), struct spa_data); - - cdp = d; - ddp = SPA_MEMBER(cdp, n_datas * sizeof(struct spa_chunk), void); - - for (j = 0; j < n_datas; j++) { - struct spa_data *d = &b->datas[j]; - - d->chunk = &cdp[j]; - if (data_sizes[j] > 0) { - d->type = t->data.MemPtr; - d->flags = 0; - d->fd = -1; - d->mapoffset = 0; - d->maxsize = data_sizes[j]; - d->data = ddp; - d->chunk->offset = 0; - d->chunk->size = data_sizes[j]; - d->chunk->stride = 0; - ddp += data_sizes[j]; - } else { - /* needs to be allocated by a node */ - d->type = SPA_ID_INVALID; - d->data = NULL; - } - } - } - return buffers; -} - static int impl_port_use_buffers(struct spa_node *node, enum spa_direction direction, uint32_t port_id, struct spa_buffer **buffers, uint32_t n_buffers) { @@ -687,21 +614,27 @@ static int impl_port_use_buffers(struct spa_node *node, enum spa_direction direc impl->n_buffers = n_buffers; if (impl->use_converter) { - uint32_t data_sizes[1]; + uint32_t data_sizes[1], data_aligns[1]; - spa_node_port_use_buffers(impl->fmtconvert, + if ((res = spa_node_port_use_buffers(impl->convert, impl->direction, 0, buffers, - n_buffers); + n_buffers)) < 0) + return res; data_sizes[0] = size * 2; - buffers = alloc_buffers(impl, n_buffers, 0, NULL, 1, data_sizes); + data_aligns[0] = 16; - spa_node_port_use_buffers(impl->fmtconvert, + buffers = spa_buffer_alloc_array(n_buffers, t->data.MemPtr, + 0, NULL, + 1, data_sizes, data_aligns); + if (buffers == NULL) + return -ENOMEM; + + if ((res = spa_node_port_use_buffers(impl->convert, SPA_DIRECTION_REVERSE(impl->direction), 0, - buffers, - n_buffers); - + buffers, n_buffers)) < 0) + return res; } for (i = 0; i < n_buffers; i++) { @@ -777,7 +710,7 @@ static int impl_node_process_output(struct spa_node *node) io->status = SPA_STATUS_HAVE_BUFFER; if (impl->use_converter) - res = spa_node_process(impl->fmtconvert); + res = spa_node_process(impl->convert); pw_log_trace("stream %p: pop %d %s", stream, b->id, spa_strerror(res)); } else { @@ -825,9 +758,6 @@ struct pw_stream * pw_stream_new(struct pw_remote *remote, const char *name, if (props == NULL) goto no_mem; - impl->fmtconvert = pw_load_spa_interface("audioconvert/libspa-audioconvert", - "fmtconvert", SPA_TYPE__Node, NULL, 0); - this->properties = props; this->remote = remote; @@ -993,16 +923,20 @@ set_init_params(struct pw_stream *stream, } impl->n_orig_params = n_init_params; - if (add_audio) { + if (add_audio && !SPA_FLAG_CHECK(impl->flags, PW_STREAM_FLAG_NO_CONVERT)) { uint32_t state = 0; int res; + if ((impl->convert = pw_load_spa_interface("audioconvert/libspa-audioconvert", + "audioconvert", SPA_TYPE__Node, NULL, 0)) == NULL) + goto done; + while (true) { uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 4096); struct spa_pod *param; - if ((res = spa_node_port_enum_params(impl->fmtconvert, + if ((res = spa_node_port_enum_params(impl->convert, impl->direction, 0, t->param.idEnumFormat, &state, NULL, ¶m, &b)) <= 0) @@ -1013,6 +947,7 @@ set_init_params(struct pw_stream *stream, impl->init_params[n_init_params++] = pw_spa_pod_copy(param); } } + done: impl->n_init_params = n_init_params; }