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  * <refsect2>
42  * <title>Example pipeline</title>
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  * </refsect2>
47  *
48  * See also: #GstRtpRedDec, #GstWebRTCBin, #GstRtpBin
49  * Since: 1.14
50  */
51 
52 #include <gst/rtp/gstrtpbuffer.h>
53 #include <string.h>
54 #include <stdio.h>
55 
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 
83 G_DEFINE_TYPE (GstRtpRedEnc, gst_rtp_red_enc, GST_TYPE_ELEMENT);
84 
85 enum
86 {
87   PROP_0,
88   PROP_PT,
89   PROP_SENT,
90   PROP_DISTANCE,
91   PROP_ALLOW_NO_RED_BLOCKS
92 };
93 
94 static void
rtp_hist_item_init(RTPHistItem * item,GstRTPBuffer * rtp,GstBuffer * rtp_payload)95 rtp_hist_item_init (RTPHistItem * item, GstRTPBuffer * rtp,
96     GstBuffer * rtp_payload)
97 {
98   item->pt = gst_rtp_buffer_get_payload_type (rtp);
99   item->timestamp = gst_rtp_buffer_get_timestamp (rtp);
100   item->payload = rtp_payload;
101 }
102 
103 static RTPHistItem *
rtp_hist_item_new(GstRTPBuffer * rtp,GstBuffer * rtp_payload)104 rtp_hist_item_new (GstRTPBuffer * rtp, GstBuffer * rtp_payload)
105 {
106   RTPHistItem *item = g_slice_new0 (RTPHistItem);
107   rtp_hist_item_init (item, rtp, rtp_payload);
108   return item;
109 }
110 
111 static void
rtp_hist_item_replace(RTPHistItem * item,GstRTPBuffer * rtp,GstBuffer * rtp_payload)112 rtp_hist_item_replace (RTPHistItem * item, GstRTPBuffer * rtp,
113     GstBuffer * rtp_payload)
114 {
115   gst_buffer_unref (item->payload);
116   rtp_hist_item_init (item, rtp, rtp_payload);
117 }
118 
119 static void
rtp_hist_item_free(gpointer _item)120 rtp_hist_item_free (gpointer _item)
121 {
122   RTPHistItem *item = _item;
123   gst_buffer_unref (item->payload);
124   g_slice_free (RTPHistItem, item);
125 }
126 
127 static GstEvent *
_create_caps_event(const GstCaps * caps,guint8 pt)128 _create_caps_event (const GstCaps * caps, guint8 pt)
129 {
130   GstEvent *ret;
131   GstCaps *new = gst_caps_copy (caps);
132   GstStructure *s = gst_caps_get_structure (new, 0);
133   gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
134   GST_INFO ("sinkcaps %" GST_PTR_FORMAT ", srccaps %" GST_PTR_FORMAT,
135       caps, new);
136   ret = gst_event_new_caps (new);
137   gst_caps_unref (new);
138   return ret;
139 }
140 
141 static GstBuffer *
_alloc_red_packet_and_fill_headers(GstRtpRedEnc * self,RTPHistItem * redundant_block,GstRTPBuffer * inp_rtp)142 _alloc_red_packet_and_fill_headers (GstRtpRedEnc * self,
143     RTPHistItem * redundant_block, GstRTPBuffer * inp_rtp)
144 {
145   guint red_header_size = rtp_red_block_header_get_length (FALSE) +
146       (redundant_block ? rtp_red_block_header_get_length (TRUE) : 0);
147 
148   guint32 timestmap = gst_rtp_buffer_get_timestamp (inp_rtp);
149   guint csrc_count = gst_rtp_buffer_get_csrc_count (inp_rtp);
150   GstBuffer *red = gst_rtp_buffer_new_allocate (red_header_size, 0, csrc_count);
151   guint8 *red_block_header;
152   GstRTPBuffer red_rtp = GST_RTP_BUFFER_INIT;
153   guint i;
154 
155   if (!gst_rtp_buffer_map (red, GST_MAP_WRITE, &red_rtp))
156     g_assert_not_reached ();
157 
158   /* Copying RTP header of incoming packet */
159   if (gst_rtp_buffer_get_extension (inp_rtp))
160     GST_WARNING_OBJECT (self, "FIXME: Ignoring RTP extension");
161 
162   gst_rtp_buffer_set_marker (&red_rtp, gst_rtp_buffer_get_marker (inp_rtp));
163   gst_rtp_buffer_set_payload_type (&red_rtp, self->pt);
164   gst_rtp_buffer_set_seq (&red_rtp, gst_rtp_buffer_get_seq (inp_rtp));
165   gst_rtp_buffer_set_timestamp (&red_rtp, timestmap);
166   gst_rtp_buffer_set_ssrc (&red_rtp, gst_rtp_buffer_get_ssrc (inp_rtp));
167   for (i = 0; i != csrc_count; ++i)
168     gst_rtp_buffer_set_csrc (&red_rtp, i,
169         gst_rtp_buffer_get_csrc ((inp_rtp), i));
170 
171   /* Filling RED block headers */
172   red_block_header = gst_rtp_buffer_get_payload (&red_rtp);
173   if (redundant_block) {
174     rtp_red_block_set_is_redundant (red_block_header, TRUE);
175     rtp_red_block_set_payload_type (red_block_header, redundant_block->pt);
176     rtp_red_block_set_timestamp_offset (red_block_header,
177         timestmap - redundant_block->timestamp);
178     rtp_red_block_set_payload_length (red_block_header,
179         gst_buffer_get_size (redundant_block->payload));
180 
181     red_block_header += rtp_red_block_header_get_length (TRUE);
182   }
183   rtp_red_block_set_is_redundant (red_block_header, FALSE);
184   rtp_red_block_set_payload_type (red_block_header,
185       gst_rtp_buffer_get_payload_type (inp_rtp));
186 
187   gst_rtp_buffer_unmap (&red_rtp);
188 
189   gst_buffer_copy_into (red, inp_rtp->buffer, GST_BUFFER_COPY_METADATA, 0, -1);
190   return red;
191 }
192 
193 static GstBuffer *
_create_red_packet(GstRtpRedEnc * self,GstRTPBuffer * rtp,RTPHistItem * redundant_block,GstBuffer * main_block)194 _create_red_packet (GstRtpRedEnc * self,
195     GstRTPBuffer * rtp, RTPHistItem * redundant_block, GstBuffer * main_block)
196 {
197   GstBuffer *red =
198       _alloc_red_packet_and_fill_headers (self, redundant_block, rtp);
199   if (redundant_block)
200     red = gst_buffer_append (red, gst_buffer_ref (redundant_block->payload));
201   red = gst_buffer_append (red, gst_buffer_ref (main_block));
202   return red;
203 }
204 
205 static RTPHistItem *
_red_history_get_redundant_block(GstRtpRedEnc * self,guint32 current_timestamp,guint distance)206 _red_history_get_redundant_block (GstRtpRedEnc * self,
207     guint32 current_timestamp, guint distance)
208 {
209   RTPHistItem *item;
210   gint32 timestamp_offset;
211 
212   if (0 == distance || 0 == self->rtp_history->length)
213     return NULL;
214 
215   item = self->rtp_history->tail->data;
216   timestamp_offset = current_timestamp - item->timestamp;
217   if (G_UNLIKELY (timestamp_offset > RED_BLOCK_TIMESTAMP_OFFSET_MAX)) {
218     GST_WARNING_OBJECT (self,
219         "Can't create redundant block with distance %u, "
220         "timestamp offset is too large %d (%u - %u) > %u",
221         distance, timestamp_offset, current_timestamp, item->timestamp,
222         RED_BLOCK_TIMESTAMP_OFFSET_MAX);
223     return NULL;
224   }
225 
226   if (G_UNLIKELY (timestamp_offset < 0)) {
227     GST_WARNING_OBJECT (self,
228         "Can't create redundant block with distance %u, "
229         "timestamp offset is negative %d (%u - %u)",
230         distance, timestamp_offset, current_timestamp, item->timestamp);
231     return NULL;
232   }
233 
234   if (G_UNLIKELY (gst_buffer_get_size (item->payload) > RED_BLOCK_LENGTH_MAX)) {
235     GST_WARNING_OBJECT (self,
236         "Can't create redundant block with distance %u, "
237         "red block is too large %u > %u",
238         distance, (guint) gst_buffer_get_size (item->payload),
239         RED_BLOCK_LENGTH_MAX);
240     return NULL;
241   }
242 
243   /* _red_history_trim should take care it never happens */
244   g_assert_cmpint (self->rtp_history->length, <=, distance);
245 
246   if (G_UNLIKELY (self->rtp_history->length < distance))
247     GST_DEBUG_OBJECT (self,
248         "Don't have enough buffers yet, "
249         "adding redundant block with distance %u and timestamp %u",
250         self->rtp_history->length, item->timestamp);
251   return item;
252 }
253 
254 static void
_red_history_prepend(GstRtpRedEnc * self,GstRTPBuffer * rtp,GstBuffer * rtp_payload,guint max_history_length)255 _red_history_prepend (GstRtpRedEnc * self,
256     GstRTPBuffer * rtp, GstBuffer * rtp_payload, guint max_history_length)
257 {
258   GList *link;
259 
260   if (0 == max_history_length) {
261     if (rtp_payload)
262       gst_buffer_unref (rtp_payload);
263     return;
264   }
265 
266   g_assert (NULL != rtp_payload);
267 
268   if (self->rtp_history->length >= max_history_length) {
269     link = g_queue_pop_tail_link (self->rtp_history);
270     rtp_hist_item_replace (link->data, rtp, rtp_payload);
271   } else {
272     link = g_list_alloc ();
273     link->data = rtp_hist_item_new (rtp, rtp_payload);
274   }
275   g_queue_push_head_link (self->rtp_history, link);
276 }
277 
278 static void
_red_history_trim(GstRtpRedEnc * self,guint max_history_length)279 _red_history_trim (GstRtpRedEnc * self, guint max_history_length)
280 {
281   while (max_history_length < self->rtp_history->length)
282     rtp_hist_item_free (g_queue_pop_tail (self->rtp_history));
283 }
284 
285 static GstFlowReturn
_pad_push(GstRtpRedEnc * self,GstBuffer * buffer,gboolean is_red)286 _pad_push (GstRtpRedEnc * self, GstBuffer * buffer, gboolean is_red)
287 {
288   if (self->send_caps || is_red != self->is_current_caps_red) {
289     GstEvent *event;
290     GstCaps *caps = gst_pad_get_current_caps (self->sinkpad);
291     if (is_red)
292       event = _create_caps_event (caps, self->pt);
293     else
294       event = gst_event_new_caps (caps);
295     gst_caps_unref (caps);
296 
297     gst_pad_push_event (self->srcpad, event);
298     self->send_caps = FALSE;
299     self->is_current_caps_red = is_red;
300   }
301   return gst_pad_push (self->srcpad, buffer);
302 }
303 
304 static GstFlowReturn
_push_nonred_packet(GstRtpRedEnc * self,GstRTPBuffer * rtp,GstBuffer * buffer,guint distance)305 _push_nonred_packet (GstRtpRedEnc * self,
306     GstRTPBuffer * rtp, GstBuffer * buffer, guint distance)
307 {
308   GstBuffer *main_block = distance > 0 ?
309       gst_rtp_buffer_get_payload_buffer (rtp) : NULL;
310   _red_history_prepend (self, rtp, main_block, distance);
311 
312   gst_rtp_buffer_unmap (rtp);
313   return _pad_push (self, buffer, FALSE);
314 }
315 
316 static GstFlowReturn
_push_red_packet(GstRtpRedEnc * self,GstRTPBuffer * rtp,GstBuffer * buffer,RTPHistItem * redundant_block,guint distance)317 _push_red_packet (GstRtpRedEnc * self,
318     GstRTPBuffer * rtp, GstBuffer * buffer, RTPHistItem * redundant_block,
319     guint distance)
320 {
321   GstBuffer *main_block = gst_rtp_buffer_get_payload_buffer (rtp);
322   GstBuffer *red_buffer =
323       _create_red_packet (self, rtp, redundant_block, main_block);
324 
325   _red_history_prepend (self, rtp, main_block, distance);
326   gst_rtp_buffer_unmap (rtp);
327   gst_buffer_unref (buffer);
328 
329   self->num_sent++;
330   return _pad_push (self, red_buffer, TRUE);
331 }
332 
333 static GstFlowReturn
gst_rtp_red_enc_chain(GstPad G_GNUC_UNUSED * pad,GstObject * parent,GstBuffer * buffer)334 gst_rtp_red_enc_chain (GstPad G_GNUC_UNUSED * pad, GstObject * parent,
335     GstBuffer * buffer)
336 {
337   GstRtpRedEnc *self = GST_RTP_RED_ENC (parent);
338   guint distance = self->distance;
339   guint only_with_redundant_data = !self->allow_no_red_blocks;
340   RTPHistItem *redundant_block;
341   GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
342 
343   /* We need to "trim" the history if 'distance' property has changed */
344   _red_history_trim (self, distance);
345 
346   if (0 == distance && only_with_redundant_data)
347     return _pad_push (self, buffer, FALSE);
348 
349   if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp))
350     return _pad_push (self, buffer, self->is_current_caps_red);
351 
352   /* If can't get data for redundant block push the packet as is */
353   redundant_block = _red_history_get_redundant_block (self,
354       gst_rtp_buffer_get_timestamp (&rtp), distance);
355   if (NULL == redundant_block && only_with_redundant_data)
356     return _push_nonred_packet (self, &rtp, buffer, distance);
357 
358   /* About to create RED packet with or without redundant data */
359   return _push_red_packet (self, &rtp, buffer, redundant_block, distance);
360 }
361 
362 static gboolean
gst_rtp_red_enc_event_sink(GstPad * pad,GstObject * parent,GstEvent * event)363 gst_rtp_red_enc_event_sink (GstPad * pad, GstObject * parent, GstEvent * event)
364 {
365   GstRtpRedEnc *self = GST_RTP_RED_ENC (parent);
366 
367   switch (GST_EVENT_TYPE (event)) {
368     case GST_EVENT_CAPS:
369     {
370       gboolean replace_with_red_caps =
371           self->is_current_caps_red || self->allow_no_red_blocks;
372 
373       if (replace_with_red_caps) {
374         GstCaps *caps;
375         gst_event_parse_caps (event, &caps);
376         gst_event_take (&event, _create_caps_event (caps, self->pt));
377 
378         self->is_current_caps_red = TRUE;
379       }
380       break;
381     }
382     default:
383       break;
384   }
385 
386   return gst_pad_event_default (pad, parent, event);
387 }
388 
389 static void
gst_rtp_red_enc_dispose(GObject * obj)390 gst_rtp_red_enc_dispose (GObject * obj)
391 {
392   GstRtpRedEnc *self = GST_RTP_RED_ENC (obj);
393 
394   g_queue_free_full (self->rtp_history, rtp_hist_item_free);
395 
396   G_OBJECT_CLASS (gst_rtp_red_enc_parent_class)->dispose (obj);
397 }
398 
399 static void
gst_rtp_red_enc_init(GstRtpRedEnc * self)400 gst_rtp_red_enc_init (GstRtpRedEnc * self)
401 {
402   GstPadTemplate *pad_template;
403 
404   pad_template =
405       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src");
406   self->srcpad = gst_pad_new_from_template (pad_template, "src");
407   gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
408 
409   pad_template =
410       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "sink");
411   self->sinkpad = gst_pad_new_from_template (pad_template, "sink");
412   gst_pad_set_chain_function (self->sinkpad,
413       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_chain));
414   gst_pad_set_event_function (self->sinkpad,
415       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_event_sink));
416   GST_PAD_SET_PROXY_CAPS (self->sinkpad);
417   GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
418   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
419 
420   self->pt = DEFAULT_PT;
421   self->distance = DEFAULT_DISTANCE;
422   self->allow_no_red_blocks = DEFAULT_ALLOW_NO_RED_BLOCKS;
423   self->num_sent = 0;
424   self->rtp_history = g_queue_new ();
425 }
426 
427 
428 static void
gst_rtp_red_enc_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)429 gst_rtp_red_enc_set_property (GObject * object, guint prop_id,
430     const GValue * value, GParamSpec * pspec)
431 {
432   GstRtpRedEnc *self = GST_RTP_RED_ENC (object);
433   switch (prop_id) {
434     case PROP_PT:
435     {
436       gint prev_pt = self->pt;
437       self->pt = g_value_get_int (value);
438       self->send_caps = self->pt != prev_pt && self->is_current_caps_red;
439     }
440       break;
441     case PROP_DISTANCE:
442       self->distance = g_value_get_uint (value);
443       break;
444     case PROP_ALLOW_NO_RED_BLOCKS:
445       self->allow_no_red_blocks = g_value_get_boolean (value);
446       break;
447     default:
448       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
449       break;
450   }
451 }
452 
453 static void
gst_rtp_red_enc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)454 gst_rtp_red_enc_get_property (GObject * object, guint prop_id,
455     GValue * value, GParamSpec * pspec)
456 {
457   GstRtpRedEnc *self = GST_RTP_RED_ENC (object);
458   switch (prop_id) {
459     case PROP_PT:
460       g_value_set_int (value, self->pt);
461       break;
462     case PROP_SENT:
463       g_value_set_uint (value, self->num_sent);
464       break;
465     case PROP_DISTANCE:
466       g_value_set_uint (value, self->distance);
467       break;
468     case PROP_ALLOW_NO_RED_BLOCKS:
469       g_value_set_boolean (value, self->allow_no_red_blocks);
470       break;
471     default:
472       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
473       break;
474   }
475 }
476 
477 static void
gst_rtp_red_enc_class_init(GstRtpRedEncClass * klass)478 gst_rtp_red_enc_class_init (GstRtpRedEncClass * klass)
479 {
480   GObjectClass *gobject_class;
481   GstElementClass *element_class;
482 
483   gobject_class = G_OBJECT_CLASS (klass);
484   element_class = GST_ELEMENT_CLASS (klass);
485 
486   gst_element_class_add_pad_template (element_class,
487       gst_static_pad_template_get (&src_template));
488   gst_element_class_add_pad_template (element_class,
489       gst_static_pad_template_get (&sink_template));
490 
491   gst_element_class_set_metadata (element_class,
492       "Redundant Audio Data (RED) Encoder",
493       "Codec/Payloader/Network/RTP",
494       "Encode Redundant Audio Data (RED)",
495       "Hani Mustafa <hani@pexip.com>, Mikhail Fludkov <misha@pexip.com>");
496 
497   gobject_class->set_property =
498       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_set_property);
499   gobject_class->get_property =
500       GST_DEBUG_FUNCPTR (gst_rtp_red_enc_get_property);
501   gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_rtp_red_enc_dispose);
502 
503   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PT,
504       g_param_spec_int ("pt", "payload type",
505           "Payload type FEC packets (-1 disable)",
506           0, 127, DEFAULT_PT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
507 
508   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SENT,
509       g_param_spec_uint ("sent", "Sent",
510           "Count of sent packets",
511           0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
512 
513   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DISTANCE,
514       g_param_spec_uint ("distance", "RED distance",
515           "Tells which media packet to use as a redundant block "
516           "(0 - no redundant blocks, 1 to use previous packet, "
517           "2 to use the packet before previous, etc.)",
518           0, G_MAXUINT32, DEFAULT_DISTANCE,
519           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
520 
521   g_object_class_install_property (G_OBJECT_CLASS (klass),
522       PROP_ALLOW_NO_RED_BLOCKS, g_param_spec_boolean ("allow-no-red-blocks",
523           "Allow no redundant blocks",
524           "true - can produce RED packets even without redundant blocks (distance==0) "
525           "false - RED packets will be produced only if distance>0",
526           DEFAULT_ALLOW_NO_RED_BLOCKS,
527           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
528 
529   GST_DEBUG_CATEGORY_INIT (gst_rtp_red_enc_debug, "rtpredenc", 0,
530       "RTP RED Encoder");
531 }
532