gstreamer/gst/rtpmanager/rtpstats.c
Knut Inge Hvidsten 0440cb12de rtptwcc: add payloadtype to RTPTWCCPacket
The consumer of the stats can then separate between different media-types,
and do individual stats for each of them.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/927>
2021-08-25 08:36:06 +00:00

680 lines
20 KiB
C

/* 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
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* 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., 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:
* @stats: an #RTPSessionStats struct
*
* Initialize @stats with its default values.
*/
void
rtp_stats_init_defaults (RTPSessionStats * stats)
{
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_set_bandwidths:
* @stats: an #RTPSessionStats struct
* @rtp_bw: RTP bandwidth
* @rtcp_bw: RTCP bandwidth
* @rs: sender RTCP bandwidth
* @rr: receiver RTCP bandwidth
*
* Configure the bandwidth parameters in the stats. When an input variable is
* set to -1, it will be calculated from the other input variables and from the
* defaults.
*/
void
rtp_stats_set_bandwidths (RTPSessionStats * stats, guint rtp_bw,
gdouble rtcp_bw, guint rs, guint rr)
{
GST_DEBUG ("recalc bandwidths: RTP %u, RTCP %f, RS %u, RR %u", rtp_bw,
rtcp_bw, rs, rr);
/* when given, sender and receive bandwidth add up to the total
* rtcp bandwidth */
if (rs != -1 && rr != -1)
rtcp_bw = rs + rr;
/* If rtcp_bw is between 0 and 1, it is a fraction of rtp_bw */
if (rtcp_bw > 0.0 && rtcp_bw < 1.0) {
if (rtp_bw > 0.0)
rtcp_bw = rtp_bw * rtcp_bw;
else
rtcp_bw = -1.0;
}
/* RTCP is 5% of the RTP bandwidth */
if (rtp_bw == -1 && rtcp_bw > 1.0)
rtp_bw = rtcp_bw * 20;
else if (rtp_bw != -1 && rtcp_bw < 0.0)
rtcp_bw = rtp_bw / 20;
else if (rtp_bw == -1 && rtcp_bw < 0.0) {
/* nothing given, take defaults */
rtp_bw = RTP_STATS_BANDWIDTH;
rtcp_bw = rtp_bw * RTP_STATS_RTCP_FRACTION;
}
stats->bandwidth = rtp_bw;
stats->rtcp_bandwidth = rtcp_bw;
/* now figure out the fractions */
if (rs == -1) {
/* rs unknown */
if (rr == -1) {
/* both not given, use defaults */
rs = stats->rtcp_bandwidth * RTP_STATS_SENDER_FRACTION;
rr = stats->rtcp_bandwidth * RTP_STATS_RECEIVER_FRACTION;
} else {
/* rr known, calculate rs */
if (stats->rtcp_bandwidth > rr)
rs = stats->rtcp_bandwidth - rr;
else
rs = 0;
}
} else if (rr == -1) {
/* rs known, calculate rr */
if (stats->rtcp_bandwidth > rs)
rr = stats->rtcp_bandwidth - rs;
else
rr = 0;
}
if (stats->rtcp_bandwidth > 0) {
stats->sender_fraction = ((gdouble) rs) / ((gdouble) stats->rtcp_bandwidth);
stats->receiver_fraction = 1.0 - stats->sender_fraction;
} else {
/* no RTCP bandwidth, set dummy values */
stats->sender_fraction = 0.0;
stats->receiver_fraction = 0.0;
}
GST_DEBUG ("bandwidths: RTP %u, RTCP %u, RS %f, RR %f", stats->bandwidth,
stats->rtcp_bandwidth, stats->sender_fraction, stats->receiver_fraction);
}
/**
* 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
* time to wait (in nanoseconds) before sending a new RTCP message.
*
* Returns: the RTCP interval.
*/
GstClockTime
rtp_stats_calculate_rtcp_interval (RTPSessionStats * stats, gboolean we_send,
GstRTPProfile profile, gboolean ptp, gboolean first)
{
gdouble members, senders, n;
gdouble avg_rtcp_size, rtcp_bw;
gdouble interval;
gdouble rtcp_min_time;
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
* more than that fraction.
*/
n = members = stats->active_sources;
senders = (gdouble) stats->sender_sources;
rtcp_bw = stats->rtcp_bandwidth;
if (senders <= members * stats->sender_fraction) {
if (we_send) {
rtcp_bw *= stats->sender_fraction;
n = senders;
} else {
rtcp_bw *= stats->receiver_fraction;
n -= senders;
}
}
/* no bandwidth for RTCP, return NONE to signal that we don't want to send
* RTCP packets */
if (rtcp_bw <= 0.0001)
return GST_CLOCK_TIME_NONE;
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.
* Dividing this by the effective bandwidth gives the time
* interval over which those packets must be sent in order to
* meet the bandwidth target, with a minimum enforced. In that
* time interval we send one report so this time is also our
* average time between reports.
*/
GST_DEBUG ("avg size %f, n %f, rtcp_bw %f", avg_rtcp_size, n, rtcp_bw);
interval = avg_rtcp_size * n / rtcp_bw;
if (interval < rtcp_min_time)
interval = rtcp_min_time;
return interval * GST_SECOND;
}
/**
* rtp_stats_add_rtcp_jitter:
* @stats: an #RTPSessionStats struct
* @interval: an RTCP interval
*
* Apply a random jitter to the @interval. @interval is typically obtained with
* rtp_stats_calculate_rtcp_interval().
*
* Returns: the new RTCP interval.
*/
GstClockTime
rtp_stats_add_rtcp_jitter (RTPSessionStats * stats, GstClockTime interval)
{
gdouble temp;
/* see RFC 3550 p 30
* To compensate for "unconditional reconsideration" converging to a
* value below the intended average.
*/
#define COMPENSATION (2.71828 - 1.5);
temp = (interval * g_random_double_range (0.5, 1.5)) / COMPENSATION;
return (GstClockTime) temp;
}
/**
* rtp_stats_calculate_bye_interval:
* @stats: an #RTPSessionStats struct
*
* Calculate the BYE interval. The result of this function is the amount of
* time to wait (in nanoseconds) before sending a BYE message.
*
* Returns: the BYE interval.
*/
GstClockTime
rtp_stats_calculate_bye_interval (RTPSessionStats * stats)
{
gdouble members;
gdouble avg_rtcp_size, rtcp_bw;
gdouble interval;
gdouble rtcp_min_time;
/* no interval when we have less than 50 members */
if (stats->active_sources < 50)
return 0;
rtcp_min_time = (stats->min_interval) / 2.0;
/* Dedicate a fraction of the RTCP bandwidth to senders unless
* the number of senders is large enough that their share is
* more than that fraction.
*/
members = stats->bye_members;
rtcp_bw = stats->rtcp_bandwidth * stats->receiver_fraction;
/* no bandwidth for RTCP, return NONE to signal that we don't want to send
* RTCP packets */
if (rtcp_bw <= 0.0001)
return GST_CLOCK_TIME_NONE;
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.
* Dividing this by the effective bandwidth gives the time
* interval over which those packets must be sent in order to
* meet the bandwidth target, with a minimum enforced. In that
* time interval we send one report so this time is also our
* average time between reports.
*/
interval = avg_rtcp_size * members / rtcp_bw;
if (interval < rtcp_min_time)
interval = rtcp_min_time;
return interval * GST_SECOND;
}
/**
* rtp_stats_get_packets_lost:
* @stats: an #RTPSourceStats struct
*
* Calculate the total number of RTP packets lost since beginning of
* reception. Packets that arrive late are not considered lost, and
* duplicates are not taken into account. Hence, the loss may be negative
* if there are duplicates.
*
* Returns: total RTP packets lost.
*/
gint64
rtp_stats_get_packets_lost (const RTPSourceStats * stats)
{
gint64 lost;
guint64 extended_max, expected;
extended_max = stats->cycles + stats->max_seq;
expected = extended_max - stats->base_seq + 1;
lost = expected - stats->packets_received;
return lost;
}
void
rtp_stats_set_min_interval (RTPSessionStats * stats, gdouble min_interval)
{
stats->min_interval = min_interval;
}
gboolean
__g_socket_address_equal (GSocketAddress * a, GSocketAddress * b)
{
GInetSocketAddress *ia, *ib;
GInetAddress *iaa, *iab;
ia = G_INET_SOCKET_ADDRESS (a);
ib = G_INET_SOCKET_ADDRESS (b);
if (g_inet_socket_address_get_port (ia) !=
g_inet_socket_address_get_port (ib))
return FALSE;
iaa = g_inet_socket_address_get_address (ia);
iab = g_inet_socket_address_get_address (ib);
return g_inet_address_equal (iaa, iab);
}
gchar *
__g_socket_address_to_string (GSocketAddress * addr)
{
GInetSocketAddress *ia;
gchar *ret, *tmp;
ia = G_INET_SOCKET_ADDRESS (addr);
tmp = g_inet_address_to_string (g_inet_socket_address_get_address (ia));
ret = g_strdup_printf ("%s:%u", tmp, g_inet_socket_address_get_port (ia));
g_free (tmp);
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,
"payload-type", G_TYPE_UCHAR, pkt->pt,
"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);
}