blob: 43a735d28af0c63c8a3d21a172e56e928dec638e [file] [log] [blame]
/* GStreamer plugin for forward error correction
* Copyright (C) 2017 Pexip
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Mikhail Fludkov <misha@pexip.com>
*/
#include <gst/rtp/gstrtp-enumtypes.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <string.h>
#include "rtpulpfeccommon.h"
#include "gstrtpulpfecenc.h"
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp"));
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp"));
#define UNDEF_PT 255
#define UNDEF_SSRC 0
#define DEFAULT_PT UNDEF_PT
#define DEFAULT_SSRC UNDEF_SSRC
#define DEFAULT_PCT 0
#define DEFAULT_PCT_IMPORTANT 0
#define DEFAULT_MULTIPACKET FALSE
#define DEFAULT_MUX_SEQ FALSE
#define PACKETS_BUF_MAX_LENGTH (RTP_ULPFEC_PROTECTED_PACKETS_MAX(TRUE))
GST_DEBUG_CATEGORY (gst_rtp_ulpfec_enc_debug);
#define GST_CAT_DEFAULT (gst_rtp_ulpfec_enc_debug)
G_DEFINE_TYPE (GstRtpUlpFecEnc, gst_rtp_ulpfec_enc, GST_TYPE_ELEMENT);
enum
{
PROP_0,
PROP_SSRC,
PROP_PT,
PROP_MULTIPACKET,
PROP_MUX_SEQ,
PROP_PROTECTED,
PROP_PERCENTAGE,
PROP_PERCENTAGE_IMPORTANT,
};
#define RTP_FEC_MAP_INFO_NTH(ctx, data) (&g_array_index (\
((GstRtpUlpFecEncStreamCtx *)ctx)->info_arr, \
RtpUlpFecMapInfo, \
GPOINTER_TO_UINT(data)))
static void
gst_rtp_ulpfec_enc_stream_ctx_start (GstRtpUlpFecEncStreamCtx * ctx,
GQueue * packets, guint fec_packets)
{
GList *it = packets->tail;
g_array_set_size (ctx->info_arr, packets->length);
for (guint i = 0; i < packets->length; ++i) {
GstBuffer *buffer = it->data;
RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (ctx, i);
if (!rtp_ulpfec_map_info_map (gst_buffer_ref (buffer), info))
g_assert_not_reached ();
GST_LOG_RTP_PACKET (ctx->parent, "rtp header (incoming)", &info->rtp);
it = g_list_previous (it);
}
ctx->fec_packets = fec_packets;
ctx->fec_packet_idx = 0;
}
static void
gst_rtp_ulpfec_enc_stream_ctx_stop (GstRtpUlpFecEncStreamCtx * ctx)
{
g_array_set_size (ctx->info_arr, 0);
g_array_set_size (ctx->scratch_buf, 0);
ctx->fec_packets = 0;
ctx->fec_packet_idx = 0;
}
static void
gst_rtp_ulpfec_enc_stream_ctx_get_protection_parameters
(GstRtpUlpFecEncStreamCtx * ctx, guint16 * dst_seq_base, guint64 * dst_mask,
guint * dst_start, guint * dst_end)
{
guint media_packets = ctx->info_arr->len;
guint start = ctx->fec_packet_idx * media_packets / ctx->fec_packets;
guint end =
((ctx->fec_packet_idx + 1) * media_packets + ctx->fec_packets -
1) / ctx->fec_packets - 1;
guint len = end - start + 1;
guint64 mask = 0;
guint16 seq_base = 0;
len = MIN (len, RTP_ULPFEC_PROTECTED_PACKETS_MAX (TRUE));
end = start + len - 1;
for (guint i = start; i <= end; ++i) {
RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (ctx, i);
guint16 seq = gst_rtp_buffer_get_seq (&info->rtp);
if (mask) {
gint diff = gst_rtp_buffer_compare_seqnum (seq_base, seq);
if (diff < 0) {
seq_base = seq;
mask = mask >> (-diff);
}
mask |= rtp_ulpfec_packet_mask_from_seqnum (seq, seq_base, TRUE);
} else {
seq_base = seq;
mask = rtp_ulpfec_packet_mask_from_seqnum (seq, seq_base, TRUE);
}
}
*dst_start = start;
*dst_end = end;
*dst_mask = mask;
*dst_seq_base = seq_base;
}
static GstBuffer *
gst_rtp_ulpfec_enc_stream_ctx_protect (GstRtpUlpFecEncStreamCtx * ctx,
guint8 pt, guint16 seq, guint32 timestamp, guint32 ssrc)
{
guint end = 0;
guint start = 0;
guint64 fec_mask = 0;
guint16 seq_base = 0;
GstBuffer *ret;
guint64 tmp_mask;
gboolean fec_mask_long;
if (ctx->fec_packet_idx >= ctx->fec_packets)
return NULL;
g_array_set_size (ctx->scratch_buf, 0);
gst_rtp_ulpfec_enc_stream_ctx_get_protection_parameters (ctx, &seq_base,
&fec_mask, &start, &end);
tmp_mask = fec_mask;
fec_mask_long = rtp_ulpfec_mask_is_long (fec_mask);
for (guint i = start; i <= end; ++i) {
RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (ctx, i);
guint64 packet_mask =
rtp_ulpfec_packet_mask_from_seqnum (gst_rtp_buffer_get_seq (&info->rtp),
seq_base,
TRUE);
if (tmp_mask & packet_mask) {
tmp_mask ^= packet_mask;
rtp_buffer_to_ulpfec_bitstring (&info->rtp, ctx->scratch_buf, FALSE,
fec_mask_long);
}
}
g_assert (tmp_mask == 0ULL);
ret =
rtp_ulpfec_bitstring_to_fec_rtp_buffer (ctx->scratch_buf, seq_base,
fec_mask_long, fec_mask, FALSE, pt, seq, timestamp, ssrc);
++ctx->fec_packet_idx;
return ret;
}
static void
gst_rtp_ulpfec_enc_stream_ctx_report_budget (GstRtpUlpFecEncStreamCtx * ctx)
{
GST_TRACE_OBJECT (ctx->parent, "budget = %f budget_important = %f",
ctx->budget, ctx->budget_important);
}
static void
gst_rtp_ulpfec_enc_stream_ctx_increment_budget (GstRtpUlpFecEncStreamCtx * ctx,
GstBuffer * buffer)
{
if (ctx->percentage == 0 && ctx->percentage_important == 0) {
if (ctx->budget > 0) {
ctx->budget = 0;
ctx->budget_important = 0;
}
if (ctx->budget < 0)
ctx->budget += ctx->budget_inc;
return;
}
ctx->budget += ctx->budget_inc;
if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_NON_DROPPABLE)) {
ctx->budget_important += ctx->budget_inc_important;
}
gst_rtp_ulpfec_enc_stream_ctx_report_budget (ctx);
}
static void
gst_rtp_ulpfec_enc_stream_ctx_decrement_budget (GstRtpUlpFecEncStreamCtx * ctx,
guint fec_packets_num)
{
if (ctx->budget_important >= 1.)
ctx->budget_important -= fec_packets_num;
ctx->budget -= fec_packets_num;
gst_rtp_ulpfec_enc_stream_ctx_report_budget (ctx);
}
static guint
gst_rtp_ulpfec_enc_stream_ctx_get_fec_packets_num (GstRtpUlpFecEncStreamCtx *
ctx)
{
g_assert_cmpfloat (ctx->budget_important, >=, 0.);
if (ctx->budget_important >= 1.)
return ctx->budget_important;
return ctx->budget > 0. ? (guint) ctx->budget : 0;
}
static void
gst_rtp_ulpfec_enc_stream_ctx_free_packets_buf (GstRtpUlpFecEncStreamCtx * ctx)
{
while (ctx->packets_buf.length)
gst_buffer_unref (g_queue_pop_tail (&ctx->packets_buf));
}
static void
gst_rtp_ulpfec_enc_stream_ctx_prepend_to_fec_buffer (GstRtpUlpFecEncStreamCtx *
ctx, GstRTPBuffer * rtp, guint buf_max_size)
{
GList *new_head;
if (ctx->packets_buf.length == buf_max_size) {
new_head = g_queue_pop_tail_link (&ctx->packets_buf);
} else {
new_head = g_list_alloc ();
}
gst_buffer_replace ((GstBuffer **) & new_head->data, rtp->buffer);
g_queue_push_head_link (&ctx->packets_buf, new_head);
g_assert_cmpint (ctx->packets_buf.length, <=, buf_max_size);
}
static GstFlowReturn
gst_rtp_ulpfec_enc_stream_ctx_push_fec_packets (GstRtpUlpFecEncStreamCtx * ctx,
guint8 pt, guint16 seq, guint32 timestamp, guint32 ssrc)
{
GstFlowReturn ret = GST_FLOW_OK;
guint fec_packets_num =
gst_rtp_ulpfec_enc_stream_ctx_get_fec_packets_num (ctx);
if (fec_packets_num) {
guint fec_packets_pushed = 0;
GstBuffer *latest_packet = ctx->packets_buf.head->data;
GstBuffer *fec = NULL;
gst_rtp_ulpfec_enc_stream_ctx_start (ctx, &ctx->packets_buf,
fec_packets_num);
while (NULL != (fec =
gst_rtp_ulpfec_enc_stream_ctx_protect (ctx, pt,
seq + fec_packets_pushed, timestamp, ssrc))) {
gst_buffer_copy_into (fec, latest_packet, GST_BUFFER_COPY_TIMESTAMPS, 0,
-1);
ret = gst_pad_push (ctx->srcpad, fec);
if (GST_FLOW_OK == ret)
++fec_packets_pushed;
else
break;
}
gst_rtp_ulpfec_enc_stream_ctx_stop (ctx);
g_assert_cmpint (fec_packets_pushed, <=, fec_packets_num);
ctx->num_packets_protected += ctx->packets_buf.length;
ctx->num_packets_fec += fec_packets_pushed;
ctx->seqnum_offset += fec_packets_pushed;
ctx->seqnum += fec_packets_pushed;
}
gst_rtp_ulpfec_enc_stream_ctx_decrement_budget (ctx, fec_packets_num);
return ret;
}
static void
gst_rtp_ulpfec_enc_stream_ctx_cache_packet (GstRtpUlpFecEncStreamCtx * ctx,
GstRTPBuffer * rtp, gboolean * dst_empty_packet_buffer,
gboolean * dst_push_fec)
{
if (ctx->multipacket) {
gst_rtp_ulpfec_enc_stream_ctx_prepend_to_fec_buffer (ctx, rtp,
PACKETS_BUF_MAX_LENGTH);
gst_rtp_ulpfec_enc_stream_ctx_increment_budget (ctx, rtp->buffer);
*dst_empty_packet_buffer = gst_rtp_buffer_get_marker (rtp);
*dst_push_fec = *dst_empty_packet_buffer;
} else {
gboolean push_fec;
gst_rtp_ulpfec_enc_stream_ctx_prepend_to_fec_buffer (ctx, rtp, 1);
push_fec = ctx->fec_nth == 0 ? FALSE :
0 == (ctx->num_packets_received % ctx->fec_nth);
ctx->budget = push_fec ? 1 : 0;
ctx->budget_important = 0;
*dst_push_fec = push_fec;
*dst_empty_packet_buffer = FALSE;
}
}
static void
gst_rtp_ulpfec_enc_stream_ctx_configure (GstRtpUlpFecEncStreamCtx * ctx,
guint pt, guint32 fec_ssrc,
guint percentage, guint percentage_important, gboolean multipacket,
gboolean mux_seq)
{
ctx->pt = pt;
ctx->fec_ssrc = fec_ssrc;
ctx->percentage = percentage;
ctx->percentage_important = percentage_important;
ctx->multipacket = multipacket;
ctx->mux_seq = mux_seq;
ctx->fec_nth = percentage ? 100 / percentage : 0;
if (percentage) {
ctx->budget_inc = percentage / 100.;
ctx->budget_inc_important = percentage > percentage_important ?
ctx->budget_inc : percentage_important / 100.;
}
/*
else {
ctx->budget_inc = 0.0;
}
*/
ctx->budget_inc_important = percentage > percentage_important ?
ctx->budget_inc : percentage_important / 100.;
}
static GstRtpUlpFecEncStreamCtx *
gst_rtp_ulpfec_enc_stream_ctx_new (guint ssrc,
GstElement * parent, GstPad * srcpad,
guint pt, guint32 fec_ssrc,
guint percentage, guint percentage_important, gboolean multipacket,
gboolean mux_seq)
{
GstRtpUlpFecEncStreamCtx *ctx = g_new0 (GstRtpUlpFecEncStreamCtx, 1);
ctx->ssrc = ssrc;
ctx->parent = parent;
ctx->srcpad = srcpad;
ctx->seqnum = g_random_int_range (0, G_MAXUINT16 / 2);
ctx->info_arr = g_array_new (FALSE, TRUE, sizeof (RtpUlpFecMapInfo));
g_array_set_clear_func (ctx->info_arr,
(GDestroyNotify) rtp_ulpfec_map_info_unmap);
ctx->parent = parent;
ctx->scratch_buf = g_array_new (FALSE, TRUE, sizeof (guint8));
gst_rtp_ulpfec_enc_stream_ctx_configure (ctx, pt, fec_ssrc,
percentage, percentage_important, multipacket, mux_seq);
return ctx;
}
static void
gst_rtp_ulpfec_enc_stream_ctx_free (GstRtpUlpFecEncStreamCtx * ctx)
{
if (ctx->num_packets_received) {
GST_INFO_OBJECT (ctx->parent, "Actual FEC overhead is %4.2f%% (%u/%u)\n",
ctx->num_packets_fec * (double) 100. / ctx->num_packets_received,
ctx->num_packets_fec, ctx->num_packets_received);
}
gst_rtp_ulpfec_enc_stream_ctx_free_packets_buf (ctx);
g_assert (0 == ctx->info_arr->len);
g_array_free (ctx->info_arr, TRUE);
g_array_free (ctx->scratch_buf, TRUE);
g_slice_free1 (sizeof (GstRtpUlpFecEncStreamCtx), ctx);
}
static GstFlowReturn
gst_rtp_ulpfec_enc_stream_ctx_process (GstRtpUlpFecEncStreamCtx * ctx,
GstBuffer * buffer)
{
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
GstFlowReturn ret;
gboolean push_fec = FALSE;
gboolean empty_packet_buffer = FALSE;
ctx->num_packets_received++;
if (ctx->mux_seq && ctx->seqnum_offset > 0) {
buffer = gst_buffer_make_writable (buffer);
if (!gst_rtp_buffer_map (buffer,
GST_MAP_READWRITE | GST_RTP_BUFFER_MAP_FLAG_SKIP_PADDING, &rtp))
g_assert_not_reached ();
gst_rtp_buffer_set_seq (&rtp,
gst_rtp_buffer_get_seq (&rtp) + ctx->seqnum_offset);
} else {
if (!gst_rtp_buffer_map (buffer,
GST_MAP_READ | GST_RTP_BUFFER_MAP_FLAG_SKIP_PADDING, &rtp))
g_assert_not_reached ();
}
gst_rtp_ulpfec_enc_stream_ctx_cache_packet (ctx, &rtp, &empty_packet_buffer,
&push_fec);
if (push_fec) {
guint32 fec_timestamp = gst_rtp_buffer_get_timestamp (&rtp);
guint32 fec_ssrc =
ctx->fec_ssrc ==
UNDEF_SSRC ? gst_rtp_buffer_get_ssrc (&rtp) : ctx->fec_ssrc;
guint16 fec_seq =
ctx->mux_seq ? gst_rtp_buffer_get_seq (&rtp) + 1 : ctx->seqnum;
gst_rtp_buffer_unmap (&rtp);
ret = gst_pad_push (ctx->srcpad, buffer);
if (GST_FLOW_OK == ret)
ret =
gst_rtp_ulpfec_enc_stream_ctx_push_fec_packets (ctx, ctx->pt, fec_seq,
fec_timestamp, fec_ssrc);
} else {
gst_rtp_buffer_unmap (&rtp);
ret = gst_pad_push (ctx->srcpad, buffer);
}
if (empty_packet_buffer)
gst_rtp_ulpfec_enc_stream_ctx_free_packets_buf (ctx);
return ret;
}
static GstRtpUlpFecEncStreamCtx *
gst_rtp_ulpfec_enc_aquire_ctx (GstRtpUlpFecEnc * fec, guint ssrc)
{
GstRtpUlpFecEncStreamCtx *ctx;
GST_OBJECT_LOCK (fec);
ctx = g_hash_table_lookup (fec->ssrc_to_ctx, GUINT_TO_POINTER (ssrc));
if (ctx == NULL) {
ctx =
gst_rtp_ulpfec_enc_stream_ctx_new (ssrc, GST_ELEMENT_CAST (fec),
fec->srcpad, fec->pt, fec->ssrc, fec->percentage,
fec->percentage_important, fec->multipacket, fec->mux_seq);
g_hash_table_insert (fec->ssrc_to_ctx, GUINT_TO_POINTER (ssrc), ctx);
}
GST_OBJECT_UNLOCK (fec);
return ctx;
}
static GstFlowReturn
gst_rtp_ulpfec_enc_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
GstRtpUlpFecEnc *fec = GST_RTP_ULPFEC_ENC (parent);
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
GstFlowReturn ret;
guint ssrc = 0;
GstRtpUlpFecEncStreamCtx *ctx;
if (fec->pt == UNDEF_PT)
return gst_pad_push (fec->srcpad, buffer);
/* FIXME: avoid this additional mapping of the buffer to get the
ssrc! */
if (!gst_rtp_buffer_map (buffer,
GST_MAP_READ | GST_RTP_BUFFER_MAP_FLAG_SKIP_PADDING, &rtp)) {
g_assert_not_reached ();
}
ssrc = gst_rtp_buffer_get_ssrc (&rtp);
gst_rtp_buffer_unmap (&rtp);
ctx = gst_rtp_ulpfec_enc_aquire_ctx (fec, ssrc);
ret = gst_rtp_ulpfec_enc_stream_ctx_process (ctx, buffer);
/* FIXME: does not work for mulitple ssrcs */
fec->num_packets_protected = ctx->num_packets_protected;
return ret;
}
static void
gst_rtp_ulpfec_enc_configure_ctx (gpointer key, gpointer value,
gpointer user_data)
{
GstRtpUlpFecEnc *fec = user_data;
GstRtpUlpFecEncStreamCtx *ctx = value;
gst_rtp_ulpfec_enc_stream_ctx_configure (ctx, fec->pt, fec->ssrc,
fec->percentage, fec->percentage_important,
fec->multipacket, fec->mux_seq);
}
static void
gst_rtp_ulpfec_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstRtpUlpFecEnc *fec = GST_RTP_ULPFEC_ENC (object);
switch (prop_id) {
case PROP_PT:
fec->pt = g_value_get_uint (value);
break;
case PROP_SSRC:
fec->ssrc = g_value_get_uint (value);
break;
case PROP_MULTIPACKET:
fec->multipacket = g_value_get_boolean (value);
break;
case PROP_PERCENTAGE:
fec->percentage = g_value_get_uint (value);
break;
case PROP_PERCENTAGE_IMPORTANT:
fec->percentage_important = g_value_get_uint (value);
break;
case PROP_MUX_SEQ:
fec->mux_seq = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_OBJECT_LOCK (fec);
g_hash_table_foreach (fec->ssrc_to_ctx, gst_rtp_ulpfec_enc_configure_ctx,
fec);
GST_OBJECT_UNLOCK (fec);
}
static void
gst_rtp_ulpfec_enc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstRtpUlpFecEnc *fec = GST_RTP_ULPFEC_ENC (object);
switch (prop_id) {
case PROP_PT:
g_value_set_uint (value, fec->pt);
break;
case PROP_SSRC:
g_value_set_uint (value, fec->ssrc);
break;
case PROP_PROTECTED:
g_value_set_uint (value, fec->num_packets_protected);
break;
case PROP_PERCENTAGE:
g_value_set_uint (value, fec->percentage);
break;
case PROP_PERCENTAGE_IMPORTANT:
g_value_set_uint (value, fec->percentage_important);
break;
case PROP_MULTIPACKET:
g_value_set_boolean (value, fec->multipacket);
break;
case PROP_MUX_SEQ:
g_value_set_boolean (value, fec->mux_seq);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rtp_ulpfec_enc_dispose (GObject * obj)
{
GstRtpUlpFecEnc *fec = GST_RTP_ULPFEC_ENC (obj);
g_hash_table_destroy (fec->ssrc_to_ctx);
G_OBJECT_CLASS (gst_rtp_ulpfec_enc_parent_class)->dispose (obj);
}
static void
gst_rtp_ulpfec_enc_init (GstRtpUlpFecEnc * fec)
{
fec->srcpad = gst_pad_new_from_static_template (&srctemplate, "src");
gst_element_add_pad (GST_ELEMENT (fec), fec->srcpad);
fec->sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink");
GST_PAD_SET_PROXY_CAPS (fec->sinkpad);
GST_PAD_SET_PROXY_ALLOCATION (fec->sinkpad);
gst_pad_set_chain_function (fec->sinkpad,
GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_enc_chain));
gst_element_add_pad (GST_ELEMENT (fec), fec->sinkpad);
fec->ssrc_to_ctx = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) gst_rtp_ulpfec_enc_stream_ctx_free);
}
static void
gst_rtp_ulpfec_enc_class_init (GstRtpUlpFecEncClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (gst_rtp_ulpfec_enc_debug, "rtpulpfecenc", 0,
"FEC encoder element");
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&srctemplate));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&sinktemplate));
gst_element_class_set_static_metadata (element_class,
"RTP FEC Encoder",
"Codec/Payloader/Network/RTP",
"Encodes RTP FEC (RFC5109)", "Mikhail Fludkov <misha@pexip.com>");
gobject_class->set_property =
GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_enc_set_property);
gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_enc_get_property);
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_rtp_ulpfec_enc_dispose);
g_object_class_install_property (gobject_class, PROP_SSRC,
g_param_spec_uint ("ssrc", "SSRC",
"The SSRC to use on FEC'd packets", 0, G_MAXUINT32, DEFAULT_SSRC,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PT,
g_param_spec_uint ("pt", "payload type",
"The payload type of FEC packets", 0, 255, DEFAULT_PT,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MULTIPACKET,
g_param_spec_boolean ("multipacket", "Multipacket",
"Apply FEC on multiple packets", DEFAULT_MULTIPACKET,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MUX_SEQ,
g_param_spec_boolean ("mux-seq", "Mux seq",
"Mux seqnum for media and fec packets in same seqnum space",
DEFAULT_MUX_SEQ,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PERCENTAGE,
g_param_spec_uint ("percentage", "Percentage",
"FEC overhead percentage for the whole stream", 0, 100, DEFAULT_PCT,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PERCENTAGE_IMPORTANT,
g_param_spec_uint ("percentage-important", "Percentage important",
"FEC overhead percentage for important packets",
0, 100, DEFAULT_PCT_IMPORTANT,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PROTECTED,
g_param_spec_uint ("protected", "Protected",
"Count of protected packets", 0, G_MAXUINT32, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}