2 * gstcmmldec.c - GStreamer annodex CMML decoder
3 * Copyright (C) 2005 Alessandro Decina
6 * Alessandro Decina <alessandro@nnva.org>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
25 * SECTION:element-cmmldec
26 * @see_also: cmmlenc, oggdemux
28 * Cmmldec extracts a CMML document from a CMML bitstream.<ulink
29 * url="http://www.annodex.net/TR/draft-pfeiffer-cmml-02.html">CMML</ulink> is
30 * an XML markup language for time-continuous data maintained by the <ulink
31 * url="http:/www.annodex.org/">Annodex Foundation</ulink>.
34 * <title>Example pipeline</title>
36 * gst-launch -v filesrc location=annotated.ogg ! oggdemux ! cmmldec ! filesink location=annotations.cmml
49 #include <gst/tag/tag.h>
50 #include "gstannodex.h"
51 #include "gstcmmltag.h"
52 #include "gstcmmldec.h"
53 #include "gstcmmlutils.h"
55 GST_DEBUG_CATEGORY_STATIC (cmmldec);
56 #define GST_CAT_DEFAULT cmmldec
58 #define CMML_IDENT_HEADER_SIZE 29
63 GST_CMML_DEC_WAIT_CLIP_END
71 static GstStaticPadTemplate gst_cmml_dec_src_factory =
72 GST_STATIC_PAD_TEMPLATE ("src",
75 GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) false")
78 static GstStaticPadTemplate gst_cmml_dec_sink_factory =
79 GST_STATIC_PAD_TEMPLATE ("sink",
82 GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) true")
86 GST_BOILERPLATE (GstCmmlDec, gst_cmml_dec, GstElement, GST_TYPE_ELEMENT);
87 static void gst_cmml_dec_get_property (GObject * dec, guint property_id,
88 GValue * value, GParamSpec * pspec);
89 static void gst_cmml_dec_set_property (GObject * dec, guint property_id,
90 const GValue * value, GParamSpec * pspec);
91 static const GstQueryType *gst_cmml_dec_query_types (GstPad * pad);
92 static gboolean gst_cmml_dec_sink_query (GstPad * pad, GstQuery * query);
93 static gboolean gst_cmml_dec_sink_event (GstPad * pad, GstEvent * event);
94 static gboolean gst_cmml_dec_convert (GstPad * pad, GstFormat src_fmt,
95 gint64 src_val, GstFormat * dest_fmt, gint64 * dest_val);
96 static GstStateChangeReturn gst_cmml_dec_change_state (GstElement * element,
97 GstStateChange transition);
98 static GstFlowReturn gst_cmml_dec_chain (GstPad * pad, GstBuffer * buffer);
100 static GstCmmlPacketType gst_cmml_dec_parse_packet_type (GstCmmlDec * dec,
102 static void gst_cmml_dec_parse_ident_header (GstCmmlDec * dec, GstBuffer * buf);
103 static void gst_cmml_dec_parse_first_header (GstCmmlDec * dec, GstBuffer * buf);
104 static void gst_cmml_dec_parse_preamble (GstCmmlDec * dec,
105 guchar * preamble, guchar * cmml_root_element);
106 static void gst_cmml_dec_parse_xml (GstCmmlDec * dec,
107 guchar * data, guint size);
108 static void gst_cmml_dec_parse_head (GstCmmlDec * dec, GstCmmlTagHead * head);
109 static void gst_cmml_dec_parse_clip (GstCmmlDec * dec, GstCmmlTagClip * clip);
111 static GstFlowReturn gst_cmml_dec_new_buffer (GstCmmlDec * dec,
112 guchar * data, gint size, GstBuffer ** buffer);
113 static void gst_cmml_dec_push_clip (GstCmmlDec * dec, GstCmmlTagClip * clip);
114 static void gst_cmml_dec_send_clip_tag (GstCmmlDec * dec,
115 GstCmmlTagClip * clip);
117 static void gst_cmml_dec_finalize (GObject * object);
120 gst_cmml_dec_base_init (gpointer g_class)
122 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
124 gst_element_class_add_static_pad_template (element_class,
125 &gst_cmml_dec_sink_factory);
126 gst_element_class_add_static_pad_template (element_class,
127 &gst_cmml_dec_src_factory);
128 gst_element_class_set_details_simple (element_class, "CMML stream decoder",
130 "Decodes CMML streams", "Alessandro Decina <alessandro@nnva.org>");
134 gst_cmml_dec_class_init (GstCmmlDecClass * dec_class)
136 GObjectClass *klass = G_OBJECT_CLASS (dec_class);
138 GST_ELEMENT_CLASS (klass)->change_state = gst_cmml_dec_change_state;
140 klass->set_property = gst_cmml_dec_set_property;
141 klass->get_property = gst_cmml_dec_get_property;
142 klass->finalize = gst_cmml_dec_finalize;
144 g_object_class_install_property (klass, GST_CMML_DEC_WAIT_CLIP_END,
145 g_param_spec_boolean ("wait-clip-end-time",
146 "Wait clip end time",
147 "Send a tag for a clip when the clip ends, setting its end-time. "
148 "Use when you need to know both clip's start-time and end-time.",
149 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
153 gst_cmml_dec_init (GstCmmlDec * dec, GstCmmlDecClass * klass)
156 gst_pad_new_from_static_template (&gst_cmml_dec_sink_factory, "sink");
157 gst_pad_set_chain_function (dec->sinkpad, gst_cmml_dec_chain);
158 gst_pad_set_query_type_function (dec->sinkpad, gst_cmml_dec_query_types);
159 gst_pad_set_query_function (dec->sinkpad, gst_cmml_dec_sink_query);
160 gst_pad_set_event_function (dec->sinkpad, gst_cmml_dec_sink_event);
161 gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
164 gst_pad_new_from_static_template (&gst_cmml_dec_src_factory, "src");
165 gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
167 dec->wait_clip_end = FALSE;
171 gst_cmml_dec_get_property (GObject * object, guint property_id,
172 GValue * value, GParamSpec * pspec)
174 GstCmmlDec *dec = GST_CMML_DEC (object);
176 switch (property_id) {
177 case GST_CMML_DEC_WAIT_CLIP_END:
178 g_value_set_boolean (value, dec->wait_clip_end);
181 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186 gst_cmml_dec_set_property (GObject * object, guint property_id,
187 const GValue * value, GParamSpec * pspec)
189 GstCmmlDec *dec = GST_CMML_DEC (object);
191 switch (property_id) {
192 case GST_CMML_DEC_WAIT_CLIP_END:
193 dec->wait_clip_end = g_value_get_boolean (value);
196 G_OBJECT_WARN_INVALID_PROPERTY_ID (dec, property_id, pspec);
201 gst_cmml_dec_finalize (GObject * object)
203 GstCmmlDec *dec = GST_CMML_DEC (object);
206 gst_cmml_track_list_destroy (dec->tracks);
210 G_OBJECT_CLASS (parent_class)->finalize (object);
213 static GstStateChangeReturn
214 gst_cmml_dec_change_state (GstElement * element, GstStateChange transition)
216 GstCmmlDec *dec = GST_CMML_DEC (element);
217 GstStateChangeReturn res;
219 switch (transition) {
220 case GST_STATE_CHANGE_READY_TO_PAUSED:
221 dec->parser = gst_cmml_parser_new (GST_CMML_PARSER_DECODE);
222 dec->parser->user_data = dec;
223 dec->parser->preamble_callback =
224 (GstCmmlParserPreambleCallback) gst_cmml_dec_parse_preamble;
225 dec->parser->head_callback =
226 (GstCmmlParserHeadCallback) gst_cmml_dec_parse_head;
227 dec->parser->clip_callback =
228 (GstCmmlParserClipCallback) gst_cmml_dec_parse_clip;
231 dec->granulerate_n = -1;
232 dec->granulerate_d = -1;
233 dec->granuleshift = 0;
235 dec->flow_return = GST_FLOW_OK;
236 dec->sent_root = FALSE;
237 dec->tracks = gst_cmml_track_list_new ();
243 res = parent_class->change_state (element, transition);
245 switch (transition) {
246 case GST_STATE_CHANGE_PAUSED_TO_READY:
247 gst_cmml_parser_free (dec->parser);
248 gst_cmml_track_list_destroy (dec->tracks);
258 static const GstQueryType *
259 gst_cmml_dec_query_types (GstPad * pad)
261 static const GstQueryType query_types[] = {
270 gst_cmml_dec_sink_query (GstPad * pad, GstQuery * query)
272 gboolean res = FALSE;
274 switch (GST_QUERY_TYPE (query)) {
275 case GST_QUERY_CONVERT:
277 GstFormat src_fmt, dest_fmt;
278 gint64 src_val, dest_val;
280 gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
281 res = gst_cmml_dec_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val);
283 gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
294 gst_cmml_dec_convert (GstPad * pad,
295 GstFormat src_fmt, gint64 src_val, GstFormat * dest_fmt, gint64 * dest_val)
297 GstCmmlDec *dec = GST_CMML_DEC (GST_PAD_PARENT (pad));
298 gboolean res = FALSE;
301 case GST_FORMAT_DEFAULT:
303 case GST_FORMAT_TIME:
305 *dest_val = gst_annodex_granule_to_time (src_val, dec->granulerate_n,
306 dec->granulerate_d, dec->granuleshift);
322 gst_cmml_dec_sink_event (GstPad * pad, GstEvent * event)
324 GstCmmlDec *dec = GST_CMML_DEC (GST_PAD_PARENT (pad));
326 switch (GST_EVENT_TYPE (event)) {
330 GstCmmlTagClip *clip;
333 GST_INFO_OBJECT (dec, "got EOS, flushing clips");
335 /* since we output a clip when the next one in the same track is found, on
336 * EOS we need to output the last clip (if any) of every track
338 clips = gst_cmml_track_list_get_clips (dec->tracks);
339 for (walk = clips; walk; walk = g_list_next (walk)) {
340 clip = GST_CMML_TAG_CLIP (walk->data);
341 gst_cmml_dec_push_clip (dec, clip);
342 if (dec->wait_clip_end) {
343 clip->end_time = dec->timestamp;
344 gst_cmml_dec_send_clip_tag (dec, clip);
349 /* send the cmml end tag */
350 dec->flow_return = gst_cmml_dec_new_buffer (dec,
351 (guchar *) "</cmml>", strlen ("</cmml>"), &buffer);
353 if (dec->flow_return == GST_FLOW_OK)
354 dec->flow_return = gst_pad_push (dec->srcpad, buffer);
355 if (dec->flow_return == GST_FLOW_NOT_LINKED)
356 dec->flow_return = GST_FLOW_OK; /* Ignore NOT_LINKED */
364 return gst_pad_event_default (pad, event);
368 gst_cmml_dec_chain (GstPad * pad, GstBuffer * buffer)
370 GstCmmlDec *dec = GST_CMML_DEC (GST_PAD_PARENT (pad));
371 GstCmmlPacketType packet;
373 if (GST_BUFFER_SIZE (buffer) == 0) {
374 /* the EOS page could be empty */
375 dec->flow_return = GST_FLOW_OK;
379 dec->granulepos = GST_BUFFER_OFFSET_END (buffer);
380 dec->timestamp = gst_annodex_granule_to_time (dec->granulepos,
381 dec->granulerate_n, dec->granulerate_d, dec->granuleshift);
383 /* identify the packet type */
384 packet = gst_cmml_dec_parse_packet_type (dec, buffer);
386 /* handle the packet. the handler will set dec->flow_return */
388 case GST_CMML_PACKET_IDENT_HEADER:
389 if (dec->sent_root == FALSE)
390 /* don't parse the ident again in case of seeking to the beginning */
391 gst_cmml_dec_parse_ident_header (dec, buffer);
393 case GST_CMML_PACKET_FIRST_HEADER:
394 if (dec->sent_root == FALSE)
395 /* don't parse the xml preamble if it has already been parsed because it
396 * would error out, so seeking to the beginning would fail */
397 gst_cmml_dec_parse_first_header (dec, buffer);
399 case GST_CMML_PACKET_SECOND_HEADER:
400 case GST_CMML_PACKET_CLIP:
401 gst_cmml_dec_parse_xml (dec,
402 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
404 case GST_CMML_PACKET_UNKNOWN:
406 GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("unknown packet type"));
407 dec->flow_return = GST_FLOW_ERROR;
411 gst_buffer_unref (buffer);
412 return dec->flow_return;
415 /* finds the packet type of the buffer
417 static GstCmmlPacketType
418 gst_cmml_dec_parse_packet_type (GstCmmlDec * dec, GstBuffer * buffer)
420 GstCmmlPacketType packet_type = GST_CMML_PACKET_UNKNOWN;
421 gchar *data = (gchar *) GST_BUFFER_DATA (buffer);
422 guint size = GST_BUFFER_SIZE (buffer);
424 if (size >= 8 && !memcmp (data, "CMML\0\0\0\0", 8)) {
425 packet_type = GST_CMML_PACKET_IDENT_HEADER;
426 } else if (size >= 5) {
427 if (!strncmp (data, "<?xml", 5))
428 packet_type = GST_CMML_PACKET_FIRST_HEADER;
429 else if (!strncmp (data, "<head", 5))
430 packet_type = GST_CMML_PACKET_SECOND_HEADER;
431 else if (!strncmp (data, "<clip", 5))
432 packet_type = GST_CMML_PACKET_CLIP;
438 /* creates a new buffer and sets caps and timestamp on it
441 gst_cmml_dec_new_buffer (GstCmmlDec * dec,
442 guchar * data, gint size, GstBuffer ** buffer)
446 res = gst_pad_alloc_buffer (dec->srcpad, GST_BUFFER_OFFSET_NONE,
447 size, gst_static_pad_template_get_caps (&gst_cmml_dec_src_factory),
450 if (res == GST_FLOW_OK) {
452 memcpy (GST_BUFFER_DATA (*buffer), data, size);
453 GST_BUFFER_TIMESTAMP (*buffer) = dec->timestamp;
454 } else if (res == GST_FLOW_NOT_LINKED) {
455 GST_DEBUG_OBJECT (dec, "alloc function return NOT-LINKED, ignoring");
457 GST_WARNING_OBJECT (dec, "alloc function returned error %s",
458 gst_flow_get_name (res));
464 /* parses the first CMML packet (the ident header)
467 gst_cmml_dec_parse_ident_header (GstCmmlDec * dec, GstBuffer * buffer)
469 guint8 *data = GST_BUFFER_DATA (buffer);
471 /* the ident header has a fixed length */
472 if (GST_BUFFER_SIZE (buffer) != CMML_IDENT_HEADER_SIZE) {
473 GST_ELEMENT_ERROR (dec, STREAM, DECODE,
474 (NULL), ("wrong ident header size: %d", GST_BUFFER_SIZE (buffer)));
475 dec->flow_return = GST_FLOW_ERROR;
481 dec->major = GST_READ_UINT16_LE (data);
483 dec->minor = GST_READ_UINT16_LE (data);
485 dec->granulerate_n = GST_READ_UINT64_LE (data);
487 dec->granulerate_d = GST_READ_UINT64_LE (data);
489 dec->granuleshift = GST_READ_UINT8 (data);
491 GST_INFO_OBJECT (dec, "bitstream initialized "
492 "(major: %" G_GINT16_FORMAT " minor: %" G_GINT16_FORMAT
493 " granulerate_n: %" G_GINT64_FORMAT " granulerate_d: %" G_GINT64_FORMAT
494 " granuleshift: %d)",
495 dec->major, dec->minor,
496 dec->granulerate_n, dec->granulerate_d, dec->granuleshift);
498 dec->flow_return = GST_FLOW_OK;
501 /* parses the first secondary header.
502 * the first secondary header contains the xml version, the doctype and the
503 * optional "cmml" processing instruction.
506 gst_cmml_dec_parse_first_header (GstCmmlDec * dec, GstBuffer * buffer)
508 gst_cmml_dec_parse_xml (dec,
509 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
511 /* if there is a processing instruction, gst_cmml_dec_parse_preamble
512 * will be triggered. Otherwise we need to call it manually.
514 if (dec->flow_return == GST_FLOW_OK && !dec->sent_root) {
515 guchar *preamble = (guchar *) g_strndup ((gchar *) GST_BUFFER_DATA (buffer),
516 GST_BUFFER_SIZE (buffer));
518 gst_cmml_dec_parse_preamble (dec, preamble, (guchar *) "<cmml>");
523 /* feeds data into the cmml parser.
526 gst_cmml_dec_parse_xml (GstCmmlDec * dec, guchar * data, guint size)
530 if (!gst_cmml_parser_parse_chunk (dec->parser, (gchar *) data, size, &err)) {
531 GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("%s", err->message));
533 dec->flow_return = GST_FLOW_ERROR;
538 gst_cmml_dec_parse_preamble (GstCmmlDec * dec, guchar * preamble,
539 guchar * root_element)
542 guchar *encoded_preamble;
544 encoded_preamble = (guchar *) g_strconcat ((gchar *) preamble,
545 (gchar *) root_element, NULL);
547 /* send the root element to the internal parser */
548 gst_cmml_dec_parse_xml (dec, root_element, strlen ((gchar *) root_element));
549 dec->sent_root = TRUE;
551 /* push the root element */
552 dec->flow_return = gst_cmml_dec_new_buffer (dec,
553 encoded_preamble, strlen ((gchar *) encoded_preamble), &buffer);
554 if (dec->flow_return == GST_FLOW_OK) {
555 dec->flow_return = gst_pad_push (dec->srcpad, buffer);
558 if (dec->flow_return == GST_FLOW_OK) {
559 GST_INFO_OBJECT (dec, "preamble parsed");
562 g_free (encoded_preamble);
566 /* outputs the cmml head element and send TITLE and CMML_HEAD tags.
567 * This callback is registered with dec->parser. It is called when the
568 * head element is parsed.
571 gst_cmml_dec_parse_head (GstCmmlDec * dec, GstCmmlTagHead * head)
574 GValue str_val = { 0 }, title_val = {
579 GST_DEBUG_OBJECT (dec, "found CMML head (title: %s base: %s)",
580 head->title, head->base);
582 /* create the GST_TAG_TITLE tag */
583 g_value_init (&str_val, G_TYPE_STRING);
584 g_value_init (&title_val, gst_tag_get_type (GST_TAG_TITLE));
585 g_value_set_string (&str_val, (gchar *) head->title);
586 g_value_transform (&str_val, &title_val);
588 tags = gst_tag_list_new ();
589 gst_tag_list_add_values (tags, GST_TAG_MERGE_APPEND,
590 GST_TAG_TITLE, &title_val, NULL);
591 gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_CMML_HEAD, head, NULL);
592 gst_element_found_tags_for_pad (GST_ELEMENT (dec), dec->srcpad, tags);
594 g_value_unset (&str_val);
595 g_value_unset (&title_val);
597 head_str = gst_cmml_parser_tag_head_to_string (dec->parser, head);
599 dec->flow_return = gst_cmml_dec_new_buffer (dec,
600 head_str, strlen ((gchar *) head_str), &buffer);
602 if (dec->flow_return == GST_FLOW_OK)
603 dec->flow_return = gst_pad_push (dec->srcpad, buffer);
604 if (dec->flow_return == GST_FLOW_NOT_LINKED)
605 dec->flow_return = GST_FLOW_OK; /* Ignore NOT_LINKED */
608 /* send a TAG_MESSAGE event for a clip */
610 gst_cmml_dec_send_clip_tag (GstCmmlDec * dec, GstCmmlTagClip * clip)
614 GST_DEBUG_OBJECT (dec, "sending clip tag %s", clip->id);
616 tags = gst_tag_list_new ();
617 gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_CMML_CLIP, clip, NULL);
618 gst_element_found_tags_for_pad (GST_ELEMENT (dec), dec->srcpad, tags);
621 /* push the string representation of a clip */
623 gst_cmml_dec_push_clip (GstCmmlDec * dec, GstCmmlTagClip * clip)
628 GST_DEBUG_OBJECT (dec, "pushing clip %s", clip->id);
630 clip_str = gst_cmml_parser_tag_clip_to_string (dec->parser, clip);
631 dec->flow_return = gst_cmml_dec_new_buffer (dec,
632 clip_str, strlen ((gchar *) clip_str), &buffer);
633 if (dec->flow_return == GST_FLOW_OK)
634 dec->flow_return = gst_pad_push (dec->srcpad, buffer);
635 if (dec->flow_return == GST_FLOW_NOT_LINKED)
636 dec->flow_return = GST_FLOW_OK; /* Ignore NOT_LINKED */
642 * this callback is registered with dec->parser. It is called whenever a
646 gst_cmml_dec_parse_clip (GstCmmlDec * dec, GstCmmlTagClip * clip)
648 GstCmmlTagClip *prev_clip;
650 dec->flow_return = GST_FLOW_OK;
653 GST_INFO_OBJECT (dec, "parsing empty clip");
655 GST_INFO_OBJECT (dec, "parsing clip (id: %s)", clip->id);
657 clip->start_time = dec->timestamp;
658 if (clip->start_time == GST_CLOCK_TIME_NONE) {
659 GST_ELEMENT_ERROR (dec, STREAM, DECODE,
660 (NULL), ("invalid clip start time"));
662 dec->flow_return = GST_FLOW_ERROR;
666 /* get the last clip in the current track */
667 prev_clip = gst_cmml_track_list_get_track_last_clip (dec->tracks,
668 (gchar *) clip->track);
670 /* output the previous clip */
672 /* the current clip marks the end of the previous one */
673 prev_clip->end_time = clip->start_time;
675 gst_cmml_dec_push_clip (dec, prev_clip);
678 if (dec->wait_clip_end) {
679 /* now it's time to send the tag for the previous clip */
681 prev_clip->end_time = clip->start_time;
682 gst_cmml_dec_send_clip_tag (dec, prev_clip);
684 } else if (!clip->empty) {
685 /* send the tag for the current clip */
686 gst_cmml_dec_send_clip_tag (dec, clip);
690 gst_cmml_track_list_del_clip (dec->tracks, prev_clip);
693 if (!gst_cmml_track_list_has_clip (dec->tracks, clip))
694 gst_cmml_track_list_add_clip (dec->tracks, clip);
698 gst_cmml_dec_plugin_init (GstPlugin * plugin)
700 if (!gst_element_register (plugin, "cmmldec", GST_RANK_PRIMARY,
704 GST_DEBUG_CATEGORY_INIT (cmmldec, "cmmldec", 0,
705 "annodex CMML decoding element");