pipewire/pinos/server/port.c
Wim Taymans 7b8bb8da6b implement alsa capture with timers
Add timer based alsa capture support
Improve format negotiation in alsa
Better track buffer owner in the links
2017-03-30 17:30:28 +02:00

359 lines
9.6 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;
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);
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->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->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;
}