pipewire/pinos/client/stream.c
Wim Taymans 5f10a933a1 remove client object
Remove the client object, it is not very useful now that we have the
nodes.
Fix some properties on the proxy objects.
Use sendmsg and recvmsg directly because the GIO ones do allocations.
make pinos_properties_merge and use it to combine properties from nodes
and ports.
2016-05-18 17:22:34 +02:00

908 lines
26 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 <sys/socket.h>
#include <string.h>
#include <gio/gunixfdlist.h>
#include <gio/gunixfdmessage.h>
#include "pinos/server/daemon.h"
#include "pinos/client/pinos.h"
#include "pinos/client/context.h"
#include "pinos/client/stream.h"
#include "pinos/client/enumtypes.h"
#include "pinos/client/private.h"
struct _PinosStreamPrivate
{
PinosContext *context;
gchar *name;
PinosProperties *properties;
guint id;
PinosStreamState state;
GError *error;
PinosDirection direction;
gchar *path;
GBytes *possible_formats;
gboolean provide;
GBytes *format;
PinosNode *node;
PinosPort *port;
gboolean disconnecting;
PinosStreamMode mode;
};
#define PINOS_STREAM_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), PINOS_TYPE_STREAM, PinosStreamPrivate))
G_DEFINE_TYPE (PinosStream, pinos_stream, G_TYPE_OBJECT);
enum
{
PROP_0,
PROP_CONTEXT,
PROP_NAME,
PROP_PROPERTIES,
PROP_STATE,
PROP_POSSIBLE_FORMATS,
PROP_FORMAT,
};
enum
{
SIGNAL_NEW_BUFFER,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
pinos_stream_get_property (GObject *_object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PinosStream *stream = PINOS_STREAM (_object);
PinosStreamPrivate *priv = stream->priv;
switch (prop_id) {
case PROP_CONTEXT:
g_value_set_object (value, priv->context);
break;
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_PROPERTIES:
g_value_set_boxed (value, priv->properties);
break;
case PROP_STATE:
g_value_set_enum (value, priv->state);
break;
case PROP_POSSIBLE_FORMATS:
g_value_set_boxed (value, priv->possible_formats);
break;
case PROP_FORMAT:
g_value_set_boxed (value, priv->format);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (stream, prop_id, pspec);
break;
}
}
static void
pinos_stream_set_property (GObject *_object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PinosStream *stream = PINOS_STREAM (_object);
PinosStreamPrivate *priv = stream->priv;
switch (prop_id) {
case PROP_CONTEXT:
priv->context = g_value_dup_object (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_FORMAT:
if (priv->format)
g_bytes_unref (priv->format);
priv->format = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (stream, prop_id, pspec);
break;
}
}
static gboolean
do_notify_state (PinosStream *stream)
{
g_object_notify (G_OBJECT (stream), "state");
g_object_unref (stream);
return FALSE;
}
static void
stream_set_state (PinosStream *stream,
PinosStreamState state,
GError *error)
{
if (stream->priv->state != state) {
if (error) {
g_clear_error (&stream->priv->error);
stream->priv->error = error;
}
stream->priv->state = state;
g_main_context_invoke (stream->priv->context->priv->context,
(GSourceFunc) do_notify_state,
g_object_ref (stream));
} else {
if (error)
g_error_free (error);
}
}
static void
subscription_cb (PinosSubscribe *subscribe,
PinosSubscriptionEvent event,
PinosSubscriptionFlags flags,
GDBusProxy *object,
gpointer user_data)
{
PinosStream *stream = PINOS_STREAM (user_data);
PinosStreamPrivate *priv = stream->priv;
switch (flags) {
case PINOS_SUBSCRIPTION_FLAG_NODE:
if (event == PINOS_SUBSCRIPTION_EVENT_REMOVE) {
if (object == priv->node && !priv->disconnecting) {
stream_set_state (stream,
PINOS_STREAM_STATE_ERROR,
g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_CLOSED,
"Node disappeared"));
}
}
break;
case PINOS_SUBSCRIPTION_FLAG_PORT:
break;
default:
break;
}
}
static void
pinos_stream_constructed (GObject * object)
{
PinosStream *stream = PINOS_STREAM (object);
PinosStreamPrivate *priv = stream->priv;
priv->id = g_signal_connect (priv->context->priv->subscribe,
"subscription-event",
(GCallback) subscription_cb,
stream);
G_OBJECT_CLASS (pinos_stream_parent_class)->constructed (object);
}
static void
pinos_stream_finalize (GObject * object)
{
PinosStream *stream = PINOS_STREAM (object);
PinosStreamPrivate *priv = stream->priv;
g_debug ("free stream %p", stream);
g_clear_object (&priv->node);
if (priv->possible_formats)
g_bytes_unref (priv->possible_formats);
if (priv->format)
g_bytes_unref (priv->format);
g_free (priv->path);
if (priv->possible_formats)
g_bytes_unref (priv->possible_formats);
g_clear_error (&priv->error);
if (priv->properties)
pinos_properties_free (priv->properties);
g_signal_handler_disconnect (priv->context->priv->subscribe, priv->id);
g_clear_object (&priv->context);
g_free (priv->name);
G_OBJECT_CLASS (pinos_stream_parent_class)->finalize (object);
}
static void
pinos_stream_class_init (PinosStreamClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (PinosStreamPrivate));
gobject_class->constructed = pinos_stream_constructed;
gobject_class->finalize = pinos_stream_finalize;
gobject_class->set_property = pinos_stream_set_property;
gobject_class->get_property = pinos_stream_get_property;
/**
* PinosStream:context
*
* The context of the stream.
*/
g_object_class_install_property (gobject_class,
PROP_CONTEXT,
g_param_spec_object ("context",
"Context",
"The context",
PINOS_TYPE_CONTEXT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* PinosStream:name
*
* The name of the stream as specified at construction time.
*/
g_object_class_install_property (gobject_class,
PROP_NAME,
g_param_spec_string ("name",
"Name",
"The name of the stream",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* PinosStream:properties
*
* The properties of the stream as specified at construction time.
*/
g_object_class_install_property (gobject_class,
PROP_PROPERTIES,
g_param_spec_boxed ("properties",
"Properties",
"The properties of the stream",
PINOS_TYPE_PROPERTIES,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* PinosStream:state
*
* The state of the stream. Use the notify::state signal to be notified
* of state changes.
*/
g_object_class_install_property (gobject_class,
PROP_STATE,
g_param_spec_enum ("state",
"State",
"The stream state",
PINOS_TYPE_STREAM_STATE,
PINOS_STREAM_STATE_UNCONNECTED,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PinosStream:possible-formats
*
* The possible formats for the stream. this can only be used after connecting
* the stream for capture or provide.
*/
g_object_class_install_property (gobject_class,
PROP_POSSIBLE_FORMATS,
g_param_spec_boxed ("possible-formats",
"Possible Formats",
"The possbile formats of the stream",
G_TYPE_BYTES,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PinosStream:formats
*
* The format of the stream. This will be set after starting the stream.
*/
g_object_class_install_property (gobject_class,
PROP_FORMAT,
g_param_spec_boxed ("format",
"Format",
"The format of the stream",
G_TYPE_BYTES,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PinosStream:new-buffer
*
* When doing pinos_stream_start() with #PINOS_STREAM_MODE_BUFFER, this signal
* will be fired whenever a new buffer can be obtained with
* pinos_stream_capture_buffer().
*/
signals[SIGNAL_NEW_BUFFER] = g_signal_new ("new-buffer",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
0,
G_TYPE_NONE);
}
static void
pinos_stream_init (PinosStream * stream)
{
PinosStreamPrivate *priv = stream->priv = PINOS_STREAM_GET_PRIVATE (stream);
g_debug ("new stream %p", stream);
priv->state = PINOS_STREAM_STATE_UNCONNECTED;
}
/**
* pinos_stream_state_as_string:
* @state: a #PinosStreamState
*
* Return the string representation of @state.
*
* Returns: the string representation of @state.
*/
const gchar *
pinos_stream_state_as_string (PinosStreamState state)
{
GEnumValue *val;
val = g_enum_get_value (G_ENUM_CLASS (g_type_class_ref (PINOS_TYPE_STREAM_STATE)),
state);
return val == NULL ? "invalid-state" : val->value_nick;
}
/**
* pinos_stream_new:
* @context: a #PinosContext
* @name: a stream name
* @properties: (transfer full): stream properties
*
* Make a new unconnected #PinosStream
*
* Returns: a new unconnected #PinosStream
*/
PinosStream *
pinos_stream_new (PinosContext *context,
const gchar *name,
PinosProperties *props)
{
PinosStream *stream;
g_return_val_if_fail (PINOS_IS_CONTEXT (context), NULL);
g_return_val_if_fail (name != NULL, NULL);
if (props == NULL) {
props = pinos_properties_new ("media.name", name, NULL);
} else if (!pinos_properties_get (props, "media.name")) {
pinos_properties_set (props, "media.name", name);
}
stream = g_object_new (PINOS_TYPE_STREAM,
"context", context,
"name", name,
"properties", props,
NULL);
pinos_properties_free (props);
return stream;
}
/**
* pinos_stream_get_state:
* @stream: a #PinosStream
*
* Get the state of @stream.
*
* Returns: the state of @stream
*/
PinosStreamState
pinos_stream_get_state (PinosStream *stream)
{
g_return_val_if_fail (PINOS_IS_STREAM (stream), PINOS_STREAM_STATE_ERROR);
return stream->priv->state;
}
/**
* pinos_stream_get_error:
* @stream: a #PinosStream
*
* Get the error of @stream.
*
* Returns: the error of @stream or %NULL when there is no error
*/
const GError *
pinos_stream_get_error (PinosStream *stream)
{
g_return_val_if_fail (PINOS_IS_STREAM (stream), NULL);
return stream->priv->error;
}
static void
on_received_buffer (PinosPort *port,
gpointer user_data)
{
PinosStream *stream = user_data;
g_signal_emit (stream, signals[SIGNAL_NEW_BUFFER], 0, NULL);
}
static void
on_port_notify (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
PinosPort *port = PINOS_PORT (object);
PinosStream *stream = user_data;
PinosStreamPrivate *priv = stream->priv;
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "format")) {
g_clear_pointer (&priv->format, g_bytes_unref);
g_object_get (port, "format", &priv->format, NULL);
g_object_notify (G_OBJECT (stream), "format");
}
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "possible-formats")) {
g_clear_pointer (&priv->possible_formats, g_bytes_unref);
g_object_get (port, "possible-formats", &priv->possible_formats, NULL);
g_object_notify (G_OBJECT (stream), "possible-formats");
}
}
static void
on_port_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PinosStream *stream = user_data;
PinosStreamPrivate *priv = stream->priv;
GError *error = NULL;
g_assert (priv->node == PINOS_NODE (source_object));
priv->port = pinos_node_create_port_finish (priv->node,
res,
&error);
if (priv->port == NULL)
goto create_failed;
on_port_notify (G_OBJECT (priv->port), NULL, stream);
g_signal_connect (priv->port, "notify", (GCallback) on_port_notify, stream);
pinos_port_set_received_buffer_cb (priv->port, on_received_buffer, stream, NULL);
stream_set_state (stream, PINOS_STREAM_STATE_READY, NULL);
g_object_unref (stream);
return;
/* ERRORS */
create_failed:
{
g_warning ("failed to create port: %s", error->message);
stream_set_state (stream, PINOS_STREAM_STATE_ERROR, error);
g_object_unref (stream);
return;
}
}
static void
on_node_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PinosStream *stream = user_data;
PinosStreamPrivate *priv = stream->priv;
PinosContext *context = priv->context;
GError *error = NULL;
priv->node = pinos_context_create_node_finish (context, res, &error);
if (priv->node == NULL)
goto create_failed;
pinos_node_create_port (priv->node,
priv->direction,
"client-port",
priv->possible_formats,
priv->properties,
NULL, /* GCancellable *cancellable */
on_port_created,
stream);
return;
/* ERRORS */
create_failed:
{
g_warning ("failed to create node: %s", error->message);
stream_set_state (stream, PINOS_STREAM_STATE_ERROR, error);
g_object_unref (stream);
return;
}
}
static gboolean
do_connect (PinosStream *stream)
{
PinosStreamPrivate *priv = stream->priv;
PinosContext *context = priv->context;
pinos_context_create_node (context,
"client-node",
"client-node",
priv->properties,
NULL, /* GCancellable *cancellable */
on_node_created,
stream);
return FALSE;
}
/**
* pinos_stream_connect:
* @stream: a #PinosStream
* @direction: the stream direction
* @port_path: the port path to connect to or %NULL to get the default port
* @flags: a #PinosStreamFlags
* @possible_formats: (transfer full): a #GBytes with possible accepted formats
*
* Connect @stream for input or output on @port_path.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_stream_connect (PinosStream *stream,
PinosDirection direction,
const gchar *port_path,
PinosStreamFlags flags,
GBytes *possible_formats)
{
PinosStreamPrivate *priv;
PinosContext *context;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
g_return_val_if_fail (possible_formats != NULL, FALSE);
priv = stream->priv;
context = priv->context;
g_return_val_if_fail (pinos_context_get_state (context) == PINOS_CONTEXT_STATE_READY, FALSE);
g_return_val_if_fail (pinos_stream_get_state (stream) == PINOS_STREAM_STATE_UNCONNECTED, FALSE);
priv->direction = direction;
g_free (priv->path);
priv->path = g_strdup (port_path);
if (priv->possible_formats)
g_bytes_unref (priv->possible_formats);
priv->possible_formats = possible_formats;
priv->provide = FALSE;
stream_set_state (stream, PINOS_STREAM_STATE_CONNECTING, NULL);
g_main_context_invoke (context->priv->context,
(GSourceFunc) do_connect,
g_object_ref (stream));
return TRUE;
}
static gboolean
do_connect_provide (PinosStream *stream)
{
#if 0
PinosStreamPrivate *priv = stream->priv;
PinosContext *context = priv->context;
g_dbus_proxy_call (context->priv->client,
"CreateUploadChannel",
g_variant_new ("(s@a{sv})",
g_bytes_get_data (priv->possible_formats, NULL),
pinos_properties_to_variant (priv->properties)),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL, /* GCancellable *cancellable */
on_channel_created,
stream);
#endif
return FALSE;
}
/**
* pinos_stream_connect_provide:
* @stream: a #PinosStream
* @flags: a #PinosStreamFlags
* @possible_formats: (transfer full): a #GBytes
*
* Connect @stream for providing data for a new source.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_stream_connect_provide (PinosStream *stream,
PinosStreamFlags flags,
GBytes *possible_formats)
{
PinosStreamPrivate *priv;
PinosContext *context;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
g_return_val_if_fail (possible_formats != NULL, FALSE);
priv = stream->priv;
context = priv->context;
g_return_val_if_fail (pinos_context_get_state (context) == PINOS_CONTEXT_STATE_READY, FALSE);
if (priv->possible_formats)
g_bytes_unref (priv->possible_formats);
priv->possible_formats = possible_formats;
priv->provide = TRUE;
stream_set_state (stream, PINOS_STREAM_STATE_CONNECTING, NULL);
g_main_context_invoke (context->priv->context,
(GSourceFunc) do_connect_provide,
g_object_ref (stream));
return TRUE;
}
static gboolean
do_start (PinosStream *stream)
{
stream_set_state (stream, PINOS_STREAM_STATE_STREAMING, NULL);
g_object_unref (stream);
return FALSE;
}
/**
* pinos_stream_start:
* @stream: a #PinosStream
* @format: (transfer full): a #GBytes with format
* @mode: a #PinosStreamMode
*
* Start capturing from @stream in @format.
*
* When @mode is #PINOS_STREAM_MODE_SOCKET, you should connect to the notify::socket
* signal to obtain a readable socket with metadata and data.
*
* When @mode is #PINOS_STREAM_MODE_BUFFER, you should connect to the new-buffer
* signal and use pinos_stream_capture_buffer() to get the latest metadata and
* data.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_stream_start (PinosStream *stream,
GBytes *format,
PinosStreamMode mode)
{
PinosStreamPrivate *priv;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state == PINOS_STREAM_STATE_READY, FALSE);
priv->mode = mode;
priv->format = format;
stream_set_state (stream, PINOS_STREAM_STATE_STARTING, NULL);
g_main_context_invoke (priv->context->priv->context,
(GSourceFunc) do_start,
g_object_ref (stream));
return TRUE;
}
static gboolean
do_stop (PinosStream *stream)
{
stream_set_state (stream, PINOS_STREAM_STATE_READY, NULL);
g_object_unref (stream);
return FALSE;
}
/**
* pinos_stream_stop:
* @stream: a #PinosStream
*
* Stop capturing from @stream.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_stream_stop (PinosStream *stream)
{
PinosStreamPrivate *priv;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state == PINOS_STREAM_STATE_STREAMING, FALSE);
g_main_context_invoke (priv->context->priv->context,
(GSourceFunc) do_stop,
g_object_ref (stream));
return TRUE;
}
static gboolean
do_disconnect (PinosStream *stream)
{
PinosStreamPrivate *priv = stream->priv;
pinos_node_remove (priv->node);
return FALSE;
}
/**
* pinos_stream_disconnect:
* @stream: a #PinosStream
*
* Disconnect @stream.
*
* Returns: %TRUE on success
*/
gboolean
pinos_stream_disconnect (PinosStream *stream)
{
PinosStreamPrivate *priv;
PinosContext *context;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state >= PINOS_STREAM_STATE_READY, FALSE);
g_return_val_if_fail (priv->node != NULL, FALSE);
context = priv->context;
g_return_val_if_fail (pinos_context_get_state (context) >= PINOS_CONTEXT_STATE_READY, FALSE);
g_return_val_if_fail (!priv->disconnecting, FALSE);
priv->disconnecting = TRUE;
g_main_context_invoke (context->priv->context,
(GSourceFunc) do_disconnect,
g_object_ref (stream));
return TRUE;
}
/**
* pinos_stream_peek_buffer:
* @stream: a #PinosStream
*
* Get the current buffer from @stream. This function should be called from
* the new-buffer signal callback.
*
* Returns: a #PinosBuffer or %NULL when there is no buffer.
*/
PinosBuffer *
pinos_stream_peek_buffer (PinosStream *stream)
{
PinosStreamPrivate *priv;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
priv = stream->priv;
//g_return_val_if_fail (priv->state == PINOS_STREAM_STATE_STREAMING, FALSE);
return pinos_port_peek_buffer (priv->port);
}
/**
* pinos_stream_buffer_builder_init:
* @stream: a #PinosStream
* @builder: a #PinosBufferBuilder
*
* Get a #PinosBufferBuilder for @stream.
*
* Returns: a #PinosBuffer or %NULL when there is no buffer.
*/
void
pinos_stream_buffer_builder_init (PinosStream *stream, PinosBufferBuilder *builder)
{
PinosStreamPrivate *priv;
g_return_if_fail (PINOS_IS_STREAM (stream));
priv = stream->priv;
pinos_port_buffer_builder_init (priv->port, builder);
}
/**
* pinos_stream_send_buffer:
* @stream: a #PinosStream
* @buffer: a #PinosBuffer
*
* Send a buffer to @stream.
*
* For provider streams, this function should be called whenever there is a new frame
* available.
*
* For capture streams, this functions should be called for each fd-payload that
* should be released.
*
* Returns: %TRUE when @buffer was handled
*/
gboolean
pinos_stream_send_buffer (PinosStream *stream,
PinosBuffer *buffer)
{
PinosStreamPrivate *priv;
GError *error = NULL;
g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE);
g_return_val_if_fail (buffer != NULL, FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state == PINOS_STREAM_STATE_STREAMING, FALSE);
if (!pinos_port_send_buffer (priv->port, buffer, &error))
goto send_error;
return TRUE;
/* ERRORS */
send_error:
{
g_warning ("failed to send message: %s", error->message);
stream_set_state (stream, PINOS_STREAM_STATE_ERROR, error);
g_clear_error (&error);
return FALSE;
}
}