/* GStreamer
* Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ * Copyright (C) 2015 Kurento (http://kurento.org/)
+ * @author: Miguel ParĂs <mparisdiaz@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
*/
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
#include "rtpstats.h"
+#include "rtptwcc.h"
+
+void
+gst_rtp_packet_rate_ctx_reset (RTPPacketRateCtx * ctx, gint32 clock_rate)
+{
+ ctx->clock_rate = clock_rate;
+ ctx->probed = FALSE;
+ ctx->avg_packet_rate = -1;
+ ctx->last_ts = -1;
+}
+
+guint32
+gst_rtp_packet_rate_ctx_update (RTPPacketRateCtx * ctx, guint16 seqnum,
+ guint32 ts)
+{
+ guint64 new_ts, diff_ts;
+ gint diff_seqnum;
+ gint32 new_packet_rate;
+ gint32 base;
+
+ if (ctx->clock_rate <= 0) {
+ return ctx->avg_packet_rate;
+ }
+
+ new_ts = ctx->last_ts;
+ gst_rtp_buffer_ext_timestamp (&new_ts, ts);
+
+ if (!ctx->probed) {
+ ctx->probed = TRUE;
+ goto done_but_save;
+ }
+
+ diff_seqnum = gst_rtp_buffer_compare_seqnum (ctx->last_seqnum, seqnum);
+ /* Ignore seqnums that are over 15,000 away from the latest one, it's close
+ * to 2^14 but far enough to avoid any risk of computing error.
+ */
+ if (diff_seqnum > 15000)
+ goto done_but_save;
+
+ /* Ignore any packet that is in the past, we're only interested in newer
+ * packets to compute the packet rate.
+ */
+ if (diff_seqnum <= 0 || new_ts <= ctx->last_ts)
+ goto done;
+
+ diff_ts = new_ts - ctx->last_ts;
+ diff_ts = gst_util_uint64_scale_int (diff_ts, GST_SECOND, ctx->clock_rate);
+ new_packet_rate = gst_util_uint64_scale (diff_seqnum, GST_SECOND, diff_ts);
+
+ /* The goal is that higher packet rates "win".
+ * If there's a sudden burst, the average will go up fast,
+ * but it will go down again slowly.
+ * This is useful for bursty cases, where a lot of packets are close
+ * to each other and should allow a higher reorder/dropout there.
+ * Round up the new average.
+ * We do it on different rates depending on the packet rate, so it's not too
+ * jumpy.
+ */
+ if (ctx->avg_packet_rate > new_packet_rate)
+ base = MAX (ctx->avg_packet_rate / 3, 8); /* about 333 ms */
+ else
+ base = MAX (ctx->avg_packet_rate / 15, 2); /* about 66 ms */
+
+ diff_seqnum = MIN (diff_seqnum, base - 1);
+
+ ctx->avg_packet_rate = (((base - diff_seqnum) * ctx->avg_packet_rate) +
+ (new_packet_rate * diff_seqnum)) / base;
+
+
+done_but_save:
+
+ ctx->last_seqnum = seqnum;
+ ctx->last_ts = new_ts;
+done:
+
+ return ctx->avg_packet_rate;
+}
+
+guint32
+gst_rtp_packet_rate_ctx_get (RTPPacketRateCtx * ctx)
+{
+ return ctx->avg_packet_rate;
+}
+
+guint32
+gst_rtp_packet_rate_ctx_get_max_dropout (RTPPacketRateCtx * ctx, gint32 time_ms)
+{
+ if (time_ms <= 0 || !ctx->probed || ctx->avg_packet_rate == -1) {
+ return RTP_DEF_DROPOUT;
+ }
+
+ return MAX (RTP_MIN_DROPOUT, ctx->avg_packet_rate * time_ms / 1000);
+}
+
+guint32
+gst_rtp_packet_rate_ctx_get_max_misorder (RTPPacketRateCtx * ctx,
+ gint32 time_ms)
+{
+ if (time_ms <= 0 || !ctx->probed || ctx->avg_packet_rate == -1) {
+ return RTP_DEF_MISORDER;
+ }
+
+ return MAX (RTP_MIN_MISORDER, ctx->avg_packet_rate * time_ms / 1000);
+}
/**
* rtp_stats_init_defaults:
rtp_stats_set_bandwidths (stats, -1, -1, -1, -1);
stats->min_interval = RTP_STATS_MIN_INTERVAL;
stats->bye_timeout = RTP_STATS_BYE_TIMEOUT;
+ stats->nacks_dropped = 0;
+ stats->nacks_sent = 0;
+ stats->nacks_received = 0;
}
/**
* rtp_stats_calculate_rtcp_interval:
* @stats: an #RTPSessionStats struct
* @sender: if we are a sender
+ * @profile: RTP profile of this session
+ * @ptp: if this session is a point-to-point session
* @first: if this is the first time
*
* Calculate the RTCP interval. The result of this function is the amount of
*/
GstClockTime
rtp_stats_calculate_rtcp_interval (RTPSessionStats * stats, gboolean we_send,
- gboolean first)
+ GstRTPProfile profile, gboolean ptp, gboolean first)
{
gdouble members, senders, n;
gdouble avg_rtcp_size, rtcp_bw;
gdouble interval;
gdouble rtcp_min_time;
- /* Very first call at application start-up uses half the min
- * delay for quicker notification while still allowing some time
- * before reporting for randomization and to learn about other
- * sources so the report interval will converge to the correct
- * interval more quickly.
- */
- rtcp_min_time = stats->min_interval;
- if (first)
- rtcp_min_time /= 2.0;
+ if (profile == GST_RTP_PROFILE_AVPF || profile == GST_RTP_PROFILE_SAVPF) {
+ /* RFC 4585 3.4d), 3.5.1 */
+
+ if (first && !ptp)
+ rtcp_min_time = 1.0;
+ else
+ rtcp_min_time = 0.0;
+ } else {
+ /* Very first call at application start-up uses half the min
+ * delay for quicker notification while still allowing some time
+ * before reporting for randomization and to learn about other
+ * sources so the report interval will converge to the correct
+ * interval more quickly.
+ */
+ rtcp_min_time = stats->min_interval;
+ if (first)
+ rtcp_min_time /= 2.0;
+ }
/* Dedicate a fraction of the RTCP bandwidth to senders unless
* the number of senders is large enough that their share is
/* no bandwidth for RTCP, return NONE to signal that we don't want to send
* RTCP packets */
- if (rtcp_bw <= 0.00001)
+ if (rtcp_bw <= 0.0001)
return GST_CLOCK_TIME_NONE;
- avg_rtcp_size = stats->avg_rtcp_packet_size;
+ avg_rtcp_size = 8.0 * stats->avg_rtcp_packet_size;
/*
* The effective number of sites times the average packet size is
* the total number of octets sent when each site sends a report.
if (rtcp_bw <= 0.0001)
return GST_CLOCK_TIME_NONE;
- avg_rtcp_size = stats->avg_rtcp_packet_size;
+ avg_rtcp_size = 8.0 * stats->avg_rtcp_packet_size;
/*
* The effective number of sites times the average packet size is
* the total number of octets sent when each site sends a report.
return ret;
}
+
+static void
+_append_structure_to_value_array (GValueArray * array, GstStructure * s)
+{
+ GValue *val;
+ g_value_array_append (array, NULL);
+ val = g_value_array_get_nth (array, array->n_values - 1);
+ g_value_init (val, GST_TYPE_STRUCTURE);
+ g_value_take_boxed (val, s);
+}
+
+static void
+_structure_take_value_array (GstStructure * s,
+ const gchar * field_name, GValueArray * array)
+{
+ GValue value = G_VALUE_INIT;
+ g_value_init (&value, G_TYPE_VALUE_ARRAY);
+ g_value_take_boxed (&value, array);
+ gst_structure_take_value (s, field_name, &value);
+ g_value_unset (&value);
+}
+
+GstStructure *
+rtp_twcc_stats_get_packets_structure (GArray * twcc_packets)
+{
+ GstStructure *ret = gst_structure_new_empty ("RTPTWCCPackets");
+ GValueArray *array = g_value_array_new (0);
+ guint i;
+
+ for (i = 0; i < twcc_packets->len; i++) {
+ RTPTWCCPacket *pkt = &g_array_index (twcc_packets, RTPTWCCPacket, i);
+
+ GstStructure *pkt_s = gst_structure_new ("RTPTWCCPacket",
+ "seqnum", G_TYPE_UINT, pkt->seqnum,
+ "local-ts", G_TYPE_UINT64, pkt->local_ts,
+ "remote-ts", G_TYPE_UINT64, pkt->remote_ts,
+ "size", G_TYPE_UINT, pkt->size,
+ "lost", G_TYPE_BOOLEAN, pkt->status == RTP_TWCC_PACKET_STATUS_NOT_RECV,
+ NULL);
+ _append_structure_to_value_array (array, pkt_s);
+ }
+
+ _structure_take_value_array (ret, "packets", array);
+ return ret;
+}
+
+static void
+rtp_twcc_stats_calculate_stats (RTPTWCCStats * stats, GArray * twcc_packets)
+{
+ guint packets_recv = 0;
+ guint i;
+
+ for (i = 0; i < twcc_packets->len; i++) {
+ RTPTWCCPacket *pkt = &g_array_index (twcc_packets, RTPTWCCPacket, i);
+
+ if (pkt->status != RTP_TWCC_PACKET_STATUS_NOT_RECV)
+ packets_recv++;
+
+ if (GST_CLOCK_TIME_IS_VALID (pkt->local_ts) &&
+ GST_CLOCK_TIME_IS_VALID (stats->last_local_ts)) {
+ pkt->local_delta = GST_CLOCK_DIFF (stats->last_local_ts, pkt->local_ts);
+ }
+
+ if (GST_CLOCK_TIME_IS_VALID (pkt->remote_ts) &&
+ GST_CLOCK_TIME_IS_VALID (stats->last_remote_ts)) {
+ pkt->remote_delta =
+ GST_CLOCK_DIFF (stats->last_remote_ts, pkt->remote_ts);
+ }
+
+ if (GST_CLOCK_STIME_IS_VALID (pkt->local_delta) &&
+ GST_CLOCK_STIME_IS_VALID (pkt->remote_delta)) {
+ pkt->delta_delta = pkt->remote_delta - pkt->local_delta;
+ }
+
+ stats->last_local_ts = pkt->local_ts;
+ stats->last_remote_ts = pkt->remote_ts;
+ }
+
+ stats->packets_sent = twcc_packets->len;
+ stats->packets_recv = packets_recv;
+}
+
+static gint
+_get_window_start_index (RTPTWCCStats * stats, GstClockTime duration,
+ GstClockTime * local_duration, GstClockTime * remote_duration)
+{
+ RTPTWCCPacket *last = NULL;
+ guint i;
+
+ if (stats->packets->len < 2)
+ return -1;
+
+ for (i = 0; i < stats->packets->len; i++) {
+ guint start_index = stats->packets->len - 1 - i;
+ RTPTWCCPacket *pkt =
+ &g_array_index (stats->packets, RTPTWCCPacket, start_index);
+ if (GST_CLOCK_TIME_IS_VALID (pkt->local_ts)
+ && GST_CLOCK_TIME_IS_VALID (pkt->remote_ts)) {
+ /* first find the last valid packet */
+ if (last == NULL) {
+ last = pkt;
+ } else {
+ /* and then get the duration in local ts */
+ GstClockTimeDiff ld = GST_CLOCK_DIFF (pkt->local_ts, last->local_ts);
+ if (ld >= duration) {
+ *local_duration = ld;
+ *remote_duration = GST_CLOCK_DIFF (pkt->remote_ts, last->remote_ts);
+ return start_index;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+static void
+rtp_twcc_stats_calculate_windowed_stats (RTPTWCCStats * stats)
+{
+ guint i;
+ gint start_idx;
+ guint bits_sent = 0;
+ guint bits_recv = 0;
+ guint packets_sent = 0;
+ guint packets_recv = 0;
+ guint packets_lost;
+ GstClockTimeDiff delta_delta_sum = 0;
+ guint delta_delta_count = 0;
+ GstClockTime local_duration;
+ GstClockTime remote_duration;
+
+ start_idx = _get_window_start_index (stats, stats->window_size,
+ &local_duration, &remote_duration);
+ if (start_idx == -1) {
+ return;
+ }
+
+ /* remove the old packets */
+ if (start_idx > 0)
+ g_array_remove_range (stats->packets, 0, start_idx);
+
+ packets_sent = stats->packets->len - 1;
+
+ for (i = 0; i < packets_sent; i++) {
+ RTPTWCCPacket *pkt = &g_array_index (stats->packets, RTPTWCCPacket, i);
+
+ if (GST_CLOCK_TIME_IS_VALID (pkt->local_ts)) {
+ bits_sent += pkt->size * 8;
+ }
+
+ if (GST_CLOCK_TIME_IS_VALID (pkt->remote_ts)) {
+ bits_recv += pkt->size * 8;
+ packets_recv++;
+ }
+
+ if (GST_CLOCK_STIME_IS_VALID (pkt->delta_delta)) {
+ delta_delta_sum += pkt->delta_delta;
+ delta_delta_count++;
+ }
+ }
+
+ packets_lost = packets_sent - packets_recv;
+ stats->packet_loss_pct = (packets_lost * 100) / (gfloat) packets_sent;
+
+ if (delta_delta_count) {
+ GstClockTimeDiff avg_delta_of_delta = delta_delta_sum / delta_delta_count;
+ if (GST_CLOCK_STIME_IS_VALID (stats->avg_delta_of_delta)) {
+ stats->avg_delta_of_delta_change =
+ (avg_delta_of_delta -
+ stats->avg_delta_of_delta) / (250 * GST_USECOND);
+ }
+ stats->avg_delta_of_delta = avg_delta_of_delta;
+ }
+
+ if (local_duration > 0)
+ stats->bitrate_sent =
+ gst_util_uint64_scale (bits_sent, GST_SECOND, local_duration);
+ if (remote_duration > 0)
+ stats->bitrate_recv =
+ gst_util_uint64_scale (bits_recv, GST_SECOND, remote_duration);
+
+ GST_DEBUG ("Got stats: bits_sent: %u, bits_recv: %u, packets_sent = %u, "
+ "packets_recv: %u, packetlost_pct = %f, sent_bitrate = %u, "
+ "recv_bitrate = %u, delta-delta-avg = %" GST_STIME_FORMAT ", "
+ "delta-delta-change: %f", bits_sent, bits_recv, stats->packets_sent,
+ packets_recv, stats->packet_loss_pct, stats->bitrate_sent,
+ stats->bitrate_recv, GST_STIME_ARGS (stats->avg_delta_of_delta),
+ stats->avg_delta_of_delta_change);
+}
+
+RTPTWCCStats *
+rtp_twcc_stats_new (void)
+{
+ RTPTWCCStats *stats = g_new0 (RTPTWCCStats, 1);
+ stats->packets = g_array_new (FALSE, FALSE, sizeof (RTPTWCCPacket));
+ stats->last_local_ts = GST_CLOCK_TIME_NONE;
+ stats->last_remote_ts = GST_CLOCK_TIME_NONE;
+ stats->avg_delta_of_delta = GST_CLOCK_STIME_NONE;
+ stats->window_size = 300 * GST_MSECOND; /* FIXME: could be configurable? */
+ return stats;
+}
+
+void
+rtp_twcc_stats_free (RTPTWCCStats * stats)
+{
+ g_array_unref (stats->packets);
+ g_free (stats);
+}
+
+static GstStructure *
+rtp_twcc_stats_get_stats_structure (RTPTWCCStats * stats)
+{
+ return gst_structure_new ("RTPTWCCStats",
+ "bitrate-sent", G_TYPE_UINT, stats->bitrate_sent,
+ "bitrate-recv", G_TYPE_UINT, stats->bitrate_recv,
+ "packets-sent", G_TYPE_UINT, stats->packets_sent,
+ "packets-recv", G_TYPE_UINT, stats->packets_recv,
+ "packet-loss-pct", G_TYPE_DOUBLE, stats->packet_loss_pct,
+ "avg-delta-of-delta", G_TYPE_INT64, stats->avg_delta_of_delta, NULL);
+}
+
+GstStructure *
+rtp_twcc_stats_process_packets (RTPTWCCStats * stats, GArray * twcc_packets)
+{
+ rtp_twcc_stats_calculate_stats (stats, twcc_packets);
+ g_array_append_vals (stats->packets, twcc_packets->data, twcc_packets->len);
+ rtp_twcc_stats_calculate_windowed_stats (stats);
+ return rtp_twcc_stats_get_stats_structure (stats);
+}