rtpulpfec: fix debug log printf format warning on 32-bit platforms
[platform/upstream/gst-plugins-good.git] / gst / rtp / gstrtpulpfecdec.c
1 /* GStreamer plugin for forward error correction
2  * Copyright (C) 2017 Pexip
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Author: Mikhail Fludkov <misha@pexip.com>
19  */
20
21 /**
22  * SECTION:element-rtpulpfecdec
23  * @short_description: Generic RTP Forward Error Correction (FEC) decoder
24  * @title: rtpulpfecdec
25  *
26  * Generic Forward Error Correction (FEC) decoder for Uneven Level
27  * Protection (ULP) as described in RFC 5109.
28  *
29  * Since: 1.14
30  */
31
32 #include <gst/rtp/gstrtpbuffer.h>
33 #include <gst/rtp/gstrtp-enumtypes.h>
34
35 #include "rtpulpfeccommon.h"
36 #include "gstrtpulpfecdec.h"
37
38 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
39     GST_PAD_SINK,
40     GST_PAD_ALWAYS,
41     GST_STATIC_CAPS ("application/x-rtp")
42     );
43
44 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
45     GST_PAD_SRC,
46     GST_PAD_ALWAYS,
47     GST_STATIC_CAPS ("application/x-rtp")
48     );
49
50 enum
51 {
52   PROP_0,
53   PROP_PT,
54   PROP_STORAGE,
55   PROP_RECOVERED,
56   PROP_UNRECOVERED,
57   N_PROPERTIES
58 };
59
60 #define DEFAULT_FEC_PT 0
61
62 static GParamSpec *klass_properties[N_PROPERTIES] = { NULL, };
63
64 GST_DEBUG_CATEGORY (gst_rtp_ulpfec_dec_debug);
65 #define GST_CAT_DEFAULT (gst_rtp_ulpfec_dec_debug)
66
67 G_DEFINE_TYPE (GstRtpUlpFecDec, gst_rtp_ulpfec_dec, GST_TYPE_ELEMENT);
68
69 #define RTP_FEC_MAP_INFO_NTH(dec, data) (&g_array_index (\
70     ((GstRtpUlpFecDec *)dec)->info_arr, \
71     RtpUlpFecMapInfo, \
72     GPOINTER_TO_UINT(data)))
73
74 static gint
75 _compare_fec_map_info (gconstpointer a, gconstpointer b, gpointer userdata)
76 {
77   guint16 aseq =
78       gst_rtp_buffer_get_seq (&RTP_FEC_MAP_INFO_NTH (userdata, a)->rtp);
79   guint16 bseq =
80       gst_rtp_buffer_get_seq (&RTP_FEC_MAP_INFO_NTH (userdata, b)->rtp);
81   return gst_rtp_buffer_compare_seqnum (bseq, aseq);
82 }
83
84 static void
85 gst_rtp_ulpfec_dec_start (GstRtpUlpFecDec * self, GstBufferList * buflist,
86     guint8 fec_pt, guint16 lost_seq)
87 {
88   guint fec_packets = 0;
89
90   g_assert (NULL == self->info_media);
91   g_assert (0 == self->info_fec->len);
92   g_assert (0 == self->info_arr->len);
93
94   g_array_set_size (self->info_arr, gst_buffer_list_length (buflist));
95
96   for (gsize i = 0;
97       i < gst_buffer_list_length (buflist) && !self->lost_packet_from_storage;
98       ++i) {
99     GstBuffer *buffer = gst_buffer_list_get (buflist, i);
100     RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (self, i);
101
102     if (!rtp_ulpfec_map_info_map (gst_buffer_ref (buffer), info))
103       g_assert_not_reached ();
104
105     if (fec_pt == gst_rtp_buffer_get_payload_type (&info->rtp)) {
106       GST_DEBUG_RTP_PACKET (self, "rtp header (fec)", &info->rtp);
107
108       ++fec_packets;
109       if (rtp_ulpfec_buffer_is_valid (&info->rtp)) {
110         GST_DEBUG_FEC_PACKET (self, &info->rtp);
111         g_ptr_array_add (self->info_fec, GUINT_TO_POINTER (i));
112       }
113     } else {
114       GST_LOG_RTP_PACKET (self, "rtp header (incoming)", &info->rtp);
115
116       if (lost_seq == gst_rtp_buffer_get_seq (&info->rtp)) {
117         GST_DEBUG_OBJECT (self, "Received lost packet from from the storage");
118         g_list_free (self->info_media);
119         self->info_media = NULL;
120         self->lost_packet_from_storage = TRUE;
121       }
122       self->info_media =
123           g_list_insert_sorted_with_data (self->info_media,
124           GUINT_TO_POINTER (i), _compare_fec_map_info, self);
125     }
126   }
127   if (!self->lost_packet_from_storage) {
128     self->fec_packets_received += fec_packets;
129     self->fec_packets_rejected += fec_packets - self->info_fec->len;
130   }
131 }
132
133 static void
134 gst_rtp_ulpfec_dec_stop (GstRtpUlpFecDec * self)
135 {
136   g_array_set_size (self->info_arr, 0);
137   g_ptr_array_set_size (self->info_fec, 0);
138   g_list_free (self->info_media);
139   self->info_media = NULL;
140   self->lost_packet_from_storage = FALSE;
141   self->lost_packet_returned = FALSE;
142 }
143
144 static guint64
145 gst_rtp_ulpfec_dec_get_media_buffers_mask (GstRtpUlpFecDec * self,
146     guint16 fec_seq_base)
147 {
148   guint64 mask = 0;
149   for (GList * it = self->info_media; it; it = it->next) {
150     RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (self, it->data);
151     mask |=
152         rtp_ulpfec_packet_mask_from_seqnum (gst_rtp_buffer_get_seq (&info->rtp),
153         fec_seq_base, TRUE);
154   }
155   return mask;
156 }
157
158 static gboolean
159 gst_rtp_ulpfec_dec_is_recovered_pt_valid (GstRtpUlpFecDec * self, gint media_pt,
160     guint8 recovered_pt)
161 {
162   if (media_pt == recovered_pt)
163     return TRUE;
164
165   for (GList * it = self->info_media; it; it = it->next) {
166     RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (self, it->data);
167     if (gst_rtp_buffer_get_payload_type (&info->rtp) == recovered_pt)
168       return TRUE;
169   }
170   return FALSE;
171 }
172
173 static GstBuffer *
174 gst_rtp_ulpfec_dec_recover_from_fec (GstRtpUlpFecDec * self,
175     RtpUlpFecMapInfo * info_fec, guint32 ssrc, gint media_pt, guint16 seq,
176     guint8 * dst_pt)
177 {
178   guint64 fec_mask = rtp_ulpfec_buffer_get_mask (&info_fec->rtp);
179   gboolean fec_mask_long = rtp_ulpfec_buffer_get_fechdr (&info_fec->rtp)->L;
180   guint16 fec_seq_base = rtp_ulpfec_buffer_get_seq_base (&info_fec->rtp);
181   GstBuffer *ret;
182
183   g_array_set_size (self->scratch_buf, 0);
184   rtp_buffer_to_ulpfec_bitstring (&info_fec->rtp, self->scratch_buf, TRUE,
185       fec_mask_long);
186
187   for (GList * it = self->info_media; it; it = it->next) {
188     RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (self, it->data);
189     guint64 packet_mask =
190         rtp_ulpfec_packet_mask_from_seqnum (gst_rtp_buffer_get_seq (&info->rtp),
191         fec_seq_base, TRUE);
192
193     if (fec_mask & packet_mask) {
194       fec_mask ^= packet_mask;
195       rtp_buffer_to_ulpfec_bitstring (&info->rtp, self->scratch_buf, FALSE,
196           fec_mask_long);
197     }
198   }
199
200   ret =
201       rtp_ulpfec_bitstring_to_media_rtp_buffer (self->scratch_buf,
202       fec_mask_long, ssrc, seq);
203   if (ret) {
204     /* We are about to put recovered packet back in self->info_media to be able
205      * to reuse it later for recovery of other packets
206      **/
207     gint i = self->info_arr->len;
208     RtpUlpFecMapInfo *info;
209     guint8 recovered_pt;
210
211     g_array_set_size (self->info_arr, self->info_arr->len + 1);
212     info = RTP_FEC_MAP_INFO_NTH (self, i);
213
214     if (!rtp_ulpfec_map_info_map (gst_buffer_ref (ret), info)) {
215       GST_WARNING_OBJECT (self, "Invalid recovered packet");
216       goto recovered_packet_invalid;
217     }
218
219     recovered_pt = gst_rtp_buffer_get_payload_type (&info->rtp);
220     if (!gst_rtp_ulpfec_dec_is_recovered_pt_valid (self, media_pt,
221             recovered_pt)) {
222       GST_WARNING_OBJECT (self,
223           "Recovered packet has unexpected payload type (%u)", recovered_pt);
224       goto recovered_packet_invalid;
225     }
226
227     GST_DEBUG_RTP_PACKET (self, "rtp header (recovered)", &info->rtp);
228     self->info_media =
229         g_list_insert_sorted_with_data (self->info_media, GUINT_TO_POINTER (i),
230         _compare_fec_map_info, self);
231     *dst_pt = recovered_pt;
232   }
233   return ret;
234
235 recovered_packet_invalid:
236   g_array_set_size (self->info_arr, self->info_arr->len - 1);
237   gst_buffer_unref (ret);
238   return NULL;
239 }
240
241 static GstBuffer *
242 gst_rtp_ulpfec_dec_recover_from_storage (GstRtpUlpFecDec * self,
243     guint8 * dst_pt, guint16 * dst_seq)
244 {
245   RtpUlpFecMapInfo *info;
246
247   if (self->lost_packet_returned)
248     return NULL;
249
250   g_assert (g_list_length (self->info_media) == 1);
251
252   info = RTP_FEC_MAP_INFO_NTH (self, self->info_media->data);
253   *dst_seq = gst_rtp_buffer_get_seq (&info->rtp);
254   *dst_pt = gst_rtp_buffer_get_payload_type (&info->rtp);
255   self->lost_packet_returned = TRUE;
256   GST_DEBUG_RTP_PACKET (self, "rtp header (recovered)", &info->rtp);
257   return gst_buffer_ref (info->rtp.buffer);
258 }
259
260 static GstBuffer *
261 gst_rtp_ulpfec_dec_recover (GstRtpUlpFecDec * self, guint32 ssrc, gint media_pt,
262     guint8 * dst_pt, guint16 * dst_seq)
263 {
264   guint64 media_mask = 0;
265   gint media_mask_seq_base = -1;
266
267   if (self->lost_packet_from_storage)
268     return gst_rtp_ulpfec_dec_recover_from_storage (self, dst_pt, dst_seq);
269
270   /* Looking for a FEC packet which can be used for recovery */
271   for (gsize i = 0; i < self->info_fec->len; ++i) {
272     RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (self,
273         g_ptr_array_index (self->info_fec, i));
274     guint16 seq_base = rtp_ulpfec_buffer_get_seq_base (&info->rtp);
275     guint64 fec_mask = rtp_ulpfec_buffer_get_mask (&info->rtp);
276     guint64 missing_packets_mask;
277
278     if (media_mask_seq_base != (gint) seq_base) {
279       media_mask_seq_base = seq_base;
280       media_mask = gst_rtp_ulpfec_dec_get_media_buffers_mask (self, seq_base);
281     }
282
283     /* media_mask has 1s if packet exist.
284      * fec_mask is the mask of protected packets
285      * The statement below excludes existing packets from the protected. So
286      * we are left with 1s only for missing packets which can be recovered
287      * by this FEC packet. */
288     missing_packets_mask = fec_mask & (~media_mask);
289
290     /* Do we have any 1s? Checking if current FEC packet can be used for recovery */
291     if (0 != missing_packets_mask) {
292       guint trailing_zeros = __builtin_ctzll (missing_packets_mask);
293
294       /* Is it the only 1 in the mask? Checking if we lacking single packet in
295        * that case FEC packet can be used for recovery */
296       if (missing_packets_mask == (1ULL << trailing_zeros)) {
297         GstBuffer *ret;
298
299         *dst_seq =
300             seq_base + (RTP_ULPFEC_SEQ_BASE_OFFSET_MAX (TRUE) - trailing_zeros);
301         ret =
302             gst_rtp_ulpfec_dec_recover_from_fec (self, info, ssrc, media_pt,
303             *dst_seq, dst_pt);
304         if (ret)
305           return ret;
306       }
307     }
308   }
309   return NULL;
310 }
311
312 static GstFlowReturn
313 gst_rtp_ulpfec_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
314 {
315   GstRtpUlpFecDec *self = GST_RTP_ULPFEC_DEC (parent);
316
317   if (G_LIKELY (GST_FLOW_OK == self->chain_return_val)) {
318     if (G_UNLIKELY (self->unset_discont_flag)) {
319       self->unset_discont_flag = FALSE;
320       buf = gst_buffer_make_writable (buf);
321       GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
322     }
323     return gst_pad_push (self->srcpad, buf);
324   }
325
326   gst_buffer_unref (buf);
327   return self->chain_return_val;
328 }
329
330 static gboolean
331 gst_rtp_ulpfec_dec_handle_packet_loss (GstRtpUlpFecDec * self, guint16 seqnum,
332     GstClockTime timestamp, GstClockTime duration)
333 {
334   gint caps_pt = self->have_caps_pt ? self->caps_pt : -1;
335   gboolean ret = TRUE;
336   GstBufferList *buflist =
337       rtp_storage_get_packets_for_recovery (self->storage, self->fec_pt,
338       self->caps_ssrc, seqnum);
339
340   if (buflist) {
341     GstBuffer *recovered_buffer = NULL;
342     guint16 recovered_seq = 0;
343     guint8 recovered_pt = 0;
344
345     gst_rtp_ulpfec_dec_start (self, buflist, self->fec_pt, seqnum);
346
347     while (NULL != (recovered_buffer =
348             gst_rtp_ulpfec_dec_recover (self, self->caps_ssrc, caps_pt,
349                 &recovered_pt, &recovered_seq))) {
350       if (seqnum == recovered_seq) {
351         recovered_buffer = gst_buffer_make_writable (recovered_buffer);
352         GST_BUFFER_PTS (recovered_buffer) = timestamp;
353         /* GST_BUFFER_DURATION (recovered_buffer) = duration;
354          * JB does not set the duration, so we will not too */
355
356         if (!self->lost_packet_from_storage)
357           rtp_storage_put_recovered_packet (self->storage,
358               gst_buffer_ref (recovered_buffer), recovered_pt, self->caps_ssrc,
359               recovered_seq);
360
361         GST_DEBUG_OBJECT (self,
362             "Pushing recovered packet ssrc=0x%08x seq=%u %" GST_PTR_FORMAT,
363             self->caps_ssrc, seqnum, recovered_buffer);
364
365         ret = FALSE;
366         self->unset_discont_flag = TRUE;
367         self->chain_return_val = gst_pad_push (self->srcpad, recovered_buffer);
368         break;
369       }
370
371       rtp_storage_put_recovered_packet (self->storage,
372           recovered_buffer, recovered_pt, self->caps_ssrc, recovered_seq);
373     }
374
375     gst_rtp_ulpfec_dec_stop (self);
376     gst_buffer_list_unref (buflist);
377   }
378
379   GST_DEBUG_OBJECT (self, "Packet lost ssrc=0x%08x seq=%u", self->caps_ssrc,
380       seqnum);
381
382   return ret;
383 }
384
385 static gboolean
386 gst_rtp_ulpfec_dec_handle_sink_event (GstPad * pad, GstObject * parent,
387     GstEvent * event)
388 {
389   GstRtpUlpFecDec *self = GST_RTP_ULPFEC_DEC (parent);
390   gboolean forward = TRUE;
391
392   GST_LOG_OBJECT (self, "Received event %" GST_PTR_FORMAT, event);
393
394   if (GST_FLOW_OK == self->chain_return_val &&
395       GST_EVENT_CUSTOM_DOWNSTREAM == GST_EVENT_TYPE (event) &&
396       gst_event_has_name (event, "GstRTPPacketLost")) {
397     guint seqnum;
398     GstClockTime timestamp, duration;
399
400     g_assert (self->have_caps_ssrc);
401     g_assert (self->storage);
402
403     if (!gst_structure_get (gst_event_get_structure (event),
404             "seqnum", G_TYPE_UINT, &seqnum,
405             "timestamp", G_TYPE_UINT64, &timestamp,
406             "duration", G_TYPE_UINT64, &duration, NULL))
407       g_assert_not_reached ();
408
409     forward =
410         gst_rtp_ulpfec_dec_handle_packet_loss (self, seqnum, timestamp,
411         duration);
412     if (forward)
413       ++self->packets_unrecovered;
414     else
415       ++self->packets_recovered;
416     GST_DEBUG_OBJECT (self, "Unrecovered / Recovered: %lu / %lu",
417         (gulong) self->packets_unrecovered, (gulong) self->packets_recovered);
418   } else if (GST_EVENT_CAPS == GST_EVENT_TYPE (event)) {
419     GstCaps *caps;
420     gboolean have_caps_pt = FALSE;
421     gboolean have_caps_ssrc = FALSE;
422     guint caps_ssrc = 0;
423     gint caps_pt = 0;
424
425     gst_event_parse_caps (event, &caps);
426     have_caps_ssrc =
427         gst_structure_get_uint (gst_caps_get_structure (caps, 0), "ssrc",
428         &caps_ssrc);
429     have_caps_pt =
430         gst_structure_get_int (gst_caps_get_structure (caps, 0), "payload",
431         &caps_pt);
432
433     if (self->have_caps_ssrc != have_caps_ssrc || self->caps_ssrc != caps_ssrc)
434       GST_DEBUG_OBJECT (self, "SSRC changed %u, 0x%08x -> %u, 0x%08x",
435           self->have_caps_ssrc, self->caps_ssrc, have_caps_ssrc, caps_ssrc);
436     if (self->have_caps_pt != have_caps_pt || self->caps_pt != caps_pt)
437       GST_DEBUG_OBJECT (self, "PT changed %u, %u -> %u, %u",
438           self->have_caps_pt, self->caps_pt, have_caps_pt, caps_pt);
439
440     self->have_caps_ssrc = have_caps_ssrc;
441     self->have_caps_pt = have_caps_pt;
442     self->caps_ssrc = caps_ssrc;
443     self->caps_pt = caps_pt;
444   }
445
446   if (forward)
447     return gst_pad_push_event (self->srcpad, event);
448   gst_event_unref (event);
449   return TRUE;
450 }
451
452 static void
453 gst_rtp_ulpfec_dec_init (GstRtpUlpFecDec * self)
454 {
455   self->srcpad = gst_pad_new_from_static_template (&srctemplate, "src");
456   self->sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink");
457   GST_PAD_SET_PROXY_CAPS (self->sinkpad);
458   GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
459   gst_pad_set_chain_function (self->sinkpad,
460       GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_dec_chain));
461   gst_pad_set_event_function (self->sinkpad,
462       GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_dec_handle_sink_event));
463
464   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
465   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
466
467   self->fec_pt = DEFAULT_FEC_PT;
468
469   self->chain_return_val = GST_FLOW_OK;
470   self->have_caps_ssrc = FALSE;
471   self->caps_ssrc = 0;
472   self->info_fec = g_ptr_array_new ();
473   self->info_arr = g_array_new (FALSE, TRUE, sizeof (RtpUlpFecMapInfo));
474   g_array_set_clear_func (self->info_arr,
475       (GDestroyNotify) rtp_ulpfec_map_info_unmap);
476   self->scratch_buf = g_array_new (FALSE, TRUE, sizeof (guint8));
477 }
478
479 static void
480 gst_rtp_ulpfec_dec_dispose (GObject * obj)
481 {
482   GstRtpUlpFecDec *self = GST_RTP_ULPFEC_DEC (obj);
483
484   GST_INFO_OBJECT (self,
485       " ssrc=0x%08x pt=%u"
486       " packets_recovered=%" G_GSIZE_FORMAT
487       " packets_unrecovered=%" G_GSIZE_FORMAT,
488       self->caps_ssrc, self->caps_pt,
489       self->packets_recovered, self->packets_unrecovered);
490
491   if (self->storage)
492     g_object_unref (self->storage);
493
494   g_assert (NULL == self->info_media);
495   g_assert (0 == self->info_fec->len);
496   g_assert (0 == self->info_arr->len);
497
498   if (self->fec_packets_received) {
499     GST_INFO_OBJECT (self,
500         " fec_packets_received=%" G_GSIZE_FORMAT
501         " fec_packets_rejected=%" G_GSIZE_FORMAT
502         " packets_rejected=%" G_GSIZE_FORMAT,
503         self->fec_packets_received,
504         self->fec_packets_rejected, self->packets_rejected);
505   }
506
507   g_ptr_array_free (self->info_fec, TRUE);
508   g_array_free (self->info_arr, TRUE);
509   g_array_free (self->scratch_buf, TRUE);
510
511   G_OBJECT_CLASS (gst_rtp_ulpfec_dec_parent_class)->dispose (obj);
512 }
513
514 static void
515 gst_rtp_ulpfec_dec_set_property (GObject * object, guint prop_id,
516     const GValue * value, GParamSpec * pspec)
517 {
518   GstRtpUlpFecDec *self = GST_RTP_ULPFEC_DEC (object);
519
520   switch (prop_id) {
521     case PROP_PT:
522       self->fec_pt = g_value_get_uint (value);
523       break;
524     case PROP_STORAGE:
525       if (self->storage)
526         g_object_unref (self->storage);
527       self->storage = g_value_get_object (value);
528       if (self->storage)
529         g_object_ref (self->storage);
530       break;
531     default:
532       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
533       break;
534   }
535 }
536
537 static void
538 gst_rtp_ulpfec_dec_get_property (GObject * object, guint prop_id,
539     GValue * value, GParamSpec * pspec)
540 {
541   GstRtpUlpFecDec *self = GST_RTP_ULPFEC_DEC (object);
542
543   switch (prop_id) {
544     case PROP_PT:
545       g_value_set_uint (value, self->fec_pt);
546       break;
547     case PROP_STORAGE:
548       g_value_set_object (value, self->storage);
549       break;
550     case PROP_RECOVERED:
551       g_value_set_uint (value, (guint) self->packets_recovered);
552       break;
553     case PROP_UNRECOVERED:
554       g_value_set_uint (value, (guint) self->packets_unrecovered);
555       break;
556     default:
557       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
558       break;
559   }
560 }
561
562 static void
563 gst_rtp_ulpfec_dec_class_init (GstRtpUlpFecDecClass * klass)
564 {
565   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
566   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
567
568   GST_DEBUG_CATEGORY_INIT (gst_rtp_ulpfec_dec_debug,
569       "rtpulpfecdec", 0, "RTP FEC Decoder");
570
571   gst_element_class_add_pad_template (element_class,
572       gst_static_pad_template_get (&srctemplate));
573   gst_element_class_add_pad_template (element_class,
574       gst_static_pad_template_get (&sinktemplate));
575
576   gst_element_class_set_static_metadata (element_class,
577       "RTP FEC Decoder",
578       "Codec/Depayloader/Network/RTP",
579       "Decodes RTP FEC (RFC5109)", "Mikhail Fludkov <misha@pexip.com>");
580
581   gobject_class->set_property =
582       GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_dec_set_property);
583   gobject_class->get_property =
584       GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_dec_get_property);
585   gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_dec_dispose);
586
587   klass_properties[PROP_PT] = g_param_spec_uint ("pt", "pt",
588       "FEC packets payload type", 0, 127,
589       DEFAULT_FEC_PT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
590   klass_properties[PROP_STORAGE] =
591       g_param_spec_object ("storage", "RTP storage", "RTP storage",
592       G_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
593   klass_properties[PROP_RECOVERED] =
594       g_param_spec_uint ("recovered", "recovered",
595       "The number of recovered packets", 0, G_MAXUINT, 0,
596       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
597   klass_properties[PROP_UNRECOVERED] =
598       g_param_spec_uint ("unrecovered", "unrecovered",
599       "The number of unrecovered packets", 0, G_MAXUINT, 0,
600       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
601
602   g_object_class_install_properties (gobject_class, N_PROPERTIES,
603       klass_properties);
604 }