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