mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-03 09:01:54 -05:00
Make events and command specific to the node Remove some unused code Improve state changes Use easier fixate by just taking the element default value Fix reuse buffer in the proxy
1299 lines
35 KiB
C
1299 lines
35 KiB
C
/* Pinos
|
|
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
|
|
*
|
|
* 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 <string.h>
|
|
#include <stdlib.h>
|
|
#include <poll.h>
|
|
#include <errno.h>
|
|
#include <sys/eventfd.h>
|
|
|
|
#include <gio/gio.h>
|
|
#include <gio/gunixfdlist.h>
|
|
|
|
#include "pinos/client/pinos.h"
|
|
#include "pinos/client/enumtypes.h"
|
|
|
|
#include "pinos/server/node.h"
|
|
#include "pinos/server/daemon.h"
|
|
|
|
#include "pinos/dbus/org-pinos.h"
|
|
|
|
#define PINOS_NODE_GET_PRIVATE(node) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((node), PINOS_TYPE_NODE, PinosNodePrivate))
|
|
|
|
struct _PinosNodePrivate
|
|
{
|
|
PinosDaemon *daemon;
|
|
PinosNode1 *iface;
|
|
|
|
gchar *sender;
|
|
gchar *object_path;
|
|
gchar *name;
|
|
|
|
unsigned int max_input_ports;
|
|
unsigned int max_output_ports;
|
|
unsigned int n_input_ports;
|
|
unsigned int n_output_ports;
|
|
uint32_t *input_port_ids;
|
|
uint32_t *output_port_ids;
|
|
|
|
PinosNodeState state;
|
|
GError *error;
|
|
guint idle_timeout;
|
|
|
|
PinosProperties *properties;
|
|
|
|
unsigned int n_poll;
|
|
SpaPollItem poll[16];
|
|
|
|
bool rebuild_fds;
|
|
SpaPollFd fds[16];
|
|
unsigned int n_fds;
|
|
|
|
gboolean running;
|
|
pthread_t thread;
|
|
|
|
GHashTable *links;
|
|
};
|
|
|
|
G_DEFINE_ABSTRACT_TYPE (PinosNode, pinos_node, G_TYPE_OBJECT);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_DAEMON,
|
|
PROP_SENDER,
|
|
PROP_OBJECT_PATH,
|
|
PROP_NAME,
|
|
PROP_STATE,
|
|
PROP_PROPERTIES,
|
|
PROP_NODE,
|
|
PROP_NODE_STATE,
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIGNAL_REMOVE,
|
|
SIGNAL_PORT_ADDED,
|
|
SIGNAL_PORT_REMOVED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static PinosDirection
|
|
get_port_direction (PinosNode *node, guint id)
|
|
{
|
|
PinosNodePrivate *priv = node->priv;
|
|
PinosDirection direction;
|
|
|
|
direction = id < priv->max_input_ports ? PINOS_DIRECTION_INPUT : PINOS_DIRECTION_OUTPUT;
|
|
|
|
return direction;
|
|
}
|
|
|
|
static void
|
|
update_port_ids (PinosNode *node, gboolean create)
|
|
{
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
if (node->node == NULL)
|
|
return;
|
|
|
|
spa_node_get_n_ports (node->node,
|
|
&priv->n_input_ports,
|
|
&priv->max_input_ports,
|
|
&priv->n_output_ports,
|
|
&priv->max_output_ports);
|
|
|
|
g_debug ("node %p: update_port ids %u, %u", node, priv->max_input_ports, priv->max_output_ports);
|
|
|
|
priv->input_port_ids = g_realloc_n (priv->input_port_ids, priv->max_input_ports, sizeof (uint32_t));
|
|
priv->output_port_ids = g_realloc_n (priv->output_port_ids, priv->max_output_ports, sizeof (uint32_t));
|
|
|
|
spa_node_get_port_ids (node->node,
|
|
priv->max_input_ports,
|
|
priv->input_port_ids,
|
|
priv->max_output_ports,
|
|
priv->output_port_ids);
|
|
}
|
|
|
|
static void *
|
|
loop (void *user_data)
|
|
{
|
|
PinosNode *this = user_data;
|
|
PinosNodePrivate *priv = this->priv;
|
|
unsigned int i, j;
|
|
|
|
g_debug ("node %p: enter thread", this);
|
|
while (priv->running) {
|
|
SpaPollNotifyData ndata;
|
|
unsigned int n_idle = 0;
|
|
int r;
|
|
|
|
/* prepare */
|
|
for (i = 0; i < priv->n_poll; i++) {
|
|
SpaPollItem *p = &priv->poll[i];
|
|
|
|
if (p->enabled && p->idle_cb) {
|
|
ndata.fds = NULL;
|
|
ndata.n_fds = 0;
|
|
ndata.user_data = p->user_data;
|
|
p->idle_cb (&ndata);
|
|
n_idle++;
|
|
}
|
|
}
|
|
if (n_idle > 0)
|
|
continue;
|
|
|
|
/* rebuild */
|
|
if (priv->rebuild_fds) {
|
|
g_debug ("node %p: rebuild fds", this);
|
|
priv->n_fds = 1;
|
|
for (i = 0; i < priv->n_poll; i++) {
|
|
SpaPollItem *p = &priv->poll[i];
|
|
|
|
if (!p->enabled)
|
|
continue;
|
|
|
|
for (j = 0; j < p->n_fds; j++)
|
|
priv->fds[priv->n_fds + j] = p->fds[j];
|
|
p->fds = &priv->fds[priv->n_fds];
|
|
priv->n_fds += p->n_fds;
|
|
}
|
|
priv->rebuild_fds = false;
|
|
}
|
|
|
|
/* before */
|
|
for (i = 0; i < priv->n_poll; i++) {
|
|
SpaPollItem *p = &priv->poll[i];
|
|
|
|
if (p->enabled && p->before_cb) {
|
|
ndata.fds = p->fds;
|
|
ndata.n_fds = p->n_fds;
|
|
ndata.user_data = p->user_data;
|
|
p->before_cb (&ndata);
|
|
}
|
|
}
|
|
|
|
r = poll ((struct pollfd *) priv->fds, priv->n_fds, -1);
|
|
if (r < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
break;
|
|
}
|
|
if (r == 0) {
|
|
g_debug ("node %p: select timeout", this);
|
|
break;
|
|
}
|
|
|
|
/* check wakeup */
|
|
if (priv->fds[0].revents & POLLIN) {
|
|
uint64_t u;
|
|
if (read (priv->fds[0].fd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
|
|
g_warning ("node %p: failed to read fd", strerror (errno));
|
|
continue;
|
|
}
|
|
|
|
/* after */
|
|
for (i = 0; i < priv->n_poll; i++) {
|
|
SpaPollItem *p = &priv->poll[i];
|
|
|
|
if (p->enabled && p->after_cb) {
|
|
ndata.fds = p->fds;
|
|
ndata.n_fds = p->n_fds;
|
|
ndata.user_data = p->user_data;
|
|
p->after_cb (&ndata);
|
|
}
|
|
}
|
|
}
|
|
g_debug ("node %p: leave thread", this);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
wakeup_thread (PinosNode *this)
|
|
{
|
|
PinosNodePrivate *priv = this->priv;
|
|
uint64_t u = 1;
|
|
|
|
if (write (priv->fds[0].fd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
|
|
g_warning ("node %p: failed to write fd", strerror (errno));
|
|
}
|
|
|
|
static void
|
|
start_thread (PinosNode *this)
|
|
{
|
|
PinosNodePrivate *priv = this->priv;
|
|
int err;
|
|
|
|
if (!priv->running) {
|
|
priv->running = true;
|
|
if ((err = pthread_create (&priv->thread, NULL, loop, this)) != 0) {
|
|
g_warning ("node %p: can't create thread", strerror (err));
|
|
priv->running = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
stop_thread (PinosNode *this)
|
|
{
|
|
PinosNodePrivate *priv = this->priv;
|
|
|
|
if (priv->running) {
|
|
priv->running = false;
|
|
wakeup_thread (this);
|
|
pthread_join (priv->thread, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pause_node (PinosNode *this)
|
|
{
|
|
SpaResult res;
|
|
SpaNodeCommand cmd;
|
|
|
|
g_debug ("node %p: pause node", this);
|
|
|
|
cmd.type = SPA_NODE_COMMAND_PAUSE;
|
|
if ((res = spa_node_send_command (this->node, &cmd)) < 0)
|
|
g_debug ("got error %d", res);
|
|
}
|
|
|
|
static void
|
|
suspend_node (PinosNode *this)
|
|
{
|
|
SpaResult res;
|
|
|
|
g_debug ("node %p: suspend node", this);
|
|
|
|
if ((res = spa_node_port_set_format (this->node, 0, 0, NULL)) < 0)
|
|
g_warning ("error unset format output: %d", res);
|
|
|
|
}
|
|
|
|
static gboolean
|
|
node_set_state (PinosNode *this,
|
|
PinosNodeState state)
|
|
{
|
|
g_debug ("node %p: set state %s", this, pinos_node_state_as_string (state));
|
|
|
|
switch (state) {
|
|
case PINOS_NODE_STATE_SUSPENDED:
|
|
suspend_node (this);
|
|
break;
|
|
|
|
case PINOS_NODE_STATE_INITIALIZING:
|
|
break;
|
|
|
|
case PINOS_NODE_STATE_IDLE:
|
|
pause_node (this);
|
|
break;
|
|
|
|
case PINOS_NODE_STATE_RUNNING:
|
|
break;
|
|
|
|
case PINOS_NODE_STATE_ERROR:
|
|
break;
|
|
}
|
|
pinos_node_update_state (this, state);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
PinosNode *node;
|
|
PinosDirection direction;
|
|
guint port_id;
|
|
} PortData;
|
|
|
|
static gboolean
|
|
do_signal_port_added (PortData *data)
|
|
{
|
|
g_signal_emit (data->node, signals[SIGNAL_PORT_ADDED], 0, data->direction, data->port_id);
|
|
g_slice_free (PortData, data);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
do_signal_port_removed (PortData *data)
|
|
{
|
|
g_signal_emit (data->node, signals[SIGNAL_PORT_REMOVED], 0, data->port_id);
|
|
g_slice_free (PortData, data);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
on_node_event (SpaNode *node, SpaNodeEvent *event, void *user_data)
|
|
{
|
|
PinosNode *this = user_data;
|
|
PinosNodePrivate *priv = this->priv;
|
|
|
|
switch (event->type) {
|
|
case SPA_NODE_EVENT_TYPE_PORT_ADDED:
|
|
{
|
|
SpaNodeEventPortAdded *pa = event->data;
|
|
PortData *data;
|
|
|
|
update_port_ids (this, FALSE);
|
|
|
|
data = g_slice_new (PortData);
|
|
data->node = this;
|
|
data->direction = get_port_direction (this, pa->port_id);
|
|
data->port_id = pa->port_id;
|
|
g_main_context_invoke (NULL, (GSourceFunc) do_signal_port_added, data);
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_PORT_REMOVED:
|
|
{
|
|
SpaNodeEventPortRemoved *pr = event->data;
|
|
PortData *data;
|
|
|
|
update_port_ids (this, FALSE);
|
|
|
|
data = g_slice_new (PortData);
|
|
data->node = this;
|
|
data->port_id = pr->port_id;
|
|
g_main_context_invoke (NULL, (GSourceFunc) do_signal_port_removed, data);
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_STATE_CHANGE:
|
|
{
|
|
SpaNodeEventStateChange *sc = event->data;
|
|
|
|
g_debug ("node %p: update SPA state to %d", this, sc->state);
|
|
if (sc->state == SPA_NODE_STATE_CONFIGURE) {
|
|
update_port_ids (this, FALSE);
|
|
}
|
|
g_object_notify (G_OBJECT (this), "node-state");
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_ADD_POLL:
|
|
{
|
|
SpaPollItem *poll = event->data;
|
|
|
|
g_debug ("node %p: add poll %d, n_fds %d", this, poll->id, poll->n_fds);
|
|
priv->poll[priv->n_poll] = *poll;
|
|
priv->n_poll++;
|
|
if (poll->n_fds)
|
|
priv->rebuild_fds = true;
|
|
wakeup_thread (this);
|
|
|
|
start_thread (this);
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_UPDATE_POLL:
|
|
{
|
|
unsigned int i;
|
|
SpaPollItem *poll = event->data;
|
|
|
|
for (i = 0; i < priv->n_poll; i++) {
|
|
if (priv->poll[i].id == poll->id)
|
|
priv->poll[i] = *poll;
|
|
}
|
|
if (poll->n_fds)
|
|
priv->rebuild_fds = true;
|
|
wakeup_thread (this);
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_REMOVE_POLL:
|
|
{
|
|
SpaPollItem *poll = event->data;
|
|
unsigned int i;
|
|
|
|
g_debug ("node %p: remove poll %d %d", this, poll->n_fds, priv->n_poll);
|
|
for (i = 0; i < priv->n_poll; i++) {
|
|
if (priv->poll[i].id == poll->id) {
|
|
priv->n_poll--;
|
|
for (; i < priv->n_poll; i++)
|
|
priv->poll[i] = priv->poll[i+1];
|
|
break;
|
|
}
|
|
}
|
|
if (priv->n_poll > 0) {
|
|
priv->rebuild_fds = true;
|
|
wakeup_thread (this);
|
|
} else {
|
|
stop_thread (this);
|
|
}
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_HAVE_OUTPUT:
|
|
{
|
|
PinosLink *link;
|
|
SpaPortOutputInfo oinfo[1] = { 0, };
|
|
SpaResult res;
|
|
|
|
if ((res = spa_node_port_pull_output (node, 1, oinfo)) < 0) {
|
|
g_warning ("node %p: got pull error %d, %d", this, res, oinfo[0].status);
|
|
break;
|
|
}
|
|
|
|
link = g_hash_table_lookup (priv->links, GUINT_TO_POINTER (oinfo[0].port_id));
|
|
if (link) {
|
|
SpaPortInputInfo iinfo[1];
|
|
|
|
iinfo[0].port_id = link->input_port;
|
|
iinfo[0].buffer_id = oinfo[0].buffer_id;
|
|
iinfo[0].flags = SPA_PORT_INPUT_FLAG_NONE;
|
|
|
|
if ((res = spa_node_port_push_input (link->input_node->node, 1, iinfo)) < 0)
|
|
g_warning ("node %p: error pushing buffer: %d, %d", this, res, iinfo[0].status);
|
|
}
|
|
break;
|
|
}
|
|
case SPA_NODE_EVENT_TYPE_REUSE_BUFFER:
|
|
{
|
|
PinosLink *link;
|
|
SpaResult res;
|
|
SpaNodeEventReuseBuffer *rb = event->data;
|
|
|
|
link = g_hash_table_lookup (priv->links, GUINT_TO_POINTER (rb->port_id));
|
|
if (link) {
|
|
if ((res = spa_node_port_reuse_buffer (link->output_node->node,
|
|
link->output_port,
|
|
rb->buffer_id)) < 0)
|
|
g_warning ("node %p: error reuse buffer: %d", node, res);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
g_debug ("node %p: got event %d", this, event->type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
handle_remove (PinosNode1 *interface,
|
|
GDBusMethodInvocation *invocation,
|
|
gpointer user_data)
|
|
{
|
|
PinosNode *node = user_data;
|
|
|
|
g_debug ("node %p: remove", node);
|
|
pinos_node_remove (node);
|
|
|
|
g_dbus_method_invocation_return_value (invocation,
|
|
g_variant_new ("()"));
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
pinos_node_get_property (GObject *_object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PinosNode *node = PINOS_NODE (_object);
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
switch (prop_id) {
|
|
case PROP_DAEMON:
|
|
g_value_set_object (value, priv->daemon);
|
|
break;
|
|
|
|
case PROP_SENDER:
|
|
g_value_set_string (value, priv->sender);
|
|
break;
|
|
|
|
case PROP_OBJECT_PATH:
|
|
g_value_set_string (value, priv->object_path);
|
|
break;
|
|
|
|
case PROP_NAME:
|
|
g_value_set_string (value, priv->name);
|
|
break;
|
|
|
|
case PROP_STATE:
|
|
g_value_set_enum (value, priv->state);
|
|
break;
|
|
|
|
case PROP_PROPERTIES:
|
|
g_value_set_boxed (value, priv->properties);
|
|
break;
|
|
|
|
case PROP_NODE:
|
|
g_value_set_pointer (value, node->node);
|
|
break;
|
|
|
|
case PROP_NODE_STATE:
|
|
g_value_set_uint (value, node->node->state);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (node, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pinos_node_set_property (GObject *_object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PinosNode *node = PINOS_NODE (_object);
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
switch (prop_id) {
|
|
case PROP_DAEMON:
|
|
priv->daemon = g_value_dup_object (value);
|
|
break;
|
|
|
|
case PROP_SENDER:
|
|
priv->sender = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_NAME:
|
|
priv->name = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_PROPERTIES:
|
|
if (priv->properties)
|
|
pinos_properties_free (priv->properties);
|
|
priv->properties = g_value_dup_boxed (value);
|
|
break;
|
|
|
|
case PROP_NODE:
|
|
node->node = g_value_get_pointer (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (node, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
node_register_object (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv = node->priv;
|
|
PinosDaemon *daemon = priv->daemon;
|
|
PinosObjectSkeleton *skel;
|
|
|
|
skel = pinos_object_skeleton_new (PINOS_DBUS_OBJECT_NODE);
|
|
|
|
pinos_object_skeleton_set_node1 (skel, priv->iface);
|
|
|
|
g_free (priv->object_path);
|
|
priv->object_path = pinos_daemon_export_uniquely (daemon, G_DBUS_OBJECT_SKELETON (skel));
|
|
g_object_unref (skel);
|
|
|
|
g_debug ("node %p: register object %s", node, priv->object_path);
|
|
pinos_daemon_add_node (daemon, node);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
node_unregister_object (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
g_debug ("node %p: unregister object %s", node, priv->object_path);
|
|
pinos_daemon_unexport (priv->daemon, priv->object_path);
|
|
g_clear_pointer (&priv->object_path, g_free);
|
|
pinos_daemon_remove_node (priv->daemon, node);
|
|
}
|
|
|
|
static void
|
|
on_property_notify (GObject *obj,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
PinosNode *node = user_data;
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "sender") == 0) {
|
|
pinos_node1_set_owner (priv->iface, priv->sender);
|
|
}
|
|
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "name") == 0) {
|
|
pinos_node1_set_name (priv->iface, pinos_node_get_name (node));
|
|
}
|
|
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "properties") == 0) {
|
|
PinosProperties *props = pinos_node_get_properties (node);
|
|
pinos_node1_set_properties (priv->iface, props ? pinos_properties_to_variant (props) : NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pinos_node_constructed (GObject * obj)
|
|
{
|
|
PinosNode *this = PINOS_NODE (obj);
|
|
PinosNodePrivate *priv = this->priv;
|
|
SpaResult res;
|
|
|
|
g_debug ("node %p: constructed", this);
|
|
|
|
g_signal_connect (this, "notify", (GCallback) on_property_notify, this);
|
|
G_OBJECT_CLASS (pinos_node_parent_class)->constructed (obj);
|
|
|
|
priv->fds[0].fd = eventfd (0, 0);
|
|
priv->fds[0].events = POLLIN | POLLPRI | POLLERR;
|
|
priv->fds[0].revents = 0;
|
|
priv->n_fds = 1;
|
|
|
|
if ((res = spa_node_set_event_callback (this->node, on_node_event, this)) < 0)
|
|
g_warning ("node %p: error setting callback", this);
|
|
|
|
update_port_ids (this, TRUE);
|
|
|
|
if (priv->sender == NULL) {
|
|
priv->sender = g_strdup (pinos_daemon_get_sender (priv->daemon));
|
|
}
|
|
on_property_notify (G_OBJECT (this), NULL, this);
|
|
|
|
node_register_object (this);
|
|
}
|
|
|
|
static void
|
|
pinos_node_dispose (GObject * obj)
|
|
{
|
|
PinosNode *node = PINOS_NODE (obj);
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
g_debug ("node %p: dispose", node);
|
|
pinos_node_set_state (node, PINOS_NODE_STATE_SUSPENDED);
|
|
stop_thread (node);
|
|
|
|
node_unregister_object (node);
|
|
|
|
g_hash_table_unref (priv->links);
|
|
|
|
G_OBJECT_CLASS (pinos_node_parent_class)->dispose (obj);
|
|
}
|
|
|
|
static void
|
|
pinos_node_finalize (GObject * obj)
|
|
{
|
|
PinosNode *node = PINOS_NODE (obj);
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
g_debug ("node %p: finalize", node);
|
|
g_clear_object (&priv->daemon);
|
|
g_clear_object (&priv->iface);
|
|
g_free (priv->sender);
|
|
g_free (priv->name);
|
|
g_clear_error (&priv->error);
|
|
if (priv->properties)
|
|
pinos_properties_free (priv->properties);
|
|
g_free (priv->input_port_ids);
|
|
g_free (priv->output_port_ids);
|
|
|
|
G_OBJECT_CLASS (pinos_node_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
pinos_node_class_init (PinosNodeClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
PinosNodeClass *node_class = PINOS_NODE_CLASS (klass);
|
|
|
|
g_type_class_add_private (klass, sizeof (PinosNodePrivate));
|
|
|
|
gobject_class->constructed = pinos_node_constructed;
|
|
gobject_class->dispose = pinos_node_dispose;
|
|
gobject_class->finalize = pinos_node_finalize;
|
|
gobject_class->set_property = pinos_node_set_property;
|
|
gobject_class->get_property = pinos_node_get_property;
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_DAEMON,
|
|
g_param_spec_object ("daemon",
|
|
"Daemon",
|
|
"The Daemon",
|
|
PINOS_TYPE_DAEMON,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_SENDER,
|
|
g_param_spec_string ("sender",
|
|
"Sender",
|
|
"The Sender",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_OBJECT_PATH,
|
|
g_param_spec_string ("object-path",
|
|
"Object Path",
|
|
"The object path",
|
|
NULL,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_NAME,
|
|
g_param_spec_string ("name",
|
|
"Name",
|
|
"The node name",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_STATE,
|
|
g_param_spec_enum ("state",
|
|
"State",
|
|
"The state of the node",
|
|
PINOS_TYPE_NODE_STATE,
|
|
PINOS_NODE_STATE_SUSPENDED,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_PROPERTIES,
|
|
g_param_spec_boxed ("properties",
|
|
"Properties",
|
|
"The properties of the node",
|
|
PINOS_TYPE_PROPERTIES,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_NODE,
|
|
g_param_spec_pointer ("node",
|
|
"Node",
|
|
"The SPA node",
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_NODE_STATE,
|
|
g_param_spec_uint ("node-state",
|
|
"Node State",
|
|
"The state of the SPA node",
|
|
0,
|
|
G_MAXUINT,
|
|
SPA_NODE_STATE_INIT,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
signals[SIGNAL_REMOVE] = g_signal_new ("remove",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE,
|
|
0,
|
|
G_TYPE_NONE);
|
|
signals[SIGNAL_PORT_ADDED] = g_signal_new ("port-added",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE,
|
|
2,
|
|
PINOS_TYPE_DIRECTION,
|
|
G_TYPE_UINT);
|
|
signals[SIGNAL_PORT_REMOVED] = g_signal_new ("port-removed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_UINT);
|
|
|
|
node_class->set_state = node_set_state;
|
|
}
|
|
|
|
static void
|
|
pinos_node_init (PinosNode * node)
|
|
{
|
|
PinosNodePrivate *priv = node->priv = PINOS_NODE_GET_PRIVATE (node);
|
|
|
|
g_debug ("node %p: new", node);
|
|
priv->iface = pinos_node1_skeleton_new ();
|
|
g_signal_connect (priv->iface, "handle-remove",
|
|
(GCallback) handle_remove,
|
|
node);
|
|
priv->state = PINOS_NODE_STATE_SUSPENDED;
|
|
pinos_node1_set_state (priv->iface, PINOS_NODE_STATE_SUSPENDED);
|
|
|
|
priv->links = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
}
|
|
|
|
/**
|
|
* pinos_node_new:
|
|
* @daemon: a #PinosDaemon
|
|
* @sender: the path of the owner
|
|
* @name: a name
|
|
* @properties: extra properties
|
|
*
|
|
* Create a new #PinosNode.
|
|
*
|
|
* Returns: a new #PinosNode
|
|
*/
|
|
PinosNode *
|
|
pinos_node_new (PinosDaemon *daemon,
|
|
const gchar *sender,
|
|
const gchar *name,
|
|
PinosProperties *properties,
|
|
SpaNode *node)
|
|
{
|
|
g_return_val_if_fail (PINOS_IS_DAEMON (daemon), NULL);
|
|
|
|
return g_object_new (PINOS_TYPE_NODE,
|
|
"daemon", daemon,
|
|
"sender", sender,
|
|
"name", name,
|
|
"properties", properties,
|
|
"node", node,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_name:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the name of @node
|
|
*
|
|
* Returns: the name of @node
|
|
*/
|
|
const gchar *
|
|
pinos_node_get_name (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
|
|
priv = node->priv;
|
|
|
|
return priv->name;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_state:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the state of @node
|
|
*
|
|
* Returns: the state of @node
|
|
*/
|
|
PinosNodeState
|
|
pinos_node_get_state (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), PINOS_NODE_STATE_ERROR);
|
|
priv = node->priv;
|
|
|
|
return priv->state;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_properties:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the properties of @node
|
|
*
|
|
* Returns: the properties of @node
|
|
*/
|
|
PinosProperties *
|
|
pinos_node_get_properties (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
|
|
priv = node->priv;
|
|
|
|
return priv->properties;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_daemon:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the daemon of @node.
|
|
*
|
|
* Returns: the daemon of @node.
|
|
*/
|
|
PinosDaemon *
|
|
pinos_node_get_daemon (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
|
|
priv = node->priv;
|
|
|
|
return priv->daemon;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_sender:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the owner path of @node.
|
|
*
|
|
* Returns: the owner path of @node.
|
|
*/
|
|
const gchar *
|
|
pinos_node_get_sender (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
|
|
priv = node->priv;
|
|
|
|
return priv->sender;
|
|
}
|
|
/**
|
|
* pinos_node_get_object_path:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the object path of @node.
|
|
*
|
|
* Returns: the object path of @node.
|
|
*/
|
|
const gchar *
|
|
pinos_node_get_object_path (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
|
|
priv = node->priv;
|
|
|
|
return priv->object_path;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_remove:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Remove @node. This will stop the transfer on the node and
|
|
* free the resources allocated by @node.
|
|
*/
|
|
void
|
|
pinos_node_remove (PinosNode *node)
|
|
{
|
|
g_return_if_fail (PINOS_IS_NODE (node));
|
|
|
|
g_debug ("node %p: remove", node);
|
|
g_signal_emit (node, signals[SIGNAL_REMOVE], 0, NULL);
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_free_port_id:
|
|
* @node: a #PinosNode
|
|
* @direction: a #PinosDirection
|
|
*
|
|
* Find a new unused port id in @node with @direction
|
|
*
|
|
* Returns: the new port id of %SPA_INVALID_ID on error
|
|
*/
|
|
guint
|
|
pinos_node_get_free_port_id (PinosNode *node,
|
|
PinosDirection direction)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
guint i, free_port = 0, n_ports, max_ports;
|
|
uint32_t *ports;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), -1);
|
|
priv = node->priv;
|
|
|
|
if (direction == PINOS_DIRECTION_INPUT) {
|
|
max_ports = priv->max_input_ports;
|
|
n_ports = priv->n_input_ports;
|
|
ports = priv->input_port_ids;
|
|
} else {
|
|
max_ports = priv->max_output_ports;
|
|
n_ports = priv->n_output_ports;
|
|
ports = priv->output_port_ids;
|
|
}
|
|
|
|
g_debug ("node %p: direction %d max %u, n %u", node, direction, max_ports, n_ports);
|
|
|
|
for (i = 0; i < n_ports; i++) {
|
|
if (free_port < ports[i])
|
|
break;
|
|
|
|
if (g_hash_table_lookup (priv->links, GUINT_TO_POINTER (free_port)) == NULL && free_port < max_ports)
|
|
return free_port;
|
|
|
|
free_port = ports[i] + 1;
|
|
}
|
|
if (free_port >= max_ports)
|
|
return -1;
|
|
|
|
return free_port;
|
|
}
|
|
|
|
static void
|
|
do_remove_link (PinosLink *link, PinosNode *node)
|
|
{
|
|
g_hash_table_remove (link->output_node->priv->links, GUINT_TO_POINTER (link->output_port));
|
|
if (g_hash_table_size (link->output_node->priv->links) == 0)
|
|
pinos_node_report_idle (link->output_node);
|
|
|
|
g_hash_table_remove (link->input_node->priv->links, GUINT_TO_POINTER (link->input_port));
|
|
if (g_hash_table_size (link->input_node->priv->links) == 0)
|
|
pinos_node_report_idle (link->input_node);
|
|
}
|
|
|
|
/**
|
|
* pinos_node_link:
|
|
* @output_node: a #PinosNode
|
|
* @output_port: a port
|
|
* @input_node: a #PinosNode
|
|
* @input_port: a port
|
|
* @format_filter: a format filter
|
|
* @properties: extra properties
|
|
*
|
|
* Make a link between @output_node and @input_node on the given ports.
|
|
*
|
|
* If the ports were already linked, the existing linke will be returned.
|
|
*
|
|
* If the source port was linked to a different destination node or port, it
|
|
* will be relinked.
|
|
*
|
|
* Returns: a new #PinosLink
|
|
*/
|
|
PinosLink *
|
|
pinos_node_link (PinosNode *output_node,
|
|
guint output_port,
|
|
PinosNode *input_node,
|
|
guint input_port,
|
|
GPtrArray *format_filter,
|
|
PinosProperties *properties)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
PinosLink *link;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (output_node), NULL);
|
|
g_return_val_if_fail (PINOS_IS_NODE (input_node), NULL);
|
|
|
|
if (get_port_direction (output_node, output_port) != PINOS_DIRECTION_OUTPUT) {
|
|
PinosNode *tmp;
|
|
guint tmp_port;
|
|
|
|
tmp = output_node;
|
|
output_node = input_node;
|
|
input_node = tmp;
|
|
|
|
tmp_port = output_port;
|
|
output_port = input_port;
|
|
input_port = tmp_port;
|
|
}
|
|
|
|
priv = output_node->priv;
|
|
|
|
link = g_hash_table_lookup (priv->links, GUINT_TO_POINTER (output_port));
|
|
if (link) {
|
|
link->input_node = input_node;
|
|
link->input_port = input_port;
|
|
g_object_ref (link);
|
|
} else {
|
|
|
|
link = g_object_new (PINOS_TYPE_LINK,
|
|
"daemon", priv->daemon,
|
|
"output-node", output_node,
|
|
"output-port", output_port,
|
|
"input-node", input_node,
|
|
"input-port", input_port,
|
|
"format-filter", format_filter,
|
|
"properties", properties,
|
|
NULL);
|
|
|
|
g_signal_connect (link,
|
|
"remove",
|
|
(GCallback) do_remove_link,
|
|
output_node);
|
|
|
|
g_hash_table_insert (priv->links, GUINT_TO_POINTER (output_port), link);
|
|
g_hash_table_insert (input_node->priv->links, GUINT_TO_POINTER (input_port), link);
|
|
}
|
|
return link;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_get_links:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Get the links in @node.
|
|
*
|
|
* Returns: a #GList of #PinosLink g_list_free after usage.
|
|
*/
|
|
GList *
|
|
pinos_node_get_links (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
|
|
priv = node->priv;
|
|
|
|
return g_hash_table_get_values (priv->links);
|
|
}
|
|
|
|
static void
|
|
remove_idle_timeout (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
if (priv->idle_timeout) {
|
|
g_source_remove (priv->idle_timeout);
|
|
priv->idle_timeout = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pinos_node_set_state:
|
|
* @node: a #PinosNode
|
|
* @state: a #PinosNodeState
|
|
*
|
|
* Set the state of @node to @state.
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
pinos_node_set_state (PinosNode *node,
|
|
PinosNodeState state)
|
|
{
|
|
PinosNodeClass *klass;
|
|
gboolean res;
|
|
|
|
g_return_val_if_fail (PINOS_IS_NODE (node), FALSE);
|
|
|
|
klass = PINOS_NODE_GET_CLASS (node);
|
|
|
|
remove_idle_timeout (node);
|
|
|
|
g_debug ("node %p: set state to %s", node, pinos_node_state_as_string (state));
|
|
if (klass->set_state)
|
|
res = klass->set_state (node, state);
|
|
else
|
|
res = FALSE;
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_update_state:
|
|
* @node: a #PinosNode
|
|
* @state: a #PinosNodeState
|
|
*
|
|
* Update the state of a node. This method is used from
|
|
* inside @node itself.
|
|
*/
|
|
void
|
|
pinos_node_update_state (PinosNode *node,
|
|
PinosNodeState state)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_if_fail (PINOS_IS_NODE (node));
|
|
priv = node->priv;
|
|
|
|
if (priv->state != state) {
|
|
g_debug ("node %p: update state to %s", node, pinos_node_state_as_string (state));
|
|
priv->state = state;
|
|
pinos_node1_set_state (priv->iface, state);
|
|
g_object_notify (G_OBJECT (node), "state");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pinos_node_report_error:
|
|
* @node: a #PinosNode
|
|
* @error: a #GError
|
|
*
|
|
* Report an error from within @node.
|
|
*/
|
|
void
|
|
pinos_node_report_error (PinosNode *node,
|
|
GError *error)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_if_fail (PINOS_IS_NODE (node));
|
|
priv = node->priv;
|
|
|
|
g_clear_error (&priv->error);
|
|
remove_idle_timeout (node);
|
|
priv->error = error;
|
|
priv->state = PINOS_NODE_STATE_ERROR;
|
|
g_debug ("node %p: got error state %s", node, error->message);
|
|
g_object_notify (G_OBJECT (node), "state");
|
|
}
|
|
|
|
static gboolean
|
|
idle_timeout (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv = node->priv;
|
|
|
|
priv->idle_timeout = 0;
|
|
g_debug ("node %p: idle timeout", node);
|
|
pinos_node_set_state (node, PINOS_NODE_STATE_SUSPENDED);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/**
|
|
* pinos_node_report_idle:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Mark @node as being idle. This will start a timeout that will
|
|
* set the node to SUSPENDED.
|
|
*/
|
|
void
|
|
pinos_node_report_idle (PinosNode *node)
|
|
{
|
|
PinosNodePrivate *priv;
|
|
|
|
g_return_if_fail (PINOS_IS_NODE (node));
|
|
priv = node->priv;
|
|
|
|
g_debug ("node %p: report idle", node);
|
|
pinos_node_set_state (node, PINOS_NODE_STATE_IDLE);
|
|
|
|
priv->idle_timeout = g_timeout_add_seconds (3,
|
|
(GSourceFunc) idle_timeout,
|
|
node);
|
|
}
|
|
|
|
/**
|
|
* pinos_node_report_busy:
|
|
* @node: a #PinosNode
|
|
*
|
|
* Mark @node as being busy. This will set the state of the node
|
|
* to the RUNNING state.
|
|
*/
|
|
void
|
|
pinos_node_report_busy (PinosNode *node)
|
|
{
|
|
g_return_if_fail (PINOS_IS_NODE (node));
|
|
|
|
g_debug ("node %p: report busy", node);
|
|
pinos_node_set_state (node, PINOS_NODE_STATE_RUNNING);
|
|
}
|