pipewire/pinos/server/client-source.c
Wim Taymans 7597e48e02 source-output -> channel
Rename the source-output object to channel because it is used for both
input and output.
Start the beginnings of sink support. This will make it possible to make
pinos consume data as well as provide data.
2016-05-03 18:00:56 +02:00

530 lines
16 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 <gst/gst.h>
#include <gio/gio.h>
#include <pinos/server/daemon.h>
#include <pinos/server/client-source.h>
#define PINOS_CLIENT_SOURCE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), PINOS_TYPE_CLIENT_SOURCE, PinosClientSourcePrivate))
struct _PinosClientSourcePrivate
{
GstElement *pipeline;
GstElement *src;
GstElement *sink;
guint id;
GstCaps *format;
GBytes *possible_formats;
PinosChannel *channel;
};
G_DEFINE_TYPE (PinosClientSource, pinos_client_source, PINOS_TYPE_SOURCE);
enum
{
PROP_0,
PROP_POSSIBLE_FORMATS
};
static void
client_source_get_property (GObject *_object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PinosClientSource *source = PINOS_CLIENT_SOURCE (_object);
PinosClientSourcePrivate *priv = source->priv;
switch (prop_id) {
case PROP_POSSIBLE_FORMATS:
g_value_set_boxed (value, priv->possible_formats);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (source, prop_id, pspec);
break;
}
}
static void
client_source_set_property (GObject *_object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PinosClientSource *source = PINOS_CLIENT_SOURCE (_object);
PinosClientSourcePrivate *priv = source->priv;
switch (prop_id) {
case PROP_POSSIBLE_FORMATS:
if (priv->possible_formats)
g_bytes_unref (priv->possible_formats);
priv->possible_formats = g_value_dup_boxed (value);
pinos_source_update_possible_formats (PINOS_SOURCE (source),
priv->possible_formats);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (source, prop_id, pspec);
break;
}
}
static gboolean
bus_handler (GstBus *bus,
GstMessage *message,
gpointer user_data)
{
PinosSource *source = user_data;
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (source)->priv;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:
{
GError *error;
gchar *debug;
gst_message_parse_error (message, &error, &debug);
g_warning ("got error %s (%s)\n", error->message, debug);
g_free (debug);
pinos_source_report_error (source, error);
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
break;
}
case GST_MESSAGE_ELEMENT:
{
if (gst_message_has_name (message, "PinosPayloaderFormatChange")) {
const GstStructure *str = gst_message_get_structure (message);
GstCaps *caps;
GBytes *format;
gchar *caps_str;
gst_structure_get (str, "format", GST_TYPE_CAPS, &caps, NULL);
gst_caps_replace (&priv->format, caps);
caps_str = gst_caps_to_string (caps);
format = g_bytes_new_take (caps_str, strlen (caps_str) + 1);
g_object_set (priv->channel, "possible-formats", format, "format", format, NULL);
pinos_source_update_possible_formats (source, format);
pinos_source_update_format (source, format);
g_bytes_unref (format);
}
break;
}
default:
break;
}
return TRUE;
}
static void
setup_pipeline (PinosClientSource *source)
{
PinosClientSourcePrivate *priv = source->priv;
GstBus *bus;
priv->pipeline = gst_parse_launch ("socketsrc "
"name=src "
"caps=application/x-pinos "
"send-messages=true ! "
"pinossocketsink "
"name=sink "
"enable-last-sample=false ",
NULL);
priv->sink = gst_bin_get_by_name (GST_BIN (priv->pipeline), "sink");
priv->src = gst_bin_get_by_name (GST_BIN (priv->pipeline), "src");
bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
priv->id = gst_bus_add_watch (bus, bus_handler, source);
gst_object_unref (bus);
g_debug ("client-source %p: setup pipeline", source);
}
static GstCaps *
collect_caps (PinosSource *source,
GstCaps *filter)
{
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (source)->priv;
if (priv->format)
return gst_caps_ref (priv->format);
else
return gst_caps_new_any ();
}
static gboolean
client_set_state (PinosSource *source,
PinosSourceState state)
{
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (source)->priv;
switch (state) {
case PINOS_SOURCE_STATE_SUSPENDED:
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
break;
case PINOS_SOURCE_STATE_INITIALIZING:
gst_element_set_state (priv->pipeline, GST_STATE_READY);
break;
case PINOS_SOURCE_STATE_IDLE:
gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
break;
case PINOS_SOURCE_STATE_RUNNING:
gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
break;
case PINOS_SOURCE_STATE_ERROR:
break;
}
pinos_source_update_state (source, state);
return TRUE;
}
static GBytes *
client_get_formats (PinosSource *source,
GBytes *filter,
GError **error)
{
GstCaps *caps, *cfilter;
gchar *str;
if (filter) {
cfilter = gst_caps_from_string (g_bytes_get_data (filter, NULL));
if (cfilter == NULL)
goto invalid_filter;
} else {
cfilter = NULL;
}
caps = collect_caps (source, cfilter);
if (caps == NULL)
goto no_format;
str = gst_caps_to_string (caps);
gst_caps_unref (caps);
if (cfilter)
gst_caps_unref (cfilter);
return g_bytes_new_take (str, strlen (str) + 1);
invalid_filter:
{
if (error)
*error = g_error_new (G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
"Invalid filter received");
return NULL;
}
no_format:
{
if (error)
*error = g_error_new (G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"No compatible format found");
if (cfilter)
gst_caps_unref (cfilter);
return NULL;
}
}
static void
on_socket_notify (GObject *gobject,
GParamSpec *pspec,
gpointer user_data)
{
PinosClientSource *source = user_data;
PinosClientSourcePrivate *priv = source->priv;
GSocket *socket;
guint num_handles;
g_object_get (gobject, "socket", &socket, NULL);
g_debug ("client-source %p: output socket notify %p", source, socket);
if (socket == NULL) {
GSocket *prev_socket = g_object_steal_data (gobject, "last-socket");
if (prev_socket) {
g_signal_emit_by_name (priv->sink, "remove", prev_socket);
g_object_unref (prev_socket);
}
} else {
g_signal_emit_by_name (priv->sink, "add", socket);
g_object_set_data_full (gobject, "last-socket", socket, g_object_unref);
}
g_object_get (priv->sink, "num-handles", &num_handles, NULL);
if (num_handles > 0 && socket) {
GBytes *format;
/* suggest what we provide */
g_object_get (priv->channel, "format", &format, NULL);
g_object_set (gobject, "format", format, NULL);
g_bytes_unref (format);
}
}
static PinosChannel *
client_create_channel (PinosSource *source,
const gchar *client_path,
GBytes *format_filter,
PinosProperties *props,
const gchar *prefix,
GError **error)
{
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (source)->priv;
PinosChannel *channel;
/* propose format of input */
g_object_get (priv->channel, "format", &format_filter, NULL);
channel = PINOS_SOURCE_CLASS (pinos_client_source_parent_class)
->create_channel (source,
client_path,
format_filter,
props,
prefix,
error);
g_bytes_unref (format_filter);
if (channel == NULL)
return NULL;
g_debug ("client-source %p: create channel %p", source, channel);
g_signal_connect (channel, "notify::socket", (GCallback) on_socket_notify, source);
return channel;
}
static gboolean
client_release_channel (PinosSource *source,
PinosChannel *channel)
{
g_debug ("client-source %p: release channel %p", source, channel);
return PINOS_SOURCE_CLASS (pinos_client_source_parent_class)->release_channel (source, channel);
}
static void
client_source_dispose (GObject * object)
{
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (object)->priv;
g_debug ("client-source %p: dispose", object);
g_source_remove (priv->id);
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
G_OBJECT_CLASS (pinos_client_source_parent_class)->dispose (object);
}
static void
client_source_finalize (GObject * object)
{
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (object)->priv;
g_debug ("client-source %p: finalize", object);
g_clear_object (&priv->channel);
g_clear_object (&priv->sink);
g_clear_object (&priv->src);
g_clear_object (&priv->pipeline);
if (priv->possible_formats)
g_bytes_unref (priv->possible_formats);
gst_caps_replace (&priv->format, NULL);
G_OBJECT_CLASS (pinos_client_source_parent_class)->finalize (object);
}
static void
on_input_socket_notify (GObject *gobject,
GParamSpec *pspec,
gpointer user_data)
{
PinosClientSource *source = user_data;
PinosClientSourcePrivate *priv = source->priv;
GSocket *socket;
GBytes *requested_format;
GstCaps *caps;
g_object_get (gobject, "socket", &socket, NULL);
g_debug ("client-source %p: input socket notify %p", source, socket);
if (socket) {
/* requested format is final format */
g_object_get (gobject, "requested-format", &requested_format, NULL);
g_assert (requested_format != NULL);
g_object_set (gobject, "format", requested_format, NULL);
/* and set as the current format */
caps = gst_caps_from_string (g_bytes_get_data (requested_format, NULL));
g_assert (caps != NULL);
gst_caps_take (&priv->format, caps);
g_bytes_unref (requested_format);
} else {
gst_caps_replace (&priv->format, NULL);
}
g_object_set (priv->src, "socket", socket, NULL);
if (socket) {
g_debug ("client-source %p: set pipeline to PLAYING", source);
gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
g_object_unref (socket);
} else {
g_debug ("client-source %p: set pipeline to READY", source);
gst_element_set_state (priv->pipeline, GST_STATE_READY);
}
}
static void
handle_remove_channel (PinosChannel *channel,
gpointer user_data)
{
PinosClientSource *source = user_data;
PinosClientSourcePrivate *priv = source->priv;
g_debug ("client-source %p: remove channel %p", source, priv->channel);
g_clear_pointer (&priv->channel, g_object_unref);
}
/**
* pinos_client_source_get_channel:
* @source: a #PinosClientSource
* @client_path: the client path
* @format_filter: a #GBytes
* @props: extra properties
* @prefix: a path prefix
* @error: a #GError or %NULL
*
* Create a new #PinosChannel that can be used to send data to
* the pinos server.
*
* Returns: a new #PinosChannel.
*/
PinosChannel *
pinos_client_source_get_channel (PinosClientSource *source,
const gchar *client_path,
GBytes *format_filter,
PinosProperties *props,
const gchar *prefix,
GError **error)
{
PinosClientSourcePrivate *priv;
g_return_val_if_fail (PINOS_IS_CLIENT_SOURCE (source), NULL);
priv = source->priv;
if (priv->channel == NULL) {
GstCaps *caps = gst_caps_from_string (g_bytes_get_data (format_filter, NULL));
gst_caps_take (&priv->format, caps);
priv->channel = PINOS_SOURCE_CLASS (pinos_client_source_parent_class)
->create_channel (PINOS_SOURCE (source),
client_path,
format_filter,
props,
prefix,
error);
if (priv->channel == NULL)
return NULL;
g_signal_connect (priv->channel,
"remove",
(GCallback) handle_remove_channel,
source);
g_debug ("client-source %p: get source input %p", source, priv->channel);
g_signal_connect (priv->channel, "notify::socket", (GCallback) on_input_socket_notify, source);
}
return g_object_ref (priv->channel);
}
static void
pinos_client_source_class_init (PinosClientSourceClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
PinosSourceClass *source_class = PINOS_SOURCE_CLASS (klass);
g_type_class_add_private (klass, sizeof (PinosClientSourcePrivate));
gobject_class->dispose = client_source_dispose;
gobject_class->finalize = client_source_finalize;
gobject_class->get_property = client_source_get_property;
gobject_class->set_property = client_source_set_property;
g_object_class_install_property (gobject_class,
PROP_POSSIBLE_FORMATS,
g_param_spec_boxed ("possible-formats",
"Possible Format",
"The possible formats of the stream",
G_TYPE_BYTES,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
source_class->get_formats = client_get_formats;
source_class->set_state = client_set_state;
source_class->create_channel = client_create_channel;
source_class->release_channel = client_release_channel;
}
static void
pinos_client_source_init (PinosClientSource * source)
{
source->priv = PINOS_CLIENT_SOURCE_GET_PRIVATE (source);
g_debug ("client-source %p: new", source);
setup_pipeline (source);
}
/**
* pinos_client_source_new:
* @daemon: the parent #PinosDaemon
* @possible_formats: a #GBytes
*
* Make a new #PinosSource that can be used to receive data from a client.
*
* Returns: a new #PinosSource.
*/
PinosSource *
pinos_client_source_new (PinosDaemon *daemon,
GBytes *possible_formats)
{
return g_object_new (PINOS_TYPE_CLIENT_SOURCE,
"daemon", daemon,
"name", "client-source",
"possible-formats", possible_formats,
NULL);
}