mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-01 21:18:52 +00:00
f51f2f70de
Implement lazy loading asset cache so gesdemux use the formatters assets while GES hasn't been initialized. And set extensions to temporary files as some formatters require the information (otio)
641 lines
18 KiB
C
641 lines
18 KiB
C
/* GStreamer GES plugin
|
|
*
|
|
* Copyright (C) 2019 Igalia S.L
|
|
* Author: 2019 Thibault Saunier <tsaunier@igalia.com>
|
|
*
|
|
* gesdemux.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:gstdemux
|
|
* @short_description: A GstBin subclasses use to use GESTimeline
|
|
* as demux inside any GstPipeline.
|
|
* @see_also: #GESTimeline
|
|
*
|
|
* The gstdemux is a bin that will simply expose the track source pads
|
|
* and implements the GstUriHandler interface using a custom ges://0Xpointer
|
|
* uri scheme.
|
|
**/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gesbasebin.h"
|
|
|
|
#include <gst/gst.h>
|
|
#include <glib/gstdio.h>
|
|
#include <gst/pbutils/pbutils.h>
|
|
#include <gst/base/gstadapter.h>
|
|
#include <ges/ges.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gesdemux);
|
|
#define GST_CAT_DEFAULT gesdemux
|
|
|
|
G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, Demux, GESBaseBin);
|
|
|
|
struct _GESDemux
|
|
{
|
|
GESBaseBin parent;
|
|
|
|
GESTimeline *timeline;
|
|
GstPad *sinkpad;
|
|
|
|
GstAdapter *input_adapter;
|
|
|
|
gchar *upstream_uri;
|
|
GStatBuf stats;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GESDemux, ges_demux, ges_base_bin_get_type ());
|
|
#define GES_DEMUX(obj) ((GESDemux*)obj)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_TIMELINE,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
static GstCaps *
|
|
ges_demux_get_sinkpad_caps ()
|
|
{
|
|
GList *tmp, *formatters;
|
|
GstCaps *sinkpad_caps = gst_caps_new_empty ();
|
|
|
|
formatters = ges_list_assets (GES_TYPE_FORMATTER);
|
|
for (tmp = formatters; tmp; tmp = tmp->next) {
|
|
GstCaps *caps;
|
|
const gchar *mimetype =
|
|
ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
|
|
GES_META_FORMATTER_MIMETYPE);
|
|
if (!mimetype)
|
|
continue;
|
|
|
|
caps = gst_caps_from_string (mimetype);
|
|
|
|
if (!caps) {
|
|
GST_INFO_OBJECT (tmp->data,
|
|
"%s - could not create caps from mimetype: %s",
|
|
ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
|
|
GES_META_FORMATTER_NAME), mimetype);
|
|
|
|
continue;
|
|
}
|
|
|
|
gst_caps_append (sinkpad_caps, caps);
|
|
}
|
|
g_list_free (formatters);
|
|
|
|
return sinkpad_caps;
|
|
}
|
|
|
|
static gchar *
|
|
ges_demux_get_extension (GstStructure * _struct)
|
|
{
|
|
GList *tmp, *formatters;
|
|
gchar *ext = NULL;
|
|
|
|
formatters = ges_list_assets (GES_TYPE_FORMATTER);
|
|
for (tmp = formatters; tmp; tmp = tmp->next) {
|
|
gchar **extensions_a;
|
|
gint i, n_exts;
|
|
GstCaps *caps;
|
|
const gchar *mimetype =
|
|
ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
|
|
GES_META_FORMATTER_MIMETYPE);
|
|
const gchar *extensions =
|
|
ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
|
|
GES_META_FORMATTER_EXTENSION);
|
|
if (!mimetype)
|
|
continue;
|
|
|
|
if (!extensions)
|
|
continue;
|
|
|
|
caps = gst_caps_from_string (mimetype);
|
|
if (!caps) {
|
|
GST_INFO_OBJECT (tmp->data,
|
|
"%s - could not create caps from mimetype: %s",
|
|
ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
|
|
GES_META_FORMATTER_NAME), mimetype);
|
|
|
|
continue;
|
|
}
|
|
|
|
extensions_a = g_strsplit (extensions, ",", -1);
|
|
n_exts = g_strv_length (extensions_a);
|
|
for (i = 0; i < gst_caps_get_size (caps) && i < n_exts; i++) {
|
|
GstStructure *structure = gst_caps_get_structure (caps, i);
|
|
|
|
if (gst_structure_has_name (_struct, gst_structure_get_name (structure))) {
|
|
ext = g_strdup (extensions_a[i]);
|
|
g_strfreev (extensions_a);
|
|
gst_caps_unref (caps);
|
|
goto done;
|
|
}
|
|
}
|
|
g_strfreev (extensions_a);
|
|
}
|
|
done:
|
|
g_list_free (formatters);
|
|
|
|
return ext;
|
|
}
|
|
|
|
static void
|
|
ges_demux_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESDemux *self = GES_DEMUX (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_TIMELINE:
|
|
g_value_set_object (value,
|
|
ges_base_bin_get_timeline (GES_BASE_BIN (self)));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_demux_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
switch (property_id) {
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_demux_class_init (GESDemuxClass * self_class)
|
|
{
|
|
GObjectClass *gclass = G_OBJECT_CLASS (self_class);
|
|
GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class);
|
|
GstCaps *sinkpad_caps = ges_demux_get_sinkpad_caps ();
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gesdemux, "gesdemux", 0, "ges demux element");
|
|
|
|
gst_tag_register ("is-ges-timeline", GST_TAG_FLAG_META, G_TYPE_BOOLEAN,
|
|
"is-ges-timeline", "The stream is a ges timeline.", NULL);
|
|
gclass->get_property = ges_demux_get_property;
|
|
gclass->set_property = ges_demux_set_property;
|
|
|
|
/**
|
|
* GESDemux:timeline:
|
|
*
|
|
* Timeline to use in this source.
|
|
*/
|
|
properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
|
|
"Timeline to use in this source.",
|
|
GES_TYPE_TIMELINE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_override_property (gclass, PROP_TIMELINE, "timeline");
|
|
|
|
gst_element_class_set_static_metadata (gstelement_klass,
|
|
"GStreamer Editing Services based 'demuxer'",
|
|
"Codec/Demux/Editing",
|
|
"Demuxer for complex timeline file formats using GES.",
|
|
"Thibault Saunier <tsaunier@igalia.com");
|
|
|
|
gst_element_class_add_pad_template (gstelement_klass,
|
|
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
sinkpad_caps));
|
|
gst_caps_unref (sinkpad_caps);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GESTimeline *timeline;
|
|
GMainLoop *ml;
|
|
GError *error;
|
|
gulong loaded_sigid;
|
|
gulong error_sigid;
|
|
gulong error_asset_sigid;
|
|
} TimelineConstructionData;
|
|
|
|
static void
|
|
project_loaded_cb (GESProject * project, GESTimeline * timeline,
|
|
TimelineConstructionData * data)
|
|
{
|
|
data->timeline = timeline;
|
|
g_signal_handler_disconnect (project, data->loaded_sigid);
|
|
data->loaded_sigid = 0;
|
|
|
|
g_main_loop_quit (data->ml);
|
|
}
|
|
|
|
static void
|
|
error_loading_cb (GESProject * project, GESTimeline * timeline,
|
|
GError * error, TimelineConstructionData * data)
|
|
{
|
|
data->error = g_error_copy (error);
|
|
g_signal_handler_disconnect (project, data->error_sigid);
|
|
data->error_sigid = 0;
|
|
|
|
g_main_loop_quit (data->ml);
|
|
}
|
|
|
|
static void
|
|
error_loading_asset_cb (GESProject * project, GError * error, gchar * id,
|
|
GType extractable_type, TimelineConstructionData * data)
|
|
{
|
|
data->error = g_error_copy (error);
|
|
g_signal_handler_disconnect (project, data->error_asset_sigid);
|
|
data->error_asset_sigid = 0;
|
|
|
|
g_main_loop_quit (data->ml);
|
|
}
|
|
|
|
static gboolean
|
|
ges_demux_src_probe (GstPad * pad, GstPadProbeInfo * info, GstElement * parent)
|
|
{
|
|
GESDemux *self = GES_DEMUX (parent);
|
|
GstEvent *event;
|
|
|
|
if (info->type & (GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM)) {
|
|
GstQuery *query = info->data;
|
|
|
|
if (GST_QUERY_TYPE (query) == GST_QUERY_CUSTOM) {
|
|
GstStructure *structure =
|
|
(GstStructure *) gst_query_get_structure (query);
|
|
|
|
if (gst_structure_has_name (structure,
|
|
"NleCompositionQueryNeedsTearDown")) {
|
|
GstQuery *uri_query = gst_query_new_uri ();
|
|
|
|
if (gst_pad_peer_query (self->sinkpad, uri_query)) {
|
|
gchar *upstream_uri = NULL;
|
|
GStatBuf stats;
|
|
gst_query_parse_uri (uri_query, &upstream_uri);
|
|
|
|
if (gst_uri_has_protocol (upstream_uri, "file")) {
|
|
gchar *location = gst_uri_get_location (upstream_uri);
|
|
|
|
g_stat (location, &stats);
|
|
g_free (location);
|
|
GST_OBJECT_LOCK (self);
|
|
if (g_strcmp0 (upstream_uri, self->upstream_uri)
|
|
|| stats.st_mtime != self->stats.st_mtime
|
|
|| stats.st_size != self->stats.st_size) {
|
|
GST_INFO_OBJECT (self,
|
|
"Underlying file changed, asking for an update");
|
|
gst_structure_set (structure, "result", G_TYPE_BOOLEAN, TRUE,
|
|
NULL);
|
|
g_free (self->upstream_uri);
|
|
self->upstream_uri = upstream_uri;
|
|
self->stats = stats;
|
|
} else {
|
|
g_free (upstream_uri);
|
|
}
|
|
GST_OBJECT_UNLOCK (self);
|
|
}
|
|
}
|
|
gst_query_unref (uri_query);
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
event = info->data;
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_STREAM_START:
|
|
{
|
|
const gchar *stream_id;
|
|
gchar *new_stream_id;
|
|
guint stream_group;
|
|
|
|
gst_event_parse_stream_start (event, &stream_id);
|
|
gst_event_parse_group_id (event, &stream_group);
|
|
new_stream_id =
|
|
gst_pad_create_stream_id (pad, GST_ELEMENT (parent), stream_id);
|
|
gst_event_unref (event);
|
|
|
|
event = gst_event_new_stream_start (new_stream_id);
|
|
gst_event_set_group_id (event, stream_group);
|
|
g_free (new_stream_id);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
info->data = event;
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static gboolean
|
|
ges_demux_set_srcpad_probe (GstElement * element, GstPad * pad,
|
|
gpointer user_data)
|
|
{
|
|
GstTagList *tlist = gst_tag_list_new ("is-ges-timeline", TRUE, NULL);
|
|
|
|
gst_pad_add_probe (pad,
|
|
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
|
|
(GstPadProbeCallback) ges_demux_src_probe, element, NULL);
|
|
|
|
gst_tag_list_set_scope (tlist, GST_TAG_SCOPE_GLOBAL);
|
|
gst_pad_push_event (pad, gst_event_new_tag (tlist));
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
ges_demux_adapt_timeline_duration (GESDemux * self, GESTimeline * timeline)
|
|
{
|
|
GType nleobject_type = g_type_from_name ("NleObject");
|
|
GstObject *parent, *tmpparent;
|
|
|
|
parent = gst_object_get_parent (GST_OBJECT (self));
|
|
while (parent) {
|
|
if (g_type_is_a (G_OBJECT_TYPE (parent), nleobject_type)) {
|
|
GstClockTime duration, inpoint, timeline_duration;
|
|
|
|
g_object_get (parent, "duration", &duration, "inpoint", &inpoint, NULL);
|
|
g_object_get (timeline, "duration", &timeline_duration, NULL);
|
|
|
|
if (inpoint + duration > timeline_duration) {
|
|
GESLayer *layer = ges_timeline_get_layer (timeline, 0);
|
|
|
|
if (layer) {
|
|
GESClip *clip = GES_CLIP (ges_test_clip_new ());
|
|
GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
|
|
|
|
g_object_set (clip, "start", timeline_duration, "duration",
|
|
inpoint + duration, "vpattern", GES_VIDEO_TEST_PATTERN_SMPTE75,
|
|
NULL);
|
|
ges_layer_add_clip (layer, clip);
|
|
for (tmp = tracks; tmp; tmp = tmp->next) {
|
|
if (GES_IS_VIDEO_TRACK (tmp->data)) {
|
|
GESEffect *text;
|
|
GstCaps *caps;
|
|
gchar *effect_str_full = NULL;
|
|
const gchar *effect_str =
|
|
"textoverlay text=\"Nested timeline too short, please FIX!\" halignment=center valignment=center";
|
|
|
|
g_object_get (tmp->data, "restriction-caps", &caps, NULL);
|
|
if (caps) {
|
|
gchar *caps_str = gst_caps_to_string (caps);
|
|
effect_str = effect_str_full =
|
|
g_strdup_printf ("capsfilter caps=\"%s\" ! %s", caps_str,
|
|
effect_str);
|
|
g_free (caps_str);
|
|
gst_caps_unref (caps);
|
|
}
|
|
text = ges_effect_new (effect_str);
|
|
g_free (effect_str_full);
|
|
|
|
ges_container_add (GES_CONTAINER (clip),
|
|
GES_TIMELINE_ELEMENT (text));
|
|
}
|
|
|
|
}
|
|
g_list_free_full (tracks, gst_object_unref);
|
|
GST_INFO_OBJECT (timeline,
|
|
"Added test clip with duration: %" GST_TIME_FORMAT " - %"
|
|
GST_TIME_FORMAT " to match parent nleobject duration",
|
|
GST_TIME_ARGS (timeline_duration),
|
|
GST_TIME_ARGS (inpoint + duration - timeline_duration));
|
|
}
|
|
}
|
|
gst_object_unref (parent);
|
|
|
|
return;
|
|
}
|
|
|
|
tmpparent = parent;
|
|
parent = gst_object_get_parent (GST_OBJECT (parent));
|
|
gst_object_unref (tmpparent);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ges_demux_create_timeline (GESDemux * self, gchar * uri, GError ** error)
|
|
{
|
|
GESProject *project = ges_project_new (uri);
|
|
G_GNUC_UNUSED void *unused;
|
|
TimelineConstructionData data = { 0, };
|
|
GMainContext *ctx = g_main_context_new ();
|
|
GstQuery *query;
|
|
|
|
g_main_context_push_thread_default (ctx);
|
|
data.ml = g_main_loop_new (ctx, TRUE);
|
|
|
|
data.loaded_sigid =
|
|
g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb),
|
|
&data);
|
|
data.error_asset_sigid =
|
|
g_signal_connect_after (project, "error-loading-asset",
|
|
G_CALLBACK (error_loading_asset_cb), &data);
|
|
data.error_sigid =
|
|
g_signal_connect_after (project, "error-loading",
|
|
G_CALLBACK (error_loading_cb), &data);
|
|
|
|
unused = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data.error));
|
|
if (data.error) {
|
|
*error = data.error;
|
|
|
|
goto done;
|
|
}
|
|
|
|
g_main_loop_run (data.ml);
|
|
g_main_loop_unref (data.ml);
|
|
if (data.error)
|
|
goto done;
|
|
|
|
ges_demux_adapt_timeline_duration (self, data.timeline);
|
|
|
|
query = gst_query_new_uri ();
|
|
if (gst_pad_peer_query (self->sinkpad, query)) {
|
|
GList *assets, *tmp;
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
g_free (self->upstream_uri);
|
|
gst_query_parse_uri (query, &self->upstream_uri);
|
|
if (gst_uri_has_protocol (self->upstream_uri, "file")) {
|
|
gchar *location = gst_uri_get_location (self->upstream_uri);
|
|
|
|
g_stat (location, &self->stats);
|
|
g_free (location);
|
|
}
|
|
|
|
assets = ges_project_list_assets (project, GES_TYPE_URI_CLIP);
|
|
for (tmp = assets; tmp; tmp = tmp->next) {
|
|
const gchar *id = ges_asset_get_id (tmp->data);
|
|
|
|
if (!g_strcmp0 (id, self->upstream_uri)) {
|
|
g_set_error (error, GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX,
|
|
"Recursively loading uri: %s", self->upstream_uri);
|
|
break;
|
|
}
|
|
}
|
|
GST_OBJECT_UNLOCK (self);
|
|
g_list_free_full (assets, g_object_unref);
|
|
}
|
|
|
|
done:
|
|
if (data.loaded_sigid)
|
|
g_signal_handler_disconnect (project, data.loaded_sigid);
|
|
|
|
if (data.error_sigid)
|
|
g_signal_handler_disconnect (project, data.error_sigid);
|
|
|
|
if (data.error_asset_sigid)
|
|
g_signal_handler_disconnect (project, data.error_asset_sigid);
|
|
|
|
g_clear_object (&project);
|
|
|
|
GST_INFO_OBJECT (self, "Timeline properly loaded: %" GST_PTR_FORMAT,
|
|
data.timeline);
|
|
|
|
if (!data.error) {
|
|
ges_base_bin_set_timeline (GES_BASE_BIN (self), data.timeline);
|
|
gst_element_foreach_src_pad (GST_ELEMENT (self), ges_demux_set_srcpad_probe,
|
|
NULL);
|
|
} else {
|
|
*error = data.error;
|
|
}
|
|
|
|
g_main_context_pop_thread_default (ctx);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
ges_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
GESDemux *self = GES_DEMUX (parent);
|
|
|
|
switch (event->type) {
|
|
case GST_EVENT_EOS:{
|
|
GstMapInfo map;
|
|
GstBuffer *xges_buffer;
|
|
gboolean ret = TRUE;
|
|
gsize available;
|
|
|
|
available = gst_adapter_available (self->input_adapter);
|
|
if (available == 0) {
|
|
GST_WARNING_OBJECT (self,
|
|
"Received EOS without any serialized timeline.");
|
|
|
|
return gst_pad_event_default (pad, parent, event);
|
|
}
|
|
|
|
xges_buffer = gst_adapter_take_buffer (self->input_adapter, available);
|
|
if (gst_buffer_map (xges_buffer, &map, GST_MAP_READ)) {
|
|
gint f;
|
|
GError *err = NULL;
|
|
gchar *template = NULL;
|
|
gchar *filename = NULL, *uri = NULL;
|
|
GstCaps *caps = gst_pad_get_current_caps (pad);
|
|
GstStructure *structure = gst_caps_get_structure (caps, 0);
|
|
gchar *ext = ges_demux_get_extension (structure);
|
|
|
|
gst_caps_unref (caps);
|
|
if (ext) {
|
|
template = g_strdup_printf ("XXXXXX.%s", ext);
|
|
g_free (ext);
|
|
}
|
|
|
|
f = g_file_open_tmp (template, &filename, &err);
|
|
g_free (template);
|
|
|
|
if (err) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE,
|
|
("Could not open temporary file to write timeline description"),
|
|
("%s", err->message));
|
|
|
|
goto error;
|
|
}
|
|
|
|
g_file_set_contents (filename, (gchar *) map.data, map.size, &err);
|
|
if (err) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
|
|
("Could not write temporary timeline description file"),
|
|
("%s", err->message));
|
|
|
|
goto error;
|
|
}
|
|
|
|
uri = gst_filename_to_uri (filename, NULL);
|
|
GST_INFO_OBJECT (self, "Pre loading the timeline.");
|
|
|
|
ges_demux_create_timeline (self, uri, &err);
|
|
if (err)
|
|
goto error;
|
|
|
|
done:
|
|
g_free (filename);
|
|
g_free (uri);
|
|
g_close (f, NULL);
|
|
return ret;
|
|
|
|
error:
|
|
ret = FALSE;
|
|
gst_element_post_message (GST_ELEMENT (self),
|
|
gst_message_new_error (parent, err,
|
|
"Could not create timeline from description"));
|
|
g_clear_error (&err);
|
|
|
|
goto done;
|
|
} else {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, READ,
|
|
("Could not map buffer containing timeline description"),
|
|
("Not info"));
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return gst_pad_event_default (pad, parent, event);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
ges_demux_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
|
{
|
|
GESDemux *self = GES_DEMUX (parent);
|
|
|
|
gst_adapter_push (self->input_adapter, buffer);
|
|
|
|
GST_INFO_OBJECT (self, "Received buffer, total size is %i bytes",
|
|
(gint) gst_adapter_available (self->input_adapter));
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static void
|
|
ges_demux_init (GESDemux * self)
|
|
{
|
|
ges_init ();
|
|
|
|
self->sinkpad =
|
|
gst_pad_new_from_template (gst_element_get_pad_template (GST_ELEMENT
|
|
(self), "sink"), "sink");
|
|
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
|
|
|
|
self->input_adapter = gst_adapter_new ();
|
|
|
|
gst_pad_set_chain_function (self->sinkpad,
|
|
GST_DEBUG_FUNCPTR (ges_demux_sink_chain));
|
|
|
|
gst_pad_set_event_function (self->sinkpad,
|
|
GST_DEBUG_FUNCPTR (ges_demux_sink_event));
|
|
}
|