From 541553be1c99b80dfee2be8621d6551e53ea25a0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Nov 2017 16:36:29 +0100 Subject: [PATCH] control: add control objects Add control objects for all controllable properties on ports. Try to link compatible control properties in autolink. Allocate shared memory for the output property memory and configure the io area on the ports when the controls are linked. Send the shared memfd to clients when the io area is configured. Add port_set_io support in remote.c, mmap the control io area and set on the port. Add some param helpers Add volume control to export-source update the volume before sending each buffer. --- spa/include/spa/param/io.h | 4 + spa/plugins/alsa/alsa-sink.c | 2 + spa/plugins/alsa/alsa-source.c | 2 + spa/plugins/audiomixer/audiomixer.c | 12 +- src/examples/export-source.c | 31 ++- src/extensions/client-node.h | 25 ++- src/modules/module-autolink.c | 20 ++ src/modules/module-client-node/client-node.c | 36 +++- .../module-client-node/protocol-native.c | 57 +++++- src/modules/module-client-node/transport.c | 6 +- src/pipewire/control.c | 178 ++++++++++++++++++ src/pipewire/control.h | 76 ++++++++ src/pipewire/core.c | 2 + src/pipewire/meson.build | 2 + src/pipewire/node.c | 2 +- src/pipewire/port.c | 64 ++++++- src/pipewire/port.h | 7 + src/pipewire/private.h | 37 ++++ src/pipewire/remote.c | 48 ++++- 19 files changed, 577 insertions(+), 34 deletions(-) create mode 100644 src/pipewire/control.c create mode 100644 src/pipewire/control.h diff --git a/spa/include/spa/param/io.h b/spa/include/spa/param/io.h index 79ef56130..f33ecd6b9 100644 --- a/spa/include/spa/param/io.h +++ b/spa/include/spa/param/io.h @@ -32,6 +32,8 @@ extern "C" { #define SPA_TYPE_PARAM_IO__id SPA_TYPE_PARAM_IO_BASE "id" /** size of the io area for a port */ #define SPA_TYPE_PARAM_IO__size SPA_TYPE_PARAM_IO_BASE "size" +/** type of the io area for a port, this is an enum spa_pod_type */ +#define SPA_TYPE_PARAM_IO__type SPA_TYPE_PARAM_IO_BASE "type" /** enumerate buffer io areas */ #define SPA_TYPE_PARAM_ID_IO__Buffers SPA_TYPE_PARAM_ID_IO_BASE "Buffers" @@ -64,6 +66,7 @@ extern "C" { struct spa_type_param_io { uint32_t id; /**< id to configure the io area */ uint32_t size; /**< size of io area */ + uint32_t type; /**< type of io area */ uint32_t idBuffers; /**< id to enumerate buffer io */ uint32_t Buffers; /**< object type of buffer io area */ uint32_t idControl; /**< id to enumerate control io */ @@ -82,6 +85,7 @@ spa_type_param_io_map(struct spa_type_map *map, if (type->id == 0) { type->id = spa_type_map_get_id(map, SPA_TYPE_PARAM_IO__id); type->size = spa_type_map_get_id(map, SPA_TYPE_PARAM_IO__size); + type->type = spa_type_map_get_id(map, SPA_TYPE_PARAM_IO__type); type->idBuffers = spa_type_map_get_id(map, SPA_TYPE_PARAM_ID_IO__Buffers); type->Buffers = spa_type_map_get_id(map, SPA_TYPE_PARAM_IO__Buffers); type->idControl = spa_type_map_get_id(map, SPA_TYPE_PARAM_ID_IO__Control); diff --git a/spa/plugins/alsa/alsa-sink.c b/spa/plugins/alsa/alsa-sink.c index 4ada603b1..44fb8a485 100644 --- a/spa/plugins/alsa/alsa-sink.c +++ b/spa/plugins/alsa/alsa-sink.c @@ -703,6 +703,8 @@ impl_init(const struct spa_handle_factory *factory, this->stream = SND_PCM_STREAM_PLAYBACK; reset_props(&this->props); + this->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + spa_list_init(&this->ready); for (i = 0; info && i < info->n_items; i++) { diff --git a/spa/plugins/alsa/alsa-source.c b/spa/plugins/alsa/alsa-source.c index 9d683b171..a5395f021 100644 --- a/spa/plugins/alsa/alsa-source.c +++ b/spa/plugins/alsa/alsa-source.c @@ -793,6 +793,8 @@ impl_init(const struct spa_handle_factory *factory, this->stream = SND_PCM_STREAM_CAPTURE; reset_props(&this->props); + this->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + spa_list_init(&this->free); spa_list_init(&this->ready); diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index e93ae6614..9523180df 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -535,7 +535,7 @@ impl_node_port_enum_params(struct spa_node *node, ":", t->param_io.id, "I", t->io_prop_volume, ":", t->param_io.size, "i", sizeof(struct spa_pod_double), ":", t->param_io.propId, "I", t->prop_volume, - ":", t->param_io.propType, "dr", p->volume, 2, 0.0, 10.0); + ":", t->param_io.propType, "dru", p->volume, 2, 0.0, 10.0); break; case 1: param = spa_pod_builder_object(&b, @@ -747,15 +747,9 @@ impl_node_port_set_io(struct spa_node *node, else if (id == t->io.ControlRange) port->io_range = data; else if (id == t->io_prop_volume && direction == SPA_DIRECTION_INPUT) - if (SPA_POD_TYPE(data) == SPA_POD_TYPE_DOUBLE) - port->io_volume = &SPA_POD_VALUE(struct spa_pod_double, data); - else - return -EINVAL; + port->io_volume = &SPA_POD_VALUE(struct spa_pod_double, data); else if (id == t->io_prop_mute && direction == SPA_DIRECTION_INPUT) - if (SPA_POD_TYPE(data) == SPA_POD_TYPE_BOOL) - port->io_mute = &SPA_POD_VALUE(struct spa_pod_bool, data); - else - return -EINVAL; + port->io_mute = &SPA_POD_VALUE(struct spa_pod_bool, data); else return -ENOENT; diff --git a/src/examples/export-source.c b/src/examples/export-source.c index 0d580525d..8eebcbd2f 100644 --- a/src/examples/export-source.c +++ b/src/examples/export-source.c @@ -101,7 +101,7 @@ struct data { void *callbacks_data; struct spa_io_buffers *io; - double *io_volume; + struct spa_pod_double *ctrl_volume; uint8_t buffer[1024]; @@ -112,8 +112,20 @@ struct data { struct spa_list empty; double accumulator; + double volume_accum; }; +static void update_volume(struct data *data) +{ + if (data->ctrl_volume == NULL) + return; + + data->ctrl_volume->value = (sin(data->volume_accum) / 2.0) + 0.5; + data->volume_accum += M_PI_M2 / 1000.0; + if (data->volume_accum >= M_PI_M2) + data->volume_accum -= M_PI_M2; +} + static int impl_send_command(struct spa_node *node, const struct spa_command *command) { return 0; @@ -158,10 +170,8 @@ static int impl_port_set_io(struct spa_node *node, enum spa_direction direction, if (id == d->t->io.Buffers) d->io = data; else if (id == d->type.io_prop_volume) { - if (SPA_POD_TYPE(data) == SPA_POD_TYPE_ID) - d->io_volume = &SPA_POD_VALUE(struct spa_pod_double, data); - else - return -EINVAL; + d->ctrl_volume = data; + *d->ctrl_volume = SPA_POD_DOUBLE_INIT(1.0); } else return -ENOENT; @@ -315,7 +325,7 @@ static int impl_port_enum_params(struct spa_node *node, ":", t->param_io.id, "I", d->type.io_prop_volume, ":", t->param_io.size, "i", sizeof(struct spa_pod_double), ":", t->param_io.propId, "I", d->type.prop_volume, - ":", t->param_io.propType, "dr", p->volume, 2, 0.0, 10.0); + ":", t->param_io.propType, "dru", p->volume, 2, 0.0, 10.0); break; default: return 0; @@ -406,7 +416,7 @@ static int impl_port_use_buffers(struct spa_node *node, enum spa_direction direc static inline void reuse_buffer(struct data *d, uint32_t id) { - pw_log_trace("sine-source %p: recycle buffer %d", d, id); + pw_log_trace("export-source %p: recycle buffer %d", d, id); spa_list_append(&d->empty, &d->buffers[id].link); } @@ -433,7 +443,7 @@ static int impl_node_process_output(struct spa_node *node) io->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&d->empty)) { - pw_log_error("sine-source %p: out of buffers", d); + pw_log_error("export-source %p: out of buffers", d); return -EPIPE; } b = spa_list_first(&d->empty, struct buffer, link); @@ -474,6 +484,8 @@ static int impl_node_process_output(struct spa_node *node) io->buffer_id = b->buffer->id; io->status = SPA_STATUS_HAVE_BUFFER; + update_volume(d); + return SPA_STATUS_HAVE_BUFFER; } @@ -500,7 +512,7 @@ static void make_node(struct data *data) if (data->path) pw_properties_set(props, PW_NODE_PROP_TARGET_NODE, data->path); - data->node = pw_node_new(data->core, "sine-source", props, 0); + data->node = pw_node_new(data->core, "export-source", props, 0); data->impl_node = impl_node; pw_node_set_implementation(data->node, &data->impl_node); @@ -551,7 +563,6 @@ int main(int argc, char *argv[]) spa_list_init(&data.empty); init_type(&data.type, data.t->map); reset_props(&data.props); - data.io_volume = &data.props.volume; spa_debug_set_type_map(data.t->map); pw_remote_add_listener(data.remote, &data.remote_listener, &remote_events, &data); diff --git a/src/extensions/client-node.h b/src/extensions/client-node.h index 9e35241c6..7b136ea6c 100644 --- a/src/extensions/client-node.h +++ b/src/extensions/client-node.h @@ -298,7 +298,8 @@ pw_client_node_proxy_destroy(struct pw_client_node_proxy *p) #define PW_CLIENT_NODE_PROXY_EVENT_PORT_ADD_MEM 7 #define PW_CLIENT_NODE_PROXY_EVENT_PORT_USE_BUFFERS 8 #define PW_CLIENT_NODE_PROXY_EVENT_PORT_COMMAND 9 -#define PW_CLIENT_NODE_PROXY_EVENT_NUM 10 +#define PW_CLIENT_NODE_PROXY_EVENT_PORT_SET_IO 10 +#define PW_CLIENT_NODE_PROXY_EVENT_NUM 11 /** \ref pw_client_node events */ struct pw_client_node_proxy_events { @@ -433,6 +434,26 @@ struct pw_client_node_proxy_events { enum spa_direction direction, uint32_t port_id, const struct spa_command *command); + + /** + * Configure the io area with \a id of \a port_id. + * + * \param seq a sequence number + * \param direction the direction of the port + * \param port_id the port id + * \param id the id of the io area to set + * \param mem_id the id of the memory to use + * \param offset offset of io area in memory + * \param size size of the io area + */ + void (*port_set_io) (void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + uint32_t mem_id, + uint32_t offset, + uint32_t size); }; static inline void @@ -464,6 +485,8 @@ pw_client_node_proxy_add_listener(struct pw_client_node_proxy *p, pw_resource_notify(r,struct pw_client_node_proxy_events,port_use_buffers,__VA_ARGS__) #define pw_client_node_resource_port_command(r,...) \ pw_resource_notify(r,struct pw_client_node_proxy_events,port_command,__VA_ARGS__) +#define pw_client_node_resource_port_set_io(r,...) \ + pw_resource_notify(r,struct pw_client_node_proxy_events,port_set_io,__VA_ARGS__) #ifdef __cplusplus } /* extern "C" */ diff --git a/src/modules/module-autolink.c b/src/modules/module-autolink.c index 38ba2959a..e5eb800f8 100644 --- a/src/modules/module-autolink.c +++ b/src/modules/module-autolink.c @@ -28,6 +28,7 @@ #include "pipewire/link.h" #include "pipewire/log.h" #include "pipewire/module.h" +#include "pipewire/control.h" #include "pipewire/private.h" struct impl { @@ -142,6 +143,23 @@ link_state_changed(void *data, enum pw_link_state old, enum pw_link_state state, } } +static void try_link_controls(struct impl *impl, struct pw_port *port, struct pw_port *target) +{ + struct pw_control *cin, *cout; + int res; + + spa_list_for_each(cout, &port->control_list[SPA_DIRECTION_OUTPUT], port_link) { + spa_list_for_each(cin, &target->control_list[SPA_DIRECTION_INPUT], port_link) { + pw_log_debug("controls %d <-> %d", cin->id, cout->id); + if (cin->id == cout->id) { + if ((res = pw_control_link(cout, cin)) < 0) + pw_log_error("failed to link controls: %s", spa_strerror(res)); + } + } + } + +} + static void link_destroy(void *data) { @@ -210,6 +228,8 @@ static void try_link_port(struct pw_node *node, struct pw_port *port, struct nod spa_list_append(&info->links, &ld->l); pw_link_register(link, NULL, pw_module_get_global(impl->module)); + try_link_controls(impl, port, target); + return; error: diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 41208dd30..4384b332c 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -113,7 +113,7 @@ struct proxy { uint32_t n_params; struct spa_pod **params; - uint8_t format_buffer[1024]; + uint32_t membase; uint32_t seq; }; @@ -337,7 +337,6 @@ do_update_port(struct proxy *this, if (spa_pod_is_object_id(port->params[i], t->param.idFormat)) port->have_format = true; } - } if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO && info) @@ -534,9 +533,27 @@ spa_proxy_node_port_set_io(struct spa_node *node, if (id == t->io.Buffers) port->io = data; - else - return -ENOENT; + else { + struct pw_memblock *mem; + uint32_t memid = this->membase++; + if ((mem = pw_memblock_find(data)) == NULL) + return -EINVAL; + + pw_client_node_resource_port_add_mem(this->resource, + direction, port_id, + memid, + t->data.MemFd, + mem->fd, mem->flags, + 0, mem->offset + mem->size); + + pw_client_node_resource_port_set_io(this->resource, + this->seq, + direction, port_id, + id, + memid, + mem->offset, mem->size); + } return 0; } @@ -582,7 +599,7 @@ spa_proxy_node_port_use_buffers(struct spa_node *node, if (this->resource == NULL) return 0; - n_mem = 0; + n_mem = this->membase; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; struct pw_memblock *m; @@ -1109,10 +1126,13 @@ static void node_initialized(void *data) impl->other_fds[1] = impl->fds[0]; spa_loop_add_source(impl->proxy.data_loop, &impl->proxy.data_source); - pw_log_debug("client-node %p: add data fd %d", node, impl->proxy.data_source.fd); + pw_log_debug("client-node %p: transport fd %d %d", node, impl->fds[0], impl->fds[1]); - pw_client_node_resource_transport(this->resource, pw_global_get_id(pw_node_get_global(node)), - impl->other_fds[0], impl->other_fds[1], impl->transport); + pw_client_node_resource_transport(this->resource, + pw_global_get_id(pw_node_get_global(node)), + impl->other_fds[0], + impl->other_fds[1], + impl->transport); } static void node_free(void *data) diff --git a/src/modules/module-client-node/protocol-native.c b/src/modules/module-client-node/protocol-native.c index 5013b68be..b83553f3e 100644 --- a/src/modules/module-client-node/protocol-native.c +++ b/src/modules/module-client-node/protocol-native.c @@ -408,6 +408,32 @@ static bool client_node_demarshal_port_command(void *object, void *data, size_t return true; } +static bool client_node_demarshal_port_set_io(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_parser prs; + uint32_t seq, direction, port_id, id, memid, off, sz; + + spa_pod_parser_init(&prs, data, size, 0); + if (spa_pod_parser_get(&prs, + "[" + "i", &seq, + "i", &direction, + "i", &port_id, + "I", &id, + "i", &memid, + "i", &off, + "i", &sz, NULL) < 0) + return false; + + pw_proxy_notify(proxy, struct pw_client_node_proxy_events, port_set_io, + seq, + direction, port_id, + id, memid, + off, sz); + return true; +} + static void client_node_marshal_transport(void *object, uint32_t node_id, int readfd, int writefd, struct pw_client_node_transport *transport) { @@ -629,6 +655,33 @@ client_node_marshal_port_command(void *object, pw_protocol_native_end_resource(resource, b); } +static void +client_node_marshal_port_set_io(void *object, + uint32_t seq, + uint32_t direction, + uint32_t port_id, + uint32_t id, + uint32_t memid, + uint32_t offset, + uint32_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_builder *b; + + b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_PROXY_EVENT_PORT_SET_IO); + + spa_pod_builder_struct(b, + "i", seq, + "i", direction, + "i", port_id, + "I", id, + "i", memid, + "i", offset, + "i", size); + + pw_protocol_native_end_resource(resource, b); +} + static bool client_node_demarshal_done(void *object, void *data, size_t size) { @@ -683,7 +736,7 @@ static bool client_node_demarshal_port_update(void *object, void *data, size_t s struct spa_pod_parser prs; uint32_t i, direction, port_id, change_mask, n_params; const struct spa_pod **params = NULL; - struct spa_port_info info, *infop = NULL; + struct spa_port_info info = { 0 }, *infop = NULL; struct spa_pod *ipod; spa_pod_parser_init(&prs, data, size, 0); @@ -799,6 +852,7 @@ static const struct pw_client_node_proxy_events pw_protocol_native_client_node_e &client_node_marshal_port_add_mem, &client_node_marshal_port_use_buffers, &client_node_marshal_port_command, + &client_node_marshal_port_set_io, }; static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_event_demarshal[] = { @@ -812,6 +866,7 @@ static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_ { &client_node_demarshal_port_add_mem, PW_PROTOCOL_NATIVE_REMAP }, { &client_node_demarshal_port_use_buffers, PW_PROTOCOL_NATIVE_REMAP }, { &client_node_demarshal_port_command, PW_PROTOCOL_NATIVE_REMAP }, + { &client_node_demarshal_port_set_io, PW_PROTOCOL_NATIVE_REMAP }, }; const struct pw_protocol_marshal pw_protocol_native_client_node_marshal = { diff --git a/src/modules/module-client-node/transport.c b/src/modules/module-client-node/transport.c index 21add9bdc..32cce5dbe 100644 --- a/src/modules/module-client-node/transport.c +++ b/src/modules/module-client-node/transport.c @@ -195,12 +195,15 @@ pw_client_node_transport_new(uint32_t max_input_ports, uint32_t max_output_ports if (impl == NULL) return NULL; + pw_log_debug("transport %p: new %d %d", impl, max_input_ports, max_output_ports); + trans = &impl->trans; impl->offset = 0; if (pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD | PW_MEMBLOCK_FLAG_MAP_READWRITE | - PW_MEMBLOCK_FLAG_SEAL, area_get_size(&area), + PW_MEMBLOCK_FLAG_SEAL, + area_get_size(&area), &impl->mem) < 0) return NULL; @@ -229,6 +232,7 @@ pw_client_node_transport_new_from_info(struct pw_client_node_transport_info *inf return NULL; trans = &impl->trans; + pw_log_debug("transport %p: new from info", impl); if ((res = pw_memblock_import(PW_MEMBLOCK_FLAG_MAP_READWRITE | PW_MEMBLOCK_FLAG_WITH_FD, diff --git a/src/pipewire/control.c b/src/pipewire/control.c new file mode 100644 index 000000000..45e7def4f --- /dev/null +++ b/src/pipewire/control.c @@ -0,0 +1,178 @@ +/* PipeWire + * 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 + +struct impl { + struct pw_control this; + + struct pw_memblock *mem; +}; + +struct pw_control * +pw_control_new(struct pw_core *core, + struct pw_port *port, + const struct spa_pod *param, + size_t user_data_size) +{ + struct impl *impl; + struct pw_control *this; + enum spa_direction direction; + struct pw_type *t = &core->type; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + goto exit; + + this = &impl->this; + + direction = spa_pod_is_object_id(param, t->param_io.idPropsOut) ? + SPA_DIRECTION_OUTPUT : SPA_DIRECTION_INPUT; + + if (spa_pod_object_parse(param, + ":", t->param_io.id, "I", &this->id, + ":", t->param_io.size, "i", &this->size) < 0) + goto exit_free; + + pw_log_debug("control %p: new %s %d", this, spa_type_map_get_type(t->map, this->id), direction); + + this->core = core; + this->port = port; + this->param = pw_spa_pod_copy(param); + this->direction = direction; + + if (user_data_size > 0) + this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); + + spa_hook_list_init(&this->listener_list); + + spa_list_append(&core->control_list[direction], &this->link); + if (port) { + spa_list_append(&port->control_list[direction], &this->port_link); + spa_hook_list_call(&port->listener_list, struct pw_port_events, control_added, this); + } + + return this; + + exit_free: + free(impl); + exit: + pw_log_error("control failed"); + return NULL; +} + +void pw_control_destroy(struct pw_control *control) +{ + struct impl *impl = SPA_CONTAINER_OF(control, struct impl, this); + + pw_log_debug("control %p: destroy", control); + + spa_hook_list_call(&control->listener_list, struct pw_control_events, destroy); + + spa_list_remove(&control->link); + + if (control->port) { + spa_list_remove(&control->port_link); + spa_hook_list_call(&control->port->listener_list, + struct pw_port_events, control_removed, control); + } + + pw_log_debug("control %p: free", control); + spa_hook_list_call(&control->listener_list, struct pw_control_events, free); + + if (control->direction == SPA_DIRECTION_OUTPUT) + if (impl->mem) + pw_memblock_free(impl->mem); + + free(control->param); + + free(control); +} + +struct pw_port *pw_control_get_port(struct pw_control *control) +{ + return control->port; +} + +void pw_control_add_listener(struct pw_control *control, + struct spa_hook *listener, + const struct pw_control_events *events, + void *data) +{ + spa_hook_list_append(&control->listener_list, listener, events, data); +} + +int pw_control_link(struct pw_control *control, struct pw_control *other) +{ + int res = 0; + struct impl *impl; + + if (control->direction == SPA_DIRECTION_INPUT) { + struct pw_control *tmp = control; + control = other; + other = tmp; + } + if (control->direction != SPA_DIRECTION_OUTPUT || + other->direction != SPA_DIRECTION_INPUT) + return -EINVAL; + + impl = SPA_CONTAINER_OF(control, struct impl, this); + + pw_log_debug("control %p: link to %p", control, other); + + if (impl->mem == NULL) { + if ((res = pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD | + PW_MEMBLOCK_FLAG_SEAL | + PW_MEMBLOCK_FLAG_MAP_READWRITE, + control->size, + &impl->mem)) < 0) + goto exit; + + if (control->port) { + struct pw_port *port = control->port; + if ((res = spa_node_port_set_io(port->node->node, + port->direction, port->port_id, + control->id, + impl->mem->ptr, control->size)) < 0) { + goto exit; + } + } + } + + if (other->port) { + struct pw_port *port = other->port; + if ((res = spa_node_port_set_io(port->node->node, + port->direction, port->port_id, + other->id, + impl->mem->ptr, control->size)) < 0) { + goto exit; + } + } + + exit: + return res; +} + +int pw_control_unlink(struct pw_control *control, struct pw_control *other) +{ + return -ENOTSUP; +} diff --git a/src/pipewire/control.h b/src/pipewire/control.h new file mode 100644 index 000000000..f3b5b13ba --- /dev/null +++ b/src/pipewire/control.h @@ -0,0 +1,76 @@ +/* PipeWire + * Copyright (C) 2015 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. + */ + +#ifndef __PIPEWIRE_CONTROL_H__ +#define __PIPEWIRE_CONTROL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PW_TYPE__Control "PipeWire:Object:Control" +#define PW_TYPE_CONTROL_BASE PW_TYPE__Control ":" + +#include + +/** \page page_control Control + * + * \section page_control_overview Overview + * + * A control can be used to control a port property. + */ +/** \class pw_control + * + * The control object + */ +struct pw_control; + +#include +#include +#include + +/** Port events, use \ref pw_control_add_listener */ +struct pw_control_events { +#define PW_VERSION_PORT_EVENTS 0 + uint32_t version; + + /** The control is destroyed */ + void (*destroy) (void *data); + + /** The control is freed */ + void (*free) (void *data); +}; + +/** Get the control parent port or NULL when not set */ +struct pw_port *pw_control_get_port(struct pw_control *control); + +/** Add an event listener on the control */ +void pw_control_add_listener(struct pw_control *control, + struct spa_hook *listener, + const struct pw_control_events *events, + void *data); + +int pw_control_link(struct pw_control *control, struct pw_control *other); +int pw_control_unlink(struct pw_control *control, struct pw_control *other); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_CONTROL_H__ */ diff --git a/src/pipewire/core.c b/src/pipewire/core.c index af3058f5d..4954e3322 100644 --- a/src/pipewire/core.c +++ b/src/pipewire/core.c @@ -440,6 +440,8 @@ struct pw_core *pw_core_new(struct pw_loop *main_loop, struct pw_properties *pro spa_list_init(&this->node_list); spa_list_init(&this->factory_list); spa_list_init(&this->link_list); + spa_list_init(&this->control_list[0]); + spa_list_init(&this->control_list[1]); spa_hook_list_init(&this->listener_list); if ((name = pw_properties_get(properties, PW_CORE_PROP_NAME)) == NULL) { diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build index 30d4ff152..9c480d1a3 100644 --- a/src/pipewire/meson.build +++ b/src/pipewire/meson.build @@ -2,6 +2,7 @@ pipewire_headers = [ 'array.h', 'client.h', 'command.h', + 'control.h', 'core.h', 'data-loop.h', 'global.h', @@ -34,6 +35,7 @@ pipewire_headers = [ pipewire_sources = [ 'client.c', 'command.c', + 'control.c', 'core.c', 'data-loop.c', 'global.c', diff --git a/src/pipewire/node.c b/src/pipewire/node.c index 99fa75f30..051e26c8e 100644 --- a/src/pipewire/node.c +++ b/src/pipewire/node.c @@ -23,6 +23,7 @@ #include #include +#include #include "pipewire/pipewire.h" #include "pipewire/interfaces.h" @@ -328,7 +329,6 @@ do_node_add(struct spa_loop *loop, return 0; } - void pw_node_register(struct pw_node *this, struct pw_client *owner, struct pw_global *parent) diff --git a/src/pipewire/port.c b/src/pipewire/port.c index d7b9d1d8c..212fe0db8 100644 --- a/src/pipewire/port.c +++ b/src/pipewire/port.c @@ -183,6 +183,8 @@ struct pw_port *pw_port_new(enum pw_direction direction, this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); spa_list_init(&this->links); + spa_list_init(&this->control_list[0]); + spa_list_init(&this->control_list[1]); spa_hook_list_init(&this->listener_list); @@ -267,13 +269,29 @@ static int do_add_port(struct spa_loop *loop, return 0; } +static int make_control(void *data, struct spa_pod *param) +{ + struct pw_port *port = data; + struct pw_node *node = port->node; + pw_control_new(node->core, port, param, 0); + return 0; +} + bool pw_port_add(struct pw_port *port, struct pw_node *node) { uint32_t port_id = port->port_id; + struct pw_type *t = &node->core->type; port->node = node; - pw_log_debug("port %p: add to node %p", port, node); + spa_node_port_get_info(node->node, + port->direction, port_id, + &port->info); + + if (port->info->props) + pw_port_update_properties(port, port->info->props); + + pw_log_debug("port %p: add to node %p %08x", port, node, port->info->flags); if (port->direction == PW_DIRECTION_INPUT) { spa_list_insert(&node->input_ports, &port->link); pw_map_insert_at(&node->input_port_map, port_id, port); @@ -287,6 +305,10 @@ bool pw_port_add(struct pw_port *port, struct pw_node *node) node->info.change_mask |= PW_NODE_CHANGE_MASK_OUTPUT_PORTS; } + pw_port_for_each_param(port, t->param_io.idPropsOut, NULL, make_control, port); + pw_port_for_each_param(port, t->param_io.idPropsIn, NULL, make_control, port); + + pw_log_debug("port %p: setting node io", port); spa_node_port_set_io(node->node, port->direction, port_id, node->core->type.io.Buffers, @@ -329,7 +351,9 @@ void pw_port_destroy(struct pw_port *port) spa_hook_list_call(&port->listener_list, struct pw_port_events, destroy); if (node) { - pw_loop_invoke(port->node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port); + if (port->rt.graph) + pw_loop_invoke(port->node->data_loop, do_remove_port, + SPA_ID_INVALID, NULL, 0, true, port); if (port->direction == PW_DIRECTION_INPUT) { pw_map_remove(&node->input_port_map, port->port_id); @@ -409,6 +433,42 @@ int pw_port_for_each_param(struct pw_port *port, return res; } +struct param_filter { + struct pw_port *in_port; + struct pw_port *out_port; + uint32_t in_param_id; + uint32_t out_param_id; + int (*callback) (void *data, struct spa_pod *param); + void *data; + uint32_t n_params; +}; + +static int do_filter(void *data, struct spa_pod *param) +{ + struct param_filter *f = data; + f->n_params++; + return pw_port_for_each_param(f->out_port, f->out_param_id, param, f->callback, f->data); +} + +int pw_port_for_each_filtered_param(struct pw_port *in_port, + struct pw_port *out_port, + uint32_t in_param_id, + uint32_t out_param_id, + int (*callback) (void *data, struct spa_pod *param), + void *data) +{ + int res; + struct param_filter filter = { in_port, out_port, in_param_id, out_param_id, callback, data, 0 }; + + if ((res = pw_port_for_each_param(in_port, in_param_id, NULL, do_filter, &filter)) < 0) + return res; + + if (filter.n_params == 0) + res = do_filter(&filter, NULL); + + return res; +} + int pw_port_set_param(struct pw_port *port, uint32_t id, uint32_t flags, const struct spa_pod *param) { diff --git a/src/pipewire/port.h b/src/pipewire/port.h index 5ca70c250..6386d4b94 100644 --- a/src/pipewire/port.h +++ b/src/pipewire/port.h @@ -41,6 +41,7 @@ extern "C" { */ struct pw_port; struct pw_link; +struct pw_control; #include #include @@ -77,6 +78,12 @@ struct pw_port_events { /** the properties of the port changed */ void (*properties_changed) (void *data, const struct pw_properties *properties); + + /** a control was added to the port */ + void (*control_added) (void *data, struct pw_control *control); + + /** a control was removed from the port */ + void (*control_removed) (void *data, struct pw_control *control); }; /** Get the port direction */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 6159491ba..3e534abcd 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -137,6 +137,7 @@ struct pw_core { struct spa_list node_list; /**< list of nodes */ struct spa_list factory_list; /**< list of factories */ struct spa_list link_list; /**< list of links */ + struct spa_list control_list[2]; /**< list of controls, indexed by direction */ struct spa_hook_list listener_list; @@ -267,6 +268,7 @@ struct pw_port { enum pw_direction direction; /**< port direction */ uint32_t port_id; /**< port id */ struct pw_properties *properties; + const struct spa_port_info *info; enum pw_port_state state; /**< state of the port */ @@ -279,6 +281,8 @@ struct pw_port { struct spa_list links; /**< list of \ref pw_link */ + struct spa_list control_list[2]; /**< list of \ref pw_control indexed by direction */ + struct spa_hook_list listener_list; struct spa_node *mix; /**< optional port buffer mix/split */ @@ -391,6 +395,24 @@ struct pw_factory { void *user_data; }; +struct pw_control { + struct spa_list link; /**< link in core control_list */ + struct pw_core *core; /**< the core */ + + struct pw_port *port; /**< owner port or NULL */ + struct spa_list port_link; /**< link in port control_list */ + + enum spa_direction direction; /**< the direction */ + struct spa_pod *param; /**< control params */ + + uint32_t id; + int32_t size; + + struct spa_hook_list listener_list; + + void *user_data; +}; + /** Find a good format between 2 ports */ int pw_core_find_format(struct pw_core *core, struct pw_port *output, @@ -438,6 +460,13 @@ int pw_port_for_each_param(struct pw_port *port, int (*callback) (void *data, struct spa_pod *param), void *data); +int pw_port_for_each_filtered_param(struct pw_port *in_port, + struct pw_port *out_port, + uint32_t in_param_id, + uint32_t out_param_id, + int (*callback) (void *data, struct spa_pod *param), + void *data); + /** Set a param on a port \memberof pw_port */ int pw_port_set_param(struct pw_port *port, uint32_t id, uint32_t flags, const struct spa_pod *param); @@ -470,6 +499,14 @@ bool pw_link_activate(struct pw_link *link); /** Deactivate a link \memberof pw_link */ bool pw_link_deactivate(struct pw_link *link); +struct pw_control * +pw_control_new(struct pw_core *core, + struct pw_port *owner, /**< can be NULL */ + const struct spa_pod *param, /**< copy is taken */ + size_t user_data_size /**< extra user data */); + +void pw_control_destroy(struct pw_control *control); + /** \endcond */ #ifdef __cplusplus diff --git a/src/pipewire/remote.c b/src/pipewire/remote.c index 4ad690539..35330f3ca 100644 --- a/src/pipewire/remote.c +++ b/src/pipewire/remote.c @@ -1053,7 +1053,7 @@ client_node_port_command(void *object, { struct pw_proxy *proxy = object; struct node_data *data = proxy->user_data; - static struct port *port; + struct port *port; port = find_port(data, direction, port_id); if (port == NULL) @@ -1062,6 +1062,51 @@ client_node_port_command(void *object, pw_port_send_command(port->port, true, command); } +static void +client_node_port_set_io(void *object, + uint32_t seq, + uint32_t direction, + uint32_t port_id, + uint32_t id, + uint32_t memid, + uint32_t offset, + uint32_t size) +{ + struct pw_proxy *proxy = object; + struct node_data *data = proxy->user_data; + struct port *port; + struct mem_id *mid; + + port = find_port(data, direction, port_id); + if (port == NULL) + return; + + mid = find_mem(port, memid); + if (mid == NULL) { + pw_log_warn("unknown memory id %u", memid); + return; + } + + if (mid->ptr == NULL) { + mid->ptr = + mmap(NULL, mid->size + mid->offset, PROT_READ|PROT_WRITE, MAP_SHARED, mid->fd, 0); + if (mid->ptr == MAP_FAILED) { + mid->ptr = NULL; + pw_log_warn("Failed to mmap memory %d %p: %s", mid->size, mid, + strerror(errno)); + return; + } + } + + spa_node_port_set_io(port->port->node->node, + direction, port_id, + id, + SPA_MEMBER(mid->ptr, offset, void), + size); + +} + + static const struct pw_client_node_proxy_events client_node_events = { PW_VERSION_CLIENT_NODE_PROXY_EVENTS, .transport = client_node_transport, @@ -1074,6 +1119,7 @@ static const struct pw_client_node_proxy_events client_node_events = { .port_add_mem = client_node_port_add_mem, .port_use_buffers = client_node_port_use_buffers, .port_command = client_node_port_command, + .port_set_io = client_node_port_set_io, }; static void do_node_init(struct pw_proxy *proxy)