rtptheoradepay: Ignore packets without a known codebook
[platform/upstream/gstreamer.git] / gst / rtp / gstrtptheoradepay.c
1 /* GStreamer
2  * Copyright (C) <2006> Wim Taymans <wim.taymans@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <gst/tag/tag.h>
25 #include <gst/rtp/gstrtpbuffer.h>
26
27 #include <string.h>
28 #include "gstrtptheoradepay.h"
29
30 GST_DEBUG_CATEGORY_STATIC (rtptheoradepay_debug);
31 #define GST_CAT_DEFAULT (rtptheoradepay_debug)
32
33 static GstStaticPadTemplate gst_rtp_theora_depay_sink_template =
34 GST_STATIC_PAD_TEMPLATE ("sink",
35     GST_PAD_SINK,
36     GST_PAD_ALWAYS,
37     GST_STATIC_CAPS ("application/x-rtp, "
38         "media = (string) \"video\", "
39         "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
40         "clock-rate = (int) 90000, " "encoding-name = (string) \"THEORA\","
41         /* only support inline delivery */
42         "delivery-method = (string) \"inline\""
43         /* All required parameters 
44          *
45          * "sampling = (string) { "YCbCr-4:2:0", "YCbCr-4:2:2", "YCbCr-4:4:4" } "
46          * "width = (string) [1, 1048561] (multiples of 16) "
47          * "height = (string) [1, 1048561] (multiples of 16) "
48          * "delivery-method = (string) { inline, in_band, out_band/<specific_name> } " 
49          * "configuration = (string) ANY" 
50          */
51         /* All optional parameters
52          *
53          * "configuration-uri =" 
54          */
55     )
56     );
57
58 static GstStaticPadTemplate gst_rtp_theora_depay_src_template =
59 GST_STATIC_PAD_TEMPLATE ("src",
60     GST_PAD_SRC,
61     GST_PAD_ALWAYS,
62     GST_STATIC_CAPS ("video/x-theora")
63     );
64
65 GST_BOILERPLATE (GstRtpTheoraDepay, gst_rtp_theora_depay, GstBaseRTPDepayload,
66     GST_TYPE_BASE_RTP_DEPAYLOAD);
67
68 static gboolean gst_rtp_theora_depay_setcaps (GstBaseRTPDepayload * depayload,
69     GstCaps * caps);
70 static GstBuffer *gst_rtp_theora_depay_process (GstBaseRTPDepayload * depayload,
71     GstBuffer * buf);
72
73 static void gst_rtp_theora_depay_finalize (GObject * object);
74
75
76 static void
77 gst_rtp_theora_depay_base_init (gpointer klass)
78 {
79   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
80
81   gst_element_class_add_pad_template (element_class,
82       gst_static_pad_template_get (&gst_rtp_theora_depay_sink_template));
83   gst_element_class_add_pad_template (element_class,
84       gst_static_pad_template_get (&gst_rtp_theora_depay_src_template));
85
86   gst_element_class_set_details_simple (element_class, "RTP Theora depayloader",
87       "Codec/Depayloader/Network",
88       "Extracts Theora video from RTP packets (draft-01 of RFC XXXX)",
89       "Wim Taymans <wim.taymans@gmail.com>");
90 }
91
92 static void
93 gst_rtp_theora_depay_class_init (GstRtpTheoraDepayClass * klass)
94 {
95   GObjectClass *gobject_class;
96   GstBaseRTPDepayloadClass *gstbasertpdepayload_class;
97
98   gobject_class = (GObjectClass *) klass;
99   gstbasertpdepayload_class = (GstBaseRTPDepayloadClass *) klass;
100
101   gobject_class->finalize = gst_rtp_theora_depay_finalize;
102
103   gstbasertpdepayload_class->process = gst_rtp_theora_depay_process;
104   gstbasertpdepayload_class->set_caps = gst_rtp_theora_depay_setcaps;
105
106   GST_DEBUG_CATEGORY_INIT (rtptheoradepay_debug, "rtptheoradepay", 0,
107       "Theora RTP Depayloader");
108 }
109
110 static void
111 gst_rtp_theora_depay_init (GstRtpTheoraDepay * rtptheoradepay,
112     GstRtpTheoraDepayClass * klass)
113 {
114   rtptheoradepay->adapter = gst_adapter_new ();
115 }
116
117 static void
118 gst_rtp_theora_depay_finalize (GObject * object)
119 {
120   GstRtpTheoraDepay *rtptheoradepay = GST_RTP_THEORA_DEPAY (object);
121
122   g_object_unref (rtptheoradepay->adapter);
123
124   G_OBJECT_CLASS (parent_class)->finalize (object);
125 }
126
127 static gboolean
128 gst_rtp_theora_depay_parse_configuration (GstRtpTheoraDepay * rtptheoradepay,
129     const gchar * configuration)
130 {
131   GstBuffer *buf;
132   guint32 num_headers;
133   guint8 *data;
134   gsize size;
135   gint i, j;
136
137   /* deserialize base64 to buffer */
138   size = strlen (configuration);
139   GST_DEBUG_OBJECT (rtptheoradepay, "base64 config size %" G_GSIZE_FORMAT,
140       size);
141
142   data = g_base64_decode (configuration, &size);
143
144   GST_DEBUG_OBJECT (rtptheoradepay, "config size %" G_GSIZE_FORMAT, size);
145
146   /* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
147    * |                     Number of packed headers                  |
148    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
149    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
150    * |                          Packed header                        |
151    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
152    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
153    * |                          Packed header                        |
154    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
155    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
156    * |                          ....                                 |
157    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
158    */
159   if (size < 4)
160     goto too_small;
161
162   num_headers = GST_READ_UINT32_BE (data);
163   size -= 4;
164   data += 4;
165
166   GST_DEBUG_OBJECT (rtptheoradepay, "have %u headers", num_headers);
167
168   /*  0                   1                   2                   3
169    *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
170    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171    * |                   Ident                       | length       ..
172    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
173    * ..              | n. of headers |    length1    |    length2   ..
174    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
175    * ..              |             Identification Header            ..
176    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
177    * .................................................................
178    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
179    * ..              |         Comment Header                       ..
180    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
181    * .................................................................
182    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
183    * ..                        Comment Header                        |
184    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
185    * |                          Setup Header                        ..
186    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
187    * .................................................................
188    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
189    * ..                         Setup Header                         |
190    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
191    */
192   for (i = 0; i < num_headers; i++) {
193     guint32 ident;
194     guint16 length;
195     guint8 n_headers, b;
196     GstRtpTheoraConfig *conf;
197     guint *h_sizes;
198
199     if (size < 6)
200       goto too_small;
201
202     ident = (data[0] << 16) | (data[1] << 8) | data[2];
203     length = (data[3] << 8) | data[4];
204     n_headers = data[5];
205     size -= 6;
206     data += 6;
207
208     GST_DEBUG_OBJECT (rtptheoradepay,
209         "header %d, ident 0x%08x, length %u, left %" G_GSIZE_FORMAT, i, ident,
210         length, size);
211
212     if (size < length)
213       goto too_small;
214
215     /* read header sizes we read 2 sizes, the third size (for which we allocate
216      * space) must be derived from the total packed header length. */
217     h_sizes = g_newa (guint, n_headers + 1);
218     for (j = 0; j < n_headers; j++) {
219       guint h_size;
220
221       h_size = 0;
222       do {
223         if (size < 1)
224           goto too_small;
225         b = *data++;
226         size--;
227         h_size = (h_size << 7) | (b & 0x7f);
228       } while (b & 0x80);
229       GST_DEBUG_OBJECT (rtptheoradepay, "headers %d: size: %u", j, h_size);
230       h_sizes[j] = h_size;
231       length -= h_size;
232     }
233     /* last header length is the remaining space */
234     GST_DEBUG_OBJECT (rtptheoradepay, "last header size: %u", length);
235     h_sizes[j] = length;
236
237     GST_DEBUG_OBJECT (rtptheoradepay, "preparing headers");
238     conf = g_new0 (GstRtpTheoraConfig, 1);
239     conf->ident = ident;
240
241     for (j = 0; j <= n_headers; j++) {
242       guint h_size;
243
244       h_size = h_sizes[j];
245       if (size < h_size)
246         goto too_small;
247
248       GST_DEBUG_OBJECT (rtptheoradepay, "reading header %d, size %u", j,
249           h_size);
250
251       buf = gst_buffer_new_and_alloc (h_size);
252       memcpy (GST_BUFFER_DATA (buf), data, h_size);
253       conf->headers = g_list_append (conf->headers, buf);
254       data += h_size;
255       size -= h_size;
256     }
257     rtptheoradepay->configs = g_list_append (rtptheoradepay->configs, conf);
258   }
259   return TRUE;
260
261   /* ERRORS */
262 too_small:
263   {
264     GST_DEBUG_OBJECT (rtptheoradepay, "configuration too small");
265     return FALSE;
266   }
267 }
268
269 static gboolean
270 gst_rtp_theora_depay_setcaps (GstBaseRTPDepayload * depayload, GstCaps * caps)
271 {
272   GstStructure *structure;
273   GstRtpTheoraDepay *rtptheoradepay;
274   GstCaps *srccaps;
275   const gchar *delivery_method;
276   const gchar *configuration;
277   gboolean res;
278
279   rtptheoradepay = GST_RTP_THEORA_DEPAY (depayload);
280
281   structure = gst_caps_get_structure (caps, 0);
282
283   /* see how the configuration parameters will be transmitted */
284   delivery_method = gst_structure_get_string (structure, "delivery-method");
285   if (delivery_method == NULL)
286     goto no_delivery_method;
287
288   if (!g_ascii_strcasecmp (delivery_method, "inline")) {
289     /* configure string is in the caps */
290   } else if (!g_ascii_strcasecmp (delivery_method, "in_band")) {
291     /* headers will (also) be transmitted in the RTP packets */
292     goto unsupported_delivery_method;
293   } else if (g_str_has_prefix (delivery_method, "out_band/")) {
294     /* some other method of header delivery. */
295     goto unsupported_delivery_method;
296   } else
297     goto unsupported_delivery_method;
298
299   /* read and parse configuration string */
300   configuration = gst_structure_get_string (structure, "configuration");
301   if (configuration == NULL)
302     goto no_configuration;
303
304   if (!gst_rtp_theora_depay_parse_configuration (rtptheoradepay, configuration))
305     goto invalid_configuration;
306
307   /* set caps on pad and on header */
308   srccaps = gst_caps_new_simple ("video/x-theora", NULL);
309   res = gst_pad_set_caps (depayload->srcpad, srccaps);
310   gst_caps_unref (srccaps);
311
312   /* Clock rate is always 90000 according to draft-barbato-avt-rtp-theora-01 */
313   depayload->clock_rate = 90000;
314
315   return res;
316
317   /* ERRORS */
318 unsupported_delivery_method:
319   {
320     GST_ERROR_OBJECT (rtptheoradepay,
321         "unsupported delivery-method \"%s\" specified", delivery_method);
322     return FALSE;
323   }
324 no_delivery_method:
325   {
326     GST_ERROR_OBJECT (rtptheoradepay, "no delivery-method specified");
327     return FALSE;
328   }
329 no_configuration:
330   {
331     GST_ERROR_OBJECT (rtptheoradepay, "no configuration specified");
332     return FALSE;
333   }
334 invalid_configuration:
335   {
336     GST_ERROR_OBJECT (rtptheoradepay, "invalid configuration specified");
337     return FALSE;
338   }
339 }
340
341 static gboolean
342 gst_rtp_theora_depay_switch_codebook (GstRtpTheoraDepay * rtptheoradepay,
343     guint32 ident)
344 {
345   GList *walk;
346   gboolean res = FALSE;
347
348   for (walk = rtptheoradepay->configs; walk; walk = g_list_next (walk)) {
349     GstRtpTheoraConfig *conf = (GstRtpTheoraConfig *) walk->data;
350
351     if (conf->ident == ident) {
352       GList *headers;
353
354       /* FIXME, remove pads, create new pad.. */
355
356       /* push out all the headers */
357       for (headers = conf->headers; headers; headers = g_list_next (headers)) {
358         GstBuffer *header = GST_BUFFER_CAST (headers->data);
359
360         gst_buffer_ref (header);
361         gst_base_rtp_depayload_push (GST_BASE_RTP_DEPAYLOAD (rtptheoradepay),
362             header);
363       }
364       /* remember the current config */
365       rtptheoradepay->config = conf;
366       res = TRUE;
367     }
368   }
369   if (!res) {
370     /* we don't know about the headers, figure out an alternative method for
371      * getting the codebooks. FIXME, fail for now. */
372   }
373   return res;
374 }
375
376 static GstBuffer *
377 gst_rtp_theora_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf)
378 {
379   GstRtpTheoraDepay *rtptheoradepay;
380   GstBuffer *outbuf;
381   GstFlowReturn ret;
382   gint payload_len;
383   guint8 *payload, *to_free = NULL;
384   guint32 timestamp;
385   guint32 header, ident;
386   guint8 F, TDT, packets;
387
388   rtptheoradepay = GST_RTP_THEORA_DEPAY (depayload);
389
390   payload_len = gst_rtp_buffer_get_payload_len (buf);
391
392   GST_DEBUG_OBJECT (depayload, "got RTP packet of size %d", payload_len);
393
394   /* we need at least 4 bytes for the packet header */
395   if (G_UNLIKELY (payload_len < 4))
396     goto packet_short;
397
398   payload = gst_rtp_buffer_get_payload (buf);
399
400   header = GST_READ_UINT32_BE (payload);
401   /*
402    *  0                   1                   2                   3
403    *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
404    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
405    * |                     Ident                     | F |TDT|# pkts.|
406    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
407    *
408    * F: Fragment type (0=none, 1=start, 2=cont, 3=end)
409    * TDT: Theora data type (0=theora, 1=config, 2=comment, 3=reserved)
410    * pkts: number of packets.
411    */
412   TDT = (header & 0x30) >> 4;
413   if (G_UNLIKELY (TDT == 3))
414     goto ignore_reserved;
415
416   ident = (header >> 8) & 0xffffff;
417   F = (header & 0xc0) >> 6;
418   packets = (header & 0xf);
419
420   GST_DEBUG_OBJECT (depayload, "ident: 0x%08x, F: %d, TDT: %d, packets: %d",
421       ident, F, TDT, packets);
422
423   if (TDT == 0) {
424     gboolean do_switch = FALSE;
425
426     /* we have a raw payload, find the codebook for the ident */
427     if (!rtptheoradepay->config) {
428       /* we don't have an active codebook, find the codebook and
429        * activate it */
430       do_switch = TRUE;
431     } else if (rtptheoradepay->config->ident != ident) {
432       /* codebook changed */
433       do_switch = TRUE;
434     }
435     if (do_switch) {
436       if (!gst_rtp_theora_depay_switch_codebook (rtptheoradepay, ident))
437         goto switch_failed;
438     }
439   }
440
441   /* skip header */
442   payload += 4;
443   payload_len -= 4;
444
445   /* fragmented packets, assemble */
446   if (F != 0) {
447     GstBuffer *vdata;
448     guint headerskip;
449
450     if (F == 1) {
451       /* if we start a packet, clear adapter and start assembling. */
452       gst_adapter_clear (rtptheoradepay->adapter);
453       GST_DEBUG_OBJECT (depayload, "start assemble");
454       rtptheoradepay->assembling = TRUE;
455     }
456
457     if (!rtptheoradepay->assembling)
458       goto no_output;
459
460     /* first assembled packet, reuse 2 bytes to store the length */
461     headerskip = (F == 1 ? 4 : 6);
462     /* skip header and length. */
463     vdata = gst_rtp_buffer_get_payload_subbuffer (buf, headerskip, -1);
464
465     GST_DEBUG_OBJECT (depayload, "assemble theora packet");
466     gst_adapter_push (rtptheoradepay->adapter, vdata);
467
468     /* packet is not complete, we are done */
469     if (F != 3)
470       goto no_output;
471
472     /* construct assembled buffer */
473     payload_len = gst_adapter_available (rtptheoradepay->adapter);
474     payload = gst_adapter_take (rtptheoradepay->adapter, payload_len);
475     /* fix the length */
476     payload[0] = ((payload_len - 2) >> 8) & 0xff;
477     payload[1] = (payload_len - 2) & 0xff;
478     to_free = payload;
479   }
480
481   GST_DEBUG_OBJECT (depayload, "assemble done");
482
483   /* we not assembling anymore now */
484   rtptheoradepay->assembling = FALSE;
485   gst_adapter_clear (rtptheoradepay->adapter);
486
487   /* payload now points to a length with that many theora data bytes.
488    * Iterate over the packets and send them out.
489    *
490    *  0                   1                   2                   3
491    *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
492    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
493    * |             length            |          theora data         ..
494    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
495    * ..                        theora data                           |
496    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
497    * |            length             |   next theora packet data    ..
498    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
499    * ..                        theora data                           |
500    * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*
501    */
502   timestamp = gst_rtp_buffer_get_timestamp (buf);
503
504   while (payload_len > 2) {
505     guint16 length;
506
507     length = GST_READ_UINT16_BE (payload);
508     payload += 2;
509     payload_len -= 2;
510
511     GST_DEBUG_OBJECT (depayload, "read length %u, avail: %d", length,
512         payload_len);
513
514     /* skip packet if something odd happens */
515     if (G_UNLIKELY (length > payload_len))
516       goto length_short;
517
518     /* create buffer for packet */
519     if (G_UNLIKELY (to_free)) {
520       outbuf = gst_buffer_new ();
521       GST_BUFFER_DATA (outbuf) = payload;
522       GST_BUFFER_MALLOCDATA (outbuf) = to_free;
523       GST_BUFFER_SIZE (outbuf) = length;
524       to_free = NULL;
525     } else {
526       outbuf = gst_buffer_new_and_alloc (length);
527       memcpy (GST_BUFFER_DATA (outbuf), payload, length);
528     }
529
530     payload += length;
531     payload_len -= length;
532
533     if (timestamp != -1)
534       /* push with timestamp of the last packet, which is the same timestamp that
535        * should apply to the first assembled packet. */
536       ret = gst_base_rtp_depayload_push_ts (depayload, timestamp, outbuf);
537     else
538       ret = gst_base_rtp_depayload_push (depayload, outbuf);
539
540     if (ret != GST_FLOW_OK)
541       break;
542
543     /* make sure we don't set a timestamp on next buffers */
544     timestamp = -1;
545   }
546
547   g_free (to_free);
548
549   return NULL;
550
551 no_output:
552   {
553     return NULL;
554   }
555   /* ERORRS */
556 switch_failed:
557   {
558     GST_ELEMENT_WARNING (rtptheoradepay, STREAM, DECODE,
559         (NULL), ("Could not switch codebooks"));
560     return NULL;
561   }
562 packet_short:
563   {
564     GST_ELEMENT_WARNING (rtptheoradepay, STREAM, DECODE,
565         (NULL), ("Packet was too short (%d < 4)", payload_len));
566     return NULL;
567   }
568 ignore_reserved:
569   {
570     GST_WARNING_OBJECT (rtptheoradepay, "reserved TDT ignored");
571     return NULL;
572   }
573 length_short:
574   {
575     GST_ELEMENT_WARNING (rtptheoradepay, STREAM, DECODE,
576         (NULL), ("Packet contains invalid data"));
577     return NULL;
578   }
579 }
580
581 gboolean
582 gst_rtp_theora_depay_plugin_init (GstPlugin * plugin)
583 {
584   return gst_element_register (plugin, "rtptheoradepay",
585       GST_RANK_MARGINAL, GST_TYPE_RTP_THEORA_DEPAY);
586 }