| /* 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. |
| */ |
| |
| #include "rtpstats.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; |
| |
| 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->last_seqnum = seqnum; |
| ctx->last_ts = new_ts; |
| ctx->probed = TRUE; |
| return ctx->avg_packet_rate; |
| } |
| |
| diff_seqnum = gst_rtp_buffer_compare_seqnum (ctx->last_seqnum, seqnum); |
| if (diff_seqnum <= 0 || new_ts <= ctx->last_ts) { |
| return ctx->avg_packet_rate; |
| } |
| |
| 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. |
| */ |
| if (ctx->avg_packet_rate > new_packet_rate) { |
| ctx->avg_packet_rate = (7 * ctx->avg_packet_rate + new_packet_rate + 7) / 8; |
| } else { |
| ctx->avg_packet_rate = (ctx->avg_packet_rate + new_packet_rate + 1) / 2; |
| } |
| |
| ctx->last_seqnum = seqnum; |
| ctx->last_ts = new_ts; |
| |
| 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) { |
| 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) { |
| 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; |
| } |