webrtc/nice: support consent-freshness RFC7675
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / gst / rtp / gstrtpredenc.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-rtpredenc
23  * @short_description: RTP Redundant Audio Data (RED) encoder
24  * @title: rtpredenc
25  *
26  * Encode Redundant Audio Data (RED) as per RFC 2198.
27  *
28  * This element is mostly provided for chrome webrtc compatibility:
29  * chrome expects protection packets generated by #GstRtpUlpFecEnc
30  * to be wrapped in RED packets for backward compatibility purposes,
31  * but does not actually make use of the redundant packets that could
32  * be encoded with this element.
33  *
34  * As such, when used for that purpose, only the #GstRtpRedEnc:pt property
35  * should be set to a payload type different from both the protected and
36  * protection packets' payload types.
37  *
38  * When using #GstRtpBin, this element should be inserted through the
39  * #GstRtpBin::request-fec-encoder signal.
40  *
41  * ## Example pipeline
42  *
43  * |[
44  * gst-launch-1.0 videotestsrc ! x264enc ! video/x-h264, profile=baseline ! rtph264pay pt=96 ! rtpulpfecenc percentage=100 pt=122 ! rtpredenc pt=122 distance=2 ! identity drop-probability=0.05 ! udpsink port=8888
45  * ]| This example will send a stream with RED and ULP FEC.
46  *
47  * See also: #GstRtpRedDec, #GstWebRTCBin, #GstRtpBin
48  * Since: 1.14
49  */
50
51 #include <gst/rtp/gstrtpbuffer.h>
52 #include <string.h>
53 #include <stdio.h>
54
55 #include "gstrtpelements.h"
56 #include "rtpredcommon.h"
57 #include "gstrtpredenc.h"
58
59 typedef struct
60 {
61   guint8 pt;
62   guint32 timestamp;
63   GstBuffer *payload;
64 } RTPHistItem;
65
66 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
67     GST_PAD_SINK,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS ("application/x-rtp"));
70
71 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
72     GST_PAD_SRC,
73     GST_PAD_ALWAYS,
74     GST_STATIC_CAPS ("application/x-rtp"));
75
76 #define DEFAULT_PT                  (0)
77 #define DEFAULT_DISTANCE            (0)
78 #define DEFAULT_ALLOW_NO_RED_BLOCKS (TRUE)
79
80 GST_DEBUG_CATEGORY_STATIC (gst_rtp_red_enc_debug);
81 #define GST_CAT_DEFAULT (gst_rtp_red_enc_debug)
82 G_DEFINE_TYPE (GstRtpRedEnc, gst_rtp_red_enc, GST_TYPE_ELEMENT);
83 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpredenc, "rtpredenc", GST_RANK_NONE,
84     GST_TYPE_RTP_RED_ENC, rtp_element_init (plugin));
85
86 enum
87 {
88   PROP_0,
89   PROP_PT,
90   PROP_SENT,
91   PROP_DISTANCE,
92   PROP_ALLOW_NO_RED_BLOCKS
93 };
94
95 static void
96 rtp_hist_item_init (RTPHistItem * item, GstRTPBuffer * rtp,
97     GstBuffer * rtp_payload)
98 {
99   item->pt = gst_rtp_buffer_get_payload_type (rtp);
100   item->timestamp = gst_rtp_buffer_get_timestamp (rtp);
101   item->payload = rtp_payload;
102 }
103
104 static RTPHistItem *
105 rtp_hist_item_new (GstRTPBuffer * rtp, GstBuffer * rtp_payload)
106 {
107   RTPHistItem *item = g_slice_new0 (RTPHistItem);
108   rtp_hist_item_init (item, rtp, rtp_payload);
109   return item;
110 }
111
112 static void
113 rtp_hist_item_replace (RTPHistItem * item, GstRTPBuffer * rtp,
114     GstBuffer * rtp_payload)
115 {
116   gst_buffer_unref (item->payload);
117   rtp_hist_item_init (item, rtp, rtp_payload);
118 }
119
120 static void
121 rtp_hist_item_free (gpointer _item)
122 {
123   RTPHistItem *item = _item;
124   gst_buffer_unref (item->payload);
125   g_slice_free (RTPHistItem, item);
126 }
127
128 static GstEvent *
129 _create_caps_event (const GstCaps * caps, guint8 pt)
130 {
131   GstEvent *ret;
132   GstCaps *new = gst_caps_copy (caps);
133   GstStructure *s = gst_caps_get_structure (new, 0);
134   gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
135   GST_INFO ("sinkcaps %" GST_PTR_FORMAT ", srccaps %" GST_PTR_FORMAT,
136       caps, new);
137   ret = gst_event_new_caps (new);
138   gst_caps_unref (new);
139   return ret;
140 }
141
142 static GstBuffer *
143 _alloc_red_packet_and_fill_headers (GstRtpRedEnc * self,
144     RTPHistItem * redundant_block, GstRTPBuffer * inp_rtp)
145 {
146   guint red_header_size = rtp_red_block_header_get_length (FALSE) +
147       (redundant_block ? rtp_red_block_header_get_length (TRUE) : 0);
148
149   guint32 timestamp = gst_rtp_buffer_get_timestamp (inp_rtp);
150   guint csrc_count = gst_rtp_buffer_get_csrc_count (inp_rtp);
151   GstBuffer *red = gst_rtp_buffer_new_allocate (red_header_size, 0, csrc_count);
152   guint8 *red_block_header;
153   GstRTPBuffer red_rtp = GST_RTP_BUFFER_INIT;
154   guint i;
155
156   if (!gst_rtp_buffer_map (red, GST_MAP_WRITE, &red_rtp))
157     g_assert_not_reached ();
158
159   /* Copying RTP header of incoming packet */
160   if (gst_rtp_buffer_get_extension (inp_rtp)
161       && !self->ignoring_extension_warned) {
162     GST_FIXME_OBJECT (self, "Ignoring RTP extension");
163     self->ignoring_extension_warned = TRUE;
164   }
165
166   gst_rtp_buffer_set_marker (&red_rtp, gst_rtp_buffer_get_marker (inp_rtp));
167   gst_rtp_buffer_set_payload_type (&red_rtp, self->pt);
168   gst_rtp_buffer_set_seq (&red_rtp, gst_rtp_buffer_get_seq (inp_rtp));
169   gst_rtp_buffer_set_timestamp (&red_rtp, timestamp);
170   gst_rtp_buffer_set_ssrc (&red_rtp, gst_rtp_buffer_get_ssrc (inp_rtp));
171   for (i = 0; i != csrc_count; ++i)
172     gst_rtp_buffer_set_csrc (&red_rtp, i,
173         gst_rtp_buffer_get_csrc ((inp_rtp), i));
174
175   /* Filling RED block headers */
176   red_block_header = gst_rtp_buffer_get_payload (&red_rtp);
177   if (redundant_block) {
178     rtp_red_block_set_is_redundant (red_block_header, TRUE);
179     rtp_red_block_set_payload_type (red_block_header, redundant_block->pt);
180     rtp_red_block_set_timestamp_offset (red_block_header,
181         timestamp - redundant_block->timestamp);
182     rtp_red_block_set_payload_length (red_block_header,
183         gst_buffer_get_size (redundant_block->payload));
184
185     red_block_header += rtp_red_block_header_get_length (TRUE);
186   }
187   rtp_red_block_set_is_redundant (red_block_header, FALSE);
188   rtp_red_block_set_payload_type (red_block_header,
189       gst_rtp_buffer_get_payload_type (inp_rtp));
190
191   /* FIXME: remove that logic once https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/923
192    * has been addressed. */
193   if (self->twcc_ext_id != 0) {
194     guint8 appbits;
195     gpointer inp_data;
196     guint inp_size;
197     guint16 data;
198
199     /* If the input buffer was meant to hold a TWCC seqnum, we also do that
200      * for our wrapper */
201     if (gst_rtp_buffer_get_extension_onebyte_header (inp_rtp, self->twcc_ext_id,
202             0, &inp_data, &inp_size)) {
203       gst_rtp_buffer_add_extension_onebyte_header (&red_rtp, self->twcc_ext_id,
204           &data, sizeof (guint16));
205     } else if (gst_rtp_buffer_get_extension_twobytes_header (inp_rtp, &appbits,
206             self->twcc_ext_id, 0, &inp_data, &inp_size)) {
207       gst_rtp_buffer_add_extension_twobytes_header (&red_rtp, appbits,
208           self->twcc_ext_id, &data, sizeof (guint16));
209     }
210   }
211
212   gst_rtp_buffer_unmap (&red_rtp);
213
214   gst_buffer_copy_into (red, inp_rtp->buffer, GST_BUFFER_COPY_METADATA, 0, -1);
215   return red;
216 }
217
218 static GstBuffer *
219 _create_red_packet (GstRtpRedEnc * self,
220     GstRTPBuffer * rtp, RTPHistItem * redundant_block, GstBuffer * main_block)
221 {
222   GstBuffer *red =
223       _alloc_red_packet_and_fill_headers (self, redundant_block, rtp);
224   if (redundant_block)
225     red = gst_buffer_append (red, gst_buffer_ref (redundant_block->payload));
226   red = gst_buffer_append (red, gst_buffer_ref (main_block));
227   return red;
228 }
229
230 static RTPHistItem *
231 _red_history_get_redundant_block (GstRtpRedEnc * self,
232     guint32 current_timestamp, guint distance)
233 {
234   RTPHistItem *item;
235   gint32 timestamp_offset;
236
237   if (0 == distance || 0 == self->rtp_history->length)
238     return NULL;
239
240   item = self->rtp_history->tail->data;
241   timestamp_offset = current_timestamp - item->timestamp;
242   if (G_UNLIKELY (timestamp_offset > RED_BLOCK_TIMESTAMP_OFFSET_MAX)) {
243     GST_WARNING_OBJECT (self,
244         "Can't create redundant block with distance %u, "
245         "timestamp offset is too large %d (%u - %u) > %u",
246         distance, timestamp_offset, current_timestamp, item->timestamp,
247         RED_BLOCK_TIMESTAMP_OFFSET_MAX);
248     return NULL;
249   }
250
251   if (G_UNLIKELY (timestamp_offset < 0)) {
252     GST_WARNING_OBJECT (self,
253         "Can't create redundant block with distance %u, "
254         "timestamp offset is negative %d (%u - %u)",
255         distance, timestamp_offset, current_timestamp, item->timestamp);
256     return NULL;
257   }
258
259   if (G_UNLIKELY (gst_buffer_get_size (item->payload) > RED_BLOCK_LENGTH_MAX)) {
260     GST_WARNING_OBJECT (self,
261         "Can't create redundant block with distance %u, "
262         "red block is too large %u > %u",
263         distance, (guint) gst_buffer_get_size (item->payload),
264         RED_BLOCK_LENGTH_MAX);
265     return NULL;
266   }
267
268   /* _red_history_trim should take care it never happens */
269   g_assert_cmpint (self->rtp_history->length, <=, distance);
270
271   if (G_UNLIKELY (self->rtp_history->length < distance))
272     GST_DEBUG_OBJECT (self,
273         "Don't have enough buffers yet, "
274         "adding redundant block with distance %u and timestamp %u",
275         self->rtp_history->length, item->timestamp);
276   return item;
277 }
278
279 static void
280 _red_history_prepend (GstRtpRedEnc * self,
281     GstRTPBuffer * rtp, GstBuffer * rtp_payload, guint max_history_length)
282 {
283   GList *link;
284
285   if (0 == max_history_length) {
286     if (rtp_payload)
287       gst_buffer_unref (rtp_payload);
288     return;
289   }
290
291   g_assert (NULL != rtp_payload);
292
293   if (self->rtp_history->length >= max_history_length) {
294     link = g_queue_pop_tail_link (self->rtp_history);
295     rtp_hist_item_replace (link->data, rtp, rtp_payload);
296   } else {
297     link = g_list_alloc ();
298     link->data = rtp_hist_item_new (rtp, rtp_payload);
299   }
300   g_queue_push_head_link (self->rtp_history, link);
301 }
302
303 static void
304 _red_history_trim (GstRtpRedEnc * self, guint max_history_length)
305 {
306   while (max_history_length < self->rtp_history->length)
307     rtp_hist_item_free (g_queue_pop_tail (self->rtp_history));
308 }
309
310 static GstFlowReturn
311 _pad_push (GstRtpRedEnc * self, GstBuffer * buffer, gboolean is_red)
312 {
313   if (self->send_caps || is_red != self->is_current_caps_red) {
314     GstEvent *event;
315     GstCaps *caps = gst_pad_get_current_caps (self->sinkpad);
316     if (is_red)
317       event = _create_caps_event (caps, self->pt);
318     else
319       event = gst_event_new_caps (caps);
320     gst_caps_unref (caps);
321
322     gst_pad_push_event (self->srcpad, event);
323     self->send_caps = FALSE;
324     self->is_current_caps_red = is_red;
325   }
326   return gst_pad_push (self->srcpad, buffer);
327 }
328
329 static GstFlowReturn
330 _push_nonred_packet (GstRtpRedEnc * self,
331     GstRTPBuffer * rtp, GstBuffer * buffer, guint distance)
332 {
333   GstBuffer *main_block = distance > 0 ?
334       gst_rtp_buffer_get_payload_buffer (rtp) : NULL;
335   _red_history_prepend (self, rtp, main_block, distance);
336
337   gst_rtp_buffer_unmap (rtp);
338   return _pad_push (self, buffer, FALSE);
339 }
340
341 static GstFlowReturn
342 _push_red_packet (GstRtpRedEnc * self,
343     GstRTPBuffer * rtp, GstBuffer * buffer, RTPHistItem * redundant_block,
344     guint distance)
345 {
346   GstBuffer *main_block = gst_rtp_buffer_get_payload_buffer (rtp);
347   GstBuffer *red_buffer =
348       _create_red_packet (self, rtp, redundant_block, main_block);
349
350   _red_history_prepend (self, rtp, main_block, distance);
351   gst_rtp_buffer_unmap (rtp);
352   gst_buffer_unref (buffer);
353
354   self->num_sent++;
355   return _pad_push (self, red_buffer, TRUE);
356 }
357
358 static GstFlowReturn
359 gst_rtp_red_enc_chain (GstPad G_GNUC_UNUSED * pad, GstObject * parent,
360     GstBuffer * buffer)
361 {
362   GstRtpRedEnc *self = GST_RTP_RED_ENC (parent);
363   guint distance = self->distance;
364   guint only_with_redundant_data = !self->allow_no_red_blocks;
365   RTPHistItem *redundant_block;
366   GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
367
368   /* We need to "trim" the history if 'distance' property has changed */
369   _red_history_trim (self, distance);
370
371   if (0 == distance && only_with_redundant_data)
372     return _pad_push (self, buffer, FALSE);
373
374   if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp))
375     return _pad_push (self, buffer, self->is_current_caps_red);
376
377   /* If can't get data for redundant block push the packet as is */
378   redundant_block = _red_history_get_redundant_block (self,
379       gst_rtp_buffer_get_timestamp (&rtp), distance);
380   if (NULL == redundant_block && only_with_redundant_data)
381     return _push_nonred_packet (self, &rtp, buffer, distance);
382
383   /* About to create RED packet with or without redundant data */
384   return _push_red_packet (self, &rtp, buffer, redundant_block, distance);
385 }
386
387 static guint8
388 _get_extmap_id_for_attribute (const GstStructure * s, const gchar * ext_name)
389 {
390   guint i;
391   guint8 extmap_id = 0;
392   guint n_fields = gst_structure_n_fields (s);
393
394   for (i = 0; i < n_fields; i++) {
395     const gchar *field_name = gst_structure_nth_field_name (s, i);
396     if (g_str_has_prefix (field_name, "extmap-")) {
397       const gchar *str = gst_structure_get_string (s, field_name);
398       if (str && g_strcmp0 (str, ext_name) == 0) {
399         gint64 id = g_ascii_strtoll (field_name + 7, NULL, 10);
400         if (id > 0 && id < 15) {
401           extmap_id = id;
402           break;
403         }
404       }
405     }
406   }
407   return extmap_id;
408 }
409
410 #define TWCC_EXTMAP_STR "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
411
412 static gboolean
413 gst_rtp_red_enc_event_sink (GstPad * pad, GstObject * parent, GstEvent * event)
414 {
415   GstRtpRedEnc *self = GST_RTP_RED_ENC (parent);
416
417   switch (GST_EVENT_TYPE (event)) {
418     case GST_EVENT_CAPS:
419     {
420       GstCaps *caps;
421       GstStructure *s;
422       gboolean replace_with_red_caps =
423           self->is_current_caps_red || self->allow_no_red_blocks;
424
425       gst_event_parse_caps (event, &caps);
426       s = gst_caps_get_structure (caps, 0);
427       self->twcc_ext_id = _get_extmap_id_for_attribute (s, TWCC_EXTMAP_STR);
428
429       GST_INFO_OBJECT (self, "TWCC extension ID: %u", self->twcc_ext_id);
430
431       if (replace_with_red_caps) {
432         gst_event_take (&event, _create_caps_event (caps, self->pt));
433
434         self->is_current_caps_red = TRUE;
435       }
436       break;
437     }
438     default:
439       break;
440   }
441
442   return gst_pad_event_default (pad, parent, event);
443 }
444
445 static void
446 gst_rtp_red_enc_dispose (GObject * obj)
447 {
448   GstRtpRedEnc *self = GST_RTP_RED_ENC (obj);
449
450   g_queue_free_full (self->rtp_history, rtp_hist_item_free);
451
452   G_OBJECT_CLASS (gst_rtp_red_enc_parent_class)->dispose (obj);
453 }
454
455 static void
456 gst_rtp_red_enc_init (GstRtpRedEnc * self)
457 {
458   GstPadTemplate *pad_template;
459
460   pad_template =
461       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src");
462   self->srcpad = gst_pad_new_from_template (pad_template, "src");
463   gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
464
465   pad_template =
466       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "sink");
467   self->sinkpad = gst_pad_new_from_template (pad_template, "sink");
468   gst_pad_set_chain_function (self->sinkpad,
469       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_chain));
470   gst_pad_set_event_function (self->sinkpad,
471       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_event_sink));
472   GST_PAD_SET_PROXY_CAPS (self->sinkpad);
473   GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
474   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
475
476   self->pt = DEFAULT_PT;
477   self->distance = DEFAULT_DISTANCE;
478   self->allow_no_red_blocks = DEFAULT_ALLOW_NO_RED_BLOCKS;
479   self->num_sent = 0;
480   self->rtp_history = g_queue_new ();
481   self->ignoring_extension_warned = FALSE;
482 }
483
484
485 static void
486 gst_rtp_red_enc_set_property (GObject * object, guint prop_id,
487     const GValue * value, GParamSpec * pspec)
488 {
489   GstRtpRedEnc *self = GST_RTP_RED_ENC (object);
490   switch (prop_id) {
491     case PROP_PT:
492     {
493       gint prev_pt = self->pt;
494       self->pt = g_value_get_int (value);
495       self->send_caps = self->pt != prev_pt && self->is_current_caps_red;
496     }
497       break;
498     case PROP_DISTANCE:
499       self->distance = g_value_get_uint (value);
500       break;
501     case PROP_ALLOW_NO_RED_BLOCKS:
502       self->allow_no_red_blocks = g_value_get_boolean (value);
503       break;
504     default:
505       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
506       break;
507   }
508 }
509
510 static void
511 gst_rtp_red_enc_get_property (GObject * object, guint prop_id,
512     GValue * value, GParamSpec * pspec)
513 {
514   GstRtpRedEnc *self = GST_RTP_RED_ENC (object);
515   switch (prop_id) {
516     case PROP_PT:
517       g_value_set_int (value, self->pt);
518       break;
519     case PROP_SENT:
520       g_value_set_uint (value, self->num_sent);
521       break;
522     case PROP_DISTANCE:
523       g_value_set_uint (value, self->distance);
524       break;
525     case PROP_ALLOW_NO_RED_BLOCKS:
526       g_value_set_boolean (value, self->allow_no_red_blocks);
527       break;
528     default:
529       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
530       break;
531   }
532 }
533
534 static void
535 gst_rtp_red_enc_class_init (GstRtpRedEncClass * klass)
536 {
537   GObjectClass *gobject_class;
538   GstElementClass *element_class;
539
540   gobject_class = G_OBJECT_CLASS (klass);
541   element_class = GST_ELEMENT_CLASS (klass);
542
543   gst_element_class_add_pad_template (element_class,
544       gst_static_pad_template_get (&src_template));
545   gst_element_class_add_pad_template (element_class,
546       gst_static_pad_template_get (&sink_template));
547
548   gst_element_class_set_metadata (element_class,
549       "Redundant Audio Data (RED) Encoder",
550       "Codec/Payloader/Network/RTP",
551       "Encode Redundant Audio Data (RED)",
552       "Hani Mustafa <hani@pexip.com>, Mikhail Fludkov <misha@pexip.com>");
553
554   gobject_class->set_property =
555       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_set_property);
556   gobject_class->get_property =
557       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_get_property);
558   gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_rtp_red_enc_dispose);
559
560   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PT,
561       g_param_spec_int ("pt", "payload type",
562           "Payload type FEC packets (-1 disable)",
563           0, 127, DEFAULT_PT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
564
565   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SENT,
566       g_param_spec_uint ("sent", "Sent",
567           "Count of sent packets",
568           0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
569
570   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DISTANCE,
571       g_param_spec_uint ("distance", "RED distance",
572           "Tells which media packet to use as a redundant block "
573           "(0 - no redundant blocks, 1 to use previous packet, "
574           "2 to use the packet before previous, etc.)",
575           0, G_MAXUINT32, DEFAULT_DISTANCE,
576           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
577
578   g_object_class_install_property (G_OBJECT_CLASS (klass),
579       PROP_ALLOW_NO_RED_BLOCKS, g_param_spec_boolean ("allow-no-red-blocks",
580           "Allow no redundant blocks",
581           "true - can produce RED packets even without redundant blocks (distance==0) "
582           "false - RED packets will be produced only if distance>0",
583           DEFAULT_ALLOW_NO_RED_BLOCKS,
584           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
585
586   GST_DEBUG_CATEGORY_INIT (gst_rtp_red_enc_debug, "rtpredenc", 0,
587       "RTP RED Encoder");
588 }