rtsp-server: fix memory leak
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / gst / rtp / gstrtpreddec.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-rtpreddec
23  * @short_description: RTP Redundant Audio Data (RED) decoder
24  * @title: rtpreddec
25  *
26  * Decode Redundant Audio Data (RED) as per RFC 2198.
27  *
28  * This element is mostly provided for chrome webrtc compatibility:
29  * chrome will wrap ulpfec-protected streams in RED packets, and such
30  * streams need to be unwrapped by this element before being passed on
31  * to #GstRtpUlpFecDec.
32  *
33  * The #GstRtpRedDec:pt property should be set to the expected payload
34  * types of the RED packets.
35  *
36  * When using #GstRtpBin, this element should be inserted through the
37  * #GstRtpBin::request-aux-receiver signal.
38  *
39  * ## Example pipeline
40  *
41  * |[
42  * gst-launch-1.0 udpsrc port=8888 caps="application/x-rtp, payload=96, clock-rate=90000" ! rtpreddec pt=122 ! rtpstorage size-time=220000000 ! rtpssrcdemux ! application/x-rtp, payload=96, clock-rate=90000, media=video, encoding-name=H264 ! rtpjitterbuffer do-lost=1 latency=200 !  rtpulpfecdec pt=122 ! rtph264depay ! avdec_h264 ! videoconvert ! autovideosink
43  * ]| This example will receive a stream with RED and ULP FEC and try to reconstruct the packets.
44  *
45  * See also: #GstRtpRedEnc, #GstWebRTCBin, #GstRtpBin
46  * Since: 1.14
47  */
48
49 #include <gst/rtp/gstrtpbuffer.h>
50
51 #include "gstrtpelements.h"
52 #include "rtpredcommon.h"
53 #include "gstrtpreddec.h"
54 #include "rtpulpfeccommon.h"
55
56 #define RTP_HISTORY_MAX_SIZE (16)
57
58 typedef struct
59 {
60   guint32 timestamp;
61   guint16 seq;
62 } RTPHistItem;
63
64 #define RTP_HIST_ITEM_TIMESTAMP(p) ((RTPHistItem *)p)->timestamp
65 #define RTP_HIST_ITEM_SEQ(p) ((RTPHistItem *)p)->seq
66
67 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
68     GST_PAD_SINK,
69     GST_PAD_ALWAYS,
70     GST_STATIC_CAPS ("application/x-rtp"));
71
72 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
73     GST_PAD_SRC,
74     GST_PAD_ALWAYS,
75     GST_STATIC_CAPS ("application/x-rtp"));
76
77 #define UNDEF_PT                -1
78 #define MIN_PT                  UNDEF_PT
79 #define MAX_PT                  127
80 #define DEFAULT_PT              UNDEF_PT
81
82 GST_DEBUG_CATEGORY_STATIC (gst_rtp_red_dec_debug);
83 #define GST_CAT_DEFAULT gst_rtp_red_dec_debug
84
85 G_DEFINE_TYPE (GstRtpRedDec, gst_rtp_red_dec, GST_TYPE_ELEMENT);
86 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpreddec, "rtpreddec", GST_RANK_NONE,
87     GST_TYPE_RTP_RED_DEC, rtp_element_init (plugin));
88
89 enum
90 {
91   PROP_0,
92   PROP_PT,
93   PROP_RECEIVED,
94   PROP_PAYLOADS,
95 };
96
97 static RTPHistItem *
98 rtp_hist_item_alloc (void)
99 {
100   return g_slice_new (RTPHistItem);
101 }
102
103 static void
104 rtp_hist_item_free (gpointer item)
105 {
106   g_slice_free (RTPHistItem, item);
107 }
108
109 static gint
110 gst_rtp_red_history_find_less_or_equal (gconstpointer item,
111     gconstpointer timestamp)
112 {
113   guint32 t = GPOINTER_TO_UINT (timestamp);
114   gint32 diff = t - RTP_HIST_ITEM_TIMESTAMP (item);
115   return diff < 0;
116 }
117
118 static gint
119 gst_rtp_red_history_find_less (gconstpointer item, gconstpointer timestamp)
120 {
121   guint32 t = GPOINTER_TO_UINT (timestamp);
122   gint32 diff = t - RTP_HIST_ITEM_TIMESTAMP (item);
123   return diff <= 0;
124 }
125
126 static void
127 gst_rtp_red_history_update (GstRtpRedDec * self, GQueue * rtp_history,
128     GstRTPBuffer * rtp)
129 {
130   RTPHistItem *item;
131   GList *link, *sibling;
132
133   /* If we have not reached MAX number of elements in the history,
134    * allocate a new link and a new item,
135    * otherwise reuse the tail (the oldest data) without any reallocations
136    */
137   if (rtp_history->length < RTP_HISTORY_MAX_SIZE) {
138     item = rtp_hist_item_alloc ();
139     link = g_list_alloc ();
140     link->data = item;
141   } else {
142     link = g_queue_pop_tail_link (rtp_history);
143     item = link->data;
144   }
145
146   item->timestamp = gst_rtp_buffer_get_timestamp (rtp);
147   item->seq = gst_rtp_buffer_get_seq (rtp);
148
149   /* Looking for a place to insert new link.
150    * The queue has newest to oldest rtp timestamps, so in 99% cases
151    * it is inserted before the head of the queue */
152   sibling = g_list_find_custom (rtp_history->head,
153       GUINT_TO_POINTER (item->timestamp),
154       gst_rtp_red_history_find_less_or_equal);
155   g_queue_push_nth_link (rtp_history,
156       g_list_position (rtp_history->head, sibling), link);
157 }
158
159 static gboolean
160 rtp_red_buffer_is_valid (GstRtpRedDec * self, GstRTPBuffer * red_rtp,
161     gsize * dst_first_red_payload_offset)
162 {
163   guint8 *payload = gst_rtp_buffer_get_payload (red_rtp);
164   gsize payload_len = gst_rtp_buffer_get_payload_len (red_rtp);
165   gsize red_hdrs_offset = 0;
166   guint red_hdrs_checked = 0;
167   guint redundant_payload_len = 0;
168
169   while (TRUE) {
170     gpointer red_hdr = payload + red_hdrs_offset;
171     gsize red_hdr_len;
172     gboolean is_redundant;
173
174     ++red_hdrs_checked;
175
176     /* Can we address the first byte where F bit is located ? */
177     if (red_hdrs_offset + 1 > payload_len)
178       goto red_buffer_invalid;
179
180     is_redundant = rtp_red_block_is_redundant (red_hdr);
181
182     /* Is it the last block? */
183     if (is_redundant) {
184       red_hdr_len = rtp_red_block_header_get_length (TRUE);
185
186       /* Can we address all the other bytes in RED block header? */
187       if (red_hdrs_offset + red_hdr_len > payload_len)
188         goto red_buffer_invalid;
189
190       redundant_payload_len += rtp_red_block_get_payload_length (red_hdr);
191       red_hdrs_offset += red_hdr_len;
192     } else {
193       red_hdr_len = rtp_red_block_header_get_length (FALSE);
194       red_hdrs_offset += red_hdr_len;
195       break;
196     }
197   }
198
199   /* Do we have enough data to create redundant packets & main packet. Keep in
200    * mind that redundant_payload_len contains the length of redundant packets only.
201    */
202   if (red_hdrs_offset + redundant_payload_len >= payload_len)
203     goto red_buffer_invalid;
204
205   *dst_first_red_payload_offset = red_hdrs_offset;
206
207   GST_LOG_OBJECT (self, "RED packet has %u blocks", red_hdrs_checked);
208   return TRUE;
209
210 red_buffer_invalid:
211   GST_WARNING_OBJECT (self, "Received invalid RED packet "
212       "ssrc=0x%08x pt=%u tstamp=%u seq=%u size=%u, "
213       "checked %u blocks",
214       gst_rtp_buffer_get_ssrc (red_rtp),
215       gst_rtp_buffer_get_payload_type (red_rtp),
216       gst_rtp_buffer_get_timestamp (red_rtp),
217       gst_rtp_buffer_get_seq (red_rtp),
218       gst_rtp_buffer_get_packet_len (red_rtp), red_hdrs_checked);
219   return FALSE;
220 }
221
222 static gboolean
223 gst_red_history_lost_seq_num_for_timestamp (GstRtpRedDec * self,
224     GQueue * rtp_history, guint32 timestamp, guint16 * dst_seq_num)
225 {
226   GList *older_sibling = g_list_find_custom (rtp_history->head,
227       GUINT_TO_POINTER (timestamp),
228       gst_rtp_red_history_find_less);
229   RTPHistItem *older;
230   RTPHistItem *newer;
231   guint32 timestamp_diff;
232   gint seq_diff, lost_packet_idx;
233
234   if (NULL == older_sibling) {
235     if (rtp_history->length == RTP_HISTORY_MAX_SIZE)
236       GST_WARNING_OBJECT (self, "History is too short. "
237           "Oldest rtp timestamp %u, looking for %u, size %u",
238           RTP_HIST_ITEM_TIMESTAMP (rtp_history->tail->data),
239           timestamp, rtp_history->length);
240     return FALSE;
241   }
242
243   if (NULL == older_sibling->prev) {
244     GST_WARNING_OBJECT (self, "RED block timestamp offset probably wrong. "
245         "Latest rtp timestamp %u, looking for %u, size %u",
246         RTP_HIST_ITEM_TIMESTAMP (rtp_history->head->data),
247         timestamp, rtp_history->length);
248     return FALSE;
249   }
250
251   older = older_sibling->data;
252   newer = older_sibling->prev->data;
253   /* We know for sure @older has lower timestamp than we are looking for,
254    * if @newer has the same timestamp, there is no packet loss and we
255    * don't need to use redundant data */
256   if (newer->timestamp == timestamp)
257     return FALSE;
258
259   seq_diff = gst_rtp_buffer_compare_seqnum (older->seq, newer->seq);
260   if (seq_diff <= 1) {
261     if (seq_diff == 1)
262       GST_WARNING_OBJECT (self, "RED block timestamp offset is wrong: "
263           "#%u,%u #%u,%u looking for %u",
264           older->seq, older->timestamp,
265           newer->seq, newer->timestamp, timestamp);
266     else
267       GST_WARNING_OBJECT (self, "RTP timestamps increasing while "
268           "sequence numbers decreasing: #%u,%u #%u,%u",
269           older->seq, older->timestamp, newer->seq, newer->timestamp);
270     return FALSE;
271   }
272
273   timestamp_diff = newer->timestamp - older->timestamp;
274   for (lost_packet_idx = 1; lost_packet_idx < seq_diff; ++lost_packet_idx) {
275     guint32 lost_timestamp = older->timestamp +
276         lost_packet_idx * timestamp_diff / seq_diff;
277     if (lost_timestamp == timestamp) {
278       *dst_seq_num = older->seq + lost_packet_idx;
279       return TRUE;
280     }
281   }
282
283   GST_WARNING_OBJECT (self, "Can't find RED block timestamp "
284       "#%u,%u #%u,%u looking for %u",
285       older->seq, older->timestamp, newer->seq, newer->timestamp, timestamp);
286   return FALSE;
287 }
288
289 static GstBuffer *
290 gst_rtp_red_create_packet (GstRtpRedDec * self, GstRTPBuffer * red_rtp,
291     gboolean marker, guint8 pt, guint16 seq_num, guint32 timestamp,
292     gsize red_payload_subbuffer_start, gsize red_payload_subbuffer_len)
293 {
294   guint csrc_count = gst_rtp_buffer_get_csrc_count (red_rtp);
295   GstBuffer *ret = gst_rtp_buffer_new_allocate (0, 0, csrc_count);
296   GstRTPBuffer ret_rtp = GST_RTP_BUFFER_INIT;
297   guint i;
298   if (!gst_rtp_buffer_map (ret, GST_MAP_WRITE, &ret_rtp))
299     g_assert_not_reached ();
300
301   gst_rtp_buffer_set_marker (&ret_rtp, marker);
302   gst_rtp_buffer_set_payload_type (&ret_rtp, pt);
303   gst_rtp_buffer_set_seq (&ret_rtp, seq_num);
304   gst_rtp_buffer_set_timestamp (&ret_rtp, timestamp);
305   gst_rtp_buffer_set_ssrc (&ret_rtp, gst_rtp_buffer_get_ssrc (red_rtp));
306   for (i = 0; i < csrc_count; ++i)
307     gst_rtp_buffer_set_csrc (&ret_rtp, i, gst_rtp_buffer_get_csrc (red_rtp, i));
308   gst_rtp_buffer_unmap (&ret_rtp);
309
310   ret = gst_buffer_append (ret,
311       gst_rtp_buffer_get_payload_subbuffer (red_rtp,
312           red_payload_subbuffer_start, red_payload_subbuffer_len));
313
314   /* Timestamps, meta, flags from the RED packet should go to main block packet */
315   gst_buffer_copy_into (ret, red_rtp->buffer, GST_BUFFER_COPY_METADATA, 0, -1);
316   if (marker)
317     GST_BUFFER_FLAG_SET (ret, GST_BUFFER_FLAG_MARKER);
318   return ret;
319 }
320
321 static GstBuffer *
322 gst_rtp_red_create_from_redundant_block (GstRtpRedDec * self,
323     GQueue * rtp_history, GstRTPBuffer * red_rtp, gsize * red_hdr_offset,
324     gsize * red_payload_offset)
325 {
326   guint8 *payload = gst_rtp_buffer_get_payload (red_rtp);
327   guint8 *red_hdr = payload + *red_hdr_offset;
328   guint32 lost_timestamp = gst_rtp_buffer_get_timestamp (red_rtp) -
329       rtp_red_block_get_timestamp_offset (red_hdr);
330
331   GstBuffer *ret = NULL;
332   guint16 lost_seq = 0;
333   if (gst_red_history_lost_seq_num_for_timestamp (self, rtp_history,
334           lost_timestamp, &lost_seq)) {
335     GST_LOG_OBJECT (self,
336         "Recovering from RED packet pt=%u ts=%u seq=%u" " len=%u present",
337         rtp_red_block_get_payload_type (red_hdr), lost_timestamp, lost_seq,
338         rtp_red_block_get_payload_length (red_hdr));
339     ret =
340         gst_rtp_red_create_packet (self, red_rtp, FALSE,
341         rtp_red_block_get_payload_type (red_hdr), lost_seq, lost_timestamp,
342         *red_payload_offset, rtp_red_block_get_payload_length (red_hdr));
343     GST_BUFFER_FLAG_SET (ret, GST_RTP_BUFFER_FLAG_REDUNDANT);
344   } else {
345     GST_LOG_OBJECT (self, "Ignore RED packet pt=%u ts=%u len=%u because already"
346         " present", rtp_red_block_get_payload_type (red_hdr), lost_timestamp,
347         rtp_red_block_get_payload_length (red_hdr));
348   }
349
350   *red_hdr_offset += rtp_red_block_header_get_length (TRUE);
351   *red_payload_offset += rtp_red_block_get_payload_length (red_hdr);
352   return ret;
353 }
354
355 static GstBuffer *
356 gst_rtp_red_create_from_main_block (GstRtpRedDec * self,
357     GstRTPBuffer * red_rtp, gsize red_hdr_offset, gsize * red_payload_offset)
358 {
359   guint8 *payload = gst_rtp_buffer_get_payload (red_rtp);
360   GstBuffer *ret = gst_rtp_red_create_packet (self, red_rtp,
361       gst_rtp_buffer_get_marker (red_rtp),
362       rtp_red_block_get_payload_type (payload + red_hdr_offset),
363       gst_rtp_buffer_get_seq (red_rtp),
364       gst_rtp_buffer_get_timestamp (red_rtp),
365       *red_payload_offset, -1);
366   *red_payload_offset = gst_rtp_buffer_get_payload_len (red_rtp);
367   GST_LOG_OBJECT (self, "Extracting main payload from RED pt=%u seq=%u ts=%u"
368       " marker=%u", rtp_red_block_get_payload_type (payload + red_hdr_offset),
369       gst_rtp_buffer_get_seq (red_rtp), gst_rtp_buffer_get_timestamp (red_rtp),
370       gst_rtp_buffer_get_marker (red_rtp));
371
372   return ret;
373 }
374
375 static GstBuffer *
376 gst_rtp_red_create_from_block (GstRtpRedDec * self, GQueue * rtp_history,
377     GstRTPBuffer * red_rtp, gsize * red_hdr_offset, gsize * red_payload_offset)
378 {
379   guint8 *payload = gst_rtp_buffer_get_payload (red_rtp);
380
381   if (rtp_red_block_is_redundant (payload + (*red_hdr_offset)))
382     return gst_rtp_red_create_from_redundant_block (self, rtp_history, red_rtp,
383         red_hdr_offset, red_payload_offset);
384
385   return gst_rtp_red_create_from_main_block (self, red_rtp, *red_hdr_offset,
386       red_payload_offset);
387 }
388
389 static GstFlowReturn
390 gst_rtp_red_process (GstRtpRedDec * self, GQueue * rtp_history,
391     GstRTPBuffer * red_rtp, gsize first_red_payload_offset)
392 {
393   gsize red_hdr_offset = 0;
394   gsize red_payload_offset = first_red_payload_offset;
395   gsize payload_len = gst_rtp_buffer_get_payload_len (red_rtp);
396   GstFlowReturn ret = GST_FLOW_OK;
397
398   do {
399     GstBuffer *buf = gst_rtp_red_create_from_block (self, rtp_history, red_rtp,
400         &red_hdr_offset,
401         &red_payload_offset);
402     if (buf)
403       ret = gst_pad_push (self->srcpad, buf);
404   } while (GST_FLOW_OK == ret && red_payload_offset < payload_len);
405
406   return ret;
407 }
408
409 static gboolean
410 is_red_pt (GstRtpRedDec * self, guint8 pt)
411 {
412   gboolean ret;
413
414   g_mutex_lock (&self->lock);
415   if (pt == self->pt) {
416     ret = TRUE;
417     goto done;
418   }
419
420   ret = self->payloads
421       && g_hash_table_contains (self->payloads, GINT_TO_POINTER (pt));
422
423 done:
424   g_mutex_unlock (&self->lock);
425   return ret;
426 }
427
428 static GstFlowReturn
429 gst_rtp_red_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
430 {
431   GstRtpRedDec *self = GST_RTP_RED_DEC (parent);
432   GstRTPBuffer irtp = GST_RTP_BUFFER_INIT;
433   GstFlowReturn ret = GST_FLOW_OK;
434   gsize first_red_payload_offset = 0;
435   GQueue *rtp_history;
436   guint32 ssrc;
437
438   if (self->pt == UNDEF_PT && self->payloads == NULL)
439     return gst_pad_push (self->srcpad, buffer);
440
441   if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &irtp))
442     return gst_pad_push (self->srcpad, buffer);
443
444   ssrc = gst_rtp_buffer_get_ssrc (&irtp);
445
446   if (!(rtp_history =
447           g_hash_table_lookup (self->rtp_histories, GUINT_TO_POINTER (ssrc)))) {
448     rtp_history = g_queue_new ();
449     g_hash_table_insert (self->rtp_histories, GUINT_TO_POINTER (ssrc),
450         rtp_history);
451   }
452
453   gst_rtp_red_history_update (self, rtp_history, &irtp);
454
455   if (!is_red_pt (self, gst_rtp_buffer_get_payload_type (&irtp))) {
456     GST_LOG_RTP_PACKET (self, "rtp header (incoming)", &irtp);
457
458     gst_rtp_buffer_unmap (&irtp);
459     return gst_pad_push (self->srcpad, buffer);
460   }
461
462   self->num_received++;
463
464   if (rtp_red_buffer_is_valid (self, &irtp, &first_red_payload_offset)) {
465     GST_DEBUG_RTP_PACKET (self, "rtp header (red)", &irtp);
466     ret =
467         gst_rtp_red_process (self, rtp_history, &irtp,
468         first_red_payload_offset);
469   }
470
471   gst_rtp_buffer_unmap (&irtp);
472   gst_buffer_unref (buffer);
473   return ret;
474 }
475
476 static void
477 gst_rtp_red_dec_dispose (GObject * obj)
478 {
479   GstRtpRedDec *self = GST_RTP_RED_DEC (obj);
480
481   g_hash_table_unref (self->rtp_histories);
482
483   if (self->payloads) {
484     g_hash_table_unref (self->payloads);
485   }
486
487   g_mutex_clear (&self->lock);
488
489   G_OBJECT_CLASS (gst_rtp_red_dec_parent_class)->dispose (obj);
490 }
491
492 static void
493 free_rtp_history (GQueue * rtp_history)
494 {
495   g_queue_free_full (rtp_history, rtp_hist_item_free);
496 }
497
498 static void
499 gst_rtp_red_dec_init (GstRtpRedDec * self)
500 {
501   GstPadTemplate *pad_template;
502
503   pad_template =
504       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src");
505   self->srcpad = gst_pad_new_from_template (pad_template, "src");
506   gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
507
508   pad_template =
509       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "sink");
510   self->sinkpad = gst_pad_new_from_template (pad_template, "sink");
511   gst_pad_set_chain_function (self->sinkpad,
512       GST_DEBUG_FUNCPTR (gst_rtp_red_dec_chain));
513   GST_PAD_SET_PROXY_CAPS (self->sinkpad);
514   GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
515   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
516
517   self->pt = DEFAULT_PT;
518   self->num_received = 0;
519   self->rtp_histories =
520       g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
521       (GDestroyNotify) free_rtp_history);
522   self->payloads = NULL;
523   g_mutex_init (&self->lock);
524 }
525
526 static void
527 gst_rtp_red_dec_set_property (GObject * object, guint prop_id,
528     const GValue * value, GParamSpec * pspec)
529 {
530   GstRtpRedDec *self = GST_RTP_RED_DEC (object);
531
532   switch (prop_id) {
533     case PROP_PT:
534       g_mutex_lock (&self->lock);
535       self->pt = g_value_get_int (value);
536       g_mutex_unlock (&self->lock);
537       break;
538     case PROP_PAYLOADS:
539     {
540       guint i, n_vals;
541
542       g_mutex_lock (&self->lock);
543       if (self->payloads) {
544         g_hash_table_unref (self->payloads);
545         self->payloads = NULL;
546       }
547
548       n_vals = gst_value_array_get_size (value);
549
550       if (n_vals > 0) {
551         self->payloads = g_hash_table_new (g_direct_hash, g_direct_equal);
552
553         for (i = 0; i < gst_value_array_get_size (value); i++) {
554           const GValue *val = gst_value_array_get_value (value, i);
555
556           g_hash_table_insert (self->payloads,
557               GINT_TO_POINTER (g_value_get_int (val)), NULL);
558         }
559       }
560       g_mutex_unlock (&self->lock);
561       break;
562     }
563     default:
564       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
565       break;
566   }
567 }
568
569 static void
570 append_payload (gpointer key, gpointer value, GValue * array)
571 {
572   GValue v = { 0, };
573   g_value_init (&v, G_TYPE_INT);
574   g_value_set_int (&v, GPOINTER_TO_INT (key));
575   gst_value_array_append_value (array, &v);
576   g_value_unset (&v);
577 }
578
579 static void
580 gst_rtp_red_dec_get_property (GObject * object, guint prop_id,
581     GValue * value, GParamSpec * pspec)
582 {
583   GstRtpRedDec *self = GST_RTP_RED_DEC (object);
584   switch (prop_id) {
585     case PROP_PT:
586       g_mutex_lock (&self->lock);
587       g_value_set_int (value, self->pt);
588       g_mutex_unlock (&self->lock);
589       break;
590     case PROP_RECEIVED:
591       g_value_set_uint (value, self->num_received);
592       break;
593     case PROP_PAYLOADS:
594     {
595       g_mutex_lock (&self->lock);
596       if (self->payloads) {
597         g_hash_table_foreach (self->payloads, (GHFunc) append_payload, value);
598       }
599       g_mutex_unlock (&self->lock);
600       break;
601     }
602     default:
603       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
604       break;
605   }
606 }
607
608 static void
609 gst_rtp_red_dec_class_init (GstRtpRedDecClass * klass)
610 {
611   GObjectClass *gobject_class;
612   GstElementClass *element_class;
613
614   gobject_class = G_OBJECT_CLASS (klass);
615   element_class = GST_ELEMENT_CLASS (klass);
616
617   gst_element_class_add_pad_template (element_class,
618       gst_static_pad_template_get (&src_template));
619   gst_element_class_add_pad_template (element_class,
620       gst_static_pad_template_get (&sink_template));
621
622   gst_element_class_set_metadata (element_class,
623       "Redundant Audio Data (RED) Decoder",
624       "Codec/Depayloader/Network/RTP",
625       "Decode Redundant Audio Data (RED)",
626       "Hani Mustafa <hani@pexip.com>, Mikhail Fludkov <misha@pexip.com>");
627
628   gobject_class->set_property =
629       GST_DEBUG_FUNCPTR (gst_rtp_red_dec_set_property);
630   gobject_class->get_property =
631       GST_DEBUG_FUNCPTR (gst_rtp_red_dec_get_property);
632   gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_rtp_red_dec_dispose);
633
634   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PT,
635       g_param_spec_int ("pt", "payload type",
636           "Payload type FEC packets",
637           MIN_PT, MAX_PT, DEFAULT_PT,
638           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
639
640   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RECEIVED,
641       g_param_spec_uint ("received", "Received",
642           "Count of received packets",
643           0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
644
645   /**
646    * rtpreddec:payloads:
647    *
648    * All the RED payloads this decoder may encounter
649    *
650    * Since: 1.20
651    */
652   g_object_class_install_property (G_OBJECT_CLASS (klass),
653       PROP_PAYLOADS,
654       gst_param_spec_array ("payloads",
655           "RED payloads",
656           "All the RED payloads this decoder may encounter",
657           g_param_spec_int ("pt",
658               "payload type",
659               "A RED payload type",
660               MIN_PT, MAX_PT,
661               DEFAULT_PT,
662               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS),
663           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
664       );
665
666   GST_DEBUG_CATEGORY_INIT (gst_rtp_red_dec_debug, "rtpreddec", 0,
667       "RTP RED Decoder");
668 }