| /* |
| * gstcmmldec.c - GStreamer annodex CMML decoder |
| * Copyright (C) 2005 Alessandro Decina |
| * |
| * Authors: |
| * Alessandro Decina <alessandro@nnva.org> |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| /** |
| * SECTION:element-cmmldec |
| * @see_also: cmmlenc, oggdemux |
| * |
| * Cmmldec extracts a CMML document from a CMML bitstream.<ulink |
| * url="http://www.annodex.net/TR/draft-pfeiffer-cmml-02.html">CMML</ulink> is |
| * an XML markup language for time-continuous data maintained by the <ulink |
| * url="http:/www.annodex.org/">Annodex Foundation</ulink>. |
| * |
| * <refsect2> |
| * <title>Example pipeline</title> |
| * |[ |
| * gst-launch -v filesrc location=annotated.ogg ! oggdemux ! cmmldec ! filesink location=annotations.cmml |
| * ]| |
| * </refsect2> |
| */ |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include <glib.h> |
| |
| #include <gst/tag/tag.h> |
| #include "gstannodex.h" |
| #include "gstcmmltag.h" |
| #include "gstcmmldec.h" |
| #include "gstcmmlutils.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (cmmldec); |
| #define GST_CAT_DEFAULT cmmldec |
| |
| #define CMML_IDENT_HEADER_SIZE 29 |
| |
| enum |
| { |
| ARG_0, |
| GST_CMML_DEC_WAIT_CLIP_END |
| }; |
| |
| enum |
| { |
| LAST_SIGNAL |
| }; |
| |
| static GstStaticPadTemplate gst_cmml_dec_src_factory = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) false") |
| ); |
| |
| static GstStaticPadTemplate gst_cmml_dec_sink_factory = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) true") |
| ); |
| |
| /* GstCmmlDec */ |
| #define gst_cmml_dec_parent_class parent_class |
| G_DEFINE_TYPE (GstCmmlDec, gst_cmml_dec, GST_TYPE_ELEMENT); |
| |
| static void gst_cmml_dec_get_property (GObject * dec, guint property_id, |
| GValue * value, GParamSpec * pspec); |
| static void gst_cmml_dec_set_property (GObject * dec, guint property_id, |
| const GValue * value, GParamSpec * pspec); |
| static gboolean gst_cmml_dec_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_cmml_dec_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_cmml_dec_convert (GstPad * pad, GstFormat src_fmt, |
| gint64 src_val, GstFormat * dest_fmt, gint64 * dest_val); |
| static GstStateChangeReturn gst_cmml_dec_change_state (GstElement * element, |
| GstStateChange transition); |
| static GstFlowReturn gst_cmml_dec_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| |
| static GstCmmlPacketType gst_cmml_dec_parse_packet_type (GstCmmlDec * dec, |
| gchar * data, gsize size); |
| static void gst_cmml_dec_parse_ident_header (GstCmmlDec * dec, guint8 * data, |
| gsize size); |
| static void gst_cmml_dec_parse_first_header (GstCmmlDec * dec, guint8 * data, |
| gsize size); |
| static void gst_cmml_dec_parse_preamble (GstCmmlDec * dec, guchar * preamble, |
| guchar * cmml_root_element); |
| static void gst_cmml_dec_parse_xml (GstCmmlDec * dec, guchar * data, |
| guint size); |
| static void gst_cmml_dec_parse_head (GstCmmlDec * dec, GstCmmlTagHead * head); |
| static void gst_cmml_dec_parse_clip (GstCmmlDec * dec, GstCmmlTagClip * clip); |
| |
| static GstFlowReturn gst_cmml_dec_new_buffer (GstCmmlDec * dec, |
| guchar * data, gint size, GstBuffer ** buffer); |
| static void gst_cmml_dec_push_clip (GstCmmlDec * dec, GstCmmlTagClip * clip); |
| static void gst_cmml_dec_send_clip_tag (GstCmmlDec * dec, |
| GstCmmlTagClip * clip); |
| |
| static void gst_cmml_dec_finalize (GObject * object); |
| |
| static void |
| gst_cmml_dec_class_init (GstCmmlDecClass * klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| object_class->set_property = gst_cmml_dec_set_property; |
| object_class->get_property = gst_cmml_dec_get_property; |
| object_class->finalize = gst_cmml_dec_finalize; |
| |
| g_object_class_install_property (object_class, GST_CMML_DEC_WAIT_CLIP_END, |
| g_param_spec_boolean ("wait-clip-end-time", |
| "Wait clip end time", |
| "Send a tag for a clip when the clip ends, setting its end-time. " |
| "Use when you need to know both clip's start-time and end-time.", |
| FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| element_class->change_state = gst_cmml_dec_change_state; |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_cmml_dec_sink_factory)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_cmml_dec_src_factory)); |
| gst_element_class_set_static_metadata (element_class, "CMML stream decoder", |
| "Codec/Decoder", |
| "Decodes CMML streams", "Alessandro Decina <alessandro@nnva.org>"); |
| } |
| |
| static void |
| gst_cmml_dec_init (GstCmmlDec * dec) |
| { |
| dec->sinkpad = |
| gst_pad_new_from_static_template (&gst_cmml_dec_sink_factory, "sink"); |
| gst_pad_set_chain_function (dec->sinkpad, gst_cmml_dec_chain); |
| gst_pad_set_query_function (dec->sinkpad, gst_cmml_dec_sink_query); |
| gst_pad_set_event_function (dec->sinkpad, gst_cmml_dec_sink_event); |
| gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad); |
| |
| dec->srcpad = |
| gst_pad_new_from_static_template (&gst_cmml_dec_src_factory, "src"); |
| gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); |
| |
| dec->wait_clip_end = FALSE; |
| } |
| |
| static void |
| gst_cmml_dec_get_property (GObject * object, guint property_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (object); |
| |
| switch (property_id) { |
| case GST_CMML_DEC_WAIT_CLIP_END: |
| g_value_set_boolean (value, dec->wait_clip_end); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| } |
| } |
| |
| static void |
| gst_cmml_dec_set_property (GObject * object, guint property_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (object); |
| |
| switch (property_id) { |
| case GST_CMML_DEC_WAIT_CLIP_END: |
| dec->wait_clip_end = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (dec, property_id, pspec); |
| } |
| } |
| |
| static void |
| gst_cmml_dec_finalize (GObject * object) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (object); |
| |
| if (dec->tracks) { |
| gst_cmml_track_list_destroy (dec->tracks); |
| dec->tracks = NULL; |
| } |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static GstStateChangeReturn |
| gst_cmml_dec_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (element); |
| GstStateChangeReturn res; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| dec->parser = gst_cmml_parser_new (GST_CMML_PARSER_DECODE); |
| dec->parser->user_data = dec; |
| dec->parser->preamble_callback = |
| (GstCmmlParserPreambleCallback) gst_cmml_dec_parse_preamble; |
| dec->parser->head_callback = |
| (GstCmmlParserHeadCallback) gst_cmml_dec_parse_head; |
| dec->parser->clip_callback = |
| (GstCmmlParserClipCallback) gst_cmml_dec_parse_clip; |
| dec->major = -1; |
| dec->minor = -1; |
| dec->granulerate_n = -1; |
| dec->granulerate_d = -1; |
| dec->granuleshift = 0; |
| dec->granulepos = 0; |
| dec->flow_return = GST_FLOW_OK; |
| dec->sent_root = FALSE; |
| dec->tracks = gst_cmml_track_list_new (); |
| break; |
| default: |
| break; |
| } |
| |
| res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_cmml_parser_free (dec->parser); |
| gst_cmml_track_list_destroy (dec->tracks); |
| dec->tracks = NULL; |
| break; |
| default: |
| break; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_cmml_dec_sink_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| gboolean res = FALSE; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_CONVERT: |
| { |
| GstFormat src_fmt, dest_fmt; |
| gint64 src_val, dest_val; |
| |
| gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); |
| res = gst_cmml_dec_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val); |
| if (res) |
| gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_cmml_dec_convert (GstPad * pad, |
| GstFormat src_fmt, gint64 src_val, GstFormat * dest_fmt, gint64 * dest_val) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (GST_PAD_PARENT (pad)); |
| gboolean res = FALSE; |
| |
| switch (src_fmt) { |
| case GST_FORMAT_DEFAULT: |
| switch (*dest_fmt) { |
| case GST_FORMAT_TIME: |
| { |
| *dest_val = gst_annodex_granule_to_time (src_val, dec->granulerate_n, |
| dec->granulerate_d, dec->granuleshift); |
| res = TRUE; |
| break; |
| } |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_cmml_dec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_EOS: |
| { |
| GstBuffer *buffer; |
| GstCmmlTagClip *clip; |
| GList *clips, *walk; |
| |
| GST_INFO_OBJECT (dec, "got EOS, flushing clips"); |
| |
| /* since we output a clip when the next one in the same track is found, on |
| * EOS we need to output the last clip (if any) of every track |
| */ |
| clips = gst_cmml_track_list_get_clips (dec->tracks); |
| for (walk = clips; walk; walk = g_list_next (walk)) { |
| clip = GST_CMML_TAG_CLIP (walk->data); |
| gst_cmml_dec_push_clip (dec, clip); |
| if (dec->wait_clip_end) { |
| clip->end_time = dec->timestamp; |
| gst_cmml_dec_send_clip_tag (dec, clip); |
| } |
| } |
| g_list_free (clips); |
| |
| /* send the cmml end tag */ |
| dec->flow_return = gst_cmml_dec_new_buffer (dec, |
| (guchar *) "</cmml>", strlen ("</cmml>"), &buffer); |
| |
| if (dec->flow_return == GST_FLOW_OK) |
| dec->flow_return = gst_pad_push (dec->srcpad, buffer); |
| if (dec->flow_return == GST_FLOW_NOT_LINKED) |
| dec->flow_return = GST_FLOW_OK; /* Ignore NOT_LINKED */ |
| |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return gst_pad_event_default (pad, parent, event); |
| } |
| |
| static GstFlowReturn |
| gst_cmml_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstCmmlDec *dec = GST_CMML_DEC (parent); |
| GstCmmlPacketType packet; |
| GstMapInfo map; |
| guint8 *data; |
| gsize size; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| data = map.data; |
| size = map.size; |
| |
| if (size == 0) { |
| /* the EOS page could be empty */ |
| dec->flow_return = GST_FLOW_OK; |
| goto done; |
| } |
| |
| dec->granulepos = GST_BUFFER_OFFSET_END (buffer); |
| dec->timestamp = gst_annodex_granule_to_time (dec->granulepos, |
| dec->granulerate_n, dec->granulerate_d, dec->granuleshift); |
| |
| /* identify the packet type */ |
| packet = gst_cmml_dec_parse_packet_type (dec, (gchar *) data, size); |
| |
| /* handle the packet. the handler will set dec->flow_return */ |
| switch (packet) { |
| case GST_CMML_PACKET_IDENT_HEADER: |
| if (dec->sent_root == FALSE) |
| /* don't parse the ident again in case of seeking to the beginning */ |
| gst_cmml_dec_parse_ident_header (dec, data, size); |
| break; |
| case GST_CMML_PACKET_FIRST_HEADER: |
| if (dec->sent_root == FALSE) |
| /* don't parse the xml preamble if it has already been parsed because it |
| * would error out, so seeking to the beginning would fail */ |
| gst_cmml_dec_parse_first_header (dec, data, size); |
| break; |
| case GST_CMML_PACKET_SECOND_HEADER: |
| case GST_CMML_PACKET_CLIP: |
| gst_cmml_dec_parse_xml (dec, data, size); |
| break; |
| case GST_CMML_PACKET_UNKNOWN: |
| default: |
| GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("unknown packet type")); |
| dec->flow_return = GST_FLOW_ERROR; |
| } |
| |
| done: |
| gst_buffer_unmap (buffer, &map); |
| gst_buffer_unref (buffer); |
| |
| return dec->flow_return; |
| } |
| |
| /* finds the packet type of the buffer |
| */ |
| static GstCmmlPacketType |
| gst_cmml_dec_parse_packet_type (GstCmmlDec * dec, gchar * data, gsize size) |
| { |
| GstCmmlPacketType packet_type = GST_CMML_PACKET_UNKNOWN; |
| |
| if (size >= 8 && !memcmp (data, "CMML\0\0\0\0", 8)) { |
| packet_type = GST_CMML_PACKET_IDENT_HEADER; |
| } else if (size >= 5) { |
| if (!strncmp (data, "<?xml", 5)) |
| packet_type = GST_CMML_PACKET_FIRST_HEADER; |
| else if (!strncmp (data, "<head", 5)) |
| packet_type = GST_CMML_PACKET_SECOND_HEADER; |
| else if (!strncmp (data, "<clip", 5)) |
| packet_type = GST_CMML_PACKET_CLIP; |
| } |
| |
| return packet_type; |
| } |
| |
| /* creates a new buffer and sets caps and timestamp on it |
| */ |
| static GstFlowReturn |
| gst_cmml_dec_new_buffer (GstCmmlDec * dec, |
| guchar * data, gint size, GstBuffer ** buffer) |
| { |
| GstFlowReturn res; |
| |
| *buffer = gst_buffer_new_allocate (NULL, size, NULL); |
| if (*buffer != NULL) { |
| if (data) |
| gst_buffer_fill (*buffer, 0, data, size); |
| GST_BUFFER_TIMESTAMP (*buffer) = dec->timestamp; |
| res = GST_FLOW_OK; |
| } else { |
| GST_WARNING_OBJECT (dec, "could not allocate buffer"); |
| res = GST_FLOW_ERROR; |
| } |
| |
| return res; |
| } |
| |
| /* parses the first CMML packet (the ident header) |
| */ |
| static void |
| gst_cmml_dec_parse_ident_header (GstCmmlDec * dec, guint8 * data, gsize size) |
| { |
| /* the ident header has a fixed length */ |
| if (size != CMML_IDENT_HEADER_SIZE) { |
| GST_ELEMENT_ERROR (dec, STREAM, DECODE, |
| (NULL), ("wrong ident header size: %" G_GSIZE_FORMAT, size)); |
| dec->flow_return = GST_FLOW_ERROR; |
| |
| return; |
| } |
| |
| data += 8; |
| dec->major = GST_READ_UINT16_LE (data); |
| data += 2; |
| dec->minor = GST_READ_UINT16_LE (data); |
| data += 2; |
| dec->granulerate_n = GST_READ_UINT64_LE (data); |
| data += 8; |
| dec->granulerate_d = GST_READ_UINT64_LE (data); |
| data += 8; |
| dec->granuleshift = GST_READ_UINT8 (data); |
| |
| GST_INFO_OBJECT (dec, "bitstream initialized " |
| "(major: %" G_GINT16_FORMAT " minor: %" G_GINT16_FORMAT |
| " granulerate_n: %" G_GINT64_FORMAT " granulerate_d: %" G_GINT64_FORMAT |
| " granuleshift: %d)", |
| dec->major, dec->minor, |
| dec->granulerate_n, dec->granulerate_d, dec->granuleshift); |
| |
| dec->flow_return = GST_FLOW_OK; |
| } |
| |
| /* parses the first secondary header. |
| * the first secondary header contains the xml version, the doctype and the |
| * optional "cmml" processing instruction. |
| */ |
| static void |
| gst_cmml_dec_parse_first_header (GstCmmlDec * dec, guint8 * data, gsize size) |
| { |
| gst_cmml_dec_parse_xml (dec, data, size); |
| |
| /* if there is a processing instruction, gst_cmml_dec_parse_preamble |
| * will be triggered. Otherwise we need to call it manually. |
| */ |
| if (dec->flow_return == GST_FLOW_OK && !dec->sent_root) { |
| guchar *preamble = (guchar *) g_strndup ((gchar *) data, size); |
| |
| gst_cmml_dec_parse_preamble (dec, preamble, (guchar *) "<cmml>"); |
| g_free (preamble); |
| } |
| } |
| |
| /* feeds data into the cmml parser. |
| */ |
| static void |
| gst_cmml_dec_parse_xml (GstCmmlDec * dec, guchar * data, guint size) |
| { |
| GError *err = NULL; |
| |
| if (!gst_cmml_parser_parse_chunk (dec->parser, (gchar *) data, size, &err)) { |
| GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("%s", err->message)); |
| g_error_free (err); |
| dec->flow_return = GST_FLOW_ERROR; |
| } |
| } |
| |
| static void |
| gst_cmml_dec_parse_preamble (GstCmmlDec * dec, guchar * preamble, |
| guchar * root_element) |
| { |
| GstBuffer *buffer; |
| guchar *encoded_preamble; |
| |
| encoded_preamble = (guchar *) g_strconcat ((gchar *) preamble, |
| (gchar *) root_element, NULL); |
| |
| /* send the root element to the internal parser */ |
| gst_cmml_dec_parse_xml (dec, root_element, strlen ((gchar *) root_element)); |
| dec->sent_root = TRUE; |
| |
| /* push the root element */ |
| dec->flow_return = gst_cmml_dec_new_buffer (dec, |
| encoded_preamble, strlen ((gchar *) encoded_preamble), &buffer); |
| if (dec->flow_return == GST_FLOW_OK) { |
| dec->flow_return = gst_pad_push (dec->srcpad, buffer); |
| } |
| |
| if (dec->flow_return == GST_FLOW_OK) { |
| GST_INFO_OBJECT (dec, "preamble parsed"); |
| } |
| |
| g_free (encoded_preamble); |
| return; |
| } |
| |
| /* outputs the cmml head element and send TITLE and CMML_HEAD tags. |
| * This callback is registered with dec->parser. It is called when the |
| * head element is parsed. |
| */ |
| static void |
| gst_cmml_dec_parse_head (GstCmmlDec * dec, GstCmmlTagHead * head) |
| { |
| GstTagList *tags; |
| GValue str_val = { 0 }, title_val = { |
| 0}; |
| guchar *head_str; |
| GstBuffer *buffer; |
| |
| GST_DEBUG_OBJECT (dec, "found CMML head (title: %s base: %s)", |
| head->title, head->base); |
| |
| /* FIXME: what's the point of all this GValue transform crack? */ |
| /* create the GST_TAG_TITLE tag */ |
| g_value_init (&str_val, G_TYPE_STRING); |
| g_value_init (&title_val, gst_tag_get_type (GST_TAG_TITLE)); |
| g_value_set_string (&str_val, (gchar *) head->title); |
| g_value_transform (&str_val, &title_val); |
| |
| tags = gst_tag_list_new_empty (); |
| gst_tag_list_add_values (tags, GST_TAG_MERGE_APPEND, |
| GST_TAG_TITLE, &title_val, NULL); |
| gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_CMML_HEAD, head, NULL); |
| gst_pad_push_event (dec->srcpad, gst_event_new_tag (tags)); |
| |
| g_value_unset (&str_val); |
| g_value_unset (&title_val); |
| |
| head_str = gst_cmml_parser_tag_head_to_string (dec->parser, head); |
| |
| dec->flow_return = gst_cmml_dec_new_buffer (dec, |
| head_str, strlen ((gchar *) head_str), &buffer); |
| g_free (head_str); |
| if (dec->flow_return == GST_FLOW_OK) |
| dec->flow_return = gst_pad_push (dec->srcpad, buffer); |
| if (dec->flow_return == GST_FLOW_NOT_LINKED) |
| dec->flow_return = GST_FLOW_OK; /* Ignore NOT_LINKED */ |
| } |
| |
| /* send a TAG_MESSAGE event for a clip */ |
| static void |
| gst_cmml_dec_send_clip_tag (GstCmmlDec * dec, GstCmmlTagClip * clip) |
| { |
| GstTagList *tags; |
| |
| GST_DEBUG_OBJECT (dec, "sending clip tag %s", clip->id); |
| |
| tags = gst_tag_list_new (GST_TAG_CMML_CLIP, clip, NULL); |
| gst_pad_push_event (dec->srcpad, gst_event_new_tag (tags)); |
| } |
| |
| /* push the string representation of a clip */ |
| static void |
| gst_cmml_dec_push_clip (GstCmmlDec * dec, GstCmmlTagClip * clip) |
| { |
| GstBuffer *buffer; |
| guchar *clip_str; |
| |
| GST_DEBUG_OBJECT (dec, "pushing clip %s", clip->id); |
| |
| clip_str = gst_cmml_parser_tag_clip_to_string (dec->parser, clip); |
| dec->flow_return = gst_cmml_dec_new_buffer (dec, |
| clip_str, strlen ((gchar *) clip_str), &buffer); |
| if (dec->flow_return == GST_FLOW_OK) |
| dec->flow_return = gst_pad_push (dec->srcpad, buffer); |
| if (dec->flow_return == GST_FLOW_NOT_LINKED) |
| dec->flow_return = GST_FLOW_OK; /* Ignore NOT_LINKED */ |
| |
| g_free (clip_str); |
| } |
| |
| /* decode a clip tag |
| * this callback is registered with dec->parser. It is called whenever a |
| * clip is parsed. |
| */ |
| static void |
| gst_cmml_dec_parse_clip (GstCmmlDec * dec, GstCmmlTagClip * clip) |
| { |
| GstCmmlTagClip *prev_clip; |
| |
| dec->flow_return = GST_FLOW_OK; |
| |
| if (clip->empty) |
| GST_INFO_OBJECT (dec, "parsing empty clip"); |
| else |
| GST_INFO_OBJECT (dec, "parsing clip (id: %s)", clip->id); |
| |
| clip->start_time = dec->timestamp; |
| if (clip->start_time == GST_CLOCK_TIME_NONE) { |
| GST_ELEMENT_ERROR (dec, STREAM, DECODE, |
| (NULL), ("invalid clip start time")); |
| |
| dec->flow_return = GST_FLOW_ERROR; |
| return; |
| } |
| |
| /* get the last clip in the current track */ |
| prev_clip = gst_cmml_track_list_get_track_last_clip (dec->tracks, |
| (gchar *) clip->track); |
| if (prev_clip) { |
| /* output the previous clip */ |
| if (clip->empty) |
| /* the current clip marks the end of the previous one */ |
| prev_clip->end_time = clip->start_time; |
| |
| gst_cmml_dec_push_clip (dec, prev_clip); |
| } |
| |
| if (dec->wait_clip_end) { |
| /* now it's time to send the tag for the previous clip */ |
| if (prev_clip) { |
| prev_clip->end_time = clip->start_time; |
| gst_cmml_dec_send_clip_tag (dec, prev_clip); |
| } |
| } else if (!clip->empty) { |
| /* send the tag for the current clip */ |
| gst_cmml_dec_send_clip_tag (dec, clip); |
| } |
| |
| if (prev_clip) |
| gst_cmml_track_list_del_clip (dec->tracks, prev_clip); |
| |
| if (!clip->empty) |
| if (!gst_cmml_track_list_has_clip (dec->tracks, clip)) |
| gst_cmml_track_list_add_clip (dec->tracks, clip); |
| } |
| |
| gboolean |
| gst_cmml_dec_plugin_init (GstPlugin * plugin) |
| { |
| if (!gst_element_register (plugin, "cmmldec", GST_RANK_PRIMARY, |
| GST_TYPE_CMML_DEC)) |
| return FALSE; |
| |
| GST_DEBUG_CATEGORY_INIT (cmmldec, "cmmldec", 0, |
| "annodex CMML decoding element"); |
| |
| return TRUE; |
| } |