cmmlenc: Remove hack to let oggmux start a new page for every CMML buffer
[platform/upstream/gst-plugins-good.git] / ext / annodex / gstcmmlenc.c
1 /*
2  * gstcmmlenc.c - GStreamer CMML encoder
3  * Copyright (C) 2005 Alessandro Decina
4  * 
5  * Authors:
6  *   Alessandro Decina <alessandro@nnva.org>
7  *
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.
12  *
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.
17  *
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.
22  */
23
24 /**
25  * SECTION:element-cmmlenc
26  * @see_also: cmmldec, oggmux
27  *
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>.
32  *
33  * <refsect2>
34  * <title>Example pipeline</title>
35  * |[
36  * gst-launch -v filesrc location=annotations.cmml ! cmmlenc ! oggmux name=mux ! filesink location=annotated.ogg
37  * ]|
38  * </refsect2>
39  */
40
41
42 #ifdef HAVE_CONFIG_H
43 #include "config.h"
44 #endif
45
46 #include <string.h>
47 #include "gstcmmlenc.h"
48 #include "gstannodex.h"
49
50 GST_DEBUG_CATEGORY_STATIC (cmmlenc);
51 #define GST_CAT_DEFAULT cmmlenc
52
53 #define CMML_IDENT_HEADER_SIZE 29
54
55 enum
56 {
57   ARG_0,
58   GST_CMML_ENC_GRANULERATE_N,
59   GST_CMML_ENC_GRANULERATE_D,
60   GST_CMML_ENC_GRANULESHIFT
61 };
62
63 enum
64 {
65   LAST_SIGNAL
66 };
67
68 static GstStaticPadTemplate gst_cmml_enc_src_factory =
69 GST_STATIC_PAD_TEMPLATE ("src",
70     GST_PAD_SRC,
71     GST_PAD_ALWAYS,
72     GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) true")
73     );
74
75 static GstStaticPadTemplate gst_cmml_enc_sink_factory =
76 GST_STATIC_PAD_TEMPLATE ("sink",
77     GST_PAD_SINK,
78     GST_PAD_ALWAYS,
79     GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) false")
80     );
81
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);
103
104 static void gst_cmml_enc_finalize (GObject * object);
105
106 static void
107 gst_cmml_enc_base_init (gpointer g_class)
108 {
109   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
110
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",
116       "Codec/Encoder",
117       "Encodes CMML streams", "Alessandro Decina <alessandro@nnva.org>");
118 }
119
120 static void
121 gst_cmml_enc_class_init (GstCmmlEncClass * enc_class)
122 {
123   GObjectClass *klass = G_OBJECT_CLASS (enc_class);
124
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;
128
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));
134
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));
140
141   g_object_class_install_property (klass, GST_CMML_ENC_GRANULESHIFT,
142       g_param_spec_uchar ("granule-shift",
143           "Granuleshift",
144           "The number of lower bits to use for partitioning a granule position",
145           0, 64, 32, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
146
147   GST_ELEMENT_CLASS (klass)->change_state = gst_cmml_enc_change_state;
148 }
149
150 static void
151 gst_cmml_enc_init (GstCmmlEnc * enc, GstCmmlEncClass * klass)
152 {
153   enc->sinkpad =
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);
158
159   enc->srcpad =
160       gst_pad_new_from_static_template (&gst_cmml_enc_src_factory, "src");
161   gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad);
162
163   enc->major = 3;
164   enc->minor = 0;
165 }
166
167 static void
168 gst_cmml_enc_set_property (GObject * object, guint property_id,
169     const GValue * value, GParamSpec * pspec)
170 {
171   GstCmmlEnc *enc = GST_CMML_ENC (object);
172
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);
177       break;
178     case GST_CMML_ENC_GRANULERATE_D:
179       enc->granulerate_d = g_value_get_int64 (value);
180       break;
181     case GST_CMML_ENC_GRANULESHIFT:
182       enc->granuleshift = g_value_get_uchar (value);
183       break;
184     default:
185       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186   }
187 }
188
189 static void
190 gst_cmml_enc_get_property (GObject * object, guint property_id,
191     GValue * value, GParamSpec * pspec)
192 {
193   GstCmmlEnc *enc = GST_CMML_ENC (object);
194
195   switch (property_id) {
196     case GST_CMML_ENC_GRANULERATE_N:
197       g_value_set_int64 (value, enc->granulerate_n);
198       break;
199     case GST_CMML_ENC_GRANULERATE_D:
200       g_value_set_int64 (value, enc->granulerate_d);
201       break;
202     case GST_CMML_ENC_GRANULESHIFT:
203       g_value_set_uchar (value, enc->granuleshift);
204       break;
205     default:
206       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
207   }
208 }
209
210 static void
211 gst_cmml_enc_finalize (GObject * object)
212 {
213   GstCmmlEnc *enc = GST_CMML_ENC (object);
214
215   if (enc->tracks) {
216     gst_cmml_track_list_destroy (enc->tracks);
217     enc->tracks = NULL;
218   }
219
220   G_OBJECT_CLASS (parent_class)->finalize (object);
221 }
222
223 static GstStateChangeReturn
224 gst_cmml_enc_change_state (GstElement * element, GstStateChange transition)
225 {
226   GstCmmlEnc *enc = GST_CMML_ENC (element);
227   GstStateChangeReturn res;
228
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;
245       break;
246     default:
247       break;
248   }
249
250   res = parent_class->change_state (element, transition);
251
252   switch (transition) {
253     case GST_STATE_CHANGE_PAUSED_TO_READY:
254     {
255       gst_cmml_track_list_destroy (enc->tracks);
256       enc->tracks = NULL;
257       g_free (enc->preamble);
258       enc->preamble = NULL;
259       gst_cmml_parser_free (enc->parser);
260       break;
261     }
262     default:
263       break;
264   }
265
266   return res;
267 }
268
269 static gboolean
270 gst_cmml_enc_sink_event (GstPad * pad, GstEvent * event)
271 {
272   GstCmmlEnc *enc = GST_CMML_ENC (GST_PAD_PARENT (pad));
273
274   switch (GST_EVENT_TYPE (event)) {
275     case GST_EVENT_EOS:
276     {
277       if (!enc->sent_eos)
278         gst_cmml_enc_parse_end_tag (enc);
279
280       break;
281     }
282     default:
283       break;
284   }
285
286   return gst_pad_event_default (pad, event);
287 }
288
289 static GstFlowReturn
290 gst_cmml_enc_new_buffer (GstCmmlEnc * enc,
291     guchar * data, gint size, GstBuffer ** buffer)
292 {
293   GstFlowReturn res;
294
295   res = gst_pad_alloc_buffer (enc->srcpad, GST_BUFFER_OFFSET_NONE, size,
296       NULL, buffer);
297   if (res == GST_FLOW_OK) {
298     if (data)
299       memcpy (GST_BUFFER_DATA (*buffer), data, size);
300   } else {
301     GST_WARNING_OBJECT (enc, "alloc function returned error %s",
302         gst_flow_get_name (res));
303   }
304
305   return res;
306 }
307
308 static GstCaps *
309 gst_cmml_enc_set_header_on_caps (GstCmmlEnc * enc, GstCaps * caps,
310     GstBuffer * ident, GstBuffer * preamble, GstBuffer * head)
311 {
312   GValue array = { 0 };
313   GValue value = { 0 };
314   GstStructure *structure;
315   GstBuffer *buffer;
316
317   caps = gst_caps_make_writable (caps);
318   structure = gst_caps_get_structure (caps, 0);
319
320   g_value_init (&array, GST_TYPE_ARRAY);
321   g_value_init (&value, GST_TYPE_BUFFER);
322
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);
328
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);
333
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);
338
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);
342
343   gst_structure_set_value (structure, "streamheader", &array);
344
345   g_value_unset (&value);
346   g_value_unset (&array);
347
348   return caps;
349 }
350
351 /* create a CMML ident header buffer
352  */
353 static GstFlowReturn
354 gst_cmml_enc_new_ident_header (GstCmmlEnc * enc, GstBuffer ** buffer)
355 {
356   guint8 ident_header[CMML_IDENT_HEADER_SIZE];
357   guint8 *wptr = ident_header;
358
359   memcpy (wptr, "CMML\0\0\0\0", 8);
360   wptr += 8;
361   GST_WRITE_UINT16_LE (wptr, enc->major);
362   wptr += 2;
363   GST_WRITE_UINT16_LE (wptr, enc->minor);
364   wptr += 2;
365   GST_WRITE_UINT64_LE (wptr, enc->granulerate_n);
366   wptr += 8;
367   GST_WRITE_UINT64_LE (wptr, enc->granulerate_d);
368   wptr += 8;
369   *wptr = enc->granuleshift;
370
371   return gst_cmml_enc_new_buffer (enc,
372       (guchar *) & ident_header, CMML_IDENT_HEADER_SIZE, buffer);
373 }
374
375 /* parse the CMML preamble */
376 static void
377 gst_cmml_enc_parse_preamble (GstCmmlEnc * enc,
378     guchar * preamble, guchar * processing_instruction)
379 {
380   GST_INFO_OBJECT (enc, "parsing preamble");
381
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);
385 }
386
387 /* parse the CMML end tag */
388 static void
389 gst_cmml_enc_parse_end_tag (GstCmmlEnc * enc)
390 {
391   GstBuffer *buffer;
392
393   GST_INFO_OBJECT (enc, "parsing end tag");
394
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;
402   }
403
404   return;
405 }
406
407 /* encode the CMML head tag and push the CMML headers
408  */
409 static void
410 gst_cmml_enc_parse_tag_head (GstCmmlEnc * enc, GstCmmlTagHead * head)
411 {
412   GList *headers = NULL;
413   GList *walk;
414   guchar *head_string;
415   GstCaps *caps;
416   GstBuffer *ident_buf, *preamble_buf, *head_buf;
417   GstBuffer *buffer;
418
419   if (enc->preamble == NULL)
420     goto flow_unexpected;
421
422   GST_INFO_OBJECT (enc, "parsing head tag");
423
424   enc->flow_return = gst_cmml_enc_new_ident_header (enc, &ident_buf);
425   if (enc->flow_return != GST_FLOW_OK)
426     goto alloc_error;
427   headers = g_list_append (headers, ident_buf);
428
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)
432     goto alloc_error;
433   headers = g_list_append (headers, preamble_buf);
434
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)
440     goto alloc_error;
441   headers = g_list_append (headers, head_buf);
442
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);
446
447   while (headers) {
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);
452
453     enc->flow_return = gst_cmml_enc_push (enc, buffer);
454     headers = g_list_delete_link (headers, headers);
455
456     if (GST_FLOW_IS_FATAL (enc->flow_return))
457       goto push_error;
458   }
459
460   gst_caps_unref (caps);
461
462   enc->sent_headers = TRUE;
463   return;
464
465 flow_unexpected:
466   GST_ELEMENT_ERROR (enc, STREAM, ENCODE,
467       (NULL), ("got head tag before preamble"));
468   enc->flow_return = GST_FLOW_ERROR;
469   return;
470 push_error:
471   gst_caps_unref (caps);
472   /* fallthrough */
473 alloc_error:
474   for (walk = headers; walk; walk = walk->next)
475     gst_buffer_unref (GST_BUFFER (walk->data));
476   g_list_free (headers);
477   return;
478 }
479
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.
484  */
485 static void
486 gst_cmml_enc_parse_tag_clip (GstCmmlEnc * enc, GstCmmlTagClip * clip)
487 {
488   GstCmmlTagClip *prev_clip;
489   GstClockTime prev_clip_time = GST_CLOCK_TIME_NONE;
490
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;
498
499     return;
500   }
501
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);
505   if (prev_clip) {
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",
510               clip->id));
511       enc->flow_return = GST_FLOW_ERROR;
512       return;
513     }
514
515     /* we don't need the prev clip anymore */
516     gst_cmml_track_list_del_clip (enc->tracks, prev_clip);
517   }
518
519   /* add the current clip to the tracklist */
520   gst_cmml_track_list_add_clip (enc->tracks, clip);
521
522   enc->flow_return = gst_cmml_enc_push_clip (enc, clip, prev_clip_time);
523 }
524
525 static GstFlowReturn
526 gst_cmml_enc_push_clip (GstCmmlEnc * enc, GstCmmlTagClip * clip,
527     GstClockTime prev_clip_time)
528 {
529   GstFlowReturn res;
530   GstBuffer *buffer;
531   gchar *clip_string;
532   gint64 granulepos;
533
534   /* encode the clip */
535   clip_string =
536       (gchar *) gst_cmml_parser_tag_clip_to_string (enc->parser, clip);
537
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)
542     goto done;
543
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));
547
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;
554   }
555
556   GST_BUFFER_OFFSET (buffer) = clip->start_time;
557   GST_BUFFER_OFFSET_END (buffer) = granulepos;
558   GST_BUFFER_TIMESTAMP (buffer) = clip->start_time;
559
560   res = gst_cmml_enc_push (enc, buffer);
561   if (GST_FLOW_IS_FATAL (res))
562     goto done;
563
564   if (clip->end_time != GST_CLOCK_TIME_NONE) {
565     /* create a new empty clip for the same cmml track starting at end_time
566      */
567     GObject *end_clip = g_object_new (GST_TYPE_CMML_TAG_CLIP,
568         "start-time", clip->end_time, "track", clip->track, NULL);
569
570     /* encode the empty end clip */
571     gst_cmml_enc_push_clip (enc, GST_CMML_TAG_CLIP (end_clip),
572         clip->start_time);
573     g_object_unref (end_clip);
574   }
575 done:
576   return res;
577
578 granule_overflow:
579   GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), ("granulepos overflow"));
580   return GST_FLOW_ERROR;
581 }
582
583 static GstFlowReturn
584 gst_cmml_enc_push (GstCmmlEnc * enc, GstBuffer * buffer)
585 {
586   GstFlowReturn res;
587
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));
591
592   return res;
593 }
594
595 static GstFlowReturn
596 gst_cmml_enc_chain (GstPad * pad, GstBuffer * buffer)
597 {
598   GError *err = NULL;
599   GstCmmlEnc *enc = GST_CMML_ENC (GST_PAD_PARENT (pad));
600
601   /* the CMML handlers registered with enc->parser will override this when
602    * encoding/pushing the buffers downstream
603    */
604   enc->flow_return = GST_FLOW_OK;
605
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));
609     g_error_free (err);
610     enc->flow_return = GST_FLOW_ERROR;
611   }
612
613   gst_buffer_unref (buffer);
614   return enc->flow_return;
615 }
616
617 gboolean
618 gst_cmml_enc_plugin_init (GstPlugin * plugin)
619 {
620   if (!gst_element_register (plugin, "cmmlenc", GST_RANK_NONE,
621           GST_TYPE_CMML_ENC))
622     return FALSE;
623
624   GST_DEBUG_CATEGORY_INIT (cmmlenc, "cmmlenc", 0,
625       "annodex cmml decoding element");
626
627   return TRUE;
628 }