pipewire/pinos/server/port.c
Wim Taymans d3682067fa node: remove node state
Remove the node state. The state of the node is based on the state
of the ports, which can be derived directly from calling the port
methods. Track this state in the Port instead.
Add a mixer module that puts a mixer in from of audio sinks. This allows
multiple clients to play on one sink (still has some bugs). do some
fixes in the mixer and the scheduler to make this work.
2017-04-08 20:33:54 +02:00

363 lines
9.7 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 <errno.h>
#include "pinos/client/pinos.h"
#include "pinos/server/port.h"
typedef struct
{
PinosPort this;
uint32_t seq;
} PinosPortImpl;
PinosPort *
pinos_port_new (PinosNode *node,
PinosDirection direction,
uint32_t port_id)
{
PinosPortImpl *impl;
PinosPort *this;
impl = calloc (1, sizeof (PinosPortImpl));
if (impl == NULL)
return NULL;
this = &impl->this;
this->node = node;
this->direction = direction;
this->port_id = port_id;
this->state = SPA_PORT_STATE_CONFIGURE;
spa_list_init (&this->links);
spa_list_init (&this->rt.links);
pinos_signal_init (&this->destroy_signal);
return this;
}
void
pinos_port_destroy (PinosPort *port)
{
pinos_log_debug ("port %p: destroy", port);
pinos_signal_emit (&port->destroy_signal, port);
spa_list_remove (&port->link);
free (port);
}
static SpaResult
do_add_link (SpaLoop *loop,
bool async,
uint32_t seq,
size_t size,
void *data,
void *user_data)
{
PinosPort *this = user_data;
PinosLink *link = ((PinosLink**)data)[0];
if (this->direction == PINOS_DIRECTION_INPUT) {
spa_list_insert (this->rt.links.prev, &link->rt.input_link);
link->rt.input = this;
}
else {
spa_list_insert (this->rt.links.prev, &link->rt.output_link);
link->rt.output = this;
}
return SPA_RESULT_OK;
}
static PinosLink *
find_link (PinosPort *output_port, PinosPort *input_port)
{
PinosLink *pl;
spa_list_for_each (pl, &output_port->links, output_link) {
if (pl->input == input_port)
return pl;
}
return NULL;
}
PinosLink *
pinos_port_get_link (PinosPort *output_port,
PinosPort *input_port)
{
return find_link (output_port, input_port);
}
/**
* pinos_port_link:
* @output_port: an output port
* @input_port: an input port
* @format_filter: a format filter
* @properties: extra properties
* @error: an error or %NULL
*
* Make a link between @output_port and @input_port
*
* If the ports were already linked, the existing links will be returned.
*
* Returns: a new #PinosLink or %NULL and @error is set.
*/
PinosLink *
pinos_port_link (PinosPort *output_port,
PinosPort *input_port,
SpaFormat **format_filter,
PinosProperties *properties,
char **error)
{
PinosNode *input_node, *output_node;
PinosLink *link;
output_node = output_port->node;
input_node = input_port->node;
pinos_log_debug ("port link %p:%u -> %p:%u", output_node, output_port->port_id, input_node, input_port->port_id);
if (output_node == input_node)
goto same_node;
if (!spa_list_is_empty (&input_port->links))
goto was_linked;
link = find_link (output_port, input_port);
if (link == NULL) {
input_node->live = output_node->live;
if (output_node->clock)
input_node->clock = output_node->clock;
pinos_log_debug ("node %p: clock %p, live %d", output_node, output_node->clock, output_node->live);
link = pinos_link_new (output_node->core,
output_port,
input_port,
format_filter,
properties);
if (link == NULL)
goto no_mem;
spa_list_insert (output_port->links.prev, &link->output_link);
spa_list_insert (input_port->links.prev, &link->input_link);
output_node->n_used_output_links++;
input_node->n_used_input_links++;
pinos_loop_invoke (output_node->data_loop->loop,
do_add_link,
SPA_ID_INVALID,
sizeof (PinosLink *),
&link,
output_port);
pinos_loop_invoke (input_node->data_loop->loop,
do_add_link,
SPA_ID_INVALID,
sizeof (PinosLink *),
&link,
input_port);
}
return link;
same_node:
{
asprintf (error, "can't link a node to itself");
return NULL;
}
was_linked:
{
asprintf (error, "input port was already linked");
return NULL;
}
no_mem:
return NULL;
}
static SpaResult
pinos_port_pause (PinosPort *port)
{
SpaCommand cmd = SPA_COMMAND_INIT (port->node->core->type.command_node.Pause);
port->state = SPA_PORT_STATE_PAUSED;
return spa_node_port_send_command (port->node->node,
port->direction,
port->port_id,
&cmd);
}
static SpaResult
do_remove_link_done (SpaLoop *loop,
bool async,
uint32_t seq,
size_t size,
void *data,
void *user_data)
{
PinosPort *port = user_data;
PinosNode *node = port->node;
PinosLink *link = ((PinosLink**)data)[0];
pinos_log_debug ("port %p: finish unlink", port);
if (port->direction == PINOS_DIRECTION_OUTPUT) {
if (link->output) {
spa_list_remove (&link->output_link);
node->n_used_output_links--;
link->output = NULL;
}
} else {
if (link->input) {
spa_list_remove (&link->input_link);
node->n_used_input_links--;
link->input = NULL;
}
}
if (!port->allocated) {
pinos_log_debug ("port %p: clear buffers on port", port);
spa_node_port_use_buffers (port->node->node,
port->direction,
port->port_id,
NULL, 0);
port->state = SPA_PORT_STATE_READY;
port->buffers = NULL;
port->n_buffers = 0;
}
if (node->n_used_output_links == 0 &&
node->n_used_input_links == 0) {
pinos_node_update_state (node, PINOS_NODE_STATE_IDLE, NULL);
}
return SPA_RESULT_OK;
}
static SpaResult
do_remove_link (SpaLoop *loop,
bool async,
uint32_t seq,
size_t size,
void *data,
void *user_data)
{
PinosPort *port = user_data;
PinosNode *this = port->node;
PinosLink *link = ((PinosLink**)data)[0];
SpaResult res;
if (port->direction == PINOS_DIRECTION_INPUT) {
spa_list_remove (&link->rt.input_link);
link->rt.input = NULL;
} else {
spa_list_remove (&link->rt.output_link);
link->rt.output = NULL;
}
res = pinos_loop_invoke (this->core->main_loop->loop,
do_remove_link_done,
seq,
sizeof (PinosLink *),
&link,
port);
return res;
}
SpaResult
pinos_port_unlink (PinosPort *port, PinosLink *link)
{
SpaResult res;
PinosPortImpl *impl = SPA_CONTAINER_OF (port, PinosPortImpl, this);
pinos_log_debug ("port %p: start unlink %p", port, link);
res = pinos_loop_invoke (port->node->data_loop->loop,
do_remove_link,
impl->seq++,
sizeof (PinosLink *),
&link,
port);
return res;
}
static SpaResult
do_clear_buffers_done (SpaLoop *loop,
bool async,
uint32_t seq,
size_t size,
void *data,
void *user_data)
{
PinosPort *port = user_data;
SpaResult res;
pinos_log_debug ("port %p: clear buffers finish", port);
res = spa_node_port_use_buffers (port->node->node,
port->direction,
port->port_id,
NULL, 0);
port->state = SPA_PORT_STATE_READY;
port->buffers = NULL;
port->n_buffers = 0;
return res;
}
static SpaResult
do_clear_buffers (SpaLoop *loop,
bool async,
uint32_t seq,
size_t size,
void *data,
void *user_data)
{
PinosPort *port = user_data;
PinosNode *node = port->node;
SpaResult res;
pinos_port_pause (port);
res = pinos_loop_invoke (node->core->main_loop->loop,
do_clear_buffers_done,
seq,
0, NULL,
port);
return res;
}
SpaResult
pinos_port_clear_buffers (PinosPort *port)
{
SpaResult res;
PinosPortImpl *impl = SPA_CONTAINER_OF (port, PinosPortImpl, this);
pinos_log_debug ("port %p: clear buffers", port);
res = pinos_loop_invoke (port->node->data_loop->loop,
do_clear_buffers,
impl->seq++,
0, NULL,
port);
return res;
}