1 /* GStreamer plugin for forward error correction
2 * Copyright (C) 2017 Pexip
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.
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.
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
18 * Author: Mikhail Fludkov <misha@pexip.com>
22 * SECTION:element-rtpredenc
23 * @short_description: RTP Redundant Audio Data (RED) encoder
26 * Encode Redundant Audio Data (RED) as per RFC 2198.
31 #include <gst/rtp/gstrtpbuffer.h>
35 #include "rtpredcommon.h"
36 #include "gstrtpredenc.h"
45 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
48 GST_STATIC_CAPS ("application/x-rtp"));
50 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
53 GST_STATIC_CAPS ("application/x-rtp"));
55 #define DEFAULT_PT (0)
56 #define DEFAULT_DISTANCE (0)
57 #define DEFAULT_ALLOW_NO_RED_BLOCKS (FALSE)
59 GST_DEBUG_CATEGORY_STATIC (gst_rtp_red_enc_debug);
60 #define GST_CAT_DEFAULT (gst_rtp_red_enc_debug)
62 G_DEFINE_TYPE (GstRtpRedEnc, gst_rtp_red_enc, GST_TYPE_ELEMENT);
70 PROP_ALLOW_NO_RED_BLOCKS
74 rtp_hist_item_init (RTPHistItem * item, GstRTPBuffer * rtp,
75 GstBuffer * rtp_payload)
77 item->pt = gst_rtp_buffer_get_payload_type (rtp);
78 item->timestamp = gst_rtp_buffer_get_timestamp (rtp);
79 item->payload = rtp_payload;
83 rtp_hist_item_new (GstRTPBuffer * rtp, GstBuffer * rtp_payload)
85 RTPHistItem *item = g_slice_new0 (RTPHistItem);
86 rtp_hist_item_init (item, rtp, rtp_payload);
91 rtp_hist_item_replace (RTPHistItem * item, GstRTPBuffer * rtp,
92 GstBuffer * rtp_payload)
94 gst_buffer_unref (item->payload);
95 rtp_hist_item_init (item, rtp, rtp_payload);
99 rtp_hist_item_free (gpointer _item)
101 RTPHistItem *item = _item;
102 gst_buffer_unref (item->payload);
103 g_slice_free (RTPHistItem, item);
107 _create_caps_event (const GstCaps * caps, guint8 pt)
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,
115 ret = gst_event_new_caps (new);
116 gst_caps_unref (new);
121 _alloc_red_packet_and_fill_headers (GstRtpRedEnc * self,
122 RTPHistItem * redundant_block, GstRTPBuffer * inp_rtp)
124 guint red_header_size = rtp_red_block_header_get_length (FALSE) +
125 (redundant_block ? rtp_red_block_header_get_length (TRUE) : 0);
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;
133 if (!gst_rtp_buffer_map (red, GST_MAP_WRITE, &red_rtp))
134 g_assert_not_reached ();
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");
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));
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));
159 red_block_header += rtp_red_block_header_get_length (TRUE);
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));
165 gst_rtp_buffer_unmap (&red_rtp);
167 gst_buffer_copy_into (red, inp_rtp->buffer, GST_BUFFER_COPY_METADATA, 0, -1);
172 _create_red_packet (GstRtpRedEnc * self,
173 GstRTPBuffer * rtp, RTPHistItem * redundant_block, GstBuffer * main_block)
176 _alloc_red_packet_and_fill_headers (self, redundant_block, rtp);
178 red = gst_buffer_append (red, gst_buffer_ref (redundant_block->payload));
179 red = gst_buffer_append (red, gst_buffer_ref (main_block));
184 _red_history_get_redundant_block (GstRtpRedEnc * self,
185 guint32 current_timestamp, guint distance)
188 gint32 timestamp_offset;
190 if (0 == distance || 0 == self->rtp_history->length)
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);
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);
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);
221 /* _red_history_trim should take care it never happens */
222 g_assert_cmpint (self->rtp_history->length, <=, distance);
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);
233 _red_history_prepend (GstRtpRedEnc * self,
234 GstRTPBuffer * rtp, GstBuffer * rtp_payload, guint max_history_length)
238 if (0 == max_history_length) {
240 gst_buffer_unref (rtp_payload);
244 g_assert (NULL != rtp_payload);
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);
250 link = g_list_alloc ();
251 link->data = rtp_hist_item_new (rtp, rtp_payload);
253 g_queue_push_head_link (self->rtp_history, link);
257 _red_history_trim (GstRtpRedEnc * self, guint max_history_length)
259 while (max_history_length < self->rtp_history->length)
260 rtp_hist_item_free (g_queue_pop_tail (self->rtp_history));
264 _pad_push (GstRtpRedEnc * self, GstBuffer * buffer, gboolean is_red)
266 if (self->send_caps || is_red != self->is_current_caps_red) {
268 GstCaps *caps = gst_pad_get_current_caps (self->sinkpad);
270 event = _create_caps_event (caps, self->pt);
272 event = gst_event_new_caps (caps);
273 gst_caps_unref (caps);
275 gst_pad_push_event (self->srcpad, event);
276 self->send_caps = FALSE;
277 self->is_current_caps_red = is_red;
279 return gst_pad_push (self->srcpad, buffer);
283 _push_nonred_packet (GstRtpRedEnc * self,
284 GstRTPBuffer * rtp, GstBuffer * buffer, guint distance)
286 GstBuffer *main_block = distance > 0 ?
287 gst_rtp_buffer_get_payload_buffer (rtp) : NULL;
288 _red_history_prepend (self, rtp, main_block, distance);
290 gst_rtp_buffer_unmap (rtp);
291 return _pad_push (self, buffer, FALSE);
295 _push_red_packet (GstRtpRedEnc * self,
296 GstRTPBuffer * rtp, GstBuffer * buffer, RTPHistItem * redundant_block,
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);
303 _red_history_prepend (self, rtp, main_block, distance);
304 gst_rtp_buffer_unmap (rtp);
305 gst_buffer_unref (buffer);
308 return _pad_push (self, red_buffer, TRUE);
312 gst_rtp_red_enc_chain (GstPad G_GNUC_UNUSED * pad, GstObject * parent,
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;
321 /* We need to "trim" the history if 'distance' property has changed */
322 _red_history_trim (self, distance);
324 if (0 == distance && only_with_redundant_data)
325 return _pad_push (self, buffer, FALSE);
327 if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp))
328 return _pad_push (self, buffer, self->is_current_caps_red);
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);
336 /* About to create RED packet with or without redundant data */
337 return _push_red_packet (self, &rtp, buffer, redundant_block, distance);
341 gst_rtp_red_enc_event_sink (GstPad * pad, GstObject * parent, GstEvent * event)
343 GstRtpRedEnc *self = GST_RTP_RED_ENC (parent);
345 switch (GST_EVENT_TYPE (event)) {
348 gboolean replace_with_red_caps =
349 self->is_current_caps_red || self->allow_no_red_blocks;
351 if (replace_with_red_caps) {
353 gst_event_parse_caps (event, &caps);
354 gst_event_take (&event, _create_caps_event (caps, self->pt));
356 self->is_current_caps_red = TRUE;
364 return gst_pad_event_default (pad, parent, event);
368 gst_rtp_red_enc_dispose (GObject * obj)
370 GstRtpRedEnc *self = GST_RTP_RED_ENC (obj);
372 g_queue_free_full (self->rtp_history, rtp_hist_item_free);
374 G_OBJECT_CLASS (gst_rtp_red_enc_parent_class)->dispose (obj);
378 gst_rtp_red_enc_init (GstRtpRedEnc * self)
380 GstPadTemplate *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);
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);
398 self->pt = DEFAULT_PT;
399 self->distance = DEFAULT_DISTANCE;
400 self->allow_no_red_blocks = DEFAULT_ALLOW_NO_RED_BLOCKS;
402 self->rtp_history = g_queue_new ();
407 gst_rtp_red_enc_set_property (GObject * object, guint prop_id,
408 const GValue * value, GParamSpec * pspec)
410 GstRtpRedEnc *self = GST_RTP_RED_ENC (object);
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;
420 self->distance = g_value_get_uint (value);
422 case PROP_ALLOW_NO_RED_BLOCKS:
423 self->allow_no_red_blocks = g_value_get_boolean (value);
426 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
432 gst_rtp_red_enc_get_property (GObject * object, guint prop_id,
433 GValue * value, GParamSpec * pspec)
435 GstRtpRedEnc *self = GST_RTP_RED_ENC (object);
438 g_value_set_int (value, self->pt);
441 g_value_set_uint (value, self->num_sent);
444 g_value_set_uint (value, self->distance);
446 case PROP_ALLOW_NO_RED_BLOCKS:
447 g_value_set_boolean (value, self->allow_no_red_blocks);
450 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
456 gst_rtp_red_enc_class_init (GstRtpRedEncClass * klass)
458 GObjectClass *gobject_class;
459 GstElementClass *element_class;
461 gobject_class = G_OBJECT_CLASS (klass);
462 element_class = GST_ELEMENT_CLASS (klass);
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));
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>");
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);
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));
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));
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));
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));
507 GST_DEBUG_CATEGORY_INIT (gst_rtp_red_enc_debug, "rtpredenc", 0,