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