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