mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-12 18:35:35 +00:00
rtpsession: expose timeout-inactive-sources property
In some situations it is not desirable to time out when no data is received any longer, users can opt in to this behavior via a new property. The property is also exposed on rtpbin and sdpdemux Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4880>
This commit is contained in:
parent
170dcd58db
commit
7445b73e76
9 changed files with 188 additions and 21 deletions
|
@ -229820,6 +229820,18 @@
|
|||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"timeout-inactive-rtp-sources": {
|
||||
"blurb": "Whether RTP sources that don't receive RTP or RTCP packets for longer than 5x RTCP interval should be removed",
|
||||
"conditionally-available": false,
|
||||
"construct": true,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "none",
|
||||
|
|
|
@ -79,6 +79,7 @@ enum
|
|||
#define DEFAULT_REDIRECT TRUE
|
||||
#define DEFAULT_RTCP_MODE GST_SDP_DEMUX_RTCP_MODE_SENDRECV
|
||||
#define DEFAULT_MEDIA NULL
|
||||
#define DEFAULT_TIMEOUT_INACTIVE_RTP_SOURCES TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -89,6 +90,7 @@ enum
|
|||
PROP_REDIRECT,
|
||||
PROP_RTCP_MODE,
|
||||
PROP_MEDIA,
|
||||
PROP_TIMEOUT_INACTIVE_RTP_SOURCES,
|
||||
};
|
||||
|
||||
static void gst_sdp_demux_finalize (GObject * object);
|
||||
|
@ -202,6 +204,23 @@ gst_sdp_demux_class_init (GstSDPDemuxClass * klass)
|
|||
"Media to use, e.g. audio or video (NULL = all)", DEFAULT_MEDIA,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* GstSDPDemux:timeout-inactive-rtp-sources:
|
||||
*
|
||||
* Whether inactive RTP sources in the underlying RTP session
|
||||
* should be timed out.
|
||||
*
|
||||
* Since: 1.24
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_TIMEOUT_INACTIVE_RTP_SOURCES,
|
||||
g_param_spec_boolean ("timeout-inactive-rtp-sources",
|
||||
"Time out inactive sources",
|
||||
"Whether RTP sources that don't receive RTP or RTCP packets for longer "
|
||||
"than 5x RTCP interval should be removed",
|
||||
DEFAULT_TIMEOUT_INACTIVE_RTP_SOURCES,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &rtptemplate);
|
||||
|
||||
|
@ -284,6 +303,9 @@ gst_sdp_demux_set_property (GObject * object, guint prop_id,
|
|||
demux->media = g_intern_string (g_value_get_string (value));
|
||||
GST_OBJECT_UNLOCK (demux);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_RTP_SOURCES:
|
||||
demux->timeout_inactive_rtp_sources = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -319,6 +341,9 @@ gst_sdp_demux_get_property (GObject * object, guint prop_id, GValue * value,
|
|||
g_value_set_string (value, demux->media);
|
||||
GST_OBJECT_UNLOCK (demux);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_RTP_SOURCES:
|
||||
g_value_set_boolean (value, demux->timeout_inactive_rtp_sources);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -1021,6 +1046,9 @@ gst_sdp_demux_configure_manager (GstSDPDemux * demux, char *rtsp_sdp)
|
|||
demux);
|
||||
g_signal_connect (demux->session, "on-timeout", (GCallback) on_timeout,
|
||||
demux);
|
||||
|
||||
g_object_set (demux->session, "timeout-inactive-sources",
|
||||
demux->timeout_inactive_rtp_sources, NULL);
|
||||
}
|
||||
|
||||
g_object_set (demux->session, "latency", demux->latency, NULL);
|
||||
|
|
|
@ -124,6 +124,7 @@ struct _GstSDPDemux {
|
|||
gboolean redirect;
|
||||
const gchar *media; /* if non-NULL only hook up these kinds of media (video/audio) */ /* interned string */
|
||||
GstSDPDemuxRTCPMode rtcp_mode;
|
||||
gboolean timeout_inactive_rtp_sources;
|
||||
|
||||
/* session management */
|
||||
GstElement *session;
|
||||
|
|
|
@ -17679,6 +17679,18 @@
|
|||
"type": "GstStructure",
|
||||
"writable": true
|
||||
},
|
||||
"timeout-inactive-sources": {
|
||||
"blurb": "Whether sources that don't receive RTP or RTCP packets for longer than 5x RTCP interval should be removed",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"ts-offset-smoothing-factor": {
|
||||
"blurb": "Sets a smoothing factor for the timestamp offset in number of values for a calculated running moving average. (0 = no smoothing factor)",
|
||||
"conditionally-available": false,
|
||||
|
@ -19611,6 +19623,18 @@
|
|||
"type": "GstStructure",
|
||||
"writable": false
|
||||
},
|
||||
"timeout-inactive-sources": {
|
||||
"blurb": "Whether sources that don't receive RTP or RTCP packets for longer than 5x RTCP interval should be removed",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"twcc-stats": {
|
||||
"blurb": "Various statistics from TWCC",
|
||||
"conditionally-available": false,
|
||||
|
@ -20454,6 +20478,18 @@
|
|||
"type": "GstStructure",
|
||||
"writable": false
|
||||
},
|
||||
"timeout-inactive-sources": {
|
||||
"blurb": "Whether sources that don't receive RTP or RTCP packets for longer than 5x RTCP interval should be removed",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"twcc-feedback-interval": {
|
||||
"blurb": "The interval to send TWCC reports on",
|
||||
"conditionally-available": false,
|
||||
|
|
|
@ -358,6 +358,7 @@ enum
|
|||
#define DEFAULT_MIN_TS_OFFSET MIN_TS_OFFSET_ROUND_OFF_COMP
|
||||
#define DEFAULT_TS_OFFSET_SMOOTHING_FACTOR 0
|
||||
#define DEFAULT_UPDATE_NTP64_HEADER_EXT TRUE
|
||||
#define DEFAULT_TIMEOUT_INACTIVE_SOURCES TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -391,6 +392,7 @@ enum
|
|||
PROP_FEC_DECODERS,
|
||||
PROP_FEC_ENCODERS,
|
||||
PROP_UPDATE_NTP64_HEADER_EXT,
|
||||
PROP_TIMEOUT_INACTIVE_SOURCES,
|
||||
};
|
||||
|
||||
#define GST_RTP_BIN_RTCP_SYNC_TYPE (gst_rtp_bin_rtcp_sync_get_type())
|
||||
|
@ -783,6 +785,9 @@ create_session (GstRtpBin * rtpbin, gint id)
|
|||
g_object_set (session, "update-ntp64-header-ext",
|
||||
rtpbin->update_ntp64_header_ext, NULL);
|
||||
|
||||
g_object_set (session, "timeout-inactive-sources",
|
||||
rtpbin->timeout_inactive_sources, NULL);
|
||||
|
||||
GST_OBJECT_UNLOCK (rtpbin);
|
||||
|
||||
/* provide clock_rate to the session manager when needed */
|
||||
|
@ -3002,6 +3007,22 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
|
|||
DEFAULT_UPDATE_NTP64_HEADER_EXT,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* GstRtpBin:timeout-inactive-sources:
|
||||
*
|
||||
* Whether inactive sources should be timed out
|
||||
*
|
||||
* Since: 1.24
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_TIMEOUT_INACTIVE_SOURCES,
|
||||
g_param_spec_boolean ("timeout-inactive-sources",
|
||||
"Time out inactive sources",
|
||||
"Whether sources that don't receive RTP or RTCP packets for longer "
|
||||
"than 5x RTCP interval should be removed",
|
||||
DEFAULT_TIMEOUT_INACTIVE_SOURCES,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_bin_change_state);
|
||||
gstelement_class->request_new_pad =
|
||||
GST_DEBUG_FUNCPTR (gst_rtp_bin_request_new_pad);
|
||||
|
@ -3093,6 +3114,7 @@ gst_rtp_bin_init (GstRtpBin * rtpbin)
|
|||
rtpbin->min_ts_offset_is_set = FALSE;
|
||||
rtpbin->ts_offset_smoothing_factor = DEFAULT_TS_OFFSET_SMOOTHING_FACTOR;
|
||||
rtpbin->update_ntp64_header_ext = DEFAULT_UPDATE_NTP64_HEADER_EXT;
|
||||
rtpbin->timeout_inactive_sources = DEFAULT_TIMEOUT_INACTIVE_SOURCES;
|
||||
|
||||
/* some default SDES entries */
|
||||
cname = g_strdup_printf ("user%u@host-%x", g_random_int (), g_random_int ());
|
||||
|
@ -3439,6 +3461,13 @@ gst_rtp_bin_set_property (GObject * object, guint prop_id,
|
|||
gst_rtp_bin_propagate_property_to_session (rtpbin,
|
||||
"update-ntp64-header-ext", value);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_SOURCES:
|
||||
GST_RTP_BIN_LOCK (rtpbin);
|
||||
rtpbin->timeout_inactive_sources = g_value_get_boolean (value);
|
||||
GST_RTP_BIN_UNLOCK (rtpbin);
|
||||
gst_rtp_bin_propagate_property_to_session (rtpbin,
|
||||
"timeout-inactive-sources", value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -3551,6 +3580,9 @@ gst_rtp_bin_get_property (GObject * object, guint prop_id,
|
|||
case PROP_UPDATE_NTP64_HEADER_EXT:
|
||||
g_value_set_boolean (value, rtpbin->update_ntp64_header_ext);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_SOURCES:
|
||||
g_value_set_boolean (value, rtpbin->timeout_inactive_sources);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
|
|
@ -100,6 +100,8 @@ struct _GstRtpBin {
|
|||
|
||||
gboolean update_ntp64_header_ext;
|
||||
|
||||
gboolean timeout_inactive_sources;
|
||||
|
||||
/*< private >*/
|
||||
GstRtpBinPrivate *priv;
|
||||
};
|
||||
|
|
|
@ -224,6 +224,7 @@ enum
|
|||
#define DEFAULT_NTP_TIME_SOURCE GST_RTP_NTP_TIME_SOURCE_NTP
|
||||
#define DEFAULT_RTCP_SYNC_SEND_TIME TRUE
|
||||
#define DEFAULT_UPDATE_NTP64_HEADER_EXT TRUE
|
||||
#define DEFAULT_TIMEOUT_INACTIVE_SOURCES TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -246,7 +247,8 @@ enum
|
|||
PROP_RTP_PROFILE,
|
||||
PROP_NTP_TIME_SOURCE,
|
||||
PROP_RTCP_SYNC_SEND_TIME,
|
||||
PROP_UPDATE_NTP64_HEADER_EXT
|
||||
PROP_UPDATE_NTP64_HEADER_EXT,
|
||||
PROP_TIMEOUT_INACTIVE_SOURCES,
|
||||
};
|
||||
|
||||
#define GST_RTP_SESSION_LOCK(sess) g_mutex_lock (&(sess)->priv->lock)
|
||||
|
@ -830,6 +832,22 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
|
|||
DEFAULT_UPDATE_NTP64_HEADER_EXT,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* GstRtpSession:timeout-inactive-sources:
|
||||
*
|
||||
* Whether inactive sources should be timed out
|
||||
*
|
||||
* Since: 1.24
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_TIMEOUT_INACTIVE_SOURCES,
|
||||
g_param_spec_boolean ("timeout-inactive-sources",
|
||||
"Time out inactive sources",
|
||||
"Whether sources that don't receive RTP or RTCP packets for longer "
|
||||
"than 5x RTCP interval should be removed",
|
||||
DEFAULT_TIMEOUT_INACTIVE_SOURCES,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gstelement_class->change_state =
|
||||
GST_DEBUG_FUNCPTR (gst_rtp_session_change_state);
|
||||
gstelement_class->request_new_pad =
|
||||
|
@ -1008,6 +1026,10 @@ gst_rtp_session_set_property (GObject * object, guint prop_id,
|
|||
g_object_set_property (G_OBJECT (priv->session),
|
||||
"update-ntp64-header-ext", value);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_SOURCES:
|
||||
g_object_set_property (G_OBJECT (priv->session),
|
||||
"timeout-inactive-sources", value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -1091,6 +1113,10 @@ gst_rtp_session_get_property (GObject * object, guint prop_id,
|
|||
g_object_get_property (G_OBJECT (priv->session),
|
||||
"update-ntp64-header-ext", value);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_SOURCES:
|
||||
g_object_get_property (G_OBJECT (priv->session),
|
||||
"timeout-inactive-sources", value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
|
|
@ -82,6 +82,7 @@ enum
|
|||
#define DEFAULT_FAVOR_NEW FALSE
|
||||
#define DEFAULT_TWCC_FEEDBACK_INTERVAL GST_CLOCK_TIME_NONE
|
||||
#define DEFAULT_UPDATE_NTP64_HEADER_EXT TRUE
|
||||
#define DEFAULT_TIMEOUT_INACTIVE_SOURCES TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -110,6 +111,7 @@ enum
|
|||
PROP_RTCP_DISABLE_SR_TIMESTAMP,
|
||||
PROP_TWCC_FEEDBACK_INTERVAL,
|
||||
PROP_UPDATE_NTP64_HEADER_EXT,
|
||||
PROP_TIMEOUT_INACTIVE_SOURCES,
|
||||
PROP_LAST,
|
||||
};
|
||||
|
||||
|
@ -660,6 +662,21 @@ rtp_session_class_init (RTPSessionClass * klass)
|
|||
DEFAULT_UPDATE_NTP64_HEADER_EXT,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* RTPSession:timeout-inactive-sources:
|
||||
*
|
||||
* Whether inactive sources should be timed out
|
||||
*
|
||||
* Since: 1.24
|
||||
*/
|
||||
properties[PROP_TIMEOUT_INACTIVE_SOURCES] =
|
||||
g_param_spec_boolean ("timeout-inactive-sources",
|
||||
"Time out inactive sources",
|
||||
"Whether sources that don't receive RTP or RTCP packets for longer "
|
||||
"than 5x RTCP interval should be removed",
|
||||
DEFAULT_TIMEOUT_INACTIVE_SOURCES,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, properties);
|
||||
|
||||
klass->get_source_by_ssrc =
|
||||
|
@ -706,6 +723,7 @@ rtp_session_init (RTPSession * sess)
|
|||
sess->mtu = DEFAULT_RTCP_MTU;
|
||||
|
||||
sess->update_ntp64_header_ext = DEFAULT_UPDATE_NTP64_HEADER_EXT;
|
||||
sess->timeout_inactive_sources = DEFAULT_TIMEOUT_INACTIVE_SOURCES;
|
||||
|
||||
sess->probation = DEFAULT_PROBATION;
|
||||
sess->max_dropout_time = DEFAULT_MAX_DROPOUT_TIME;
|
||||
|
@ -950,6 +968,9 @@ rtp_session_set_property (GObject * object, guint prop_id,
|
|||
case PROP_UPDATE_NTP64_HEADER_EXT:
|
||||
sess->update_ntp64_header_ext = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_SOURCES:
|
||||
sess->timeout_inactive_sources = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -1039,6 +1060,9 @@ rtp_session_get_property (GObject * object, guint prop_id,
|
|||
case PROP_UPDATE_NTP64_HEADER_EXT:
|
||||
g_value_set_boolean (value, sess->update_ntp64_header_ext);
|
||||
break;
|
||||
case PROP_TIMEOUT_INACTIVE_SOURCES:
|
||||
g_value_set_boolean (value, sess->timeout_inactive_sources);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -3793,6 +3817,7 @@ typedef struct
|
|||
gboolean may_suppress;
|
||||
GQueue output;
|
||||
guint nacked_seqnums;
|
||||
gboolean timeout_inactive_sources;
|
||||
} ReportData;
|
||||
|
||||
static void
|
||||
|
@ -4186,27 +4211,29 @@ session_cleanup (const gchar * key, RTPSource * source, ReportData * data)
|
|||
remove = TRUE;
|
||||
}
|
||||
|
||||
/* sources that were inactive for more than 5 times the deterministic reporting
|
||||
* interval get timed out. the min timeout is 5 seconds. */
|
||||
/* mind old time that might pre-date last time going to PLAYING */
|
||||
btime = MAX (source->last_activity, sess->start_time);
|
||||
if (data->current_time > btime) {
|
||||
interval = MAX (binterval * 5, 5 * GST_SECOND);
|
||||
if (data->current_time - btime > interval) {
|
||||
GST_DEBUG ("removing timeout source %08x, last %" GST_TIME_FORMAT,
|
||||
source->ssrc, GST_TIME_ARGS (btime));
|
||||
if (source->internal) {
|
||||
/* this is an internal source that is not using our suggested ssrc.
|
||||
* since there must be another source using this ssrc, we can remove
|
||||
* this one instead of making it a receiver forever */
|
||||
if (source->ssrc != sess->suggested_ssrc
|
||||
&& source->media_ssrc != sess->suggested_ssrc) {
|
||||
rtp_source_mark_bye (source, "timed out");
|
||||
/* do not schedule bye here, since we are inside the RTCP timeout
|
||||
* processing and scheduling bye will interfere with SR/RR sending */
|
||||
if (data->timeout_inactive_sources) {
|
||||
/* sources that were inactive for more than 5 times the deterministic reporting
|
||||
* interval get timed out. the min timeout is 5 seconds. */
|
||||
/* mind old time that might pre-date last time going to PLAYING */
|
||||
btime = MAX (source->last_activity, sess->start_time);
|
||||
if (data->current_time > btime) {
|
||||
interval = MAX (binterval * 5, 5 * GST_SECOND);
|
||||
if (data->current_time - btime > interval) {
|
||||
GST_DEBUG ("removing timeout source %08x, last %" GST_TIME_FORMAT,
|
||||
source->ssrc, GST_TIME_ARGS (btime));
|
||||
if (source->internal) {
|
||||
/* this is an internal source that is not using our suggested ssrc.
|
||||
* since there must be another source using this ssrc, we can remove
|
||||
* this one instead of making it a receiver forever */
|
||||
if (source->ssrc != sess->suggested_ssrc
|
||||
&& source->media_ssrc != sess->suggested_ssrc) {
|
||||
rtp_source_mark_bye (source, "timed out");
|
||||
/* do not schedule bye here, since we are inside the RTCP timeout
|
||||
* processing and scheduling bye will interfere with SR/RR sending */
|
||||
}
|
||||
} else {
|
||||
remove = TRUE;
|
||||
}
|
||||
} else {
|
||||
remove = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4660,6 +4687,7 @@ rtp_session_on_timeout (RTPSession * sess, GstClockTime current_time,
|
|||
data.num_to_report = 0;
|
||||
data.may_suppress = FALSE;
|
||||
data.nacked_seqnums = 0;
|
||||
data.timeout_inactive_sources = sess->timeout_inactive_sources;
|
||||
g_queue_init (&data.output);
|
||||
|
||||
RTP_SESSION_LOCK (sess);
|
||||
|
|
|
@ -315,6 +315,8 @@ struct _RTPSession {
|
|||
|
||||
gboolean update_ntp64_header_ext;
|
||||
|
||||
gboolean timeout_inactive_sources;
|
||||
|
||||
/* Transport-wide cc-extension */
|
||||
RTPTWCCManager *twcc;
|
||||
RTPTWCCStats *twcc_stats;
|
||||
|
|
Loading…
Reference in a new issue