| /* GStreamer Base Class for Tag Demuxing |
| * Copyright (C) 2005 Jan Schmidt <thaytan@mad.scientist.com> |
| * Copyright (C) 2006-2007 Tim-Philipp Müller <tim centricular net> |
| * |
| * 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:gsttagdemux |
| * @title: GstTagDemux |
| * @see_also: GstApeDemux, GstID3Demux |
| * @short_description: Base class for demuxing tags that are in chunks |
| * directly at the beginning or at the end of a file |
| * |
| * Provides a base class for demuxing tags at the beginning or end of a |
| * stream and handles things like typefinding, querying, seeking, and |
| * different modes of operation (chain-based, pull_range-based, and providing |
| * downstream elements with random access if upstream supports that). The tag |
| * is stripped from the output, and all offsets are adjusted for the tag |
| * sizes, so that to the downstream element the stream will appear as if |
| * there was no tag at all. Also, once the tag has been parsed, GstTagDemux |
| * will try to determine the media type of the resulting stream and add a |
| * source pad with the appropriate caps in order to facilitate auto-plugging. |
| * |
| * ## Deriving from GstTagDemux |
| * |
| * Subclasses have to do four things: |
| * |
| * * In their base init function, they must add a pad template for the sink |
| * pad to the element class, describing the media type they can parse in |
| * the caps of the pad template. |
| * * In their class init function, they must override |
| * GST_TAG_DEMUX_CLASS(demux_klass)->identify_tag with their own identify |
| * function. |
| * * In their class init function, they must override |
| * GST_TAG_DEMUX_CLASS(demux_klass)->parse_tag with their own parse |
| * function. |
| * * In their class init function, they must also set |
| * GST_TAG_DEMUX_CLASS(demux_klass)->min_start_size and/or |
| * GST_TAG_DEMUX_CLASS(demux_klass)->min_end_size to the minimum size required |
| * for the identify function to decide whether the stream has a supported tag |
| * or not. A class parsing ID3v1 tags, for example, would set min_end_size to |
| * 128 bytes. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gsttagdemux.h" |
| |
| #include <gst/base/gsttypefindhelper.h> |
| #include <gst/base/gstadapter.h> |
| #include <gst/gst-i18n-plugin.h> |
| #include <string.h> |
| |
| typedef enum |
| { |
| GST_TAG_DEMUX_READ_START_TAG, |
| GST_TAG_DEMUX_TYPEFINDING, |
| GST_TAG_DEMUX_STREAMING |
| } GstTagDemuxState; |
| |
| struct _GstTagDemuxPrivate |
| { |
| GstPad *srcpad; |
| GstPad *sinkpad; |
| |
| /* Number of bytes to remove from the |
| * start of file (tag at beginning) */ |
| guint strip_start; |
| |
| /* Number of bytes to remove from the |
| * end of file (tag at end) */ |
| guint strip_end; |
| |
| gint64 upstream_size; |
| |
| GstTagDemuxState state; |
| GstAdapter *adapter; |
| GstBuffer *collect; |
| gsize collect_size; |
| guint tagsize; |
| GstCaps *src_caps; |
| |
| GstTagList *event_tags; |
| GstTagList *parsed_tags; |
| gboolean send_tag_event; |
| |
| GstSegment segment; |
| gboolean need_newseg; |
| |
| guint64 offset; |
| |
| GList *pending_events; |
| }; |
| |
| /* Require at least 8kB of data before we attempt typefind. |
| * Seems a decent value based on test files |
| * 40kB is massive overkill for the maximum, I think, but it |
| * doesn't do any harm (tpm: increased to 64kB after watching |
| * typefinding fail on a wavpack file that needed 42kB to succeed) */ |
| #define TYPE_FIND_MIN_SIZE 8192 |
| #define TYPE_FIND_MAX_SIZE 65536 |
| |
| #define DEFAULT_PULL_BLOCKSIZE 4096 |
| |
| GST_DEBUG_CATEGORY_STATIC (tagdemux_debug); |
| #define GST_CAT_DEFAULT (tagdemux_debug) |
| |
| static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("ANY") |
| ); |
| |
| static void gst_tag_demux_element_loop (GstTagDemux * demux); |
| |
| static void gst_tag_demux_dispose (GObject * object); |
| |
| static GstFlowReturn gst_tag_demux_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buf); |
| static gboolean gst_tag_demux_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| |
| static gboolean gst_tag_demux_sink_activate_mode (GstPad * pad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| static gboolean gst_tag_demux_src_activate_mode (GstPad * pad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| static GstFlowReturn gst_tag_demux_read_range (GstTagDemux * tagdemux, |
| GstObject * parent, guint64 offset, guint length, GstBuffer ** buffer); |
| |
| static GstFlowReturn gst_tag_demux_src_getrange (GstPad * srcpad, |
| GstObject * parent, guint64 offset, guint length, GstBuffer ** buffer); |
| |
| static void gst_tag_demux_set_src_caps (GstTagDemux * tagdemux, |
| GstCaps * new_caps); |
| |
| static gboolean gst_tag_demux_srcpad_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_tag_demux_sink_activate (GstPad * sinkpad, |
| GstObject * parent); |
| static GstStateChangeReturn gst_tag_demux_change_state (GstElement * element, |
| GstStateChange transition); |
| static gboolean gst_tag_demux_pad_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_tag_demux_get_upstream_size (GstTagDemux * tagdemux); |
| static void gst_tag_demux_send_pending_events (GstTagDemux * tagdemux); |
| static void gst_tag_demux_send_tag_event (GstTagDemux * tagdemux); |
| static gboolean gst_tag_demux_send_new_segment (GstTagDemux * tagdemux); |
| |
| static void gst_tag_demux_base_init (gpointer g_class); |
| static void gst_tag_demux_class_init (gpointer g_class, gpointer d); |
| static void gst_tag_demux_init (GstTagDemux * obj, GstTagDemuxClass * klass); |
| |
| static gpointer parent_class; /* NULL */ |
| |
| /* Cannot use boilerplate macros here because we want the abstract flag */ |
| GType |
| gst_tag_demux_get_type (void) |
| { |
| static GType object_type; /* 0 */ |
| |
| if (object_type == 0) { |
| static const GTypeInfo object_info = { |
| sizeof (GstTagDemuxClass), |
| gst_tag_demux_base_init, |
| NULL, /* base_finalize */ |
| gst_tag_demux_class_init, |
| NULL, /* class_finalize */ |
| NULL, /* class_data */ |
| sizeof (GstTagDemux), |
| 0, /* n_preallocs */ |
| (GInstanceInitFunc) gst_tag_demux_init |
| }; |
| |
| object_type = g_type_register_static (GST_TYPE_ELEMENT, |
| "GstTagDemux", &object_info, G_TYPE_FLAG_ABSTRACT); |
| } |
| |
| return object_type; |
| } |
| |
| static void |
| gst_tag_demux_base_init (gpointer klass) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| gst_element_class_add_static_pad_template (element_class, &src_factory); |
| |
| GST_DEBUG_CATEGORY_INIT (tagdemux_debug, "tagdemux", 0, |
| "tag demux base class"); |
| } |
| |
| static void |
| gst_tag_demux_class_init (gpointer klass, gpointer d) |
| { |
| GstTagDemuxClass *tagdemux_class = GST_TAG_DEMUX_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->dispose = gst_tag_demux_dispose; |
| |
| element_class->change_state = GST_DEBUG_FUNCPTR (gst_tag_demux_change_state); |
| |
| g_type_class_add_private (klass, sizeof (GstTagDemuxPrivate)); |
| |
| /* subclasses must set at least one of these */ |
| tagdemux_class->min_start_size = 0; |
| tagdemux_class->min_end_size = 0; |
| } |
| |
| static void |
| gst_tag_demux_reset (GstTagDemux * tagdemux) |
| { |
| GstBuffer **buffer_p = &tagdemux->priv->collect; |
| GstCaps **caps_p = &tagdemux->priv->src_caps; |
| |
| tagdemux->priv->strip_start = 0; |
| tagdemux->priv->strip_end = 0; |
| tagdemux->priv->upstream_size = -1; |
| tagdemux->priv->state = GST_TAG_DEMUX_READ_START_TAG; |
| tagdemux->priv->send_tag_event = FALSE; |
| |
| gst_buffer_replace (buffer_p, NULL); |
| tagdemux->priv->collect_size = 0; |
| tagdemux->priv->tagsize = 0; |
| gst_adapter_clear (tagdemux->priv->adapter); |
| gst_caps_replace (caps_p, NULL); |
| |
| if (tagdemux->priv->event_tags) { |
| gst_tag_list_unref (tagdemux->priv->event_tags); |
| tagdemux->priv->event_tags = NULL; |
| } |
| if (tagdemux->priv->parsed_tags) { |
| gst_tag_list_unref (tagdemux->priv->parsed_tags); |
| tagdemux->priv->parsed_tags = NULL; |
| } |
| |
| gst_segment_init (&tagdemux->priv->segment, GST_FORMAT_UNDEFINED); |
| tagdemux->priv->need_newseg = TRUE; |
| |
| g_list_foreach (tagdemux->priv->pending_events, |
| (GFunc) gst_mini_object_unref, NULL); |
| g_list_free (tagdemux->priv->pending_events); |
| tagdemux->priv->pending_events = NULL; |
| } |
| |
| static void |
| gst_tag_demux_init (GstTagDemux * demux, GstTagDemuxClass * gclass) |
| { |
| GstElementClass *element_klass = GST_ELEMENT_CLASS (gclass); |
| GstPadTemplate *tmpl; |
| |
| demux->priv = g_type_instance_get_private ((GTypeInstance *) demux, |
| GST_TYPE_TAG_DEMUX); |
| |
| /* sink pad */ |
| tmpl = gst_element_class_get_pad_template (element_klass, "sink"); |
| if (tmpl) { |
| demux->priv->sinkpad = gst_pad_new_from_template (tmpl, "sink"); |
| |
| gst_pad_set_activatemode_function (demux->priv->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_sink_activate_mode)); |
| gst_pad_set_activate_function (demux->priv->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_sink_activate)); |
| gst_pad_set_event_function (demux->priv->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_sink_event)); |
| gst_pad_set_chain_function (demux->priv->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_chain)); |
| gst_element_add_pad (GST_ELEMENT (demux), demux->priv->sinkpad); |
| } else { |
| g_warning ("GstTagDemux subclass %s must provide a sink pad template", |
| G_OBJECT_TYPE_NAME (demux)); |
| } |
| |
| /* source pad */ |
| tmpl = gst_element_class_get_pad_template (element_klass, "src"); |
| demux->priv->srcpad = gst_pad_new_from_template (tmpl, "src"); |
| gst_pad_set_query_function (demux->priv->srcpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_pad_query)); |
| gst_pad_set_event_function (demux->priv->srcpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_srcpad_event)); |
| gst_pad_set_activatemode_function (demux->priv->srcpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_src_activate_mode)); |
| gst_pad_set_getrange_function (demux->priv->srcpad, |
| GST_DEBUG_FUNCPTR (gst_tag_demux_src_getrange)); |
| gst_pad_use_fixed_caps (demux->priv->srcpad); |
| gst_element_add_pad (GST_ELEMENT (demux), demux->priv->srcpad); |
| |
| demux->priv->adapter = gst_adapter_new (); |
| gst_tag_demux_reset (demux); |
| } |
| |
| static void |
| gst_tag_demux_dispose (GObject * object) |
| { |
| GstTagDemux *tagdemux = GST_TAG_DEMUX (object); |
| |
| gst_tag_demux_reset (tagdemux); |
| if (tagdemux->priv->adapter) { |
| g_object_unref (tagdemux->priv->adapter); |
| tagdemux->priv->adapter = NULL; |
| } |
| |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| // FIXME: convert to set_caps / sending a caps event |
| static void |
| gst_tag_demux_set_src_caps (GstTagDemux * tagdemux, GstCaps * new_caps) |
| { |
| GstCaps *old_caps = tagdemux->priv->src_caps; |
| |
| if (old_caps == NULL || !gst_caps_is_equal (new_caps, old_caps)) { |
| GstEvent *event; |
| |
| gst_caps_replace (&tagdemux->priv->src_caps, new_caps); |
| |
| GST_DEBUG_OBJECT (tagdemux, "Changing src pad caps to %" GST_PTR_FORMAT, |
| tagdemux->priv->src_caps); |
| |
| event = |
| gst_pad_get_sticky_event (tagdemux->priv->sinkpad, |
| GST_EVENT_STREAM_START, 0); |
| if (!event) { |
| gchar *stream_id = gst_pad_create_stream_id (tagdemux->priv->srcpad, |
| GST_ELEMENT_CAST (tagdemux), NULL); |
| GST_DEBUG_OBJECT (tagdemux, "Creating new STREAM_START event"); |
| event = gst_event_new_stream_start (stream_id); |
| g_free (stream_id); |
| gst_event_set_group_id (event, gst_util_group_id_next ()); |
| } |
| gst_pad_push_event (tagdemux->priv->srcpad, event); |
| |
| gst_pad_set_caps (tagdemux->priv->srcpad, tagdemux->priv->src_caps); |
| } else { |
| /* Caps never changed */ |
| } |
| } |
| |
| /* will return FALSE if buffer is beyond end of data; will return TRUE |
| * if buffer was trimmed successfully or didn't need trimming, but may |
| * also return TRUE and set *buf_ref to NULL if the buffer was before |
| * the start of the data */ |
| static gboolean |
| gst_tag_demux_trim_buffer (GstTagDemux * tagdemux, GstBuffer ** buf_ref, |
| gsize * buf_size) |
| { |
| GstBuffer *buf = *buf_ref; |
| |
| guint trim_start = 0; |
| guint out_size, bsize; |
| guint64 out_offset, boffset; |
| gboolean need_sub = FALSE; |
| |
| bsize = out_size = gst_buffer_get_size (buf); |
| boffset = out_offset = GST_BUFFER_OFFSET (buf); |
| |
| /* Adjust offset and length */ |
| if (!GST_BUFFER_OFFSET_IS_VALID (buf)) { |
| /* Can't change anything without an offset */ |
| *buf_size = bsize; |
| return TRUE; |
| } |
| |
| /* If the buffer crosses the tag at the end of file, trim it */ |
| if (tagdemux->priv->strip_end > 0) { |
| if (gst_tag_demux_get_upstream_size (tagdemux)) { |
| guint64 v1tag_offset = |
| tagdemux->priv->upstream_size - tagdemux->priv->strip_end; |
| |
| if (out_offset >= v1tag_offset) { |
| GST_DEBUG_OBJECT (tagdemux, "Buffer is past the end of the data"); |
| goto no_out_buffer_end; |
| } |
| |
| if (out_offset + out_size > v1tag_offset) { |
| out_size = v1tag_offset - out_offset; |
| need_sub = TRUE; |
| } |
| } |
| } |
| |
| if (tagdemux->priv->strip_start > 0) { |
| /* If the buffer crosses the tag at the start of file, trim it */ |
| if (out_offset <= tagdemux->priv->strip_start) { |
| if (out_offset + out_size <= tagdemux->priv->strip_start) { |
| GST_DEBUG_OBJECT (tagdemux, "Buffer is before the start of the data"); |
| goto no_out_buffer_start; |
| } |
| |
| trim_start = tagdemux->priv->strip_start - out_offset; |
| out_size -= trim_start; |
| out_offset = 0; |
| } else { |
| out_offset -= tagdemux->priv->strip_start; |
| } |
| need_sub = TRUE; |
| } |
| |
| if (need_sub) { |
| if (out_size != bsize || !gst_buffer_is_writable (buf)) { |
| GstBuffer *sub; |
| |
| GST_DEBUG_OBJECT (tagdemux, "Sub-buffering to trim size %d offset %" |
| G_GINT64_FORMAT " to %d offset %" G_GINT64_FORMAT, |
| bsize, boffset, out_size, out_offset); |
| |
| sub = |
| gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, trim_start, |
| out_size); |
| g_return_val_if_fail (sub != NULL, FALSE); |
| if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) |
| GST_BUFFER_TIMESTAMP (sub) = GST_BUFFER_TIMESTAMP (buf); |
| if (GST_BUFFER_DURATION_IS_VALID (buf)) |
| GST_BUFFER_DURATION (sub) = GST_BUFFER_DURATION (buf); |
| gst_buffer_unref (buf); |
| *buf_ref = buf = sub; |
| *buf_size = out_size; |
| } else { |
| GST_DEBUG_OBJECT (tagdemux, "Adjusting buffer from size %d offset %" |
| G_GINT64_FORMAT " to %d offset %" G_GINT64_FORMAT, |
| bsize, boffset, out_size, out_offset); |
| } |
| |
| GST_BUFFER_OFFSET (buf) = out_offset; |
| GST_BUFFER_OFFSET_END (buf) = out_offset + out_size; |
| } |
| |
| return TRUE; |
| |
| no_out_buffer_end: |
| { |
| gst_buffer_unref (buf); |
| *buf_ref = NULL; |
| return FALSE; |
| } |
| no_out_buffer_start: |
| { |
| gst_buffer_unref (buf); |
| *buf_ref = NULL; |
| return TRUE; |
| } |
| } |
| |
| static void |
| update_collected (GstTagDemux * demux) |
| { |
| guint avail; |
| GstBuffer *buf; |
| |
| avail = gst_adapter_available (demux->priv->adapter); |
| if (avail == 0) |
| return; |
| |
| buf = gst_adapter_take_buffer (demux->priv->adapter, avail); |
| |
| if (demux->priv->collect == NULL) { |
| demux->priv->collect = buf; |
| } else { |
| demux->priv->collect = gst_buffer_append (demux->priv->collect, buf); |
| } |
| demux->priv->collect_size += avail; |
| } |
| |
| static void |
| gst_tag_demux_chain_parse_tag (GstTagDemux * demux) |
| { |
| GstBuffer *collect; |
| GstTagDemuxResult parse_ret; |
| GstTagDemuxClass *klass; |
| guint tagsize = 0; |
| guint available; |
| |
| available = |
| demux->priv->collect_size + gst_adapter_available (demux->priv->adapter); |
| |
| klass = GST_TAG_DEMUX_CLASS (G_OBJECT_GET_CLASS (demux)); |
| |
| if (available < klass->min_start_size) { |
| GST_DEBUG_OBJECT (demux, "Only %u bytes available, but %u needed " |
| "to identify tag", available, klass->min_start_size); |
| return; /* wait for more data */ |
| } |
| |
| if (available < demux->priv->tagsize) { |
| GST_DEBUG_OBJECT (demux, "Only %u bytes available, but %u needed " |
| "to parse tag", available, demux->priv->tagsize); |
| return; /* wait for more data */ |
| } |
| |
| update_collected (demux); |
| demux->priv->collect = gst_buffer_make_writable (demux->priv->collect); |
| collect = demux->priv->collect; |
| |
| g_assert (gst_buffer_is_writable (collect)); |
| |
| |
| /* If we receive a buffer that's from the middle of the file, |
| * we can't read tags so move to typefinding */ |
| if (GST_BUFFER_OFFSET_IS_VALID (collect) && GST_BUFFER_OFFSET (collect) != 0) { |
| GST_DEBUG_OBJECT (demux, "Received buffer from non-zero offset %" |
| G_GINT64_FORMAT ". Can't read tags", GST_BUFFER_OFFSET (collect)); |
| demux->priv->state = GST_TAG_DEMUX_TYPEFINDING; |
| return; |
| } |
| |
| g_assert (klass->identify_tag != NULL); |
| g_assert (klass->parse_tag != NULL); |
| |
| if (!klass->identify_tag (demux, collect, TRUE, &tagsize)) { |
| GST_DEBUG_OBJECT (demux, "Could not identify start tag"); |
| demux->priv->state = GST_TAG_DEMUX_TYPEFINDING; |
| return; |
| } |
| |
| demux->priv->tagsize = tagsize; |
| |
| /* need to set offset of first buffer to 0 or trimming won't work */ |
| if (!GST_BUFFER_OFFSET_IS_VALID (collect)) { |
| GST_WARNING_OBJECT (demux, "Fixing up first buffer without offset"); |
| GST_BUFFER_OFFSET (collect) = 0; |
| } |
| |
| GST_DEBUG_OBJECT (demux, "Identified tag, size = %u bytes", tagsize); |
| |
| do { |
| GstTagList *tags = NULL; |
| guint newsize, saved_size; |
| |
| demux->priv->strip_start = tagsize; |
| |
| if (available < tagsize) { |
| GST_DEBUG_OBJECT (demux, "Only %u bytes available, but %u needed " |
| "to parse tag", available, tagsize); |
| return; /* wait for more data */ |
| } |
| |
| saved_size = gst_buffer_get_size (collect); |
| gst_buffer_set_size (collect, tagsize); |
| newsize = tagsize; |
| |
| parse_ret = klass->parse_tag (demux, collect, TRUE, &newsize, &tags); |
| |
| gst_buffer_set_size (collect, saved_size); |
| |
| switch (parse_ret) { |
| case GST_TAG_DEMUX_RESULT_OK: |
| demux->priv->strip_start = newsize; |
| demux->priv->parsed_tags = tags; |
| GST_DEBUG_OBJECT (demux, "Read start tag of size %u", newsize); |
| break; |
| case GST_TAG_DEMUX_RESULT_BROKEN_TAG: |
| demux->priv->strip_start = newsize; |
| demux->priv->parsed_tags = tags; |
| GST_WARNING_OBJECT (demux, "Ignoring broken start tag of size %d", |
| demux->priv->strip_start); |
| break; |
| case GST_TAG_DEMUX_RESULT_AGAIN: |
| GST_DEBUG_OBJECT (demux, "Re-parse, this time with %u bytes", newsize); |
| g_assert (newsize != tagsize); |
| tagsize = newsize; |
| break; |
| } |
| } while (parse_ret == GST_TAG_DEMUX_RESULT_AGAIN); |
| |
| GST_LOG_OBJECT (demux, "Parsed tag. Proceeding to typefinding"); |
| demux->priv->state = GST_TAG_DEMUX_TYPEFINDING; |
| demux->priv->send_tag_event = TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_tag_demux_chain_buffer (GstTagDemux * demux, GstBuffer * buf, |
| gboolean at_eos) |
| { |
| gsize size; |
| |
| size = gst_buffer_get_size (buf); |
| |
| /* Update our segment position info */ |
| if (demux->priv->segment.format == GST_FORMAT_BYTES) { |
| if (GST_BUFFER_OFFSET_IS_VALID (buf)) |
| demux->priv->segment.position = GST_BUFFER_OFFSET (buf); |
| demux->priv->segment.position += size; |
| } else if (demux->priv->segment.format == GST_FORMAT_TIME) { |
| if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) |
| demux->priv->segment.position = GST_BUFFER_TIMESTAMP (buf); |
| if (GST_BUFFER_DURATION_IS_VALID (buf)) |
| demux->priv->segment.position += GST_BUFFER_DURATION (buf); |
| } |
| |
| gst_adapter_push (demux->priv->adapter, buf); |
| buf = NULL; |
| |
| switch (demux->priv->state) { |
| case GST_TAG_DEMUX_READ_START_TAG: |
| gst_tag_demux_chain_parse_tag (demux); |
| if (demux->priv->state != GST_TAG_DEMUX_TYPEFINDING) |
| break; |
| /* Fall-through */ |
| case GST_TAG_DEMUX_TYPEFINDING:{ |
| GstTypeFindProbability probability = 0; |
| GstBuffer *typefind_buf = NULL; |
| gsize typefind_size; |
| GstCaps *caps; |
| |
| update_collected (demux); |
| |
| if (!at_eos && demux->priv->collect_size < |
| TYPE_FIND_MIN_SIZE + demux->priv->strip_start) |
| break; /* Go get more data first */ |
| |
| GST_DEBUG_OBJECT (demux, "Typefinding with size %" G_GSIZE_FORMAT, |
| demux->priv->collect_size); |
| |
| /* Trim the buffer and adjust offset for typefinding */ |
| typefind_buf = demux->priv->collect; |
| gst_buffer_ref (typefind_buf); |
| if (!gst_tag_demux_trim_buffer (demux, &typefind_buf, &typefind_size)) |
| return GST_FLOW_EOS; |
| |
| if (typefind_buf == NULL) |
| break; /* Still need more data */ |
| |
| caps = gst_type_find_helper_for_buffer (GST_OBJECT (demux), |
| typefind_buf, &probability); |
| |
| if (caps == NULL) { |
| if (typefind_size < TYPE_FIND_MAX_SIZE) { |
| /* Just break for more data */ |
| gst_buffer_unref (typefind_buf); |
| return GST_FLOW_OK; |
| } |
| |
| /* We failed typefind */ |
| GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, (NULL), |
| ("Could not detect type for contents within tag")); |
| gst_buffer_unref (typefind_buf); |
| gst_buffer_unref (demux->priv->collect); |
| demux->priv->collect = NULL; |
| demux->priv->collect_size = 0; |
| return GST_FLOW_ERROR; |
| } |
| gst_buffer_unref (typefind_buf); |
| |
| GST_DEBUG_OBJECT (demux, "Found type %" GST_PTR_FORMAT " with a " |
| "probability of %u", caps, probability); |
| |
| gst_tag_demux_set_src_caps (demux, caps); |
| gst_caps_unref (caps); |
| |
| /* Move onto streaming and fall-through to push out existing |
| * data */ |
| demux->priv->state = GST_TAG_DEMUX_STREAMING; |
| /* fall-through */ |
| } |
| case GST_TAG_DEMUX_STREAMING:{ |
| GstBuffer *outbuf = NULL; |
| gsize outbuf_size; |
| |
| update_collected (demux); |
| |
| /* Trim the buffer and adjust offset */ |
| if (demux->priv->collect) { |
| outbuf = demux->priv->collect; |
| demux->priv->collect = NULL; |
| demux->priv->collect_size = 0; |
| if (!gst_tag_demux_trim_buffer (demux, &outbuf, &outbuf_size)) |
| return GST_FLOW_EOS; |
| } |
| if (outbuf) { |
| /* Might need a new segment before the buffer */ |
| if (demux->priv->need_newseg) { |
| if (!gst_tag_demux_send_new_segment (demux)) { |
| GST_WARNING_OBJECT (demux, "Downstream did not handle newsegment " |
| "event as it should"); |
| } |
| demux->priv->need_newseg = FALSE; |
| } |
| |
| /* send any pending events we cached */ |
| gst_tag_demux_send_pending_events (demux); |
| |
| /* Send our own pending tag event */ |
| if (demux->priv->send_tag_event) { |
| gst_tag_demux_send_tag_event (demux); |
| demux->priv->send_tag_event = FALSE; |
| } |
| |
| GST_LOG_OBJECT (demux, "Pushing buffer %" GST_PTR_FORMAT, outbuf); |
| |
| return gst_pad_push (demux->priv->srcpad, outbuf); |
| } |
| } |
| } |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_tag_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) |
| { |
| return gst_tag_demux_chain_buffer (GST_TAG_DEMUX (parent), buf, FALSE); |
| } |
| |
| static gboolean |
| gst_tag_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstTagDemux *demux; |
| gboolean ret; |
| |
| demux = GST_TAG_DEMUX (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_EOS: |
| if (!gst_pad_has_current_caps (demux->priv->srcpad)) { |
| GST_INFO_OBJECT (demux, "EOS before we found a type"); |
| |
| /* push final buffer with eos indication to force typefinding */ |
| gst_tag_demux_chain_buffer (demux, gst_buffer_new (), TRUE); |
| |
| if (!gst_pad_has_current_caps (demux->priv->srcpad)) { |
| GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, (NULL), (NULL)); |
| } |
| } |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| case GST_EVENT_SEGMENT: |
| { |
| gst_event_copy_segment (event, &demux->priv->segment); |
| |
| demux->priv->need_newseg = TRUE; |
| gst_event_unref (event); |
| ret = TRUE; |
| break; |
| } |
| case GST_EVENT_FLUSH_STOP: |
| case GST_EVENT_FLUSH_START: |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| case GST_EVENT_CAPS: |
| /* we drop the caps event. We do typefind and push a new caps event. */ |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| default: |
| if (demux->priv->need_newseg && GST_EVENT_IS_SERIALIZED (event)) { |
| /* Cache all events if we have a pending segment, so they don't get |
| * lost (esp. tag events) */ |
| GST_INFO_OBJECT (demux, "caching event: %" GST_PTR_FORMAT, event); |
| GST_OBJECT_LOCK (demux); |
| demux->priv->pending_events = |
| g_list_append (demux->priv->pending_events, event); |
| GST_OBJECT_UNLOCK (demux); |
| ret = TRUE; |
| } else { |
| ret = gst_pad_event_default (pad, parent, event); |
| } |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_tag_demux_get_upstream_size (GstTagDemux * tagdemux) |
| { |
| gint64 len; |
| |
| /* Short-cut if we already queried upstream */ |
| if (tagdemux->priv->upstream_size > 0) |
| return TRUE; |
| |
| if (!gst_pad_peer_query_duration (tagdemux->priv->sinkpad, GST_FORMAT_BYTES, |
| &len) || len <= 0) { |
| return FALSE; |
| } |
| |
| tagdemux->priv->upstream_size = len; |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_tag_demux_seek_pull (GstTagDemux * tagdemux, GstEvent * event) |
| { |
| GstSeekFlags flags; |
| GstSeekType start_type, stop_type; |
| GstFormat format; |
| gboolean flush; |
| gdouble rate; |
| gint64 start, stop; |
| GstSegment seeksegment = { 0, }; |
| |
| gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, |
| &stop_type, &stop); |
| |
| /* we can only seek on bytes */ |
| if (format != GST_FORMAT_BYTES) { |
| GST_DEBUG_OBJECT (tagdemux, "Can only seek on BYTES"); |
| return FALSE; |
| } |
| |
| if (tagdemux->priv->state != GST_TAG_DEMUX_STREAMING) { |
| GST_DEBUG_OBJECT (tagdemux, "Can only seek if streaming already"); |
| return FALSE; |
| } |
| |
| switch (start_type) { |
| case GST_SEEK_TYPE_SET: |
| if (start == -1) |
| start = 0; |
| start += tagdemux->priv->strip_start; |
| break; |
| case GST_SEEK_TYPE_END: |
| /* Adjust the seek to be relative to the start of any end tag |
| * (note: 10 bytes before end is represented by stop=-10) */ |
| if (start > 0) |
| start = 0; |
| start -= tagdemux->priv->strip_end; |
| break; |
| case GST_SEEK_TYPE_NONE: |
| default: |
| break; |
| } |
| switch (stop_type) { |
| case GST_SEEK_TYPE_SET: |
| if (stop != -1) { |
| /* -1 means the end of the file, pass it upstream intact */ |
| stop += tagdemux->priv->strip_start; |
| } |
| break; |
| case GST_SEEK_TYPE_END: |
| /* Adjust the seek to be relative to the start of any end tag |
| * (note: 10 bytes before end is represented by stop=-10) */ |
| if (stop > 0) |
| stop = 0; |
| stop -= tagdemux->priv->strip_end; |
| break; |
| case GST_SEEK_TYPE_NONE: |
| default: |
| break; |
| } |
| |
| /* copy segment, we need this because we still need the old |
| * segment when we close the current segment. */ |
| memcpy (&seeksegment, &tagdemux->priv->segment, sizeof (GstSegment)); |
| |
| GST_DEBUG_OBJECT (tagdemux, "configuring seek"); |
| gst_segment_do_seek (&seeksegment, rate, format, flags, |
| start_type, start, stop_type, stop, NULL); |
| |
| flush = ! !(flags & GST_SEEK_FLAG_FLUSH); |
| |
| GST_DEBUG_OBJECT (tagdemux, "New segment %" GST_SEGMENT_FORMAT, &seeksegment); |
| |
| if (flush) { |
| GST_DEBUG_OBJECT (tagdemux, "Starting flush"); |
| gst_pad_push_event (tagdemux->priv->sinkpad, gst_event_new_flush_start ()); |
| gst_pad_push_event (tagdemux->priv->srcpad, gst_event_new_flush_start ()); |
| } else { |
| GST_DEBUG_OBJECT (tagdemux, "Non-flushing seek, pausing task"); |
| gst_pad_pause_task (tagdemux->priv->sinkpad); |
| } |
| |
| /* now grab the stream lock so that streaming cannot continue, for |
| * non flushing seeks when the element is in PAUSED this could block |
| * forever. */ |
| GST_DEBUG_OBJECT (tagdemux, "Waiting for streaming to stop"); |
| GST_PAD_STREAM_LOCK (tagdemux->priv->sinkpad); |
| |
| if (flush) { |
| GST_DEBUG_OBJECT (tagdemux, "Stopping flush"); |
| gst_pad_push_event (tagdemux->priv->sinkpad, |
| gst_event_new_flush_stop (TRUE)); |
| gst_pad_push_event (tagdemux->priv->srcpad, |
| gst_event_new_flush_stop (TRUE)); |
| } |
| |
| /* now update the real segment info */ |
| GST_DEBUG_OBJECT (tagdemux, "Committing new seek segment"); |
| memcpy (&tagdemux->priv->segment, &seeksegment, sizeof (GstSegment)); |
| tagdemux->priv->offset = tagdemux->priv->segment.start; |
| |
| /* notify start of new segment */ |
| if (tagdemux->priv->segment.flags & GST_SEGMENT_FLAG_SEGMENT) { |
| GstMessage *msg; |
| |
| msg = gst_message_new_segment_start (GST_OBJECT (tagdemux), |
| GST_FORMAT_BYTES, tagdemux->priv->segment.start); |
| gst_element_post_message (GST_ELEMENT (tagdemux), msg); |
| } |
| |
| tagdemux->priv->need_newseg = TRUE; |
| |
| /* restart our task since it might have been stopped when we did the |
| * flush. */ |
| gst_pad_start_task (tagdemux->priv->sinkpad, |
| (GstTaskFunction) gst_tag_demux_element_loop, tagdemux, NULL); |
| |
| /* streaming can continue now */ |
| GST_PAD_STREAM_UNLOCK (tagdemux->priv->sinkpad); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_tag_demux_seek_push (GstTagDemux * tagdemux, GstEvent * event) |
| { |
| gboolean res = FALSE; |
| gdouble rate; |
| GstFormat format; |
| GstSeekType start_type, stop_type; |
| GstSeekFlags flags; |
| gint64 start, stop; |
| |
| gst_event_parse_seek (event, &rate, &format, &flags, |
| &start_type, &start, &stop_type, &stop); |
| |
| if (format == GST_FORMAT_BYTES && |
| tagdemux->priv->state == GST_TAG_DEMUX_STREAMING && |
| gst_pad_is_linked (tagdemux->priv->sinkpad)) { |
| GstEvent *upstream; |
| |
| switch (start_type) { |
| case GST_SEEK_TYPE_SET: |
| if (start == -1) |
| start = 0; |
| start += tagdemux->priv->strip_start; |
| break; |
| case GST_SEEK_TYPE_END: |
| /* Adjust the seek to be relative to the start of any end tag |
| * (note: 10 bytes before end is represented by stop=-10) */ |
| if (start > 0) |
| start = 0; |
| start -= tagdemux->priv->strip_end; |
| break; |
| case GST_SEEK_TYPE_NONE: |
| default: |
| break; |
| } |
| switch (stop_type) { |
| case GST_SEEK_TYPE_SET: |
| if (stop != -1) { |
| /* -1 means the end of the file, pass it upstream intact */ |
| stop += tagdemux->priv->strip_start; |
| } |
| break; |
| case GST_SEEK_TYPE_END: |
| /* Adjust the seek to be relative to the start of any end tag |
| * (note: 10 bytes before end is represented by stop=-10) */ |
| if (stop > 0) |
| stop = 0; |
| stop -= tagdemux->priv->strip_end; |
| break; |
| case GST_SEEK_TYPE_NONE: |
| default: |
| break; |
| } |
| upstream = gst_event_new_seek (rate, format, flags, |
| start_type, start, stop_type, stop); |
| res = gst_pad_push_event (tagdemux->priv->sinkpad, upstream); |
| } else if (format == GST_FORMAT_TIME && |
| tagdemux->priv->state == GST_TAG_DEMUX_STREAMING && |
| gst_pad_is_linked (tagdemux->priv->sinkpad)) { |
| res = gst_pad_push_event (tagdemux->priv->sinkpad, gst_event_ref (event)); |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_tag_demux_srcpad_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstTagDemux *tagdemux; |
| gboolean res = FALSE; |
| |
| tagdemux = GST_TAG_DEMUX (parent); |
| |
| /* Handle SEEK events, with adjusted byte offsets and sizes. */ |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| { |
| if (GST_PAD_MODE (tagdemux->priv->sinkpad) == GST_PAD_MODE_PUSH) |
| res = gst_tag_demux_seek_push (tagdemux, event); |
| else |
| res = gst_tag_demux_seek_pull (tagdemux, event); |
| break; |
| } |
| default: |
| res = gst_pad_push_event (tagdemux->priv->sinkpad, event); |
| event = NULL; |
| break; |
| } |
| |
| if (event) |
| gst_event_unref (event); |
| |
| return res; |
| } |
| |
| /* Read and interpret any end tag when activating in pull_range. |
| * Returns FALSE if pad activation should fail. */ |
| static GstFlowReturn |
| gst_tag_demux_pull_end_tag (GstTagDemux * demux, GstTagList ** tags) |
| { |
| GstTagDemuxResult parse_ret; |
| GstTagDemuxClass *klass; |
| GstFlowReturn flow_ret; |
| GstTagList *new_tags = NULL; |
| GstBuffer *buffer = NULL; |
| gboolean have_tag; |
| guint64 offset; |
| guint tagsize; |
| gsize bsize; |
| |
| klass = GST_TAG_DEMUX_CLASS (G_OBJECT_GET_CLASS (demux)); |
| |
| g_assert (klass->identify_tag != NULL); |
| g_assert (klass->parse_tag != NULL); |
| |
| if (klass->min_end_size == 0) { |
| GST_DEBUG_OBJECT (demux, "Not looking for tag at the end"); |
| return GST_FLOW_OK; |
| } |
| |
| if (demux->priv->upstream_size < klass->min_end_size) { |
| GST_DEBUG_OBJECT (demux, "File too small"); |
| return GST_FLOW_OK; |
| } |
| |
| /* Pull enough to identify the tag and retrieve its total size */ |
| offset = demux->priv->upstream_size - klass->min_end_size; |
| |
| flow_ret = gst_pad_pull_range (demux->priv->sinkpad, offset, |
| klass->min_end_size, &buffer); |
| |
| if (flow_ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Could not read tag header from end of file, " |
| "ret = %s", gst_flow_get_name (flow_ret)); |
| goto done; |
| } |
| |
| bsize = gst_buffer_get_size (buffer); |
| |
| if (bsize < klass->min_end_size) { |
| GST_DEBUG_OBJECT (demux, "Only managed to read %" G_GSIZE_FORMAT " bytes" |
| "from file (required: %u bytes)", bsize, klass->min_end_size); |
| flow_ret = GST_FLOW_EOS; |
| goto done; |
| } |
| |
| have_tag = klass->identify_tag (demux, buffer, FALSE, &tagsize); |
| |
| if (!have_tag) { |
| GST_DEBUG_OBJECT (demux, "Could not find tag at end"); |
| flow_ret = GST_FLOW_OK; |
| goto done; |
| } |
| |
| /* Now pull the entire tag */ |
| do { |
| guint newsize, saved_size; |
| |
| GST_DEBUG_OBJECT (demux, "Identified tag at end, size=%u bytes", tagsize); |
| |
| demux->priv->strip_end = tagsize; |
| |
| g_assert (tagsize >= klass->min_end_size); |
| |
| /* Get buffer that's exactly the requested size */ |
| if (bsize != tagsize) { |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| |
| offset = demux->priv->upstream_size - tagsize; |
| |
| flow_ret = gst_pad_pull_range (demux->priv->sinkpad, offset, |
| tagsize, &buffer); |
| |
| if (flow_ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Could not read data from end of file at " |
| "offset %" G_GUINT64_FORMAT ". ret = %s", offset, |
| gst_flow_get_name (flow_ret)); |
| goto done; |
| } |
| |
| bsize = gst_buffer_get_size (buffer); |
| |
| if (bsize < tagsize) { |
| GST_DEBUG_OBJECT (demux, "Only managed to read %" G_GSIZE_FORMAT |
| " bytes from file", bsize); |
| flow_ret = GST_FLOW_EOS; |
| goto done; |
| } |
| } |
| |
| GST_BUFFER_OFFSET (buffer) = offset; |
| |
| saved_size = bsize; |
| gst_buffer_set_size (buffer, tagsize); |
| newsize = tagsize; |
| |
| parse_ret = klass->parse_tag (demux, buffer, FALSE, &newsize, &new_tags); |
| |
| gst_buffer_set_size (buffer, saved_size); |
| |
| switch (parse_ret) { |
| case GST_TAG_DEMUX_RESULT_OK: |
| flow_ret = GST_FLOW_OK; |
| demux->priv->strip_end = newsize; |
| GST_DEBUG_OBJECT (demux, "Read tag at end, size %d", |
| demux->priv->strip_end); |
| break; |
| case GST_TAG_DEMUX_RESULT_BROKEN_TAG: |
| flow_ret = GST_FLOW_OK; |
| demux->priv->strip_end = newsize; |
| GST_WARNING_OBJECT (demux, "Ignoring broken tag at end, size %d", |
| demux->priv->strip_end); |
| break; |
| case GST_TAG_DEMUX_RESULT_AGAIN: |
| GST_DEBUG_OBJECT (demux, "Re-parse, this time with %d bytes", newsize); |
| g_assert (newsize != tagsize); |
| tagsize = newsize; |
| break; |
| } |
| } while (parse_ret == GST_TAG_DEMUX_RESULT_AGAIN); |
| |
| *tags = new_tags; |
| new_tags = NULL; |
| |
| done: |
| if (new_tags) |
| gst_tag_list_unref (new_tags); |
| if (buffer) |
| gst_buffer_unref (buffer); |
| return flow_ret; |
| } |
| |
| /* Read and interpret any tag at the start when activating in |
| * pull_range. Returns FALSE if pad activation should fail. */ |
| static GstFlowReturn |
| gst_tag_demux_pull_start_tag (GstTagDemux * demux, GstTagList ** tags) |
| { |
| GstTagDemuxResult parse_ret; |
| GstTagDemuxClass *klass; |
| GstFlowReturn flow_ret; |
| GstTagList *new_tags = NULL; |
| GstBuffer *buffer = NULL; |
| gboolean have_tag; |
| guint req, tagsize; |
| gsize bsize; |
| |
| klass = GST_TAG_DEMUX_CLASS (G_OBJECT_GET_CLASS (demux)); |
| |
| g_assert (klass->identify_tag != NULL); |
| g_assert (klass->parse_tag != NULL); |
| |
| if (klass->min_start_size == 0) { |
| GST_DEBUG_OBJECT (demux, "Not looking for tag at the beginning"); |
| return GST_FLOW_OK; |
| } |
| |
| /* Handle tag at start. Try with 4kB to start with */ |
| req = MAX (klass->min_start_size, 4096); |
| |
| /* Pull enough to identify the tag and retrieve its total size */ |
| flow_ret = gst_pad_pull_range (demux->priv->sinkpad, 0, req, &buffer); |
| if (flow_ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Could not read data from start of file ret=%s", |
| gst_flow_get_name (flow_ret)); |
| goto done; |
| } |
| |
| bsize = gst_buffer_get_size (buffer); |
| |
| if (bsize < klass->min_start_size) { |
| GST_DEBUG_OBJECT (demux, "Only managed to read %" G_GSIZE_FORMAT |
| " bytes from file - no tag in this file", bsize); |
| flow_ret = GST_FLOW_EOS; |
| goto done; |
| } |
| |
| have_tag = klass->identify_tag (demux, buffer, TRUE, &tagsize); |
| |
| if (!have_tag) { |
| GST_DEBUG_OBJECT (demux, "Could not find start tag"); |
| flow_ret = GST_FLOW_OK; |
| goto done; |
| } |
| |
| GST_DEBUG_OBJECT (demux, "Identified start tag, size = %u bytes", tagsize); |
| |
| do { |
| guint newsize, saved_size; |
| |
| demux->priv->strip_start = tagsize; |
| |
| /* Now pull the entire tag */ |
| g_assert (tagsize >= klass->min_start_size); |
| |
| if (bsize < tagsize) { |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| |
| flow_ret = gst_pad_pull_range (demux->priv->sinkpad, 0, tagsize, &buffer); |
| if (flow_ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Could not read data from start of file, " |
| "ret = %s", gst_flow_get_name (flow_ret)); |
| goto done; |
| } |
| |
| bsize = gst_buffer_get_size (buffer); |
| |
| if (bsize < tagsize) { |
| GST_DEBUG_OBJECT (demux, "Only managed to read %" G_GSIZE_FORMAT |
| " bytes from file", bsize); |
| GST_ELEMENT_ERROR (demux, STREAM, DECODE, |
| (_("Failed to read tag: not enough data")), (NULL)); |
| flow_ret = GST_FLOW_EOS; |
| goto done; |
| } |
| } |
| |
| saved_size = bsize; |
| gst_buffer_set_size (buffer, tagsize); |
| newsize = tagsize; |
| parse_ret = klass->parse_tag (demux, buffer, TRUE, &newsize, &new_tags); |
| |
| gst_buffer_set_size (buffer, saved_size); |
| |
| switch (parse_ret) { |
| case GST_TAG_DEMUX_RESULT_OK: |
| flow_ret = GST_FLOW_OK; |
| demux->priv->strip_start = newsize; |
| GST_DEBUG_OBJECT (demux, "Read start tag of size %d", newsize); |
| break; |
| case GST_TAG_DEMUX_RESULT_BROKEN_TAG: |
| flow_ret = GST_FLOW_OK; |
| demux->priv->strip_start = newsize; |
| GST_WARNING_OBJECT (demux, "Ignoring broken start tag of size %d", |
| demux->priv->strip_start); |
| break; |
| case GST_TAG_DEMUX_RESULT_AGAIN: |
| GST_DEBUG_OBJECT (demux, "Re-parse, this time with %d bytes", newsize); |
| g_assert (newsize != tagsize); |
| tagsize = newsize; |
| break; |
| } |
| } while (parse_ret == GST_TAG_DEMUX_RESULT_AGAIN); |
| |
| *tags = new_tags; |
| new_tags = NULL; |
| |
| done: |
| if (new_tags) |
| gst_tag_list_unref (new_tags); |
| if (buffer) |
| gst_buffer_unref (buffer); |
| return flow_ret; |
| } |
| |
| /* This function operates similarly to gst_type_find_element_loop |
| * in the typefind element |
| * 1. try to read tags in pull mode |
| * 2. typefind the contents |
| * 3. if we didn't find any caps, fail. |
| * 4. set caps on srcpad |
| */ |
| static GstFlowReturn |
| gst_tag_demux_element_find (GstTagDemux * demux) |
| { |
| GstTagDemuxClass *klass; |
| GstTypeFindProbability probability = 0; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstTagList *start_tags = NULL; |
| GstTagList *end_tags = NULL; |
| gboolean e_tag_ok, s_tag_ok; |
| GstCaps *caps = NULL; |
| |
| /* Look for tags at start and end of file */ |
| GST_DEBUG_OBJECT (demux, "Activated pull mode. Looking for tags"); |
| if (!gst_tag_demux_get_upstream_size (demux)) |
| goto no_size; |
| |
| demux->priv->strip_start = 0; |
| demux->priv->strip_end = 0; |
| |
| /* 1 - Read tags */ |
| ret = gst_tag_demux_pull_start_tag (demux, &start_tags); |
| if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS) |
| goto read_tag_error; |
| s_tag_ok = ret == GST_FLOW_OK; |
| ret = gst_tag_demux_pull_end_tag (demux, &end_tags); |
| if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS) |
| goto read_tag_error; |
| e_tag_ok = ret == GST_FLOW_OK; |
| ret = GST_FLOW_OK; |
| |
| klass = GST_TAG_DEMUX_CLASS (G_OBJECT_GET_CLASS (demux)); |
| |
| if (klass->merge_tags != NULL) { |
| demux->priv->parsed_tags = klass->merge_tags (demux, start_tags, end_tags); |
| } else { |
| /* we merge in REPLACE mode, so put the less important tags first, which |
| * we'll just assume is the end tag (subclasses may change this behaviour |
| * or make it configurable by overriding the merge_tags vfunc) */ |
| demux->priv->parsed_tags = |
| gst_tag_list_merge (end_tags, start_tags, GST_TAG_MERGE_REPLACE); |
| } |
| |
| if (start_tags) |
| gst_tag_list_unref (start_tags); |
| if (end_tags) |
| gst_tag_list_unref (end_tags); |
| |
| /* Only happens if both are EOS, i.e. not enough data could be read */ |
| if (!e_tag_ok && !s_tag_ok) |
| goto no_tags; |
| |
| if (demux->priv->parsed_tags != NULL) { |
| demux->priv->send_tag_event = TRUE; |
| } |
| |
| if (demux->priv->upstream_size <= |
| demux->priv->strip_start + demux->priv->strip_end) |
| goto no_data; |
| |
| /* 2 - Do typefinding on data, but not if downstream is in charge */ |
| if (GST_PAD_MODE (demux->priv->srcpad) == GST_PAD_MODE_PULL) |
| goto skip_typefinding; |
| |
| ret = gst_type_find_helper_get_range_full (GST_OBJECT (demux), NULL, |
| (GstTypeFindHelperGetRangeFunction) gst_tag_demux_read_range, |
| demux->priv->upstream_size |
| - (demux->priv->strip_start + demux->priv->strip_end), NULL, |
| &caps, &probability); |
| if (ret != GST_FLOW_OK) |
| goto read_tag_error; |
| |
| GST_INFO_OBJECT (demux, "Found type %" GST_PTR_FORMAT " with a " |
| "probability of %u", caps, probability); |
| |
| /* 3 - If we didn't find the caps, fail */ |
| if (caps == NULL) |
| goto no_caps; |
| |
| /* tag reading and typefinding were already done, don't do them again in |
| * the chain function if we end up in push mode */ |
| demux->priv->state = GST_TAG_DEMUX_STREAMING; |
| |
| /* 6 Set the srcpad caps now that we know them */ |
| gst_tag_demux_set_src_caps (demux, caps); |
| gst_caps_unref (caps); |
| |
| skip_typefinding: |
| |
| /* set it again, in case we skipped typefinding */ |
| demux->priv->state = GST_TAG_DEMUX_STREAMING; |
| demux->priv->offset += demux->priv->strip_start; |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_size: |
| { |
| GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, |
| ("Could not get stream size"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| read_tag_error: |
| { |
| if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) |
| GST_ELEMENT_FLOW_ERROR (demux, ret); |
| return ret; |
| } |
| no_tags: |
| { |
| GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, |
| ("Could not get start and/or end tag"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| no_data: |
| { |
| /* There was no data (probably due to a truncated file) */ |
| /* so we don't know about type either */ |
| GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, ("No data in file"), |
| (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| no_caps: |
| { |
| GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, |
| ("Could not detect type of contents"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| /* This function operates similarly to gst_type_find_element_loop |
| * in the typefind element |
| * 1. try to read tags in pull mode |
| * 2. typefind the contents |
| * 3. if we didn't find any caps, fail. |
| * 4. set caps on srcpad |
| */ |
| static void |
| gst_tag_demux_element_loop (GstTagDemux * demux) |
| { |
| GstFlowReturn ret; |
| |
| switch (demux->priv->state) { |
| case GST_TAG_DEMUX_READ_START_TAG: |
| case GST_TAG_DEMUX_TYPEFINDING: |
| ret = gst_tag_demux_element_find (demux); |
| break; |
| case GST_TAG_DEMUX_STREAMING: |
| { |
| GstBuffer *outbuf = NULL; |
| |
| if (demux->priv->need_newseg) { |
| demux->priv->need_newseg = FALSE; |
| /* FIXME: check segment, should be 0-N for downstream */ |
| gst_tag_demux_send_new_segment (demux); |
| } |
| |
| /* Send our own pending tag event */ |
| if (demux->priv->send_tag_event) { |
| gst_tag_demux_send_tag_event (demux); |
| demux->priv->send_tag_event = FALSE; |
| } |
| |
| /* Pull data and push it downstream */ |
| ret = gst_pad_pull_range (demux->priv->sinkpad, demux->priv->offset, |
| DEFAULT_PULL_BLOCKSIZE, &outbuf); |
| |
| if (ret != GST_FLOW_OK) |
| break; |
| |
| GST_BUFFER_OFFSET (outbuf) = |
| demux->priv->offset - demux->priv->strip_start; |
| demux->priv->offset += gst_buffer_get_size (outbuf); |
| GST_BUFFER_OFFSET_END (outbuf) = |
| demux->priv->offset - demux->priv->strip_start; |
| |
| ret = gst_pad_push (demux->priv->srcpad, outbuf); |
| break; |
| } |
| default: |
| ret = GST_FLOW_ERROR; |
| break; |
| } |
| if (ret != GST_FLOW_OK) |
| goto pause; |
| |
| return; |
| |
| /* ERRORS */ |
| pause: |
| { |
| const gchar *reason = gst_flow_get_name (ret); |
| gboolean push_eos = FALSE; |
| |
| GST_LOG_OBJECT (demux, "pausing task, reason %s", reason); |
| gst_pad_pause_task (demux->priv->sinkpad); |
| |
| if (ret == GST_FLOW_EOS) { |
| /* perform EOS logic */ |
| |
| if (demux->priv->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
| gint64 stop; |
| |
| /* for segment playback we need to post when (in stream time) |
| * we stopped, this is either stop (when set) or the duration. */ |
| if ((stop = demux->priv->segment.stop) == -1) |
| stop = demux->priv->offset; |
| |
| GST_LOG_OBJECT (demux, "Sending segment done, at end of segment"); |
| gst_element_post_message (GST_ELEMENT_CAST (demux), |
| gst_message_new_segment_done (GST_OBJECT_CAST (demux), |
| GST_FORMAT_BYTES, stop)); |
| gst_pad_push_event (demux->priv->srcpad, |
| gst_event_new_segment_done (GST_FORMAT_BYTES, stop)); |
| } else { |
| push_eos = TRUE; |
| } |
| } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { |
| /* for fatal errors we post an error message */ |
| GST_ELEMENT_FLOW_ERROR (demux, ret); |
| push_eos = TRUE; |
| } |
| if (push_eos) { |
| /* send EOS, and prevent hanging if no streams yet */ |
| GST_LOG_OBJECT (demux, "Sending EOS, at end of stream"); |
| gst_pad_push_event (demux->priv->srcpad, gst_event_new_eos ()); |
| } |
| return; |
| } |
| } |
| |
| static gboolean |
| gst_tag_demux_sink_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| GstTagDemux *demux = GST_TAG_DEMUX (parent); |
| gboolean res; |
| |
| switch (mode) { |
| case GST_PAD_MODE_PULL: |
| if (active) { |
| demux->priv->need_newseg = TRUE; |
| demux->priv->offset = 0; |
| res = TRUE; |
| } else { |
| res = gst_pad_stop_task (pad); |
| } |
| break; |
| default: |
| res = TRUE; |
| break; |
| } |
| |
| if (active) |
| GST_TAG_DEMUX (parent)->priv->state = GST_TAG_DEMUX_READ_START_TAG; |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_tag_demux_sink_activate (GstPad * sinkpad, GstObject * parent) |
| { |
| GstTagDemux *demux; |
| GstQuery *query; |
| gboolean pull_mode; |
| |
| demux = GST_TAG_DEMUX (parent); |
| |
| /* 1: */ |
| /* If we can activate pull_range upstream, then read any end and start |
| * tags, otherwise activate in push mode and the chain function will |
| * collect buffers, read the start tag and output a buffer to end |
| * preroll. |
| */ |
| query = gst_query_new_scheduling (); |
| |
| if (!gst_pad_peer_query (sinkpad, query)) { |
| gst_query_unref (query); |
| goto activate_push; |
| } |
| |
| pull_mode = gst_query_has_scheduling_mode_with_flags (query, |
| GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE); |
| gst_query_unref (query); |
| |
| if (!pull_mode) |
| goto activate_push; |
| |
| if (!gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE)) |
| goto activate_push; |
| |
| /* only start our task if we ourselves decide to start in pull mode */ |
| return gst_pad_start_task (sinkpad, |
| (GstTaskFunction) gst_tag_demux_element_loop, demux, NULL); |
| |
| activate_push: |
| { |
| GST_DEBUG_OBJECT (demux, "No pull mode. Changing to push, but won't be " |
| "able to read end tags"); |
| return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); |
| } |
| } |
| |
| static gboolean |
| gst_tag_demux_src_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean res; |
| GstTagDemux *demux = GST_TAG_DEMUX (parent); |
| |
| switch (mode) { |
| case GST_PAD_MODE_PULL: |
| /* make sure our task stops pushing, we can't call _stop here |
| * because this activation might happen from the streaming thread. */ |
| gst_pad_pause_task (demux->priv->sinkpad); |
| res = gst_pad_activate_mode (demux->priv->sinkpad, mode, active); |
| break; |
| default: |
| res = TRUE; |
| break; |
| } |
| return res; |
| } |
| |
| static inline GstFlowReturn |
| gst_tag_demux_ensure_tags (GstTagDemux * demux) |
| { |
| GstFlowReturn flow = GST_FLOW_OK; |
| |
| if (G_UNLIKELY (demux->priv->state == GST_TAG_DEMUX_READ_START_TAG && |
| GST_PAD_MODE (demux->priv->srcpad) == GST_PAD_MODE_PULL)) { |
| |
| flow = gst_tag_demux_element_find (demux); |
| GST_INFO_OBJECT (demux, "pulled tags: %s", gst_flow_get_name (flow)); |
| } |
| return flow; |
| } |
| |
| static GstFlowReturn |
| gst_tag_demux_read_range (GstTagDemux * demux, GstObject * parent, |
| guint64 offset, guint length, GstBuffer ** buffer) |
| { |
| GstFlowReturn ret; |
| guint64 in_offset; |
| guint in_length; |
| gsize size; |
| |
| g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); |
| |
| /* Ensure we already have computed our tags to properly use the offsets |
| * below */ |
| ret = gst_tag_demux_ensure_tags (demux); |
| if (ret != GST_FLOW_OK) |
| return ret; |
| |
| /* Adjust offset and length of the request to trim off tag information. |
| * For the returned buffer, adjust the output offset to match what downstream |
| * should see */ |
| in_offset = offset + demux->priv->strip_start; |
| |
| if (!gst_tag_demux_get_upstream_size (demux)) |
| return GST_FLOW_ERROR; |
| |
| if (in_offset + length >= demux->priv->upstream_size - demux->priv->strip_end) { |
| if (in_offset + demux->priv->strip_end >= demux->priv->upstream_size) |
| return GST_FLOW_EOS; |
| in_length = demux->priv->upstream_size - demux->priv->strip_end - in_offset; |
| } else { |
| in_length = length; |
| } |
| |
| ret = gst_pad_pull_range (demux->priv->sinkpad, in_offset, in_length, buffer); |
| |
| if (ret == GST_FLOW_OK && *buffer) { |
| if (!gst_tag_demux_trim_buffer (demux, buffer, &size)) |
| goto read_beyond_end; |
| |
| /* this should only happen in streaming mode */ |
| g_assert (*buffer != NULL); |
| } |
| |
| return ret; |
| |
| read_beyond_end: |
| { |
| GST_DEBUG_OBJECT (demux, "attempted read beyond end of file"); |
| if (*buffer != NULL) { |
| gst_buffer_unref (*buffer); |
| *buffer = NULL; |
| } |
| return GST_FLOW_EOS; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_tag_demux_src_getrange (GstPad * srcpad, GstObject * parent, |
| guint64 offset, guint length, GstBuffer ** buffer) |
| { |
| GstTagDemux *demux = GST_TAG_DEMUX (parent); |
| |
| /* downstream in pull mode won't miss a newsegment event, |
| * but it likely appreciates other (tag) events */ |
| if (demux->priv->need_newseg) { |
| gst_tag_demux_send_pending_events (demux); |
| demux->priv->need_newseg = FALSE; |
| } |
| |
| if (demux->priv->send_tag_event) { |
| gst_tag_demux_send_tag_event (demux); |
| demux->priv->send_tag_event = FALSE; |
| } |
| |
| return gst_tag_demux_read_range (demux, NULL, offset, length, buffer); |
| } |
| |
| static GstStateChangeReturn |
| gst_tag_demux_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn ret; |
| GstTagDemux *demux = GST_TAG_DEMUX (element); |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| /* Ensure that nothing is in any of the streaming thread functions |
| * anymore. While the above has deactivated all pads, there is nothing |
| * preventing downstream from activating our srcpad again and calling the |
| * getrange() function. Although we're in READY! |
| */ |
| GST_PAD_STREAM_LOCK (demux->priv->srcpad); |
| gst_tag_demux_reset (demux); |
| GST_PAD_STREAM_UNLOCK (demux->priv->srcpad); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_tag_demux_pad_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| /* For a position or duration query, adjust the returned |
| * bytes to strip off the end and start areas */ |
| GstTagDemux *demux = GST_TAG_DEMUX (parent); |
| GstFormat format; |
| gint64 result; |
| gboolean res = TRUE; |
| |
| /* FIXME: locking ? */ |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_SCHEDULING: |
| res = gst_pad_peer_query (demux->priv->sinkpad, query); |
| break; |
| case GST_QUERY_POSITION: |
| { |
| if (!(res = gst_pad_peer_query (demux->priv->sinkpad, query))) |
| goto done; |
| |
| gst_query_parse_position (query, &format, &result); |
| if (format == GST_FORMAT_BYTES) { |
| result -= demux->priv->strip_start; |
| gst_query_set_position (query, format, result); |
| } |
| break; |
| } |
| case GST_QUERY_DURATION: |
| { |
| if (!(res = gst_pad_peer_query (demux->priv->sinkpad, query))) |
| goto done; |
| |
| gst_query_parse_duration (query, &format, &result); |
| if (format == GST_FORMAT_BYTES) { |
| /* if downstream activated us in pull mode right away, e.g. in case of |
| * filesrc ! id3demux ! xyzparse ! .., read tags here, since we don't |
| * have a streaming thread of our own to do that. We do it here and |
| * not in get_range(), so we can return the right size in bytes.. */ |
| gst_tag_demux_ensure_tags (demux); |
| result -= demux->priv->strip_start + demux->priv->strip_end; |
| if (result < 0) |
| result = 0; |
| gst_query_set_duration (query, format, result); |
| } |
| break; |
| } |
| case GST_QUERY_CAPS: |
| { |
| |
| /* We can hijack caps query if we typefind already */ |
| if (demux->priv->src_caps) { |
| gst_query_set_caps_result (query, demux->priv->src_caps); |
| res = TRUE; |
| } else { |
| res = gst_pad_query_default (pad, parent, query); |
| } |
| break; |
| } |
| |
| default: |
| res = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| done: |
| return res; |
| } |
| |
| static void |
| gst_tag_demux_send_pending_events (GstTagDemux * demux) |
| { |
| GList *events; |
| |
| /* send any pending events we cached */ |
| GST_OBJECT_LOCK (demux); |
| events = demux->priv->pending_events; |
| demux->priv->pending_events = NULL; |
| GST_OBJECT_UNLOCK (demux); |
| |
| while (events != NULL) { |
| GST_DEBUG_OBJECT (demux->priv->srcpad, "sending cached %s event: %" |
| GST_PTR_FORMAT, GST_EVENT_TYPE_NAME (events->data), events->data); |
| gst_pad_push_event (demux->priv->srcpad, GST_EVENT (events->data)); |
| events = g_list_delete_link (events, events); |
| } |
| } |
| |
| static void |
| gst_tag_demux_send_tag_event (GstTagDemux * demux) |
| { |
| /* FIXME: what's the correct merge mode? Docs need to tell... */ |
| GstTagList *merged = gst_tag_list_merge (demux->priv->event_tags, |
| demux->priv->parsed_tags, GST_TAG_MERGE_KEEP); |
| |
| if (merged) { |
| GstEvent *event = gst_event_new_tag (merged); |
| |
| GST_EVENT_TIMESTAMP (event) = 0; |
| GST_DEBUG_OBJECT (demux, "Sending tag event on src pad"); |
| gst_pad_push_event (demux->priv->srcpad, event); |
| } |
| } |
| |
| static gboolean |
| gst_tag_demux_send_new_segment (GstTagDemux * tagdemux) |
| { |
| GstEvent *event; |
| gint64 start, stop, time; |
| GstSegment *seg = &tagdemux->priv->segment; |
| GstSegment newseg; |
| |
| if (seg->format == GST_FORMAT_UNDEFINED) { |
| GST_LOG_OBJECT (tagdemux, |
| "No new segment received before first buffer. Using default"); |
| gst_segment_init (seg, GST_FORMAT_BYTES); |
| seg->start = tagdemux->priv->strip_start; |
| seg->time = tagdemux->priv->strip_start; |
| } |
| |
| /* Can't adjust segments in non-BYTES formats */ |
| if (tagdemux->priv->segment.format != GST_FORMAT_BYTES) { |
| event = gst_event_new_segment (seg); |
| return gst_pad_push_event (tagdemux->priv->srcpad, event); |
| } |
| |
| start = seg->start; |
| stop = seg->stop; |
| time = seg->time; |
| |
| g_return_val_if_fail (start != -1, FALSE); |
| g_return_val_if_fail (time != -1, FALSE); |
| |
| if (tagdemux->priv->strip_end > 0) { |
| if (gst_tag_demux_get_upstream_size (tagdemux)) { |
| guint64 v1tag_offset = |
| tagdemux->priv->upstream_size - tagdemux->priv->strip_end; |
| |
| if (start >= v1tag_offset) { |
| /* Segment is completely within the end tag, output an open-ended |
| * segment, even though all the buffers will get trimmed away */ |
| start = v1tag_offset; |
| stop = -1; |
| } |
| |
| if (stop != -1 && stop >= v1tag_offset) { |
| GST_DEBUG_OBJECT (tagdemux, |
| "Segment crosses the end tag. Trimming end"); |
| stop = v1tag_offset; |
| } |
| } |
| } |
| |
| if (tagdemux->priv->strip_start > 0) { |
| if (start > tagdemux->priv->strip_start) |
| start -= tagdemux->priv->strip_start; |
| else |
| start = 0; |
| |
| if (time > tagdemux->priv->strip_start) |
| time -= tagdemux->priv->strip_start; |
| else |
| time = 0; |
| |
| if (stop != -1) { |
| if (stop > tagdemux->priv->strip_start) |
| stop -= tagdemux->priv->strip_start; |
| else |
| stop = 0; |
| } |
| } |
| |
| GST_DEBUG_OBJECT (tagdemux, "Sending segment %" GST_SEGMENT_FORMAT, seg); |
| |
| gst_segment_copy_into (seg, &newseg); |
| newseg.start = start; |
| newseg.stop = stop; |
| newseg.time = time; |
| event = gst_event_new_segment (&newseg); |
| |
| return gst_pad_push_event (tagdemux->priv->srcpad, event); |
| } |