mirror of
https://gitlab.freedesktop.org/pipewire/pipewire
synced 2024-10-06 16:09:43 +00:00
Add gstreamer source element
Add a source gstreamer element Expose error in context Make it possible to set the source in the error state Add properties to a stream and use those to get a source-output Fix signal for new-buffer Attach the socket source to the thread default mainloop Make subscribe cancellable. Propagate state and error in context. Add bus handler for v4l2 source Use negotiated properties to set capsfilter in v4l2 Fix subscribe in test-client
This commit is contained in:
parent
e151150cad
commit
592e99a317
|
@ -201,6 +201,25 @@ libpulsevideocore_@PV_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS)
|
|||
libpulsevideocore_@PV_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version
|
||||
libpulsevideocore_@PV_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LTLIBICONV)
|
||||
|
||||
###################################
|
||||
# GStreamer Plugin #
|
||||
###################################
|
||||
|
||||
plugindir = $(libdir)/gstreamer-1.0
|
||||
|
||||
plugin_LTLIBRARIES = libgstpulsevideosrc.la
|
||||
|
||||
libgstpulsevideosrc_la_SOURCES = \
|
||||
gst/gstpvsrc.c
|
||||
|
||||
libgstpulsevideosrc_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(GLIB_CFLAGS)
|
||||
libgstpulsevideosrc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||
libgstpulsevideosrc_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(GLIB_LIBS) $(LIBM) -lgstvideo-1.0 \
|
||||
libpulsevideo-@PV_MAJORMINOR@.la libpulsevideocore-@PV_MAJORMINOR@.la
|
||||
libgstpulsevideosrc_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
|
||||
|
||||
noinst_HEADERS = gst/gstpvsrc.h
|
||||
|
||||
###################################
|
||||
# Some minor stuff #
|
||||
###################################
|
||||
|
|
|
@ -46,6 +46,8 @@ struct _PvContextPrivate
|
|||
GList *sources;
|
||||
|
||||
GDBusObjectManagerServer *server_manager;
|
||||
|
||||
GError *error;
|
||||
};
|
||||
|
||||
|
||||
|
@ -154,6 +156,7 @@ pv_context_finalize (GObject * object)
|
|||
PvContextPrivate *priv = context->priv;
|
||||
|
||||
g_object_unref (priv->server_manager);
|
||||
g_clear_error (&priv->error);
|
||||
|
||||
G_OBJECT_CLASS (pv_context_parent_class)->finalize (object);
|
||||
}
|
||||
|
@ -310,9 +313,9 @@ on_client_proxy (GObject *source_object,
|
|||
|
||||
priv->client = pv_client1_proxy_new_finish (res, &error);
|
||||
if (priv->client == NULL) {
|
||||
priv->error = error;
|
||||
context_set_state (context, PV_CONTEXT_STATE_ERROR);
|
||||
g_error ("failed to get client proxy: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
context_set_state (context, PV_CONTEXT_STATE_READY);
|
||||
|
@ -329,9 +332,9 @@ on_client_connected (GObject *source_object,
|
|||
gchar *client_path;
|
||||
|
||||
if (!pv_daemon1_call_connect_client_finish (priv->daemon, &client_path, res, &error)) {
|
||||
priv->error = error;
|
||||
context_set_state (context, PV_CONTEXT_STATE_ERROR);
|
||||
g_error ("failed to connect client: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -462,6 +465,7 @@ on_name_vanished (GDBusConnection *connection,
|
|||
if (priv->flags & PV_CONTEXT_FLAGS_NOFAIL) {
|
||||
context_set_state (context, PV_CONTEXT_STATE_CONNECTING);
|
||||
} else {
|
||||
priv->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CLOSED, "Connection closed");
|
||||
context_set_state (context, PV_CONTEXT_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
|
@ -514,9 +518,9 @@ on_client_disconnected (GObject *source_object,
|
|||
GError *error = NULL;
|
||||
|
||||
if (!pv_client1_call_disconnect_finish (priv->client, res, &error)) {
|
||||
priv->error = error;
|
||||
context_set_state (context, PV_CONTEXT_STATE_ERROR);
|
||||
g_error ("failed to disconnect client: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
context_set_state (context, PV_CONTEXT_STATE_UNCONNECTED);
|
||||
|
@ -612,6 +616,26 @@ pv_context_get_state (PvContext *context)
|
|||
return priv->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* pv_context_error:
|
||||
* @context: a #PvContext
|
||||
*
|
||||
* Get the current error of @context or %NULL when the context state
|
||||
* is not #PV_CONTEXT_STATE_ERROR
|
||||
*
|
||||
* Returns: the last error or %NULL
|
||||
*/
|
||||
const GError *
|
||||
pv_context_error (PvContext *context)
|
||||
{
|
||||
PvContextPrivate *priv;
|
||||
|
||||
g_return_val_if_fail (PV_IS_CONTEXT (context), NULL);
|
||||
priv = context->priv;
|
||||
|
||||
return priv->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* pv_context_get_connection:
|
||||
* @context: a #PvContext
|
||||
|
|
|
@ -107,6 +107,7 @@ gboolean pv_context_register_source (PvContext *context, PvSource
|
|||
gboolean pv_context_unregister_source (PvContext *context, PvSource *source);
|
||||
|
||||
PvContextState pv_context_get_state (PvContext *context);
|
||||
const GError * pv_context_error (PvContext *context);
|
||||
|
||||
GDBusConnection * pv_context_get_connection (PvContext *context);
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ struct _PvSourcePrivate
|
|||
gchar *name;
|
||||
PvSourceState state;
|
||||
GVariant *properties;
|
||||
|
||||
GError *error;
|
||||
};
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE (PvSource, pv_source, G_TYPE_OBJECT);
|
||||
|
@ -379,6 +381,20 @@ pv_source_update_state (PvSource *source, PvSourceState state)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
pv_source_report_error (PvSource *source, GError *error)
|
||||
{
|
||||
PvSourcePrivate *priv;
|
||||
|
||||
g_return_if_fail (PV_IS_SOURCE (source));
|
||||
priv = source->priv;
|
||||
|
||||
g_clear_error (&priv->error);
|
||||
priv->error = error;
|
||||
priv->state = PV_SOURCE_STATE_ERROR;
|
||||
g_object_notify (G_OBJECT (source), "state");
|
||||
}
|
||||
|
||||
PvSourceOutput *
|
||||
pv_source_create_source_output (PvSource *source, GVariant *props, const gchar *prefix)
|
||||
{
|
||||
|
|
|
@ -100,6 +100,7 @@ GVariant * pv_source_get_capabilities (PvSource *source, GVariant *p
|
|||
|
||||
gboolean pv_source_set_state (PvSource *source, PvSourceState state);
|
||||
void pv_source_update_state (PvSource *source, PvSourceState state);
|
||||
void pv_source_report_error (PvSource *source, GError *error);
|
||||
|
||||
PvSourceOutput * pv_source_create_source_output (PvSource *source, GVariant *props, const gchar *prefix);
|
||||
gboolean pv_source_release_source_output (PvSource *source, PvSourceOutput *output);
|
||||
|
|
|
@ -30,6 +30,7 @@ struct _PvStreamPrivate
|
|||
{
|
||||
PvContext *context;
|
||||
gchar *name;
|
||||
GVariant *properties;
|
||||
gchar *target;
|
||||
PvStreamState state;
|
||||
|
||||
|
@ -54,6 +55,7 @@ enum
|
|||
PROP_0,
|
||||
PROP_CONTEXT,
|
||||
PROP_NAME,
|
||||
PROP_PROPERTIES,
|
||||
PROP_STATE,
|
||||
PROP_SOCKET
|
||||
};
|
||||
|
@ -84,6 +86,10 @@ pv_stream_get_property (GObject *_object,
|
|||
g_value_set_string (value, priv->name);
|
||||
break;
|
||||
|
||||
case PROP_PROPERTIES:
|
||||
g_value_set_variant (value, priv->properties);
|
||||
break;
|
||||
|
||||
case PROP_STATE:
|
||||
g_value_set_enum (value, priv->state);
|
||||
break;
|
||||
|
@ -116,6 +122,12 @@ pv_stream_set_property (GObject *_object,
|
|||
priv->name = g_value_dup_string (value);
|
||||
break;
|
||||
|
||||
case PROP_PROPERTIES:
|
||||
if (priv->properties)
|
||||
g_variant_unref (priv->properties);
|
||||
priv->properties = g_value_dup_variant (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (stream, prop_id, pspec);
|
||||
break;
|
||||
|
@ -172,6 +184,21 @@ pv_stream_class_init (PvStreamClass * klass)
|
|||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* PvStream:properties
|
||||
*
|
||||
* The properties of the stream as specified at construction time.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_PROPERTIES,
|
||||
g_param_spec_variant ("properties",
|
||||
"Properties",
|
||||
"The properties of the stream",
|
||||
G_VARIANT_TYPE_VARIANT,
|
||||
NULL,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* PvStream:state
|
||||
*
|
||||
|
@ -236,18 +263,19 @@ pv_stream_init (PvStream * stream)
|
|||
* pv_stream_new:
|
||||
* @context: a #PvContext
|
||||
* @name: a stream name
|
||||
* @properties: stream properties
|
||||
*
|
||||
* Make a new unconnected #PvStream
|
||||
*
|
||||
* Returns: a new unconnected #PvStream
|
||||
*/
|
||||
PvStream *
|
||||
pv_stream_new (PvContext * context, const gchar *name)
|
||||
pv_stream_new (PvContext * context, const gchar *name, GVariant *props)
|
||||
{
|
||||
g_return_val_if_fail (PV_IS_CONTEXT (context), NULL);
|
||||
g_return_val_if_fail (name != NULL, NULL);
|
||||
|
||||
return g_object_new (PV_TYPE_STREAM, "context", context, "name", name, NULL);
|
||||
return g_object_new (PV_TYPE_STREAM, "context", context, "name", name, "properties", props, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -390,13 +418,15 @@ remove_source_output (PvStream *stream)
|
|||
gboolean
|
||||
pv_stream_connect_capture (PvStream *stream,
|
||||
const gchar *source,
|
||||
PvStreamFlags flags)
|
||||
PvStreamFlags flags,
|
||||
GVariant *spec)
|
||||
{
|
||||
PvStreamPrivate *priv;
|
||||
GVariantBuilder builder;
|
||||
PvContext *context;
|
||||
|
||||
g_return_val_if_fail (PV_IS_STREAM (stream), FALSE);
|
||||
g_return_val_if_fail (spec != NULL, FALSE);
|
||||
|
||||
priv = stream->priv;
|
||||
context = priv->context;
|
||||
g_return_val_if_fail (pv_context_get_state (context) == PV_CONTEXT_STATE_READY, FALSE);
|
||||
|
@ -408,15 +438,13 @@ pv_stream_connect_capture (PvStream *stream,
|
|||
priv->source = PV_SOURCE1 (pv_context_find_source (context, priv->target, NULL));
|
||||
if (priv->source == NULL) {
|
||||
g_warning ("can't find source");
|
||||
stream_set_state (stream, PV_STREAM_STATE_READY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
||||
g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string ("hello"));
|
||||
|
||||
pv_source1_call_create_source_output (priv->source,
|
||||
g_variant_builder_end (&builder), /* GVariant *arg_props */
|
||||
NULL, /* GCancellable *cancellable */
|
||||
spec, /* GVariant *arg_props */
|
||||
NULL, /* GCancellable *cancellable */
|
||||
on_source_output_created,
|
||||
stream);
|
||||
return TRUE;
|
||||
|
@ -497,7 +525,7 @@ on_socket_data (GSocket *socket,
|
|||
priv->info.size = msg.size;
|
||||
priv->info.message = num_messages > 0 ? messages[0] : NULL;
|
||||
|
||||
g_signal_emit (stream, SIGNAL_NEW_BUFFER, 0, NULL);
|
||||
g_signal_emit (stream, signals[SIGNAL_NEW_BUFFER], 0, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -530,7 +558,8 @@ handle_socket (PvStream *stream, gint fd)
|
|||
|
||||
source = g_socket_create_source (priv->socket, G_IO_IN, NULL);
|
||||
g_source_set_callback (source, (GSourceFunc) on_socket_data, stream, NULL);
|
||||
priv->socket_id = g_source_attach (source, NULL);
|
||||
g_print ("%p\n", g_main_context_get_thread_default ());
|
||||
priv->socket_id = g_source_attach (source, g_main_context_get_thread_default ());
|
||||
g_source_unref (source);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -94,13 +94,15 @@ GType pv_stream_get_type (void);
|
|||
|
||||
|
||||
PvStream * pv_stream_new (PvContext * context,
|
||||
const gchar *name);
|
||||
const gchar *name,
|
||||
GVariant * props);
|
||||
|
||||
PvStreamState pv_stream_get_state (PvStream *stream);
|
||||
|
||||
gboolean pv_stream_connect_capture (PvStream *stream,
|
||||
const gchar *source,
|
||||
PvStreamFlags flags);
|
||||
PvStreamFlags flags,
|
||||
GVariant *spec);
|
||||
gboolean pv_stream_disconnect (PvStream *stream);
|
||||
|
||||
gboolean pv_stream_start (PvStream *stream, PvStreamMode mode);
|
||||
|
|
|
@ -29,6 +29,7 @@ struct _PvSubscribePrivate
|
|||
PvSubscriptionState state;
|
||||
GDBusConnection *connection;
|
||||
gchar *service;
|
||||
GCancellable *cancellable;
|
||||
|
||||
PvSubscriptionFlags subscription_mask;
|
||||
|
||||
|
@ -36,6 +37,8 @@ struct _PvSubscribePrivate
|
|||
guint pending_subscribes;
|
||||
|
||||
GHashTable *senders;
|
||||
|
||||
GError *error;
|
||||
};
|
||||
|
||||
|
||||
|
@ -68,6 +71,8 @@ typedef struct {
|
|||
guint id;
|
||||
PvSubscribe *sender_subscribe;
|
||||
GList *clients;
|
||||
gulong signal_event;
|
||||
gulong signal_state;
|
||||
} SenderData;
|
||||
|
||||
static void
|
||||
|
@ -140,6 +145,7 @@ client_name_appeared_handler (GDBusConnection *connection,
|
|||
{
|
||||
SenderData *data = user_data;
|
||||
|
||||
g_print ("appeared client %s %p\n", name, data);
|
||||
/* subscribe to Source events. We want to be notified when this new
|
||||
* sender add/change/remove sources and outputs */
|
||||
data->sender_subscribe = pv_subscribe_new ();
|
||||
|
@ -148,11 +154,11 @@ client_name_appeared_handler (GDBusConnection *connection,
|
|||
"connection", connection,
|
||||
NULL);
|
||||
|
||||
g_signal_connect (data->sender_subscribe,
|
||||
data->signal_event = g_signal_connect (data->sender_subscribe,
|
||||
"subscription-event",
|
||||
(GCallback) on_sender_subscription_event,
|
||||
data);
|
||||
g_signal_connect (data->sender_subscribe,
|
||||
data->signal_state = g_signal_connect (data->sender_subscribe,
|
||||
"notify::state",
|
||||
(GCallback) on_sender_subscription_state,
|
||||
data);
|
||||
|
@ -175,18 +181,27 @@ client_name_vanished_handler (GDBusConnection *connection,
|
|||
gpointer user_data)
|
||||
{
|
||||
SenderData *data = user_data;
|
||||
PvSubscribePrivate *priv = data->subscribe->priv;
|
||||
|
||||
g_print ("vanished client %s\n", name);
|
||||
g_print ("vanished client %s %p\n", name, data);
|
||||
|
||||
g_bus_unwatch_name (data->id);
|
||||
}
|
||||
|
||||
static void
|
||||
data_free (SenderData *data)
|
||||
{
|
||||
g_print ("free client %s %p\n", data->sender, data);
|
||||
g_list_foreach (data->clients, (GFunc) remove_client, data);
|
||||
|
||||
g_hash_table_remove (priv->senders, data->sender);
|
||||
g_hash_table_remove (data->subscribe->priv->senders, data->sender);
|
||||
|
||||
if (data->sender_subscribe)
|
||||
if (data->sender_subscribe) {
|
||||
g_signal_handler_disconnect (data->sender_subscribe, data->signal_event);
|
||||
g_signal_handler_disconnect (data->sender_subscribe, data->signal_state);
|
||||
g_object_unref (data->sender_subscribe);
|
||||
}
|
||||
|
||||
g_free (data->sender);
|
||||
g_bus_unwatch_name (data->id);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
|
@ -196,19 +211,19 @@ sender_data_new (PvSubscribe *subscribe, const gchar *sender)
|
|||
PvSubscribePrivate *priv = subscribe->priv;
|
||||
SenderData *data;
|
||||
|
||||
g_print ("watch name %s\n", sender);
|
||||
|
||||
data = g_new0 (SenderData, 1);
|
||||
data->subscribe = subscribe;
|
||||
data->sender = g_strdup (sender);
|
||||
|
||||
g_print ("watch name %s %p\n", sender, data);
|
||||
|
||||
data->id = g_bus_watch_name_on_connection (priv->connection,
|
||||
sender,
|
||||
G_BUS_NAME_WATCHER_FLAGS_NONE,
|
||||
client_name_appeared_handler,
|
||||
client_name_vanished_handler,
|
||||
data,
|
||||
NULL);
|
||||
(GDestroyNotify) data_free);
|
||||
|
||||
g_hash_table_insert (priv->senders, data->sender, data);
|
||||
priv->pending_subscribes++;
|
||||
|
@ -444,14 +459,17 @@ on_client_manager_ready (GObject *source_object,
|
|||
connect_client_signals (subscribe);
|
||||
|
||||
on_client_manager_name_owner (G_OBJECT (priv->client_manager), NULL, subscribe);
|
||||
g_object_unref (subscribe);
|
||||
|
||||
return;
|
||||
|
||||
/* ERRORS */
|
||||
manager_error:
|
||||
{
|
||||
g_warning ("could not create client manager: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
subscription_set_state (subscribe, PV_SUBSCRIPTION_STATE_ERROR);
|
||||
priv->error = error;
|
||||
g_object_unref (subscribe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -468,9 +486,9 @@ install_subscription (PvSubscribe *subscribe)
|
|||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
priv->service,
|
||||
PV_DBUS_OBJECT_PREFIX,
|
||||
NULL,
|
||||
priv->cancellable,
|
||||
on_client_manager_ready,
|
||||
subscribe);
|
||||
g_object_ref (subscribe));
|
||||
priv->pending_subscribes++;
|
||||
}
|
||||
|
||||
|
@ -480,6 +498,7 @@ uninstall_subscription (PvSubscribe *subscribe)
|
|||
PvSubscribePrivate *priv = subscribe->priv;
|
||||
|
||||
g_clear_object (&priv->client_manager);
|
||||
g_clear_error (&priv->error);
|
||||
subscription_set_state (subscribe, PV_SUBSCRIPTION_STATE_UNCONNECTED);
|
||||
}
|
||||
|
||||
|
@ -557,9 +576,13 @@ pv_subscribe_finalize (GObject * object)
|
|||
PvSubscribe *subscribe = PV_SUBSCRIBE (object);
|
||||
PvSubscribePrivate *priv = subscribe->priv;
|
||||
|
||||
g_free (priv->service);
|
||||
g_object_unref (priv->client_manager);
|
||||
g_print ("cancel\n");
|
||||
g_cancellable_cancel (priv->cancellable);
|
||||
g_hash_table_unref (priv->senders);
|
||||
if (priv->client_manager)
|
||||
g_object_unref (priv->client_manager);
|
||||
g_object_unref (priv->cancellable);
|
||||
g_free (priv->service);
|
||||
|
||||
G_OBJECT_CLASS (pv_subscribe_parent_class)->finalize (object);
|
||||
}
|
||||
|
@ -661,6 +684,7 @@ pv_subscribe_init (PvSubscribe * subscribe)
|
|||
priv->service = g_strdup (PV_DBUS_SERVICE);
|
||||
priv->senders = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
priv->state = PV_SUBSCRIPTION_STATE_UNCONNECTED;
|
||||
priv->cancellable = g_cancellable_new ();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -677,3 +701,26 @@ pv_subscribe_new (void)
|
|||
{
|
||||
return g_object_new (PV_TYPE_SUBSCRIBE, NULL);
|
||||
}
|
||||
|
||||
PvSubscriptionState
|
||||
pv_subscribe_get_state (PvSubscribe *subscribe)
|
||||
{
|
||||
PvSubscribePrivate *priv;
|
||||
|
||||
g_return_val_if_fail (PV_IS_SUBSCRIBE (subscribe), PV_SUBSCRIPTION_STATE_ERROR);
|
||||
priv = subscribe->priv;
|
||||
|
||||
return priv->state;
|
||||
}
|
||||
|
||||
GError *
|
||||
pv_subscribe_get_error (PvSubscribe *subscribe)
|
||||
{
|
||||
PvSubscribePrivate *priv;
|
||||
|
||||
g_return_val_if_fail (PV_IS_SUBSCRIBE (subscribe), NULL);
|
||||
priv = subscribe->priv;
|
||||
|
||||
return priv->error;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,9 +80,14 @@ struct _PvSubscribeClass {
|
|||
};
|
||||
|
||||
/* normal GObject stuff */
|
||||
GType pv_subscribe_get_type (void);
|
||||
GType pv_subscribe_get_type (void);
|
||||
|
||||
PvSubscribe * pv_subscribe_new (void);
|
||||
|
||||
PvSubscriptionState pv_subscribe_get_state (PvSubscribe *subscribe);
|
||||
GError * pv_subscribe_get_error (PvSubscribe *subscribe);
|
||||
|
||||
|
||||
PvSubscribe * pv_subscribe_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ main (gint argc, gchar *argv[])
|
|||
daemon = pv_daemon_new ();
|
||||
|
||||
source = pv_v4l2_source_new();
|
||||
//pv_daemon_add_source (daemon, source);
|
||||
pv_daemon_add_source (daemon, source);
|
||||
pv_daemon_start (daemon);
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
|
602
src/gst/gstpvsrc.c
Normal file
602
src/gst/gstpvsrc.c
Normal file
|
@ -0,0 +1,602 @@
|
|||
/* GStreamer
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-pulsevideosrc
|
||||
*
|
||||
* <refsect2>
|
||||
* <title>Example launch line</title>
|
||||
* |[
|
||||
* gst-launch -v pulsevideosrc ! ximagesink
|
||||
* ]| Shows pulsevideo output in an X window.
|
||||
* </refsect2>
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include "gstpvsrc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gio/gunixfdmessage.h>
|
||||
#include <gst/allocators/gstfdmemory.h>
|
||||
|
||||
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (pulsevideo_src_debug);
|
||||
#define GST_CAT_DEFAULT pulsevideo_src_debug
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
|
||||
#define PVS_VIDEO_CAPS GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL)
|
||||
|
||||
static GstStaticPadTemplate gst_pulsevideo_src_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (PVS_VIDEO_CAPS)
|
||||
);
|
||||
|
||||
#define gst_pulsevideo_src_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstPulsevideoSrc, gst_pulsevideo_src, GST_TYPE_PUSH_SRC);
|
||||
|
||||
static void gst_pulsevideo_src_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_pulsevideo_src_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_pulsevideo_src_change_state (GstElement * element, GstStateChange transition);
|
||||
|
||||
static GstCaps *gst_pulsevideo_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter);
|
||||
static gboolean gst_pulsevideo_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps);
|
||||
static GstCaps *gst_pulsevideo_src_src_fixate (GstBaseSrc * bsrc,
|
||||
GstCaps * caps);
|
||||
|
||||
static gboolean gst_pulsevideo_src_query (GstBaseSrc * bsrc, GstQuery * query);
|
||||
|
||||
static gboolean gst_pulsevideo_src_decide_allocation (GstBaseSrc * bsrc,
|
||||
GstQuery * query);
|
||||
static GstFlowReturn gst_pulsevideo_src_create (GstPushSrc * psrc,
|
||||
GstBuffer ** buffer);
|
||||
static gboolean gst_pulsevideo_src_start (GstBaseSrc * basesrc);
|
||||
static gboolean gst_pulsevideo_src_stop (GstBaseSrc * basesrc);
|
||||
|
||||
static void
|
||||
gst_pulsevideo_src_class_init (GstPulsevideoSrcClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSrcClass *gstbasesrc_class;
|
||||
GstPushSrcClass *gstpushsrc_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesrc_class = (GstBaseSrcClass *) klass;
|
||||
gstpushsrc_class = (GstPushSrcClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_pulsevideo_src_set_property;
|
||||
gobject_class->get_property = gst_pulsevideo_src_get_property;
|
||||
|
||||
gstelement_class->change_state = gst_pulsevideo_src_change_state;
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class,
|
||||
"Pulsevideo source", "Source/Video",
|
||||
"Uses pulsevideo to create video", "Wim Taymans <wim.taymans@gmail.com>");
|
||||
|
||||
gst_element_class_add_pad_template (gstelement_class,
|
||||
gst_static_pad_template_get (&gst_pulsevideo_src_template));
|
||||
|
||||
gstbasesrc_class->get_caps = gst_pulsevideo_src_getcaps;
|
||||
gstbasesrc_class->set_caps = gst_pulsevideo_src_setcaps;
|
||||
gstbasesrc_class->fixate = gst_pulsevideo_src_src_fixate;
|
||||
gstbasesrc_class->query = gst_pulsevideo_src_query;
|
||||
gstbasesrc_class->start = gst_pulsevideo_src_start;
|
||||
gstbasesrc_class->stop = gst_pulsevideo_src_stop;
|
||||
gstbasesrc_class->decide_allocation = gst_pulsevideo_src_decide_allocation;
|
||||
|
||||
gstpushsrc_class->create = gst_pulsevideo_src_create;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pulsevideo_src_init (GstPulsevideoSrc * src)
|
||||
{
|
||||
/* we operate in time */
|
||||
gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
|
||||
gst_base_src_set_live (GST_BASE_SRC (src), TRUE);
|
||||
|
||||
src->fd_allocator = gst_fd_allocator_new ();
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_pulsevideo_src_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
|
||||
{
|
||||
GstStructure *structure;
|
||||
|
||||
caps = gst_caps_make_writable (caps);
|
||||
|
||||
structure = gst_caps_get_structure (caps, 0);
|
||||
|
||||
gst_structure_fixate_field_nearest_int (structure, "width", 320);
|
||||
gst_structure_fixate_field_nearest_int (structure, "height", 240);
|
||||
gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
|
||||
|
||||
if (gst_structure_has_field (structure, "pixel-aspect-ratio"))
|
||||
gst_structure_fixate_field_nearest_fraction (structure,
|
||||
"pixel-aspect-ratio", 1, 1);
|
||||
else
|
||||
gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
|
||||
NULL);
|
||||
|
||||
if (gst_structure_has_field (structure, "colorimetry"))
|
||||
gst_structure_fixate_field_string (structure, "colorimetry", "bt601");
|
||||
if (gst_structure_has_field (structure, "chroma-site"))
|
||||
gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2");
|
||||
|
||||
if (gst_structure_has_field (structure, "interlace-mode"))
|
||||
gst_structure_fixate_field_string (structure, "interlace-mode",
|
||||
"progressive");
|
||||
else
|
||||
gst_structure_set (structure, "interlace-mode", G_TYPE_STRING,
|
||||
"progressive", NULL);
|
||||
|
||||
caps = GST_BASE_SRC_CLASS (parent_class)->fixate (bsrc, caps);
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pulsevideo_src_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
switch (prop_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_pulsevideo_src_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
switch (prop_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pulsevideo_src_decide_allocation (GstBaseSrc * bsrc, GstQuery * query)
|
||||
{
|
||||
GstPulsevideoSrc *pvsrc;
|
||||
GstBufferPool *pool;
|
||||
gboolean update;
|
||||
guint size, min, max;
|
||||
GstStructure *config;
|
||||
GstCaps *caps = NULL;
|
||||
|
||||
pvsrc = GST_PULSEVIDEO_SRC (bsrc);
|
||||
|
||||
if (gst_query_get_n_allocation_pools (query) > 0) {
|
||||
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
|
||||
|
||||
/* adjust size */
|
||||
size = MAX (size, pvsrc->info.size);
|
||||
update = TRUE;
|
||||
} else {
|
||||
pool = NULL;
|
||||
size = pvsrc->info.size;
|
||||
min = max = 0;
|
||||
update = FALSE;
|
||||
}
|
||||
|
||||
/* no downstream pool, make our own */
|
||||
if (pool == NULL) {
|
||||
pool = gst_video_buffer_pool_new ();
|
||||
}
|
||||
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
|
||||
gst_query_parse_allocation (query, &caps, NULL);
|
||||
if (caps)
|
||||
gst_buffer_pool_config_set_params (config, caps, size, min, max);
|
||||
|
||||
if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) {
|
||||
gst_buffer_pool_config_add_option (config,
|
||||
GST_BUFFER_POOL_OPTION_VIDEO_META);
|
||||
}
|
||||
gst_buffer_pool_set_config (pool, config);
|
||||
|
||||
if (update)
|
||||
gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
|
||||
else
|
||||
gst_query_add_allocation_pool (query, pool, size, min, max);
|
||||
|
||||
if (pool)
|
||||
gst_object_unref (pool);
|
||||
|
||||
return GST_BASE_SRC_CLASS (parent_class)->decide_allocation (bsrc, query);
|
||||
}
|
||||
|
||||
static void
|
||||
on_new_buffer (GObject *gobject,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstPulsevideoSrc *pvsrc = user_data;
|
||||
|
||||
g_main_loop_quit (pvsrc->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_socket_notify (GObject *gobject,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSocket *socket;
|
||||
|
||||
g_object_get (gobject, "socket", &socket, NULL);
|
||||
g_print ("got socket %p\n", socket);
|
||||
}
|
||||
|
||||
static void
|
||||
on_stream_notify (GObject *gobject,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
PvStreamState state;
|
||||
GstPulsevideoSrc *pvsrc = user_data;
|
||||
|
||||
g_object_get (gobject, "state", &state, NULL);
|
||||
g_print ("got stream state %d\n", state);
|
||||
|
||||
switch (state) {
|
||||
case PV_STREAM_STATE_ERROR:
|
||||
g_main_loop_quit (pvsrc->loop);
|
||||
break;
|
||||
case PV_STREAM_STATE_READY:
|
||||
g_main_loop_quit (pvsrc->loop);
|
||||
g_main_context_push_thread_default (pvsrc->context);
|
||||
pv_stream_start (pvsrc->stream, PV_STREAM_MODE_BUFFER);
|
||||
g_main_context_pop_thread_default (pvsrc->context);
|
||||
break;
|
||||
case PV_STREAM_STATE_STREAMING:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_pulsevideo_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter)
|
||||
{
|
||||
return GST_BASE_SRC_CLASS (parent_class)->get_caps (bsrc, filter);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pulsevideo_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps)
|
||||
{
|
||||
const GstStructure *structure;
|
||||
GstPulsevideoSrc *pvsrc;
|
||||
GstVideoInfo info;
|
||||
GVariantBuilder builder;
|
||||
|
||||
pvsrc = GST_PULSEVIDEO_SRC (bsrc);
|
||||
|
||||
structure = gst_caps_get_structure (caps, 0);
|
||||
|
||||
if (gst_structure_has_name (structure, "video/x-raw")) {
|
||||
/* we can use the parsing code */
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto parse_failed;
|
||||
|
||||
} else {
|
||||
goto unsupported_caps;
|
||||
}
|
||||
|
||||
/* looks ok here */
|
||||
pvsrc->info = info;
|
||||
|
||||
g_main_context_push_thread_default (pvsrc->context);
|
||||
pvsrc->stream = pv_stream_new (pvsrc->ctx, "test", NULL);
|
||||
g_signal_connect (pvsrc->stream, "notify::state", (GCallback) on_stream_notify, pvsrc);
|
||||
g_signal_connect (pvsrc->stream, "notify::socket", (GCallback) on_socket_notify, pvsrc);
|
||||
g_signal_connect (pvsrc->stream, "new-buffer", (GCallback) on_new_buffer, pvsrc);
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
||||
g_variant_builder_add (&builder, "{sv}", "format.encoding", g_variant_new_string ("video/x-raw"));
|
||||
g_variant_builder_add (&builder, "{sv}", "format.format",
|
||||
g_variant_new_string (gst_video_format_to_string (info.finfo->format)));
|
||||
g_variant_builder_add (&builder, "{sv}", "format.width", g_variant_new_int32 (info.width));
|
||||
g_variant_builder_add (&builder, "{sv}", "format.height", g_variant_new_int32 (info.height));
|
||||
g_variant_builder_add (&builder, "{sv}", "format.views", g_variant_new_int32 (info.views));
|
||||
// g_variant_builder_add (&builder, "{sv}", "format.chroma-site",
|
||||
// g_variant_new_string (gst_video_chroma_to_string (info.chroma_site)));
|
||||
// g_variant_builder_add (&builder, "{sv}", "format.colorimetry",
|
||||
// g_variant_new_take_string (gst_video_colorimetry_to_string (&info.colorimetry)));
|
||||
// g_variant_builder_add (&builder, "{sv}", "format.interlace-mode",
|
||||
// g_variant_new_string (gst_video_interlace_mode_to_string (info.interlace_mode)));
|
||||
|
||||
pv_stream_connect_capture (pvsrc->stream, NULL, 0, g_variant_builder_end (&builder));
|
||||
g_main_context_pop_thread_default (pvsrc->context);
|
||||
|
||||
g_main_loop_run (pvsrc->loop);
|
||||
|
||||
GST_DEBUG_OBJECT (pvsrc, "size %dx%d, %d/%d fps",
|
||||
info.width, info.height, info.fps_n, info.fps_d);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
parse_failed:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsrc, "failed to parse caps");
|
||||
return FALSE;
|
||||
}
|
||||
unsupported_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsrc, "unsupported caps: %" GST_PTR_FORMAT, caps);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pulsevideo_src_query (GstBaseSrc * bsrc, GstQuery * query)
|
||||
{
|
||||
gboolean res = FALSE;
|
||||
GstPulsevideoSrc *src;
|
||||
|
||||
src = GST_PULSEVIDEO_SRC (bsrc);
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_CONVERT:
|
||||
{
|
||||
GstFormat src_fmt, dest_fmt;
|
||||
gint64 src_val, dest_val;
|
||||
|
||||
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
|
||||
res =
|
||||
gst_video_info_convert (&src->info, src_fmt, src_val, dest_fmt,
|
||||
&dest_val);
|
||||
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
|
||||
break;
|
||||
}
|
||||
case GST_QUERY_LATENCY:
|
||||
{
|
||||
if (src->info.fps_n > 0) {
|
||||
GstClockTime latency;
|
||||
|
||||
latency =
|
||||
gst_util_uint64_scale (GST_SECOND, src->info.fps_d,
|
||||
src->info.fps_n);
|
||||
gst_query_set_latency (query,
|
||||
gst_base_src_is_live (GST_BASE_SRC_CAST (src)), latency,
|
||||
GST_CLOCK_TIME_NONE);
|
||||
GST_DEBUG_OBJECT (src, "Reporting latency of %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (latency));
|
||||
res = TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_QUERY_DURATION:{
|
||||
if (bsrc->num_buffers != -1) {
|
||||
GstFormat format;
|
||||
|
||||
gst_query_parse_duration (query, &format, NULL);
|
||||
switch (format) {
|
||||
case GST_FORMAT_TIME:{
|
||||
gint64 dur = gst_util_uint64_scale_int_round (bsrc->num_buffers
|
||||
* GST_SECOND, src->info.fps_d, src->info.fps_n);
|
||||
res = TRUE;
|
||||
gst_query_set_duration (query, GST_FORMAT_TIME, dur);
|
||||
goto done;
|
||||
}
|
||||
case GST_FORMAT_BYTES:
|
||||
res = TRUE;
|
||||
gst_query_set_duration (query, GST_FORMAT_BYTES,
|
||||
bsrc->num_buffers * src->info.size);
|
||||
goto done;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* fall through */
|
||||
}
|
||||
default:
|
||||
res = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
|
||||
break;
|
||||
}
|
||||
done:
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_pulsevideo_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
|
||||
{
|
||||
GstPulsevideoSrc *pvsrc;
|
||||
PvBufferInfo info;
|
||||
|
||||
pvsrc = GST_PULSEVIDEO_SRC (psrc);
|
||||
|
||||
if (G_UNLIKELY (GST_VIDEO_INFO_FORMAT (&pvsrc->info) ==
|
||||
GST_VIDEO_FORMAT_UNKNOWN))
|
||||
goto not_negotiated;
|
||||
|
||||
g_main_loop_run (pvsrc->loop);
|
||||
pv_stream_capture_buffer (pvsrc->stream, &info);
|
||||
|
||||
*buffer = gst_buffer_new ();
|
||||
|
||||
if (g_socket_control_message_get_msg_type (info.message) == SCM_RIGHTS) {
|
||||
gint *fds, n_fds;
|
||||
GstMemory *fdmem = NULL;
|
||||
|
||||
fds = g_unix_fd_message_steal_fds (G_UNIX_FD_MESSAGE (info.message), &n_fds);
|
||||
|
||||
fdmem = gst_fd_allocator_alloc (pvsrc->fd_allocator, fds[0],
|
||||
info.offset + info.size, GST_FD_MEMORY_FLAG_NONE);
|
||||
gst_memory_resize (fdmem, info.offset, info.size);
|
||||
|
||||
gst_buffer_append_memory (*buffer, fdmem);
|
||||
}
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
not_negotiated:
|
||||
{
|
||||
return GST_FLOW_NOT_NEGOTIATED;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pulsevideo_src_start (GstBaseSrc * basesrc)
|
||||
{
|
||||
GstPulsevideoSrc *src = GST_PULSEVIDEO_SRC (basesrc);
|
||||
|
||||
gst_video_info_init (&src->info);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pulsevideo_src_stop (GstBaseSrc * basesrc)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
handle_mainloop (GstPulsevideoSrc *this)
|
||||
{
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
on_state_notify (GObject *gobject,
|
||||
GParamSpec *pspec,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstPulsevideoSrc *pvsrc = user_data;
|
||||
PvContextState state;
|
||||
|
||||
g_object_get (gobject, "state", &state, NULL);
|
||||
g_print ("got context state %d\n", state);
|
||||
|
||||
switch (state) {
|
||||
case PV_CONTEXT_STATE_ERROR:
|
||||
g_main_loop_quit (pvsrc->loop);
|
||||
GST_ELEMENT_ERROR (pvsrc, RESOURCE, FAILED,
|
||||
("Failed to connect stream: %s",
|
||||
pv_context_error (pvsrc->ctx)->message), (NULL));
|
||||
break;
|
||||
case PV_CONTEXT_STATE_READY:
|
||||
g_main_loop_quit (pvsrc->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pulsevideo_src_open (GstPulsevideoSrc * pvsrc)
|
||||
{
|
||||
g_main_context_push_thread_default (pvsrc->context);
|
||||
pvsrc->ctx = pv_context_new ("test-client", NULL);
|
||||
g_signal_connect (pvsrc->ctx, "notify::state", (GCallback) on_state_notify, pvsrc);
|
||||
pv_context_connect(pvsrc->ctx, PV_CONTEXT_FLAGS_NONE);
|
||||
g_main_context_pop_thread_default (pvsrc->context);
|
||||
|
||||
g_main_loop_run (pvsrc->loop);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_pulsevideo_src_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstPulsevideoSrc *this = GST_PULSEVIDEO_SRC_CAST (element);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
this->context = g_main_context_new ();
|
||||
g_print ("context %p\n", this->context);
|
||||
this->loop = g_main_loop_new (this->context, FALSE);
|
||||
this->thread = g_thread_new ("pulsevideo", (GThreadFunc) handle_mainloop, this);
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
gst_pulsevideo_src_open (this);
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
/* uncork and start recording */
|
||||
break;
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
/* stop recording ASAP by corking */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
g_main_loop_quit (this->loop);
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
g_main_loop_quit (this->loop);
|
||||
g_thread_join (this->thread);
|
||||
g_main_loop_unref (this->loop);
|
||||
g_main_context_unref (this->context);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
GST_DEBUG_CATEGORY_INIT (pulsevideo_src_debug, "pulsevideosrc", 0,
|
||||
"Pulsevideo Source");
|
||||
|
||||
return gst_element_register (plugin, "pulsevideosrc", GST_RANK_NONE,
|
||||
GST_TYPE_PULSEVIDEO_SRC);
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
pulsevideo,
|
||||
"Uses pulsevideo to create a video stream",
|
||||
plugin_init, VERSION, "LGPL", "pulsevideo", "pulsevideo.org")
|
78
src/gst/gstpvsrc.h
Normal file
78
src/gst/gstpvsrc.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* GStreamer
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __GST_PULSEVIDEO_SRC_H__
|
||||
#define __GST_PULSEVIDEO_SRC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstpushsrc.h>
|
||||
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include <client/pv-context.h>
|
||||
#include <client/pv-stream.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_PULSEVIDEO_SRC \
|
||||
(gst_pulsevideo_src_get_type())
|
||||
#define GST_PULSEVIDEO_SRC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PULSEVIDEO_SRC,GstPulsevideoSrc))
|
||||
#define GST_PULSEVIDEO_SRC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSEVIDEO_SRC,GstPulsevideoSrcClass))
|
||||
#define GST_IS_PULSEVIDEO_SRC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PULSEVIDEO_SRC))
|
||||
#define GST_IS_PULSEVIDEO_SRC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PULSEVIDEO_SRC))
|
||||
#define GST_PULSEVIDEO_SRC_CAST(obj) \
|
||||
((GstPulsevideoSrc *) (obj))
|
||||
|
||||
typedef struct _GstPulsevideoSrc GstPulsevideoSrc;
|
||||
typedef struct _GstPulsevideoSrcClass GstPulsevideoSrcClass;
|
||||
|
||||
/**
|
||||
* GstPulsevideoSrc:
|
||||
*
|
||||
* Opaque data structure.
|
||||
*/
|
||||
struct _GstPulsevideoSrc {
|
||||
GstPushSrc element;
|
||||
|
||||
/*< private >*/
|
||||
|
||||
/* video state */
|
||||
GstVideoInfo info;
|
||||
|
||||
GMainContext *context;
|
||||
GMainLoop *loop;
|
||||
GThread *thread;
|
||||
PvContext *ctx;
|
||||
PvStream *stream;
|
||||
GstAllocator *fd_allocator;
|
||||
};
|
||||
|
||||
struct _GstPulsevideoSrcClass {
|
||||
GstPushSrcClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_pulsevideo_src_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_PULSEVIDEO_SRC_H__ */
|
|
@ -29,6 +29,7 @@ struct _PvV4l2SourcePrivate
|
|||
{
|
||||
GstElement *pipeline;
|
||||
GstElement *src;
|
||||
GstElement *filter;
|
||||
GstElement *sink;
|
||||
|
||||
GSocket *socket;
|
||||
|
@ -36,13 +37,40 @@ struct _PvV4l2SourcePrivate
|
|||
|
||||
G_DEFINE_TYPE (PvV4l2Source, pv_v4l2_source, PV_TYPE_SOURCE);
|
||||
|
||||
static gboolean
|
||||
bus_handler (GstBus * bus, GstMessage * message, gpointer user_data)
|
||||
{
|
||||
PvSource *source = user_data;
|
||||
PvV4l2SourcePrivate *priv = PV_V4L2_SOURCE (source)->priv;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
GError *error;
|
||||
gchar *debug;
|
||||
|
||||
gst_message_parse_error (message, &error, &debug);
|
||||
g_print ("got error %s (%s)\n", error->message, debug);
|
||||
g_free (debug);
|
||||
|
||||
pv_source_report_error (source, error);
|
||||
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_pipeline (PvV4l2Source *source)
|
||||
{
|
||||
PvV4l2SourcePrivate *priv = source->priv;
|
||||
GstBus *bus;
|
||||
|
||||
priv->pipeline = gst_parse_launch ("v4l2src name=src ! "
|
||||
"video/x-raw,width=640,height=480,framerate=30/1 ! "
|
||||
"capsfilter name=filter ! "
|
||||
"pvfdpay ! "
|
||||
"multisocketsink "
|
||||
"buffers-max=2 "
|
||||
|
@ -53,8 +81,13 @@ setup_pipeline (PvV4l2Source *source)
|
|||
"sync=true "
|
||||
"enable-last-sample=false",
|
||||
NULL);
|
||||
priv->filter = gst_bin_get_by_name (GST_BIN (priv->pipeline), "filter");
|
||||
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));
|
||||
gst_bus_add_watch (bus, bus_handler, source);
|
||||
gst_object_unref (bus);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -140,6 +173,36 @@ v4l2_create_source_output (PvSource *source, GVariant *props, const gchar *prefi
|
|||
{
|
||||
PvV4l2SourcePrivate *priv = PV_V4L2_SOURCE (source)->priv;
|
||||
PvSourceOutput *output;
|
||||
GVariantDict dict;
|
||||
GstCaps *caps;
|
||||
const gchar *str;
|
||||
gint32 i32;
|
||||
|
||||
g_variant_dict_init (&dict, props);
|
||||
if (!g_variant_dict_lookup (&dict, "format.encoding", "&s", &str))
|
||||
goto invalid_encoding;
|
||||
|
||||
caps = gst_caps_new_empty_simple (str);
|
||||
|
||||
if (g_variant_dict_lookup (&dict, "format.format", "&s", &str))
|
||||
gst_caps_set_simple (caps, "format", G_TYPE_STRING, str, NULL);
|
||||
if (g_variant_dict_lookup (&dict, "format.width", "i", &i32))
|
||||
gst_caps_set_simple (caps, "width", G_TYPE_INT, (gint) i32, NULL);
|
||||
if (g_variant_dict_lookup (&dict, "format.height", "i", &i32))
|
||||
gst_caps_set_simple (caps, "height", G_TYPE_INT, (gint) i32, NULL);
|
||||
if (g_variant_dict_lookup (&dict, "format.views", "i", &i32))
|
||||
gst_caps_set_simple (caps, "views", G_TYPE_INT, (gint) i32, NULL);
|
||||
if (g_variant_dict_lookup (&dict, "format.chroma-site", "&s", &str))
|
||||
gst_caps_set_simple (caps, "chroma-site", G_TYPE_STRING, str, NULL);
|
||||
if (g_variant_dict_lookup (&dict, "format.colorimetry", "&s", &str))
|
||||
gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, str, NULL);
|
||||
if (g_variant_dict_lookup (&dict, "format.interlace-mode", "&s", &str))
|
||||
gst_caps_set_simple (caps, "interlace-mode", G_TYPE_STRING, str, NULL);
|
||||
|
||||
g_print ("caps %s\n", gst_caps_to_string (caps));
|
||||
|
||||
g_object_set (priv->filter, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
output = PV_SOURCE_CLASS (pv_v4l2_source_parent_class)->create_source_output (source, props, prefix);
|
||||
|
||||
|
@ -148,6 +211,13 @@ v4l2_create_source_output (PvSource *source, GVariant *props, const gchar *prefi
|
|||
g_signal_connect (output, "notify::socket", (GCallback) on_socket_notify, source);
|
||||
|
||||
return output;
|
||||
|
||||
/* ERRORS */
|
||||
invalid_encoding:
|
||||
{
|
||||
g_variant_dict_clear (&dict);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
|
|
@ -75,7 +75,6 @@ on_stream_notify (GObject *gobject,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_state_notify (GObject *gobject,
|
||||
GParamSpec *pspec,
|
||||
|
@ -94,11 +93,14 @@ on_state_notify (GObject *gobject,
|
|||
case PV_CONTEXT_STATE_READY:
|
||||
{
|
||||
PvStream *stream;
|
||||
GVariantBuilder builder;
|
||||
|
||||
stream = pv_stream_new (c, "test");
|
||||
stream = pv_stream_new (c, "test", NULL);
|
||||
g_signal_connect (stream, "notify::state", (GCallback) on_stream_notify, stream);
|
||||
g_signal_connect (stream, "notify::socket", (GCallback) on_socket_notify, stream);
|
||||
pv_stream_connect_capture (stream, NULL, 0);
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
||||
pv_stream_connect_capture (stream, NULL, 0, g_variant_builder_end (&builder));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue