2 * gstcmmlenc.c - GStreamer CMML encoder
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-cmmlenc
26 * @see_also: cmmldec, oggmux
28 * Cmmlenc encodes a CMML document into a CMML stream. <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=annotations.cmml ! cmmlenc ! oggmux name=mux ! filesink location=annotated.ogg
47 #include "gstcmmlenc.h"
48 #include "gstannodex.h"
50 GST_DEBUG_CATEGORY_STATIC (cmmlenc);
51 #define GST_CAT_DEFAULT cmmlenc
53 #define CMML_IDENT_HEADER_SIZE 29
58 GST_CMML_ENC_GRANULERATE_N,
59 GST_CMML_ENC_GRANULERATE_D,
60 GST_CMML_ENC_GRANULESHIFT
68 static GstStaticPadTemplate gst_cmml_enc_src_factory =
69 GST_STATIC_PAD_TEMPLATE ("src",
72 GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) true")
75 static GstStaticPadTemplate gst_cmml_enc_sink_factory =
76 GST_STATIC_PAD_TEMPLATE ("sink",
79 GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) false")
82 GST_BOILERPLATE (GstCmmlEnc, gst_cmml_enc, GstElement, GST_TYPE_ELEMENT);
83 static void gst_cmml_enc_get_property (GObject * object, guint property_id,
84 GValue * value, GParamSpec * pspec);
85 static void gst_cmml_enc_set_property (GObject * object, guint property_id,
86 const GValue * value, GParamSpec * pspec);
87 static gboolean gst_cmml_enc_sink_event (GstPad * pad, GstEvent * event);
88 static GstStateChangeReturn gst_cmml_enc_change_state (GstElement * element,
89 GstStateChange transition);
90 static GstFlowReturn gst_cmml_enc_chain (GstPad * pad, GstBuffer * buffer);
91 static void gst_cmml_enc_parse_preamble (GstCmmlEnc * enc,
92 guchar * preamble, guchar * processing_instruction);
93 static void gst_cmml_enc_parse_end_tag (GstCmmlEnc * enc);
94 static void gst_cmml_enc_parse_tag_head (GstCmmlEnc * enc,
95 GstCmmlTagHead * head);
96 static void gst_cmml_enc_parse_tag_clip (GstCmmlEnc * enc,
97 GstCmmlTagClip * tag);
98 static GstFlowReturn gst_cmml_enc_new_buffer (GstCmmlEnc * enc,
99 guchar * data, gint size, GstBuffer ** buffer);
100 static GstFlowReturn gst_cmml_enc_push_clip (GstCmmlEnc * enc,
101 GstCmmlTagClip * clip, GstClockTime prev_clip_time);
102 static GstFlowReturn gst_cmml_enc_push (GstCmmlEnc * enc, GstBuffer * buffer);
104 static void gst_cmml_enc_finalize (GObject * object);
107 gst_cmml_enc_base_init (gpointer g_class)
109 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
111 gst_element_class_add_pad_template (element_class,
112 gst_static_pad_template_get (&gst_cmml_enc_sink_factory));
113 gst_element_class_add_pad_template (element_class,
114 gst_static_pad_template_get (&gst_cmml_enc_src_factory));
115 gst_element_class_set_details_simple (element_class, "CMML streams encoder",
117 "Encodes CMML streams", "Alessandro Decina <alessandro@nnva.org>");
121 gst_cmml_enc_class_init (GstCmmlEncClass * enc_class)
123 GObjectClass *klass = G_OBJECT_CLASS (enc_class);
125 klass->get_property = gst_cmml_enc_get_property;
126 klass->set_property = gst_cmml_enc_set_property;
127 klass->finalize = gst_cmml_enc_finalize;
129 g_object_class_install_property (klass, GST_CMML_ENC_GRANULERATE_N,
130 g_param_spec_int64 ("granule-rate-numerator",
131 "Granulerate numerator",
132 "Granulerate numerator",
133 0, G_MAXINT64, 1000, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
135 g_object_class_install_property (klass, GST_CMML_ENC_GRANULERATE_D,
136 g_param_spec_int64 ("granule-rate-denominator",
137 "Granulerate denominator",
138 "Granulerate denominator",
139 0, G_MAXINT64, 1, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
141 g_object_class_install_property (klass, GST_CMML_ENC_GRANULESHIFT,
142 g_param_spec_uchar ("granule-shift",
144 "The number of lower bits to use for partitioning a granule position",
145 0, 64, 32, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
147 GST_ELEMENT_CLASS (klass)->change_state = gst_cmml_enc_change_state;
151 gst_cmml_enc_init (GstCmmlEnc * enc, GstCmmlEncClass * klass)
154 gst_pad_new_from_static_template (&gst_cmml_enc_sink_factory, "sink");
155 gst_pad_set_chain_function (enc->sinkpad, gst_cmml_enc_chain);
156 gst_pad_set_event_function (enc->sinkpad, gst_cmml_enc_sink_event);
157 gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad);
160 gst_pad_new_from_static_template (&gst_cmml_enc_src_factory, "src");
161 gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad);
168 gst_cmml_enc_set_property (GObject * object, guint property_id,
169 const GValue * value, GParamSpec * pspec)
171 GstCmmlEnc *enc = GST_CMML_ENC (object);
173 switch (property_id) {
174 case GST_CMML_ENC_GRANULERATE_N:
175 /* XXX: may need to flush clips */
176 enc->granulerate_n = g_value_get_int64 (value);
178 case GST_CMML_ENC_GRANULERATE_D:
179 enc->granulerate_d = g_value_get_int64 (value);
181 case GST_CMML_ENC_GRANULESHIFT:
182 enc->granuleshift = g_value_get_uchar (value);
185 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
190 gst_cmml_enc_get_property (GObject * object, guint property_id,
191 GValue * value, GParamSpec * pspec)
193 GstCmmlEnc *enc = GST_CMML_ENC (object);
195 switch (property_id) {
196 case GST_CMML_ENC_GRANULERATE_N:
197 g_value_set_int64 (value, enc->granulerate_n);
199 case GST_CMML_ENC_GRANULERATE_D:
200 g_value_set_int64 (value, enc->granulerate_d);
202 case GST_CMML_ENC_GRANULESHIFT:
203 g_value_set_uchar (value, enc->granuleshift);
206 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
211 gst_cmml_enc_finalize (GObject * object)
213 GstCmmlEnc *enc = GST_CMML_ENC (object);
216 gst_cmml_track_list_destroy (enc->tracks);
220 G_OBJECT_CLASS (parent_class)->finalize (object);
223 static GstStateChangeReturn
224 gst_cmml_enc_change_state (GstElement * element, GstStateChange transition)
226 GstCmmlEnc *enc = GST_CMML_ENC (element);
227 GstStateChangeReturn res;
229 switch (transition) {
230 case GST_STATE_CHANGE_READY_TO_PAUSED:
231 enc->parser = gst_cmml_parser_new (GST_CMML_PARSER_ENCODE);
232 enc->parser->user_data = enc;
233 enc->parser->preamble_callback =
234 (GstCmmlParserPreambleCallback) gst_cmml_enc_parse_preamble;
235 enc->parser->head_callback =
236 (GstCmmlParserHeadCallback) gst_cmml_enc_parse_tag_head;
237 enc->parser->clip_callback =
238 (GstCmmlParserClipCallback) gst_cmml_enc_parse_tag_clip;
239 enc->parser->cmml_end_callback =
240 (GstCmmlParserCmmlEndCallback) gst_cmml_enc_parse_end_tag;
241 enc->tracks = gst_cmml_track_list_new ();
242 enc->sent_headers = FALSE;
243 enc->sent_eos = FALSE;
244 enc->flow_return = GST_FLOW_OK;
250 res = parent_class->change_state (element, transition);
252 switch (transition) {
253 case GST_STATE_CHANGE_PAUSED_TO_READY:
255 gst_cmml_track_list_destroy (enc->tracks);
257 g_free (enc->preamble);
258 enc->preamble = NULL;
259 gst_cmml_parser_free (enc->parser);
270 gst_cmml_enc_sink_event (GstPad * pad, GstEvent * event)
272 GstCmmlEnc *enc = GST_CMML_ENC (GST_PAD_PARENT (pad));
274 switch (GST_EVENT_TYPE (event)) {
278 gst_cmml_enc_parse_end_tag (enc);
286 return gst_pad_event_default (pad, event);
290 gst_cmml_enc_new_buffer (GstCmmlEnc * enc,
291 guchar * data, gint size, GstBuffer ** buffer)
295 res = gst_pad_alloc_buffer (enc->srcpad, GST_BUFFER_OFFSET_NONE, size,
297 if (res == GST_FLOW_OK) {
299 memcpy (GST_BUFFER_DATA (*buffer), data, size);
301 GST_WARNING_OBJECT (enc, "alloc function returned error %s",
302 gst_flow_get_name (res));
309 gst_cmml_enc_set_header_on_caps (GstCmmlEnc * enc, GstCaps * caps,
310 GstBuffer * ident, GstBuffer * preamble, GstBuffer * head)
312 GValue array = { 0 };
313 GValue value = { 0 };
314 GstStructure *structure;
317 caps = gst_caps_make_writable (caps);
318 structure = gst_caps_get_structure (caps, 0);
320 g_value_init (&array, GST_TYPE_ARRAY);
321 g_value_init (&value, GST_TYPE_BUFFER);
323 /* Make copies of header buffers to avoid circular references */
324 buffer = gst_buffer_copy (ident);
325 gst_value_set_buffer (&value, buffer);
326 gst_value_array_append_value (&array, &value);
327 gst_buffer_unref (buffer);
329 buffer = gst_buffer_copy (preamble);
330 gst_value_set_buffer (&value, buffer);
331 gst_value_array_append_value (&array, &value);
332 gst_buffer_unref (buffer);
334 buffer = gst_buffer_copy (head);
335 gst_value_set_buffer (&value, buffer);
336 gst_value_array_append_value (&array, &value);
337 gst_buffer_unref (buffer);
339 GST_BUFFER_FLAG_SET (ident, GST_BUFFER_FLAG_IN_CAPS);
340 GST_BUFFER_FLAG_SET (preamble, GST_BUFFER_FLAG_IN_CAPS);
341 GST_BUFFER_FLAG_SET (head, GST_BUFFER_FLAG_IN_CAPS);
343 gst_structure_set_value (structure, "streamheader", &array);
345 g_value_unset (&value);
346 g_value_unset (&array);
351 /* create a CMML ident header buffer
354 gst_cmml_enc_new_ident_header (GstCmmlEnc * enc, GstBuffer ** buffer)
356 guint8 ident_header[CMML_IDENT_HEADER_SIZE];
357 guint8 *wptr = ident_header;
359 memcpy (wptr, "CMML\0\0\0\0", 8);
361 GST_WRITE_UINT16_LE (wptr, enc->major);
363 GST_WRITE_UINT16_LE (wptr, enc->minor);
365 GST_WRITE_UINT64_LE (wptr, enc->granulerate_n);
367 GST_WRITE_UINT64_LE (wptr, enc->granulerate_d);
369 *wptr = enc->granuleshift;
371 return gst_cmml_enc_new_buffer (enc,
372 (guchar *) & ident_header, CMML_IDENT_HEADER_SIZE, buffer);
375 /* parse the CMML preamble */
377 gst_cmml_enc_parse_preamble (GstCmmlEnc * enc,
378 guchar * preamble, guchar * processing_instruction)
380 GST_INFO_OBJECT (enc, "parsing preamble");
382 /* save the preamble: it will be pushed when the head tag is found */
383 enc->preamble = (guchar *) g_strconcat ((gchar *) preamble,
384 (gchar *) processing_instruction, NULL);
387 /* parse the CMML end tag */
389 gst_cmml_enc_parse_end_tag (GstCmmlEnc * enc)
393 GST_INFO_OBJECT (enc, "parsing end tag");
395 /* push an empty buffer to signal EOS */
396 enc->flow_return = gst_cmml_enc_new_buffer (enc, NULL, 0, &buffer);
397 if (enc->flow_return == GST_FLOW_OK) {
398 /* set granulepos 0 on EOS */
399 GST_BUFFER_OFFSET_END (buffer) = 0;
400 enc->flow_return = gst_cmml_enc_push (enc, buffer);
401 enc->sent_eos = TRUE;
407 /* encode the CMML head tag and push the CMML headers
410 gst_cmml_enc_parse_tag_head (GstCmmlEnc * enc, GstCmmlTagHead * head)
412 GList *headers = NULL;
416 GstBuffer *ident_buf, *preamble_buf, *head_buf;
419 if (enc->preamble == NULL)
420 goto flow_unexpected;
422 GST_INFO_OBJECT (enc, "parsing head tag");
424 enc->flow_return = gst_cmml_enc_new_ident_header (enc, &ident_buf);
425 if (enc->flow_return != GST_FLOW_OK)
427 headers = g_list_append (headers, ident_buf);
429 enc->flow_return = gst_cmml_enc_new_buffer (enc,
430 enc->preamble, strlen ((gchar *) enc->preamble), &preamble_buf);
431 if (enc->flow_return != GST_FLOW_OK)
433 headers = g_list_append (headers, preamble_buf);
435 head_string = gst_cmml_parser_tag_head_to_string (enc->parser, head);
436 enc->flow_return = gst_cmml_enc_new_buffer (enc,
437 head_string, strlen ((gchar *) head_string), &head_buf);
438 g_free (head_string);
439 if (enc->flow_return != GST_FLOW_OK)
441 headers = g_list_append (headers, head_buf);
443 caps = gst_pad_get_caps (enc->srcpad);
444 caps = gst_cmml_enc_set_header_on_caps (enc, caps,
445 ident_buf, preamble_buf, head_buf);
448 buffer = GST_BUFFER (headers->data);
449 /* set granulepos 0 on headers */
450 GST_BUFFER_OFFSET_END (buffer) = 0;
451 gst_buffer_set_caps (buffer, caps);
453 enc->flow_return = gst_cmml_enc_push (enc, buffer);
454 headers = g_list_delete_link (headers, headers);
456 if (GST_FLOW_IS_FATAL (enc->flow_return))
460 gst_caps_unref (caps);
462 enc->sent_headers = TRUE;
466 GST_ELEMENT_ERROR (enc, STREAM, ENCODE,
467 (NULL), ("got head tag before preamble"));
468 enc->flow_return = GST_FLOW_ERROR;
471 gst_caps_unref (caps);
474 for (walk = headers; walk; walk = walk->next)
475 gst_buffer_unref (GST_BUFFER (walk->data));
476 g_list_free (headers);
480 /* encode a CMML clip tag
481 * remove the start and end attributes (GstCmmlParser does this itself) and
482 * push the tag with the timestamp of its start attribute. If the tag has the
483 * end attribute, create a new empty clip and encode it.
486 gst_cmml_enc_parse_tag_clip (GstCmmlEnc * enc, GstCmmlTagClip * clip)
488 GstCmmlTagClip *prev_clip;
489 GstClockTime prev_clip_time = GST_CLOCK_TIME_NONE;
491 /* this can happen if there's a programming error (eg user forgets to set
492 * the start-time property) or if one of the gst_cmml_clock_time_from_*
493 * overflows in GstCmmlParser */
494 if (clip->start_time == GST_CLOCK_TIME_NONE) {
495 GST_ELEMENT_ERROR (enc, STREAM, ENCODE,
496 (NULL), ("invalid start time for clip (%s)", clip->id));
497 enc->flow_return = GST_FLOW_ERROR;
502 /* get the previous clip's start time to encode the current granulepos */
503 prev_clip = gst_cmml_track_list_get_track_last_clip (enc->tracks,
504 (gchar *) clip->track);
506 prev_clip_time = prev_clip->start_time;
507 if (prev_clip_time > clip->start_time) {
508 GST_ELEMENT_ERROR (enc, STREAM, ENCODE,
509 (NULL), ("previous clip start time > current clip (%s) start time",
511 enc->flow_return = GST_FLOW_ERROR;
515 /* we don't need the prev clip anymore */
516 gst_cmml_track_list_del_clip (enc->tracks, prev_clip);
519 /* add the current clip to the tracklist */
520 gst_cmml_track_list_add_clip (enc->tracks, clip);
522 enc->flow_return = gst_cmml_enc_push_clip (enc, clip, prev_clip_time);
526 gst_cmml_enc_push_clip (GstCmmlEnc * enc, GstCmmlTagClip * clip,
527 GstClockTime prev_clip_time)
534 /* encode the clip */
536 (gchar *) gst_cmml_parser_tag_clip_to_string (enc->parser, clip);
538 res = gst_cmml_enc_new_buffer (enc,
539 (guchar *) clip_string, strlen (clip_string), &buffer);
540 g_free (clip_string);
541 if (res != GST_FLOW_OK)
544 GST_INFO_OBJECT (enc, "encoding clip"
545 "(start-time: %" GST_TIME_FORMAT " end-time: %" GST_TIME_FORMAT,
546 GST_TIME_ARGS (clip->start_time), GST_TIME_ARGS (clip->end_time));
548 /* set the granulepos */
549 granulepos = gst_cmml_clock_time_to_granule (prev_clip_time, clip->start_time,
550 enc->granulerate_n, enc->granulerate_d, enc->granuleshift);
551 if (granulepos == -1) {
552 gst_buffer_unref (buffer);
553 goto granule_overflow;
556 GST_BUFFER_OFFSET (buffer) = clip->start_time;
557 GST_BUFFER_OFFSET_END (buffer) = granulepos;
558 GST_BUFFER_TIMESTAMP (buffer) = clip->start_time;
560 res = gst_cmml_enc_push (enc, buffer);
561 if (GST_FLOW_IS_FATAL (res))
564 if (clip->end_time != GST_CLOCK_TIME_NONE) {
565 /* create a new empty clip for the same cmml track starting at end_time
567 GObject *end_clip = g_object_new (GST_TYPE_CMML_TAG_CLIP,
568 "start-time", clip->end_time, "track", clip->track, NULL);
570 /* encode the empty end clip */
571 gst_cmml_enc_push_clip (enc, GST_CMML_TAG_CLIP (end_clip),
573 g_object_unref (end_clip);
579 GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), ("granulepos overflow"));
580 return GST_FLOW_ERROR;
584 gst_cmml_enc_push (GstCmmlEnc * enc, GstBuffer * buffer)
588 res = gst_pad_push (enc->srcpad, buffer);
589 if (GST_FLOW_IS_FATAL (res))
590 GST_WARNING_OBJECT (enc, "push returned: %s", gst_flow_get_name (res));
596 gst_cmml_enc_chain (GstPad * pad, GstBuffer * buffer)
599 GstCmmlEnc *enc = GST_CMML_ENC (GST_PAD_PARENT (pad));
601 /* the CMML handlers registered with enc->parser will override this when
602 * encoding/pushing the buffers downstream
604 enc->flow_return = GST_FLOW_OK;
606 if (!gst_cmml_parser_parse_chunk (enc->parser,
607 (gchar *) GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), &err)) {
608 GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), ("%s", err->message));
610 enc->flow_return = GST_FLOW_ERROR;
613 gst_buffer_unref (buffer);
614 return enc->flow_return;
618 gst_cmml_enc_plugin_init (GstPlugin * plugin)
620 if (!gst_element_register (plugin, "cmmlenc", GST_RANK_NONE,
624 GST_DEBUG_CATEGORY_INIT (cmmlenc, "cmmlenc", 0,
625 "annodex cmml decoding element");