From 4b324da3f4eb1d3c190682c358fc558d168672ee Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Sat, 14 Mar 2015 20:52:47 +0000 Subject: [PATCH] plugins: implement a gessrc element useable from playbin This is a new simple GstBin that can handle the ges:// uris and will directly expose the srcppads of the tracks present in the timeline. --- examples/c/gessrc.c | 101 +++++++++++++++++ examples/c/meson.build | 1 + examples/python/gst-player.py | 49 ++++++++ plugins/ges/gesplugin.c | 45 ++++++++ plugins/ges/gessrc.c | 255 ++++++++++++++++++++++++++++++++++++++++++ plugins/ges/gessrc.h | 53 +++++++++ plugins/ges/meson.build | 11 ++ plugins/meson.build | 1 + 8 files changed, 516 insertions(+) create mode 100644 examples/c/gessrc.c create mode 100755 examples/python/gst-player.py create mode 100644 plugins/ges/gesplugin.c create mode 100644 plugins/ges/gessrc.c create mode 100644 plugins/ges/gessrc.h create mode 100644 plugins/ges/meson.build diff --git a/examples/c/gessrc.c b/examples/c/gessrc.c new file mode 100644 index 0000000..2e8567a --- /dev/null +++ b/examples/c/gessrc.c @@ -0,0 +1,101 @@ +#include + +static void +bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + g_printerr ("Got error message on the bus\n"); + g_main_loop_quit (mainloop); + break; + case GST_MESSAGE_EOS: + g_print ("Done\n"); + g_main_loop_quit (mainloop); + break; + default: + break; + } +} + +static void +source_setup_cb (GstElement * playbin, GstElement * source, + GESTimeline * timeline) +{ + g_object_set (source, "timeline", timeline, NULL); +} + +int +main (int argc, char **argv) +{ + GMainLoop *mainloop = NULL; + GstElement *pipeline = NULL; + GESTimeline *timeline; + GESLayer *layer = NULL; + GstBus *bus = NULL; + guint i, ret = 0; + gchar *uri = NULL; + GstClockTime start = 0; + + if (argc < 2) { + g_print ("Usage: %s \n", argv[0]); + return -1; + } + + gst_init (&argc, &argv); + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = (GESLayer *) ges_layer_new (); + if (!ges_timeline_add_layer (timeline, layer)) + return -1; + + /* Build the timeline */ + for (i = 1; i < argc; i++) { + GESClip *clip; + + uri = g_strdup (argv[i]); + if (!gst_uri_is_valid (uri)) { + g_free (uri); + uri = gst_filename_to_uri (argv[i], NULL); + } + clip = GES_CLIP (ges_uri_clip_new (uri)); + + if (!clip) { + g_printerr ("Could not create clip for file: %s\n", argv[i]); + g_free (uri); + goto err; + } + g_object_set (clip, "start", start, NULL); + ges_layer_add_clip (layer, clip); + + start += ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (clip)); + + g_free (uri); + } + + /* Use a usual playbin pipeline */ + pipeline = gst_element_factory_make ("playbin", NULL); + g_object_set (pipeline, "uri", "ges://", NULL); + g_signal_connect (pipeline, "source-setup", G_CALLBACK (source_setup_cb), + timeline); + + mainloop = g_main_loop_new (NULL, FALSE); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + g_main_loop_run (mainloop); + gst_element_set_state (pipeline, GST_STATE_NULL); + +done: + gst_clear_object (&pipeline); + if (mainloop) + g_main_loop_unref (mainloop); + + return ret; +err: + goto done; +} diff --git a/examples/c/meson.build b/examples/c/meson.build index bcc188f..ac751b0 100644 --- a/examples/c/meson.build +++ b/examples/c/meson.build @@ -1,5 +1,6 @@ examples = [ 'concatenate', + 'gessrc', 'simple1', 'test1', 'test2', diff --git a/examples/python/gst-player.py b/examples/python/gst-player.py new file mode 100755 index 0000000..7cb2ae5 --- /dev/null +++ b/examples/python/gst-player.py @@ -0,0 +1,49 @@ +import sys +import gi + +gi.require_version('Gst', '1.0') +gi.require_version('GES', '1.0') +gi.require_version('GstPlayer', '1.0') +gi.require_version('GLib', '2.0') + +from gi.repository import Gst, GES, GLib, GstPlayer + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("You must specify a file URI") + sys.exit(-1) + + Gst.init(None) + GES.init() + + timeline = GES.Timeline.new_audio_video() + layer = timeline.append_layer() + start = 0 + for uri in sys.argv[1:]: + if not Gst.uri_is_valid(uri): + uri = Gst.filename_to_uri(uri) + + clip = GES.UriClip.new(uri) + clip.props.start = start + layer.add_clip(clip) + + start += clip.props.duration + + player = GstPlayer + player = GstPlayer.Player.new(None, GstPlayer.PlayerGMainContextSignalDispatcher.new(None)) + player.set_uri("ges://") + player.get_pipeline().connect("source-setup", + lambda playbin, source: source.set_property("timeline", timeline)) + + loop = GLib.MainLoop() + player.connect("end-of-stream", lambda x: loop.quit()) + + def error(player, err): + loop.quit() + print("Got error: %s" % err) + sys.exit(1) + + player.connect("error", error) + player.play() + loop.run() diff --git a/plugins/ges/gesplugin.c b/plugins/ges/gesplugin.c new file mode 100644 index 0000000..c56edd1 --- /dev/null +++ b/plugins/ges/gesplugin.c @@ -0,0 +1,45 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2015 Thibault Saunier + * + * gstges.c + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gessrc.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean res = 1; + res |= gst_element_register (plugin, "gessrc", GST_RANK_NONE, GES_SRC_TYPE); + + return res; +} + +/* plugin export resolution */ +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + ges, + "GStreamer Editing Services Plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/plugins/ges/gessrc.c b/plugins/ges/gessrc.c new file mode 100644 index 0000000..89c9543 --- /dev/null +++ b/plugins/ges/gessrc.c @@ -0,0 +1,255 @@ +/* GStreamer Editing Services GStreamer plugin + * Copyright (C) 2019 Thibault Saunier + * + * gessrc.c + * + * 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:gessrc + * @short_description: A GstBin subclasses use to use GESTimeline + * as sources inside any GstPipeline. + * @see_also: #GESTimeline + * + * The gessrc is a bin that will simply expose the track src pads + * and implements the GstUriHandler interface using a custom `ges://` + * uri scheme. + * + * NOTE: That to use it inside playbin and friends you **need** to + * set gessrc::timeline property yourself. + * + * Example with #playbin: + * + * {{../../examples/c/gessrc.c}} + * + * Example with #GstPlayer: + * + * {{../../examples/python/gst-player.py}} + **/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gessrc.h" + +GST_DEBUG_CATEGORY_STATIC (gessrc); +#define GST_CAT_DEFAULT gessrc + +static GstStaticPadTemplate video_src_template = +GST_STATIC_PAD_TEMPLATE ("video_src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/x-raw(ANY)")); + +static GstStaticPadTemplate audio_src_template = + GST_STATIC_PAD_TEMPLATE ("audio_src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("audio/x-raw(ANY);")); + +enum +{ + PROP_0, + PROP_TIMELINE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static gboolean +ges_src_set_timeline (GESSrc * self, GESTimeline * timeline) +{ + GList *tmp; + guint naudiopad = 0, nvideopad = 0; + GstBin *sbin = GST_BIN (self); + + g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE); + + if (self->timeline) { + GST_FIXME_OBJECT (self, "Implement changing timeline support"); + + return FALSE; + } + + self->timeline = timeline; + + gst_bin_add (sbin, GST_ELEMENT (self->timeline)); + for (tmp = self->timeline->tracks; tmp; tmp = tmp->next) { + GstPad *gpad; + gchar *name = NULL; + GstElement *queue; + GESTrack *track = GES_TRACK (tmp->data); + GstPad *tmppad, *pad = + ges_timeline_get_pad_for_track (self->timeline, track); + GstStaticPadTemplate *template; + + if (!pad) { + GST_INFO_OBJECT (self, "No pad for track: %" GST_PTR_FORMAT, track); + + continue; + } + + if (track->type == GES_TRACK_TYPE_AUDIO) { + name = g_strdup_printf ("audio_%u", naudiopad++); + template = &audio_src_template; + } else if (track->type == GES_TRACK_TYPE_VIDEO) { + name = g_strdup_printf ("video_%u", nvideopad++); + template = &video_src_template; + } else { + GST_INFO_OBJECT (self, "Track type not handled: %" GST_PTR_FORMAT, track); + continue; + } + + queue = gst_element_factory_make ("queue", NULL); + /* Add queues the same way as in GESPipeline */ + g_object_set (G_OBJECT (queue), "max-size-buffers", 0, + "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND, NULL); + gst_bin_add (GST_BIN (self), queue); + + tmppad = gst_element_get_static_pad (queue, "sink"); + if (gst_pad_link (pad, tmppad) != GST_PAD_LINK_OK) { + GST_ERROR ("Could not link %s:%s and %s:%s", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (tmppad)); + + gst_object_unref (tmppad); + gst_object_unref (queue); + continue; + } + + tmppad = gst_element_get_static_pad (queue, "src"); + gpad = gst_ghost_pad_new_from_template (name, tmppad, + gst_static_pad_template_get (template)); + + gst_pad_set_active (gpad, TRUE); + gst_element_add_pad (GST_ELEMENT (self), gpad); + } + + gst_element_sync_state_with_parent (GST_ELEMENT (self->timeline)); + + return TRUE; +} + +/*** GSTURIHANDLER INTERFACE *************************************************/ + +static GstURIType +ges_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +ges_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { "ges", NULL }; + + return protocols; +} + +static gchar * +ges_src_uri_get_uri (GstURIHandler * handler) +{ + GESSrc *self = GES_SRC (handler); + + return self->timeline ? g_strdup_printf ("ges://%s", + GST_OBJECT_NAME (self->timeline)) : NULL; +} + +static gboolean +ges_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + return TRUE; +} + +static void +ges_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = ges_src_uri_get_type; + iface->get_protocols = ges_src_uri_get_protocols; + iface->get_uri = ges_src_uri_get_uri; + iface->set_uri = ges_src_uri_set_uri; +} + +G_DEFINE_TYPE_WITH_CODE (GESSrc, ges_src, GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, ges_src_uri_handler_init)); + +static void +ges_src_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESSrc *self = GES_SRC (object); + + switch (property_id) { + case PROP_TIMELINE: + g_value_set_object (value, self->timeline); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_src_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESSrc *self = GES_SRC (object); + + switch (property_id) { + case PROP_TIMELINE: + ges_src_set_timeline (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_src_class_init (GESSrcClass * self_class) +{ + GObjectClass *gclass = G_OBJECT_CLASS (self_class); + GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class); + + GST_DEBUG_CATEGORY_INIT (gessrc, "gessrc", 0, "ges src element"); + + gclass->get_property = ges_src_get_property; + gclass->set_property = ges_src_set_property; + + /** + * GESSrc:timeline: + * + * Timeline to use in this src. + */ + properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline", + "Timeline to use in this src.", + GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gclass, PROP_LAST, properties); + + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&video_src_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&audio_src_template)); +} + +static void +ges_src_init (GESSrc * self) +{ +} diff --git a/plugins/ges/gessrc.h b/plugins/ges/gessrc.h new file mode 100644 index 0000000..976321a --- /dev/null +++ b/plugins/ges/gessrc.h @@ -0,0 +1,53 @@ +/* GStreamer GES plugin + * + * Copyright (C) 2019 Thibault Saunier + * + * gstges.c + * + * 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 __GES_SRC_H__ +#define __GES_SRC_H__ + +#include +#include + +G_BEGIN_DECLS + +GType ges_src_get_type (void); + +#define GES_SRC_TYPE (ges_src_get_type ()) +#define GES_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_SRC_TYPE, GESSrc)) +#define GES_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_SRC_TYPE, GESSrcClass)) +#define GES_IS_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_SRC_TYPE)) +#define GES_IS_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_SRC_TYPE)) +#define GES_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_SRC_TYPE, GESSrcClass)) + +typedef struct { + GstBin parent; + + GESTimeline *timeline; +} GESSrc; + +typedef struct { + GstBinClass parent; + +} GESSrcClass; + +G_END_DECLS +#endif /* __GES_SRC_H__ */ diff --git a/plugins/ges/meson.build b/plugins/ges/meson.build new file mode 100644 index 0000000..6beeb05 --- /dev/null +++ b/plugins/ges/meson.build @@ -0,0 +1,11 @@ +gstges_sources = ['gesplugin.c', 'gessrc.c', 'gesdemux.c'] + +gstges = library('gstges', gstges_sources, + dependencies : [gst_dep, ges_dep], + include_directories: [configinc], + c_args : ges_c_args, + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(nle, install_dir : plugins_pkgconfig_install_dir) + diff --git a/plugins/meson.build b/plugins/meson.build index 8dbfcde..7fa5b4b 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -1 +1,2 @@ subdir('nle') +subdir('ges') \ No newline at end of file -- 2.7.4