SIGNAL_ON_SENDING_RTCP,
SIGNAL_ON_FEEDBACK_RTCP,
SIGNAL_SEND_RTCP,
+ SIGNAL_SEND_RTCP_FULL,
+ SIGNAL_ON_RECEIVING_RTCP,
LAST_SIGNAL
};
#define DEFAULT_INTERNAL_SOURCE NULL
-#define DEFAULT_BANDWIDTH RTP_STATS_BANDWIDTH
-#define DEFAULT_RTCP_FRACTION (RTP_STATS_RTCP_FRACTION * RTP_STATS_BANDWIDTH)
+#define DEFAULT_BANDWIDTH 0.0
+#define DEFAULT_RTCP_FRACTION RTP_STATS_RTCP_FRACTION
#define DEFAULT_RTCP_RR_BANDWIDTH -1
#define DEFAULT_RTCP_RS_BANDWIDTH -1
#define DEFAULT_RTCP_MTU 1400
PROP_RTCP_FEEDBACK_RETENTION_WINDOW,
PROP_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD,
PROP_PROBATION,
- PROP_STATS,
- PROP_LAST
+ PROP_STATS
};
/* update average packet size */
static void rtp_session_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
-static void rtp_session_send_rtcp (RTPSession * sess, GstClockTime max_delay);
+static gboolean rtp_session_send_rtcp (RTPSession * sess,
+ GstClockTime max_delay);
static guint rtp_session_signals[LAST_SIGNAL] = { 0 };
static RTPSource *obtain_source (RTPSession * sess, guint32 ssrc,
gboolean * created, RTPPacketInfo * pinfo, gboolean rtp);
static RTPSource *obtain_internal_source (RTPSession * sess,
- guint32 ssrc, gboolean * created);
+ guint32 ssrc, gboolean * created, GstClockTime current_time);
static GstFlowReturn rtp_session_schedule_bye_locked (RTPSession * sess,
GstClockTime current_time);
static GstClockTime calculate_rtcp_interval (RTPSession * sess,
G_STRUCT_OFFSET (RTPSessionClass, send_rtcp), NULL, NULL,
g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT64);
+ /**
+ * RTPSession::send-rtcp-full:
+ * @session: the object which received the signal
+ * @max_delay: The maximum delay after which the feedback will not be useful
+ * anymore
+ *
+ * Requests that the #RTPSession initiate a new RTCP packet as soon as
+ * possible within the requested delay.
+ *
+ * Returns: TRUE if the new RTCP packet could be scheduled within the
+ * requested delay, FALSE otherwise.
+ *
+ * Since: 1.6
+ */
+ rtp_session_signals[SIGNAL_SEND_RTCP_FULL] =
+ g_signal_new ("send-rtcp-full", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (RTPSessionClass, send_rtcp), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_UINT64);
+
+ /**
+ * RTPSession::on-receiving-rtcp
+ * @session: the object which received the signal
+ * @buffer: the #GstBuffer containing the RTCP packet that was received
+ *
+ * This signal is emitted when receiving an RTCP packet before it is handled
+ * by the session. It can be used to extract custom information from RTCP packets.
+ *
+ * Since: 1.6
+ */
+ rtp_session_signals[SIGNAL_ON_RECEIVING_RTCP] =
+ g_signal_new ("on-receiving-rtcp", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_receiving_rtcp),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
+ GST_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE);
+
g_object_class_install_property (gobject_class, PROP_INTERNAL_SSRC,
g_param_spec_uint ("internal-ssrc", "Internal SSRC",
"The internal SSRC used for the session (deprecated)",
g_param_spec_uint ("rtcp-immediate-feedback-threshold",
"RTCP Immediate Feedback threshold",
"The maximum number of members of a RTP session for which immediate"
- " feedback is used",
+ " feedback is used (DEPRECATED: has no effect and is not needed)",
0, G_MAXUINT, DEFAULT_RTCP_IMMEDIATE_FEEDBACK_THRESHOLD,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED));
g_object_class_install_property (gobject_class, PROP_PROBATION,
g_param_spec_uint ("probation", "Number of probations",
sess->mask_idx = 0;
sess->mask = 0;
- for (i = 0; i < 32; i++) {
+ /* TODO: We currently only use the first hash table but this is the
+ * beginning of an implementation for RFC2762
+ for (i = 0; i < 32; i++) {
+ */
+ for (i = 0; i < 1; i++) {
sess->ssrcs[i] =
g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) g_object_unref);
sess->first_rtcp = TRUE;
sess->next_rtcp_check_time = GST_CLOCK_TIME_NONE;
+ sess->last_rtcp_send_time = GST_CLOCK_TIME_NONE;
sess->allow_early = TRUE;
sess->next_early_rtcp_time = GST_CLOCK_TIME_NONE;
g_list_free_full (sess->conflicting_addresses,
(GDestroyNotify) rtp_conflicting_address_free);
- for (i = 0; i < 32; i++)
+ /* TODO: Change this again when implementing RFC 2762
+ * for (i = 0; i < 32; i++)
+ */
+ for (i = 0; i < 1; i++)
g_hash_table_destroy (sess->ssrcs[i]);
g_mutex_clear (&sess->lock);
* of each non-internal (=remotes) source have to be compared
* to each other.
*/
- gboolean is_doing_rtp_ptp = FALSE;
- gboolean is_doing_rtcp_ptp = FALSE;
+ gboolean is_doing_rtp_ptp;
+ gboolean is_doing_rtcp_ptp;
CompareAddrData data;
/* compare the first remote source's ip addr that receive rtp packets
/* must be called with the session lock, the returned source needs to be
* unreffed after usage. */
static RTPSource *
-obtain_internal_source (RTPSession * sess, guint32 ssrc, gboolean * created)
+obtain_internal_source (RTPSession * sess, guint32 ssrc, gboolean * created,
+ GstClockTime current_time)
{
RTPSource *source;
} else {
*created = FALSE;
}
+ /* update last activity */
+ if (current_time != GST_CLOCK_TIME_NONE) {
+ source->last_activity = current_time;
+ source->last_rtp_activity = current_time;
+ }
g_object_ref (source);
return source;
"RTT (%" GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")",
fir ? "FIR" : "PLI",
GST_TIME_ARGS (current_time - sess->last_keyframe_request),
- GST_TIME_ARGS (round_trip_in_ns));;
+ GST_TIME_ARGS (round_trip_in_ns));
return FALSE;
}
}
return;
rtp_session_request_local_key_unit (sess, src, FALSE, current_time);
+
+ src->stats.recv_pli_count++;
}
static void
return;
rtp_session_request_local_key_unit (sess, src, TRUE, current_time);
+ src->stats.recv_fir_count++;
}
static void
GST_DEBUG ("received RTCP packet");
+ g_signal_emit (sess, rtp_session_signals[SIGNAL_ON_RECEIVING_RTCP], 0,
+ buffer);
+
RTP_SESSION_LOCK (sess);
/* update pinfo stats */
update_packet_info (sess, &pinfo, FALSE, FALSE, FALSE, buffer, current_time,
gboolean created;
RTP_SESSION_LOCK (sess);
- source = obtain_internal_source (sess, ssrc, &created);
+ source = obtain_internal_source (sess, ssrc, &created, GST_CLOCK_TIME_NONE);
if (source) {
rtp_source_update_caps (source, caps);
g_object_unref (source);
}
+
+ if (gst_structure_get_uint (s, "rtx-ssrc", &ssrc)) {
+ source =
+ obtain_internal_source (sess, ssrc, &created, GST_CLOCK_TIME_NONE);
+ if (source) {
+ rtp_source_update_caps (source, caps);
+ g_object_unref (source);
+ }
+ }
RTP_SESSION_UNLOCK (sess);
}
}
current_time, running_time, -1))
goto invalid_packet;
- source = obtain_internal_source (sess, pinfo.ssrc, &created);
-
- /* update last activity */
- source->last_rtp_activity = current_time;
+ source = obtain_internal_source (sess, pinfo.ssrc, &created, current_time);
prevsender = RTP_SOURCE_IS_SENDER (source);
oldrate = source->bitrate;
g_hash_table_foreach (sess->ssrcs[sess->mask_idx],
(GHFunc) add_bitrates, &bandwidth);
- bandwidth /= 8.0;
}
- if (bandwidth < 8000)
+ if (bandwidth < RTP_STATS_BANDWIDTH)
bandwidth = RTP_STATS_BANDWIDTH;
rtp_stats_set_bandwidths (&sess->stats, bandwidth,
GstFlowReturn
rtp_session_schedule_bye (RTPSession * sess, GstClockTime current_time)
{
- GstFlowReturn result = GST_FLOW_OK;
+ GstFlowReturn result;
g_return_val_if_fail (RTP_IS_SESSION (sess), GST_FLOW_ERROR);
fci_data[1] = fci_data[2] = fci_data[3] = 0;
source->send_fir = FALSE;
+ source->stats.sent_fir_count++;
}
static void
source->send_pli = FALSE;
data->may_suppress = FALSE;
+
+ source->stats.sent_pli_count++;
}
/* construct NACK */
}
}
+ if (source->internal && source->sent_bye) {
+ GST_DEBUG ("removing internal source that has sent BYE %08x", source->ssrc);
+ 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 */
if (data->current_time > btime) {
interval = MAX (binterval * 2, 5 * GST_SECOND);
if (data->current_time - btime > interval) {
- if (source->internal && source->sent_bye) {
- /* an internal source is BYE and stopped sending RTP, remove */
- GST_DEBUG ("internal BYE source %08x timed out, last %"
- GST_TIME_FORMAT, source->ssrc, GST_TIME_ARGS (btime));
- remove = TRUE;
- } else {
- GST_DEBUG ("sender source %08x timed out and became receiver, last %"
- GST_TIME_FORMAT, source->ssrc, GST_TIME_ARGS (btime));
- sendertimeout = TRUE;
- }
+ GST_DEBUG ("sender source %08x timed out and became receiver, last %"
+ GST_TIME_FORMAT, source->ssrc, GST_TIME_ARGS (btime));
+ sendertimeout = TRUE;
}
}
}
static gboolean
is_rtcp_time (RTPSession * sess, GstClockTime current_time, ReportData * data)
{
- GstClockTime new_send_time, elapsed;
+ GstClockTime new_send_time;
GstClockTime interval;
RTPSessionStats *stats;
}
early:
- /* get elapsed time since we last reported */
- elapsed = current_time - sess->last_rtcp_send_time;
/* take interval and add jitter */
interval = data->interval;
if (interval != GST_CLOCK_TIME_NONE)
interval = rtp_stats_add_rtcp_jitter (stats, interval);
- /* perform forward reconsideration */
- if (interval != GST_CLOCK_TIME_NONE) {
- GST_DEBUG ("forward reconsideration %" GST_TIME_FORMAT ", elapsed %"
- GST_TIME_FORMAT, GST_TIME_ARGS (interval), GST_TIME_ARGS (elapsed));
- new_send_time = interval + sess->last_rtcp_send_time;
+ if (sess->last_rtcp_send_time != GST_CLOCK_TIME_NONE) {
+ /* perform forward reconsideration */
+ if (interval != GST_CLOCK_TIME_NONE) {
+ GstClockTime elapsed;
+
+ /* get elapsed time since we last reported */
+ elapsed = current_time - sess->last_rtcp_send_time;
+
+ GST_DEBUG ("forward reconsideration %" GST_TIME_FORMAT ", elapsed %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (interval), GST_TIME_ARGS (elapsed));
+ new_send_time = interval + sess->last_rtcp_send_time;
+ } else {
+ new_send_time = sess->last_rtcp_send_time;
+ }
} else {
- new_send_time = sess->last_rtcp_send_time;
+ /* If this is the first RTCP packet, we can reconsider anything based
+ * on the last RTCP send time because there was none.
+ */
+ g_warn_if_fail (!data->is_early);
+ data->is_early = FALSE;
+ new_send_time = current_time;
}
if (!data->is_early) {
RTPSource *source;
gboolean created;
- source = obtain_internal_source (sess, sess->suggested_ssrc, &created);
+ source = obtain_internal_source (sess, sess->suggested_ssrc, &created,
+ current_time);
g_object_unref (source);
}
sess->next_early_rtcp_time = GST_CLOCK_TIME_NONE;
sess->scheduled_bye = FALSE;
+ /* RFC 4585 section 3.5.2 step 6 */
+ if (!data.is_early) {
+ sess->allow_early = TRUE;
+ }
+
done:
RTP_SESSION_UNLOCK (sess);
* @max_delay: maximum delay
*
* Request transmission of early RTCP
+ *
+ * Returns: %TRUE if the related RTCP can be scheduled.
*/
-void
+gboolean
rtp_session_request_early_rtcp (RTPSession * sess, GstClockTime current_time,
GstClockTime max_delay)
{
- GstClockTime T_dither_max;
+ GstClockTime T_dither_max, T_rr;
+ gboolean ret;
/* Implements the algorithm described in RFC 4585 section 3.5.2 */
/* RFC 4585 section 3.5.2 step 2 */
if (GST_CLOCK_TIME_IS_VALID (sess->next_early_rtcp_time)) {
GST_LOG_OBJECT (sess, "already have next early rtcp time");
- goto dont_send;
+ ret = TRUE;
+ goto end;
}
if (!GST_CLOCK_TIME_IS_VALID (sess->next_rtcp_check_time)) {
GST_LOG_OBJECT (sess, "no next RTCP check time");
- goto dont_send;
+ ret = FALSE;
+ goto end;
}
- /* Ignore the request a scheduled packet will be in time anyway */
- if (current_time + max_delay > sess->next_rtcp_check_time) {
- GST_LOG_OBJECT (sess, "next scheduled time is soon %" GST_TIME_FORMAT " + %"
- GST_TIME_FORMAT " > %" GST_TIME_FORMAT,
- GST_TIME_ARGS (current_time),
- GST_TIME_ARGS (max_delay), GST_TIME_ARGS (sess->next_rtcp_check_time));
- goto dont_send;
+ /* RFC 4585 section 3.5.3 step 1
+ * If no regular RTCP packet has been sent before, then a regular
+ * RTCP packet has to be scheduled first and FB messages might be
+ * included there
+ */
+ if (!GST_CLOCK_TIME_IS_VALID (sess->last_rtcp_send_time)) {
+ GST_LOG_OBJECT (sess, "no RTCP sent yet");
+
+ if (current_time + max_delay > sess->next_rtcp_check_time) {
+ GST_LOG_OBJECT (sess,
+ "next scheduled time is soon %" GST_TIME_FORMAT " + %" GST_TIME_FORMAT
+ " > %" GST_TIME_FORMAT, GST_TIME_ARGS (current_time),
+ GST_TIME_ARGS (max_delay),
+ GST_TIME_ARGS (sess->next_rtcp_check_time));
+ ret = TRUE;
+ } else {
+ GST_LOG_OBJECT (sess,
+ "can't allow early feedback, next scheduled time is too late %"
+ GST_TIME_FORMAT " + %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (current_time), GST_TIME_ARGS (max_delay),
+ GST_TIME_ARGS (sess->next_rtcp_check_time));
+ ret = FALSE;
+ }
+ goto end;
}
+ T_rr = sess->next_rtcp_check_time - sess->last_rtcp_send_time;
+
/* RFC 4585 section 3.5.2 step 2b */
/* If the total sources is <=2, then there is only us and one peer */
/* When there is one auxiliary stream the session can still do point
T_dither_max = 0;
} else {
/* Divide by 2 because l = 0.5 */
- T_dither_max = sess->next_rtcp_check_time - sess->last_rtcp_send_time;
+ T_dither_max = T_rr;
T_dither_max /= 2;
}
/* RFC 4585 section 3.5.2 step 3 */
if (current_time + T_dither_max > sess->next_rtcp_check_time) {
- GST_LOG_OBJECT (sess, "don't send because of dither");
- goto dont_send;
- }
-
- /* RFC 4585 section 3.5.2 step 4
- * Don't send if allow_early is FALSE, but not if we are in
- * immediate mode, meaning we are part of a group of at most the
- * application-specific threshold.
- */
- if (sess->total_sources > sess->rtcp_immediate_feedback_threshold &&
- sess->allow_early == FALSE) {
- GST_LOG_OBJECT (sess, "can't allow early feedback");
- goto dont_send;
+ GST_LOG_OBJECT (sess,
+ "don't send because of dither, next scheduled time is soon %"
+ GST_TIME_FORMAT " + %" GST_TIME_FORMAT " > %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (current_time), GST_TIME_ARGS (T_dither_max),
+ GST_TIME_ARGS (sess->next_rtcp_check_time));
+ ret = TRUE;
+ goto end;
+ }
+
+ /* RFC 4585 section 3.5.2 step 4a */
+ if (sess->allow_early == FALSE) {
+ /* Ignore the request a scheduled packet will be in time anyway */
+ if (current_time + max_delay > sess->next_rtcp_check_time) {
+ GST_LOG_OBJECT (sess,
+ "next scheduled time is soon %" GST_TIME_FORMAT " + %" GST_TIME_FORMAT
+ " > %" GST_TIME_FORMAT, GST_TIME_ARGS (current_time),
+ GST_TIME_ARGS (max_delay),
+ GST_TIME_ARGS (sess->next_rtcp_check_time));
+ ret = TRUE;
+ } else {
+ GST_LOG_OBJECT (sess,
+ "can't allow early feedback, next scheduled time is too late %"
+ GST_TIME_FORMAT " + %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (current_time), GST_TIME_ARGS (max_delay),
+ GST_TIME_ARGS (sess->next_rtcp_check_time));
+ ret = FALSE;
+ }
+ goto end;
}
+ /* RFC 4585 section 3.5.2 step 4b */
if (T_dither_max) {
/* Schedule an early transmission later */
sess->next_early_rtcp_time = g_random_double () * T_dither_max +
sess->next_early_rtcp_time = current_time;
}
- GST_LOG_OBJECT (sess, "next early RTCP time %" GST_TIME_FORMAT,
- GST_TIME_ARGS (sess->next_early_rtcp_time));
+ /* RFC 4585 section 3.5.2 step 6 */
+ sess->allow_early = FALSE;
+ /* Delay next regular RTCP packet to not exceed the short-term
+ * RTCP bandwidth when using early feedback as compared to
+ * without */
+ sess->next_rtcp_check_time = sess->last_rtcp_send_time + 2 * T_rr;
+ sess->last_rtcp_send_time += T_rr;
+
+ GST_LOG_OBJECT (sess, "next early RTCP time %" GST_TIME_FORMAT
+ ", next regular RTCP time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (sess->next_early_rtcp_time),
+ GST_TIME_ARGS (sess->next_rtcp_check_time));
RTP_SESSION_UNLOCK (sess);
/* notify app of need to send packet early
if (sess->callbacks.reconsider)
sess->callbacks.reconsider (sess, sess->reconsider_user_data);
- return;
+ return TRUE;
-dont_send:
+end:
RTP_SESSION_UNLOCK (sess);
+
+ return ret;
}
-static void
+static gboolean
rtp_session_send_rtcp (RTPSession * sess, GstClockTime max_delay)
{
GstClockTime now;
if (!sess->callbacks.send_rtcp)
- return;
+ return FALSE;
now = sess->callbacks.request_time (sess, sess->request_time_user_data);
- rtp_session_request_early_rtcp (sess, now, max_delay);
+ return rtp_session_request_early_rtcp (sess, now, max_delay);
}
gboolean
{
RTPSource *src;
+ if (!rtp_session_send_rtcp (sess, 5 * GST_SECOND)) {
+ GST_DEBUG ("FIR/PLI not sent");
+ return FALSE;
+ }
+
RTP_SESSION_LOCK (sess);
src = find_source (sess, ssrc);
if (src == NULL)
}
RTP_SESSION_UNLOCK (sess);
- rtp_session_send_rtcp (sess, 200 * GST_MSECOND);
-
return TRUE;
/* ERRORS */
{
RTPSource *source;
+ if (!rtp_session_send_rtcp (sess, max_delay)) {
+ GST_DEBUG ("NACK not sent");
+ return FALSE;
+ }
+
RTP_SESSION_LOCK (sess);
source = find_source (sess, ssrc);
if (source == NULL)
rtp_source_register_nack (source, seqnum);
RTP_SESSION_UNLOCK (sess);
- rtp_session_send_rtcp (sess, max_delay);
-
return TRUE;
/* ERRORS */