mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-15 07:00:05 -05:00
node: implement activation
Make an eventfd for each node and listen for events when the node is activated. Reorganize some graphs links to make it possible to activiate nodes by signaling the eventfd Pass the peer node to each remote node and let the remote node directly activate the peer when needed. Let each node signal the driver node when finished. With this we don't need to go through the daemon to schedule the graph, nodes will simply activate eachother. We only go to the server when there is a server node to schedule. Keep stats about the state of each node and the time it was triggered, running and finished.
This commit is contained in:
parent
f45e0b8966
commit
5de7898808
15 changed files with 470 additions and 173 deletions
|
|
@ -178,7 +178,8 @@ pw_client_node_proxy_event(struct pw_client_node_proxy *p, struct spa_event *eve
|
|||
#define PW_CLIENT_NODE_PROXY_EVENT_PORT_USE_BUFFERS 9
|
||||
#define PW_CLIENT_NODE_PROXY_EVENT_PORT_COMMAND 10
|
||||
#define PW_CLIENT_NODE_PROXY_EVENT_PORT_SET_IO 11
|
||||
#define PW_CLIENT_NODE_PROXY_EVENT_NUM 12
|
||||
#define PW_CLIENT_NODE_PROXY_EVENT_SET_ACTIVATION 12
|
||||
#define PW_CLIENT_NODE_PROXY_EVENT_NUM 13
|
||||
|
||||
/** \ref pw_client_node events */
|
||||
struct pw_client_node_proxy_events {
|
||||
|
|
@ -351,6 +352,13 @@ struct pw_client_node_proxy_events {
|
|||
uint32_t mem_id,
|
||||
uint32_t offset,
|
||||
uint32_t size);
|
||||
|
||||
void (*set_activation) (void *object,
|
||||
uint32_t node_id,
|
||||
int signalfd,
|
||||
uint32_t mem_id,
|
||||
uint32_t offset,
|
||||
uint32_t size);
|
||||
};
|
||||
|
||||
static inline void
|
||||
|
|
@ -386,6 +394,8 @@ pw_client_node_proxy_add_listener(struct pw_client_node_proxy *p,
|
|||
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__)
|
||||
#define pw_client_node_resource_set_activation(r,...) \
|
||||
pw_resource_notify(r,struct pw_client_node_proxy_events,set_activation,__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
|
|
|||
|
|
@ -27,10 +27,7 @@
|
|||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <spa/node/node.h>
|
||||
|
|
@ -162,6 +159,7 @@ struct impl {
|
|||
|
||||
struct pw_map io_map;
|
||||
struct pw_memblock *io_areas;
|
||||
struct pw_node_activation *activation;
|
||||
|
||||
struct spa_hook node_listener;
|
||||
struct spa_hook resource_listener;
|
||||
|
|
@ -1011,12 +1009,19 @@ static int impl_node_process(struct spa_node *node)
|
|||
{
|
||||
struct node *this = SPA_CONTAINER_OF(node, struct node, node);
|
||||
struct impl *impl = this->impl;
|
||||
uint64_t cmd = 1;
|
||||
struct pw_node *n = impl->this.node;
|
||||
struct timespec ts;
|
||||
uint64_t cmd = 1, nsec;
|
||||
|
||||
spa_log_trace(this->log, "%p: send process %p", this, impl->this.node->driver_node);
|
||||
|
||||
if (write(this->writefd, &cmd, 8) != 8)
|
||||
spa_log_warn(this->log, "node %p: error %s", this, strerror(errno));
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
nsec = SPA_TIMESPEC_TO_NSEC(&ts);
|
||||
n->rt.activation->status = TRIGGERED;
|
||||
n->rt.activation->signal_time = nsec;
|
||||
|
||||
if (write(this->writefd, &cmd, sizeof(cmd)) != sizeof(cmd))
|
||||
spa_log_warn(this->log, "node %p: error %m", this);
|
||||
|
||||
return SPA_STATUS_OK;
|
||||
}
|
||||
|
|
@ -1155,9 +1160,8 @@ static void node_on_data_fd_events(struct spa_source *source)
|
|||
if (source->rmask & SPA_IO_IN) {
|
||||
uint64_t cmd;
|
||||
|
||||
if (read(this->data_source.fd, &cmd, sizeof(uint64_t)) != sizeof(uint64_t))
|
||||
spa_log_warn(this->log, "node %p: error reading message: %s",
|
||||
this, strerror(errno));
|
||||
if (read(this->data_source.fd, &cmd, sizeof(cmd)) != sizeof(cmd) || cmd != 1)
|
||||
spa_log_warn(this->log, "node %p: read %"PRIu64" failed %m", this, cmd);
|
||||
|
||||
spa_log_trace(this->log, "node %p: got process", this);
|
||||
this->callbacks->process(this->callbacks_data, SPA_STATUS_HAVE_BUFFER);
|
||||
|
|
@ -1278,12 +1282,24 @@ static void client_node_resource_destroy(void *data)
|
|||
void pw_client_node_registered(struct pw_client_node *this, uint32_t node_id)
|
||||
{
|
||||
struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
|
||||
struct pw_node *node = this->node;
|
||||
struct mem *m;
|
||||
|
||||
pw_log_debug("client-node %p: %d", this, node_id);
|
||||
pw_client_node_resource_transport(this->resource,
|
||||
node_id,
|
||||
impl->other_fds[0],
|
||||
impl->other_fds[1]);
|
||||
|
||||
|
||||
m = ensure_mem(impl, node->activation->fd, SPA_DATA_MemFd, node->activation->flags);
|
||||
|
||||
pw_client_node_resource_set_activation(this->resource,
|
||||
node_id,
|
||||
impl->other_fds[1],
|
||||
m->id,
|
||||
0,
|
||||
sizeof(struct pw_node_activation));
|
||||
}
|
||||
|
||||
static void node_initialized(void *data)
|
||||
|
|
@ -1572,6 +1588,50 @@ static void node_port_removed(void *data, struct pw_port *port)
|
|||
clear_port(this, p);
|
||||
}
|
||||
|
||||
static void node_peer_added(void *data, struct pw_node *peer)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
struct node *this = &impl->node;
|
||||
struct mem *m;
|
||||
|
||||
if (this->resource == NULL)
|
||||
return;
|
||||
|
||||
m = ensure_mem(impl, peer->activation->fd, SPA_DATA_MemFd, peer->activation->flags);
|
||||
|
||||
pw_log_debug("client-node %p: peer %p %u added %u", &impl->this, peer,
|
||||
peer->info.id, m->id);
|
||||
|
||||
pw_client_node_resource_set_activation(this->resource,
|
||||
peer->info.id,
|
||||
peer->source.fd,
|
||||
m->id,
|
||||
0,
|
||||
sizeof(struct pw_node_activation));
|
||||
}
|
||||
|
||||
static void node_peer_removed(void *data, struct pw_node *peer)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
struct node *this = &impl->node;
|
||||
|
||||
if (this->resource == NULL)
|
||||
return;
|
||||
|
||||
pw_client_node_resource_set_activation(this->resource,
|
||||
peer->info.id,
|
||||
-1,
|
||||
SPA_ID_INVALID,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
static void node_driver_changed(void *data, struct pw_node *old, struct pw_node *driver)
|
||||
{
|
||||
node_peer_removed(data, old);
|
||||
node_peer_added(data, driver);
|
||||
}
|
||||
|
||||
static const struct pw_node_events node_events = {
|
||||
PW_VERSION_NODE_EVENTS,
|
||||
.free = node_free,
|
||||
|
|
@ -1579,6 +1639,9 @@ static const struct pw_node_events node_events = {
|
|||
.port_init = node_port_init,
|
||||
.port_added = node_port_added,
|
||||
.port_removed = node_port_removed,
|
||||
.peer_added = node_peer_added,
|
||||
.peer_removed = node_peer_removed,
|
||||
.driver_changed = node_driver_changed,
|
||||
};
|
||||
|
||||
static const struct pw_resource_events resource_events = {
|
||||
|
|
@ -1586,6 +1649,18 @@ static const struct pw_resource_events resource_events = {
|
|||
.destroy = client_node_resource_destroy,
|
||||
};
|
||||
|
||||
static int root_impl_process(void *data, struct spa_graph_node *node)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
pw_log_trace("client-node %p: process", impl);
|
||||
return spa_node_process(&impl->node.node);
|
||||
}
|
||||
|
||||
static const struct spa_graph_node_callbacks root_impl = {
|
||||
SPA_VERSION_GRAPH_NODE_CALLBACKS,
|
||||
.process = root_impl_process,
|
||||
};
|
||||
|
||||
/** Create a new client node
|
||||
* \param client an owner \ref pw_client
|
||||
* \param id an id
|
||||
|
|
@ -1646,6 +1721,8 @@ struct pw_client_node *pw_client_node_new(struct pw_resource *resource,
|
|||
|
||||
this->node->remote = true;
|
||||
|
||||
spa_graph_node_set_callbacks(&this->node->rt.root, &root_impl, this);
|
||||
|
||||
pw_resource_add_listener(this->resource,
|
||||
&impl->resource_listener,
|
||||
&resource_events,
|
||||
|
|
|
|||
|
|
@ -893,7 +893,7 @@ static int impl_node_process(struct spa_node *node)
|
|||
trigger = status & SPA_STATUS_HAVE_BUFFER;
|
||||
|
||||
if (trigger && !impl->this.node->driver)
|
||||
spa_graph_run(impl->client_node->node->rt.driver);
|
||||
spa_graph_node_process(&impl->client_node->node->rt.root);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
@ -988,9 +988,6 @@ static void client_node_initialized(void *data)
|
|||
else
|
||||
monitor = false;
|
||||
|
||||
spa_graph_node_add(impl->client_node->node->rt.driver, &impl->client_node->node->rt.root);
|
||||
impl->client_node->node->driver_root = impl->this.node;
|
||||
|
||||
impl->client_port = pw_node_find_port(impl->client_node->node, impl->direction, 0);
|
||||
if (impl->client_port == NULL)
|
||||
return;
|
||||
|
|
@ -1185,17 +1182,32 @@ static void node_initialized(void *data)
|
|||
pw_client_node_registered(impl->client_node, impl->this.node->global->id);
|
||||
}
|
||||
|
||||
static void node_driver_changed(void *data, struct pw_node *driver)
|
||||
static void node_peer_added(void *data, struct pw_node *peer)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
pw_log_debug("client-stream %p: driver changed %p", &impl->this, driver);
|
||||
pw_node_events_peer_added(impl->client_node->node, peer);
|
||||
}
|
||||
|
||||
static void node_peer_removed(void *data, struct pw_node *peer)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
pw_node_events_peer_removed(impl->client_node->node, peer);
|
||||
}
|
||||
|
||||
static void node_driver_changed(void *data, struct pw_node *old, struct pw_node *driver)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
pw_log_debug("client-stream %p: driver changed %p->%p", &impl->this, old, driver);
|
||||
impl->client_node->node->driver_node = driver;
|
||||
pw_node_events_driver_changed(impl->client_node->node, old, driver);
|
||||
}
|
||||
|
||||
static const struct pw_node_events node_events = {
|
||||
PW_VERSION_NODE_EVENTS,
|
||||
.free = node_free,
|
||||
.initialized = node_initialized,
|
||||
.peer_added = node_peer_added,
|
||||
.peer_removed = node_peer_removed,
|
||||
.driver_changed = node_driver_changed,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -430,6 +430,34 @@ static int client_node_demarshal_port_set_io(void *object, void *data, size_t si
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int client_node_demarshal_set_activation(void *object, void *data, size_t size)
|
||||
{
|
||||
struct pw_proxy *proxy = object;
|
||||
struct spa_pod_parser prs;
|
||||
uint32_t node_id, sigidx, memid, off, sz;
|
||||
int signalfd;
|
||||
|
||||
spa_pod_parser_init(&prs, data, size);
|
||||
if (spa_pod_parser_get_struct(&prs,
|
||||
SPA_POD_Int(&node_id),
|
||||
SPA_POD_Int(&sigidx),
|
||||
SPA_POD_Int(&memid),
|
||||
SPA_POD_Int(&off),
|
||||
SPA_POD_Int(&sz)) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
signalfd = pw_protocol_native_get_proxy_fd(proxy, sigidx);
|
||||
if (signalfd == -1)
|
||||
return -EINVAL;
|
||||
|
||||
pw_proxy_notify(proxy, struct pw_client_node_proxy_events, set_activation, 0,
|
||||
node_id,
|
||||
signalfd,
|
||||
memid,
|
||||
off, sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_node_demarshal_set_io(void *object, void *data, size_t size)
|
||||
{
|
||||
struct pw_proxy *proxy = object;
|
||||
|
|
@ -692,6 +720,29 @@ client_node_marshal_port_set_io(void *object,
|
|||
pw_protocol_native_end_resource(resource, b);
|
||||
}
|
||||
|
||||
static void
|
||||
client_node_marshal_set_activation(void *object,
|
||||
uint32_t node_id,
|
||||
int signalfd,
|
||||
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_SET_ACTIVATION);
|
||||
|
||||
spa_pod_builder_add_struct(b,
|
||||
SPA_POD_Int(node_id),
|
||||
SPA_POD_Int(pw_protocol_native_add_resource_fd(resource, signalfd)),
|
||||
SPA_POD_Int(memid),
|
||||
SPA_POD_Int(offset),
|
||||
SPA_POD_Int(size));
|
||||
|
||||
pw_protocol_native_end_resource(resource, b);
|
||||
}
|
||||
|
||||
static void
|
||||
client_node_marshal_set_io(void *object,
|
||||
uint32_t id,
|
||||
|
|
@ -899,6 +950,7 @@ static const struct pw_client_node_proxy_events pw_protocol_native_client_node_e
|
|||
&client_node_marshal_port_use_buffers,
|
||||
&client_node_marshal_port_command,
|
||||
&client_node_marshal_port_set_io,
|
||||
&client_node_marshal_set_activation,
|
||||
};
|
||||
|
||||
static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_event_demarshal[] = {
|
||||
|
|
@ -914,6 +966,7 @@ static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_
|
|||
{ &client_node_demarshal_port_use_buffers, 0 },
|
||||
{ &client_node_demarshal_port_command, 0 },
|
||||
{ &client_node_demarshal_port_set_io, 0 },
|
||||
{ &client_node_demarshal_set_activation, 0 }
|
||||
};
|
||||
|
||||
static const struct pw_protocol_marshal pw_protocol_native_client_node_marshal = {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,13 @@ struct mix {
|
|||
bool active;
|
||||
};
|
||||
|
||||
struct link {
|
||||
struct spa_graph_link link;
|
||||
struct pw_node_activation *activation;
|
||||
int signalfd;
|
||||
uint32_t mem_id;
|
||||
};
|
||||
|
||||
struct node_data {
|
||||
struct pw_remote *remote;
|
||||
struct pw_core *core;
|
||||
|
|
@ -101,11 +108,7 @@ struct node_data {
|
|||
|
||||
struct spa_io_position *position;
|
||||
|
||||
struct spa_graph_node_callbacks callbacks;
|
||||
void *callbacks_data;
|
||||
|
||||
struct spa_graph_state state;
|
||||
struct spa_graph_link link;
|
||||
struct pw_array links;
|
||||
};
|
||||
|
||||
/** \endcond */
|
||||
|
|
@ -135,7 +138,6 @@ on_rtsocket_condition(void *user_data, int fd, enum spa_io mask)
|
|||
{
|
||||
struct pw_proxy *proxy = user_data;
|
||||
struct node_data *data = proxy->user_data;
|
||||
struct spa_graph_node *node = &data->node->rt.root;
|
||||
|
||||
if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
|
||||
pw_log_warn("got error");
|
||||
|
|
@ -146,11 +148,15 @@ on_rtsocket_condition(void *user_data, int fd, enum spa_io mask)
|
|||
if (mask & SPA_IO_IN) {
|
||||
uint64_t cmd;
|
||||
|
||||
if (read(fd, &cmd, sizeof(uint64_t)) != sizeof(uint64_t) || cmd != 1)
|
||||
if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd) || cmd != 1)
|
||||
pw_log_warn("proxy %p: read %"PRIu64" failed %m", proxy, cmd);
|
||||
|
||||
pw_log_trace("remote %p: process %p", data->remote, proxy);
|
||||
spa_graph_run(node->graph);
|
||||
|
||||
|
||||
|
||||
|
||||
spa_graph_node_process(&data->node->rt.root);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -536,7 +542,7 @@ static void client_node_command(void *object, uint32_t seq, const struct spa_com
|
|||
pw_loop_invoke(data->core->data_loop,
|
||||
do_pause_source, 1, NULL, 0, true, data);
|
||||
}
|
||||
if ((res = spa_node_send_command(data->node->node, command)) < 0)
|
||||
if ((res = pw_node_set_state(data->node, PW_NODE_STATE_IDLE)) < 0)
|
||||
pw_log_warn("node %p: pause failed", proxy);
|
||||
|
||||
pw_client_node_proxy_done(data->node_proxy, seq, res);
|
||||
|
|
@ -544,7 +550,7 @@ static void client_node_command(void *object, uint32_t seq, const struct spa_com
|
|||
case SPA_NODE_COMMAND_Start:
|
||||
pw_log_debug("node %p: start %d", proxy, seq);
|
||||
|
||||
if ((res = spa_node_send_command(data->node->node, command)) < 0) {
|
||||
if ((res = pw_node_set_state(data->node, PW_NODE_STATE_RUNNING)) < 0) {
|
||||
pw_log_warn("node %p: start failed", proxy);
|
||||
}
|
||||
else if (data->rtsocket_source) {
|
||||
|
|
@ -867,6 +873,66 @@ client_node_port_set_io(void *object,
|
|||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int link_signal_func(void *user_data)
|
||||
{
|
||||
struct link *link = user_data;
|
||||
uint64_t cmd = 1;
|
||||
pw_log_trace("link %p: signal", link);
|
||||
if (write(link->signalfd, &cmd, sizeof(cmd)) != sizeof(cmd))
|
||||
pw_log_warn("link %p: write failed %m", link);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
client_node_set_activation(void *object,
|
||||
uint32_t node_id,
|
||||
int signalfd,
|
||||
uint32_t memid,
|
||||
uint32_t offset,
|
||||
uint32_t size)
|
||||
{
|
||||
struct pw_proxy *proxy = object;
|
||||
struct node_data *data = proxy->user_data;
|
||||
struct pw_node *node = data->node;
|
||||
struct mem *m;
|
||||
struct pw_node_activation *ptr;
|
||||
|
||||
if (memid == SPA_ID_INVALID) {
|
||||
ptr = NULL;
|
||||
size = 0;
|
||||
}
|
||||
else {
|
||||
m = find_mem(data, memid);
|
||||
if (m == NULL) {
|
||||
pw_log_warn("unknown memory id %u", memid);
|
||||
return;
|
||||
}
|
||||
ptr = mem_map(data, &m->map, m->fd,
|
||||
PROT_READ|PROT_WRITE, offset, size);
|
||||
if (ptr == NULL)
|
||||
return;
|
||||
m->ref++;
|
||||
}
|
||||
pw_log_debug("node %p: set activation %d", node, node_id);
|
||||
|
||||
#if 0
|
||||
if (ptr) {
|
||||
struct link *link;
|
||||
link = pw_array_add(&data->links, sizeof(struct link));
|
||||
link->activation = ptr;
|
||||
link->signalfd = signalfd;
|
||||
link->link.signal = link_signal_func;
|
||||
link->link.signal_data = link;
|
||||
spa_graph_link_add(&node->rt.root, &link->activation->state[0], &link->link);
|
||||
pw_log_debug("node %p: required %d, pending %d", node,
|
||||
link->link.state->required,
|
||||
link->link.state->pending);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static const struct pw_client_node_proxy_events client_node_events = {
|
||||
PW_VERSION_CLIENT_NODE_PROXY_EVENTS,
|
||||
.add_mem = client_node_add_mem,
|
||||
|
|
@ -881,6 +947,7 @@ static const struct pw_client_node_proxy_events client_node_events = {
|
|||
.port_use_buffers = client_node_port_use_buffers,
|
||||
.port_command = client_node_port_command,
|
||||
.port_set_io = client_node_port_set_io,
|
||||
.set_activation = client_node_set_activation,
|
||||
};
|
||||
|
||||
static void do_node_init(struct pw_proxy *proxy)
|
||||
|
|
@ -996,28 +1063,6 @@ static const struct pw_proxy_events proxy_events = {
|
|||
.destroy = node_proxy_destroy,
|
||||
};
|
||||
|
||||
static int remote_impl_signal(void *data)
|
||||
{
|
||||
struct node_data *d = data;
|
||||
uint64_t cmd = 1;
|
||||
pw_log_trace("remote %p: send process", data);
|
||||
write(d->rtwritefd, &cmd, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int remote_process(void *data, struct spa_graph_node *node)
|
||||
{
|
||||
struct node_data *d = data;
|
||||
spa_debug("remote %p: begin graph", data);
|
||||
spa_graph_state_reset(&d->state);
|
||||
return d->callbacks.process(d->callbacks_data, node);
|
||||
}
|
||||
|
||||
static const struct spa_graph_node_callbacks impl_root = {
|
||||
SPA_VERSION_GRAPH_NODE_CALLBACKS,
|
||||
.process = remote_process,
|
||||
};
|
||||
|
||||
static struct pw_proxy *node_export(struct pw_remote *remote, void *object, bool do_free)
|
||||
{
|
||||
struct pw_node *node = object;
|
||||
|
|
@ -1042,13 +1087,6 @@ static struct pw_proxy *node_export(struct pw_remote *remote, void *object, bool
|
|||
data->node_proxy = (struct pw_client_node_proxy *)proxy;
|
||||
data->remote_id = SPA_ID_INVALID;
|
||||
|
||||
data->link.signal = remote_impl_signal;
|
||||
data->link.signal_data = data;
|
||||
data->callbacks = *node->rt.root.callbacks;
|
||||
spa_graph_node_set_callbacks(&node->rt.root, &impl_root, data);
|
||||
spa_graph_link_add(&node->rt.root, &data->state, &data->link);
|
||||
spa_graph_node_add(node->rt.driver, &node->rt.root);
|
||||
|
||||
node->exported = true;
|
||||
|
||||
spa_list_init(&data->free_mix);
|
||||
|
|
@ -1059,6 +1097,8 @@ static struct pw_proxy *node_export(struct pw_remote *remote, void *object, bool
|
|||
|
||||
pw_array_init(&data->mems, 64);
|
||||
pw_array_ensure_size(&data->mems, sizeof(struct mem) * 64);
|
||||
pw_array_init(&data->links, 64);
|
||||
pw_array_ensure_size(&data->links, sizeof(struct link) * 64);
|
||||
|
||||
pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
|
||||
pw_node_add_listener(node, &data->node_listener, &node_events, data);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <spa/pod/parser.h>
|
||||
#include <spa/pod/compare.h>
|
||||
|
|
@ -65,6 +66,8 @@ struct impl {
|
|||
struct spa_hook output_node_listener;
|
||||
|
||||
struct spa_io_buffers io;
|
||||
|
||||
struct pw_node *inode, *onode;
|
||||
};
|
||||
|
||||
struct resource_data {
|
||||
|
|
@ -771,8 +774,8 @@ do_activate_link(struct spa_loop *loop,
|
|||
bool async, uint32_t seq, const void *data, size_t size, void *user_data)
|
||||
{
|
||||
struct pw_link *this = user_data;
|
||||
struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
|
||||
struct spa_graph_port *in, *out;
|
||||
struct pw_node *inode, *onode;
|
||||
|
||||
pw_log_trace("link %p: activate", this);
|
||||
|
||||
|
|
@ -782,18 +785,10 @@ do_activate_link(struct spa_loop *loop,
|
|||
spa_graph_port_add(&this->output->rt.mix_node, out);
|
||||
spa_graph_port_add(&this->input->rt.mix_node, in);
|
||||
spa_graph_port_link(out, in);
|
||||
if (this->feedback) {
|
||||
inode = this->output->node;
|
||||
onode = this->input->node;
|
||||
}
|
||||
else {
|
||||
onode = this->output->node;
|
||||
inode = this->input->node;
|
||||
}
|
||||
if (inode != onode) {
|
||||
this->rt.link.signal_data = &inode->rt.root;
|
||||
spa_graph_link_add(&onode->rt.root,
|
||||
inode->rt.root.state,
|
||||
|
||||
if (impl->inode != impl->onode) {
|
||||
spa_graph_link_add(&impl->onode->rt.root,
|
||||
impl->inode->rt.root.state,
|
||||
&this->rt.link);
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -1021,7 +1016,6 @@ do_deactivate_link(struct spa_loop *loop,
|
|||
spa_graph_port_remove(in);
|
||||
if (this->input->node != this->output->node)
|
||||
spa_graph_link_remove(&this->rt.link);
|
||||
this->rt.link.signal_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1162,7 +1156,7 @@ static int find_driver(struct pw_link *this)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool pw_link_is_feedback(struct pw_node *output, struct pw_node *input)
|
||||
static bool pw_node_can_reach(struct pw_node *output, struct pw_node *input)
|
||||
{
|
||||
struct pw_port *p;
|
||||
|
||||
|
|
@ -1181,7 +1175,7 @@ static bool pw_link_is_feedback(struct pw_node *output, struct pw_node *input)
|
|||
spa_list_for_each(l, &p->links, output_link) {
|
||||
if (l->feedback)
|
||||
continue;
|
||||
if (pw_link_is_feedback(l->input->node, input))
|
||||
if (pw_node_can_reach(l->input->node, input))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1228,6 +1222,18 @@ static void try_unlink_controls(struct impl *impl, struct pw_port *port, struct
|
|||
}
|
||||
}
|
||||
|
||||
static int link_signal_node(void *data)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
impl->onode->rt.activation->status = FINISHED;
|
||||
impl->onode->rt.activation->finish_time = SPA_TIMESPEC_TO_NSEC(&ts);
|
||||
pw_log_trace("link %p finish %p, process %p", impl, impl->inode, impl->onode);
|
||||
return spa_graph_node_process(&impl->inode->rt.root);
|
||||
}
|
||||
|
||||
SPA_EXPORT
|
||||
struct pw_link *pw_link_new(struct pw_core *core,
|
||||
struct pw_port *output,
|
||||
|
|
@ -1259,7 +1265,7 @@ struct pw_link *pw_link_new(struct pw_core *core,
|
|||
goto no_mem;
|
||||
|
||||
this = &impl->this;
|
||||
this->feedback = pw_link_is_feedback(input_node, output_node);
|
||||
this->feedback = pw_node_can_reach(input_node, output_node);
|
||||
pw_log_debug("link %p: new %p -> %p", this, input, output);
|
||||
|
||||
if (user_data_size > 0)
|
||||
|
|
@ -1305,7 +1311,17 @@ struct pw_link *pw_link_new(struct pw_core *core,
|
|||
pw_port_init_mix(output, &this->rt.out_mix);
|
||||
pw_port_init_mix(input, &this->rt.in_mix);
|
||||
|
||||
this->rt.link.signal = spa_graph_link_signal_node;
|
||||
this->rt.link.signal = link_signal_node;
|
||||
this->rt.link.signal_data = impl;
|
||||
|
||||
if (this->feedback) {
|
||||
impl->inode = output_node;
|
||||
impl->onode = input_node;
|
||||
}
|
||||
else {
|
||||
impl->onode = output_node;
|
||||
impl->inode = input_node;
|
||||
}
|
||||
|
||||
pw_log_debug("link %p: constructed %p:%d.%d -> %p:%d.%d", impl,
|
||||
output_node, output->port_id, this->rt.out_mix.port.port_id,
|
||||
|
|
@ -1313,11 +1329,13 @@ struct pw_link *pw_link_new(struct pw_core *core,
|
|||
|
||||
find_driver(this);
|
||||
|
||||
spa_hook_list_call(&output->listener_list, struct pw_port_events, link_added, 0, this);
|
||||
spa_hook_list_call(&input->listener_list, struct pw_port_events, link_added, 0, this);
|
||||
pw_port_events_link_added(output, this);
|
||||
pw_port_events_link_added(input, this);
|
||||
|
||||
try_link_controls(impl, output, input);
|
||||
|
||||
pw_node_events_peer_added(output_node, input_node);
|
||||
|
||||
return this;
|
||||
|
||||
same_ports:
|
||||
|
|
@ -1411,6 +1429,8 @@ void pw_link_destroy(struct pw_link *link)
|
|||
if (link->registered)
|
||||
spa_list_remove(&link->link);
|
||||
|
||||
pw_node_events_peer_removed(link->output->node, link->input->node);
|
||||
|
||||
try_unlink_controls(impl, link->input, link->output);
|
||||
|
||||
input_remove(link, link->input);
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@
|
|||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <spa/pod/parser.h>
|
||||
|
||||
|
|
@ -58,8 +60,6 @@ struct impl {
|
|||
struct pw_node_activation node_activation;
|
||||
|
||||
uint32_t next_position;
|
||||
|
||||
struct pw_memblock *activation;
|
||||
};
|
||||
|
||||
struct resource_data {
|
||||
|
|
@ -91,7 +91,9 @@ do_node_remove(struct spa_loop *loop,
|
|||
{
|
||||
struct pw_node *this = user_data;
|
||||
if (this->rt.root.graph != NULL) {
|
||||
spa_loop_remove_source(loop, &this->source);
|
||||
spa_graph_node_remove(&this->rt.root);
|
||||
spa_graph_link_remove(&this->rt.driver_link);
|
||||
this->rt.root.graph = NULL;
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -122,8 +124,15 @@ do_node_add(struct spa_loop *loop,
|
|||
bool async, uint32_t seq, const void *data, size_t size, void *user_data)
|
||||
{
|
||||
struct pw_node *this = user_data;
|
||||
if (this->rt.root.graph == NULL)
|
||||
spa_graph_node_add(this->driver_node->rt.driver, &this->rt.root);
|
||||
struct pw_node *driver = this->driver_node;
|
||||
|
||||
if (this->rt.root.graph == NULL) {
|
||||
spa_loop_add_source(loop, &this->source);
|
||||
spa_graph_node_add(driver->rt.driver, &this->rt.root);
|
||||
spa_graph_link_add(&this->rt.root,
|
||||
driver->rt.root.state,
|
||||
&this->rt.driver_link);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -516,24 +525,30 @@ do_move_nodes(struct spa_loop *loop,
|
|||
bool async, uint32_t seq, const void *data, size_t size, void *user_data)
|
||||
{
|
||||
struct impl *src = user_data;
|
||||
struct pw_node *this = &src->this;
|
||||
struct impl *dst = *(struct impl **)data;
|
||||
struct pw_node *this = &src->this, *driver = &dst->this;
|
||||
struct spa_graph_node *n, *t;
|
||||
|
||||
pw_log_trace("node %p: root %p driver:%p->%p", this,
|
||||
&this->rt.root, &src->driver_graph, &dst->driver_graph);
|
||||
&this->rt.root, src, dst);
|
||||
|
||||
if (this->rt.root.graph != NULL) {
|
||||
spa_graph_node_remove(&this->rt.root);
|
||||
spa_graph_node_add(&dst->driver_graph, &this->rt.root);
|
||||
spa_graph_node_add(driver->rt.driver, &this->rt.root);
|
||||
spa_graph_link_remove(&this->rt.driver_link);
|
||||
spa_graph_link_add(&this->rt.root,
|
||||
driver->rt.root.state,
|
||||
&this->rt.driver_link);
|
||||
}
|
||||
|
||||
if (&src->driver_graph == &dst->driver_graph)
|
||||
return 0;
|
||||
|
||||
spa_list_for_each_safe(n, t, &src->driver_graph.nodes, link) {
|
||||
spa_list_for_each_safe(n, t, &this->rt.driver->nodes, link) {
|
||||
struct pw_node *pn = SPA_CONTAINER_OF(n, struct pw_node, rt.root);
|
||||
spa_graph_node_remove(n);
|
||||
spa_graph_node_add(&dst->driver_graph, n);
|
||||
spa_graph_node_add(driver->rt.driver, n);
|
||||
spa_graph_link_remove(&pn->rt.driver_link);
|
||||
spa_graph_link_add(&pn->rt.root,
|
||||
driver->rt.root.state,
|
||||
&pn->rt.driver_link);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -569,15 +584,17 @@ int pw_node_set_driver(struct pw_node *node, struct pw_node *driver)
|
|||
driver = node;
|
||||
|
||||
spa_list_for_each_safe(n, t, &node->driver_list, driver_link) {
|
||||
pw_log_debug("driver %p: add %p old %p", driver, n, n->driver_node);
|
||||
old = n->driver_node;
|
||||
|
||||
if (n->driver_node == driver)
|
||||
pw_log_debug("driver %p: add %p old %p", driver, n, old);
|
||||
|
||||
if (old == driver)
|
||||
continue;
|
||||
|
||||
spa_list_remove(&n->driver_link);
|
||||
spa_list_append(&driver->driver_list, &n->driver_link);
|
||||
n->driver_node = driver;
|
||||
pw_node_events_driver_changed(n, driver);
|
||||
pw_node_events_driver_changed(n, old, driver);
|
||||
|
||||
if ((res = spa_node_set_io(n->node,
|
||||
SPA_IO_Position,
|
||||
|
|
@ -634,10 +651,64 @@ static void check_properties(struct pw_node *node)
|
|||
} else
|
||||
node->quantum_size = DEFAULT_QUANTUM;
|
||||
|
||||
pw_log_debug("node %p: graph %p driver:%d", node, &impl->driver_graph, node->driver);
|
||||
pw_log_debug("node %p: driver:%d", node, node->driver);
|
||||
|
||||
}
|
||||
|
||||
static void node_on_fd_events(struct spa_source *source)
|
||||
{
|
||||
struct pw_node *this = source->data;
|
||||
|
||||
if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) {
|
||||
pw_log_warn("node %p: got socket error %08x", this, source->rmask);
|
||||
return;
|
||||
}
|
||||
|
||||
if (source->rmask & SPA_IO_IN) {
|
||||
uint64_t cmd;
|
||||
|
||||
if (read(this->source.fd, &cmd, sizeof(cmd)) != sizeof(cmd) || cmd != 1)
|
||||
pw_log_warn("node %p: read %"PRIu64" failed %m", this, cmd);
|
||||
|
||||
pw_log_trace("node %p: got process", this);
|
||||
spa_graph_node_process(&this->rt.root);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int root_impl_sub_process(void *data, struct spa_graph_node *node)
|
||||
{
|
||||
struct spa_graph *graph = node->subgraph;
|
||||
struct pw_node *this = data;
|
||||
struct timespec ts;
|
||||
|
||||
pw_log_trace("node %p: sub process %p", this, graph);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
this->rt.activation->status = AWAKE;
|
||||
this->rt.activation->awake_time = SPA_TIMESPEC_TO_NSEC(&ts);
|
||||
|
||||
return spa_graph_run(graph);
|
||||
}
|
||||
|
||||
static const struct spa_graph_node_callbacks root_impl = {
|
||||
SPA_VERSION_GRAPH_NODE_CALLBACKS,
|
||||
.process = root_impl_sub_process,
|
||||
};
|
||||
|
||||
static int signal_driver(void *data)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
struct pw_node *this = &impl->this;
|
||||
struct pw_node *driver = this->driver_node;
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
this->rt.activation->status = FINISHED;
|
||||
this->rt.activation->finish_time = SPA_TIMESPEC_TO_NSEC(&ts);
|
||||
pw_log_trace("node %p process driver %p", this, driver);
|
||||
return spa_graph_node_process(&driver->rt.root);
|
||||
}
|
||||
|
||||
SPA_EXPORT
|
||||
struct pw_node *pw_node_new(struct pw_core *core,
|
||||
const char *name,
|
||||
|
|
@ -672,11 +743,20 @@ struct pw_node *pw_node_new(struct pw_core *core,
|
|||
|
||||
size = sizeof(struct pw_node_activation);
|
||||
|
||||
this->source.fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
if (this->source.fd == -1)
|
||||
goto clean_impl;
|
||||
|
||||
this->source.func = node_on_fd_events;
|
||||
this->source.data = this;
|
||||
this->source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP;
|
||||
this->source.rmask = 0;
|
||||
|
||||
if (pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD |
|
||||
PW_MEMBLOCK_FLAG_MAP_READWRITE |
|
||||
PW_MEMBLOCK_FLAG_SEAL,
|
||||
size,
|
||||
&impl->activation) < 0)
|
||||
&this->activation) < 0)
|
||||
goto clean_impl;
|
||||
|
||||
impl->work = pw_work_queue_new(this->core->main_loop);
|
||||
|
|
@ -699,17 +779,15 @@ struct pw_node *pw_node_new(struct pw_core *core,
|
|||
spa_list_init(&this->output_ports);
|
||||
pw_map_init(&this->output_port_map, 64, 64);
|
||||
|
||||
spa_graph_init(&impl->driver_graph, &impl->driver_state);
|
||||
|
||||
this->rt.driver = &impl->driver_graph;
|
||||
this->rt.activation = impl->activation->ptr;
|
||||
this->rt.activation = this->activation->ptr;
|
||||
|
||||
spa_graph_init(&impl->driver_graph, &impl->driver_state);
|
||||
spa_graph_node_init(&this->rt.root, &this->rt.activation->state[0]);
|
||||
|
||||
spa_graph_init(&impl->graph, &impl->graph_state);
|
||||
|
||||
spa_graph_node_set_subgraph(&this->rt.root, &impl->graph);
|
||||
spa_graph_node_set_callbacks(&this->rt.root,
|
||||
&spa_graph_node_sub_impl_default, this);
|
||||
spa_graph_node_set_callbacks(&this->rt.root, &root_impl, this);
|
||||
|
||||
impl->node_activation.state[0].status = SPA_STATUS_NEED_BUFFER;
|
||||
spa_graph_node_init(&this->rt.node, &impl->node_activation.state[0]);
|
||||
|
|
@ -718,15 +796,19 @@ struct pw_node *pw_node_new(struct pw_core *core,
|
|||
this->rt.activation->position.clock.rate = SPA_FRACTION(1, 48000);
|
||||
this->rt.activation->position.size = DEFAULT_QUANTUM;
|
||||
|
||||
this->rt.driver_link.signal = signal_driver;
|
||||
this->rt.driver_link.signal_data = impl;
|
||||
|
||||
check_properties(this);
|
||||
|
||||
this->driver_node = this;
|
||||
this->driver_root = this;
|
||||
spa_list_append(&this->driver_list, &this->driver_link);
|
||||
|
||||
return this;
|
||||
|
||||
clean_impl:
|
||||
if (this->source.func != NULL)
|
||||
close(this->source.fd);
|
||||
if (properties)
|
||||
pw_properties_free(properties);
|
||||
free(impl);
|
||||
|
|
@ -827,43 +909,20 @@ static void node_event(void *data, struct spa_event *event)
|
|||
|
||||
static void node_process(void *data, int status)
|
||||
{
|
||||
struct pw_node *node = data, *driver, *root;
|
||||
struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
|
||||
bool is_driving;
|
||||
struct pw_node *node = data;
|
||||
struct pw_node *driver = node->driver_node;
|
||||
|
||||
driver = node->driver_node;
|
||||
root = node->driver_root ? node->driver_root : driver;
|
||||
pw_log_trace("node %p: process driver:%d exported:%d %p", node,
|
||||
node->driver, node->exported, driver);
|
||||
|
||||
pw_log_trace("node %p: process driver:%d exported:%d %p %p", node,
|
||||
node->driver, node->exported, driver, driver->rt.driver);
|
||||
if (driver->rt.root.graph == NULL)
|
||||
return;
|
||||
|
||||
is_driving = (node->driver && driver == node);
|
||||
if (status == SPA_STATUS_HAVE_BUFFER)
|
||||
spa_graph_node_process(&driver->rt.root);
|
||||
|
||||
if (is_driving && (root->rt.driver->state->pending == 0 || !node->remote)) {
|
||||
struct timespec ts;
|
||||
struct spa_io_position *q = node->rt.position;
|
||||
|
||||
if (root->rt.driver->state->pending != 0) {
|
||||
pw_log_warn("node %p: graph not finished", node);
|
||||
}
|
||||
|
||||
if (!node->rt.clock) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
q->clock.nsec = SPA_TIMESPEC_TO_NSEC(&ts);
|
||||
q->clock.position = impl->next_position;
|
||||
q->clock.delay = 0;
|
||||
}
|
||||
|
||||
pw_log_trace("node %p: run %"PRIu64" %d/%d %"PRIu64" %"PRIi64" %d", node,
|
||||
q->clock.nsec, q->clock.rate.num, q->clock.rate.denom,
|
||||
q->clock.position, q->clock.delay, q->size);
|
||||
|
||||
spa_graph_run(root->rt.driver);
|
||||
|
||||
impl->next_position += q->size;
|
||||
}
|
||||
else
|
||||
spa_graph_node_trigger(&node->rt.node);
|
||||
spa_graph_run(driver->rt.driver);
|
||||
spa_graph_link_trigger(&driver->rt.driver_link);
|
||||
}
|
||||
|
||||
static void node_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id)
|
||||
|
|
@ -996,6 +1055,7 @@ void pw_node_destroy(struct pw_node *node)
|
|||
|
||||
clear_info(node);
|
||||
|
||||
close(node->source.fd);
|
||||
free(impl);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,12 @@ struct pw_node_events {
|
|||
void (*event) (void *data, const struct spa_event *event);
|
||||
|
||||
/** the driver of the node changed */
|
||||
void (*driver_changed) (void *data, struct pw_node *driver);
|
||||
void (*driver_changed) (void *data, struct pw_node *old, struct pw_node *driver);
|
||||
|
||||
/** a peer was added */
|
||||
void (*peer_added) (void *data, struct pw_node *peer);
|
||||
/** a peer was removed */
|
||||
void (*peer_removed) (void *data, struct pw_node *peer);
|
||||
};
|
||||
|
||||
/** Media type of the node, Audio, Video, Midi */
|
||||
|
|
|
|||
|
|
@ -318,10 +318,9 @@ struct pw_node_activation {
|
|||
#define pw_node_events_state_changed(n,o,s,e) pw_node_events_emit(n, state_changed, 0, o, s, e)
|
||||
#define pw_node_events_async_complete(n,s,r) pw_node_events_emit(n, async_complete, 0, s, r)
|
||||
#define pw_node_events_event(n,e) pw_node_events_emit(n, event, 0, e)
|
||||
#define pw_node_events_driver_changed(n,d) pw_node_events_emit(n, driver_changed, 0, d)
|
||||
#define pw_node_events_process(n) pw_node_events_emit(n, process, 0)
|
||||
#define pw_node_events_reuse_buffer(n,p,b) pw_node_events_emit(n, reuse_buffer, 0, p, b)
|
||||
#define pw_node_events_finish(n) pw_node_events_emit(n, finish, 0)
|
||||
#define pw_node_events_driver_changed(n,o,d) pw_node_events_emit(n, driver_changed, 0, o, d)
|
||||
#define pw_node_events_peer_added(n,p) pw_node_events_emit(n, peer_added, 0, p)
|
||||
#define pw_node_events_peer_removed(n,p) pw_node_events_emit(n, peer_removed, 0, p)
|
||||
|
||||
struct pw_node {
|
||||
struct pw_core *core; /**< core object */
|
||||
|
|
@ -346,7 +345,6 @@ struct pw_node {
|
|||
uint32_t port_user_data_size; /**< extra size for port user data */
|
||||
|
||||
struct pw_node *driver_node;
|
||||
struct pw_node *driver_root;
|
||||
struct spa_list driver_list;
|
||||
struct spa_list driver_link;
|
||||
|
||||
|
|
@ -370,6 +368,8 @@ struct pw_node {
|
|||
struct pw_loop *data_loop; /**< the data loop for this node */
|
||||
|
||||
uint32_t quantum_size; /**< desired quantum */
|
||||
struct spa_source source; /**< source to remotely trigger this node */
|
||||
struct pw_memblock *activation;
|
||||
struct {
|
||||
struct spa_io_clock *clock; /**< io area of the clock or NULL */
|
||||
struct spa_io_position *position;
|
||||
|
|
@ -377,8 +377,7 @@ struct pw_node {
|
|||
struct spa_graph_node root;
|
||||
struct pw_node_activation *activation;
|
||||
struct spa_graph_node node;
|
||||
struct spa_graph_node subnode;
|
||||
struct spa_graph_link sublink;
|
||||
struct spa_graph_link driver_link;
|
||||
} rt;
|
||||
|
||||
void *user_data; /**< extra user data */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue