X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtpmanager%2Frtpjitterbuffer.c;h=6ef7df2abfa2f5f3cd4709d9634968fbc21ba412;hb=deeb3be3ec26feef48f277d85bf55e816a228d4e;hp=d6cfc7d5bd4235983ecf7267ed736d9ff4248927;hpb=3e11ce43b9e83aff5d92e6209e38d268d564edcf;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/rtpmanager/rtpjitterbuffer.c b/gst/rtpmanager/rtpjitterbuffer.c index d6cfc7d..6ef7df2 100644 --- a/gst/rtpmanager/rtpjitterbuffer.c +++ b/gst/rtpmanager/rtpjitterbuffer.c @@ -85,7 +85,9 @@ rtp_jitter_buffer_class_init (RTPJitterBufferClass * klass) static void rtp_jitter_buffer_init (RTPJitterBuffer * jbuf) { - jbuf->packets = g_queue_new (); + g_mutex_init (&jbuf->clock_lock); + + g_queue_init (&jbuf->packets); jbuf->mode = RTP_JITTER_BUFFER_MODE_SLAVE; rtp_jitter_buffer_reset_skew (jbuf); @@ -98,7 +100,24 @@ rtp_jitter_buffer_finalize (GObject * object) jbuf = RTP_JITTER_BUFFER_CAST (object); - g_queue_free (jbuf->packets); + if (jbuf->media_clock_synced_id) + g_signal_handler_disconnect (jbuf->media_clock, + jbuf->media_clock_synced_id); + if (jbuf->media_clock) { + /* Make sure to clear any clock master before releasing the clock */ + gst_clock_set_master (jbuf->media_clock, NULL); + gst_object_unref (jbuf->media_clock); + } + + if (jbuf->pipeline_clock) + gst_object_unref (jbuf->pipeline_clock); + + /* We cannot use g_queue_clear() as it would pass the wrong size to + * g_slice_free() which may lead to data corruption in the slice allocator. + */ + rtp_jitter_buffer_flush (jbuf, NULL, NULL); + + g_mutex_clear (&jbuf->clock_lock); G_OBJECT_CLASS (rtp_jitter_buffer_parent_class)->finalize (object); } @@ -170,6 +189,7 @@ rtp_jitter_buffer_set_delay (RTPJitterBuffer * jbuf, GstClockTime delay) /** * rtp_jitter_buffer_set_clock_rate: * @jbuf: an #RTPJitterBuffer + * @clock_rate: the new clock rate * * Set the clock rate in the jitterbuffer. */ @@ -177,13 +197,8 @@ void rtp_jitter_buffer_set_clock_rate (RTPJitterBuffer * jbuf, guint32 clock_rate) { if (jbuf->clock_rate != clock_rate) { - if (jbuf->clock_rate == -1) { - GST_DEBUG ("Clock rate changed from %" G_GUINT32_FORMAT " to %" - G_GUINT32_FORMAT, jbuf->clock_rate, clock_rate); - } else { - GST_WARNING ("Clock rate changed from %" G_GUINT32_FORMAT " to %" - G_GUINT32_FORMAT, jbuf->clock_rate, clock_rate); - } + GST_DEBUG ("Clock rate changed from %" G_GUINT32_FORMAT " to %" + G_GUINT32_FORMAT, jbuf->clock_rate, clock_rate); jbuf->clock_rate = clock_rate; rtp_jitter_buffer_reset_skew (jbuf); } @@ -203,6 +218,110 @@ rtp_jitter_buffer_get_clock_rate (RTPJitterBuffer * jbuf) return jbuf->clock_rate; } +static void +media_clock_synced_cb (GstClock * clock, gboolean synced, + RTPJitterBuffer * jbuf) +{ + GstClockTime internal, external; + + g_mutex_lock (&jbuf->clock_lock); + if (jbuf->pipeline_clock) { + internal = gst_clock_get_internal_time (jbuf->media_clock); + external = gst_clock_get_time (jbuf->pipeline_clock); + + gst_clock_set_calibration (jbuf->media_clock, internal, external, 1, 1); + } + g_mutex_unlock (&jbuf->clock_lock); +} + +/** + * rtp_jitter_buffer_set_media_clock: + * @jbuf: an #RTPJitterBuffer + * @clock: (transfer full): media #GstClock + * @clock_offset: RTP time at clock epoch or -1 + * + * Sets the media clock for the media and the clock offset + * + */ +void +rtp_jitter_buffer_set_media_clock (RTPJitterBuffer * jbuf, GstClock * clock, + guint64 clock_offset) +{ + g_mutex_lock (&jbuf->clock_lock); + if (jbuf->media_clock) { + if (jbuf->media_clock_synced_id) + g_signal_handler_disconnect (jbuf->media_clock, + jbuf->media_clock_synced_id); + jbuf->media_clock_synced_id = 0; + gst_object_unref (jbuf->media_clock); + } + jbuf->media_clock = clock; + jbuf->media_clock_offset = clock_offset; + + if (jbuf->pipeline_clock && jbuf->media_clock && + jbuf->pipeline_clock != jbuf->media_clock) { + jbuf->media_clock_synced_id = + g_signal_connect (jbuf->media_clock, "synced", + G_CALLBACK (media_clock_synced_cb), jbuf); + if (gst_clock_is_synced (jbuf->media_clock)) { + GstClockTime internal, external; + + internal = gst_clock_get_internal_time (jbuf->media_clock); + external = gst_clock_get_time (jbuf->pipeline_clock); + + gst_clock_set_calibration (jbuf->media_clock, internal, external, 1, 1); + } + + gst_clock_set_master (jbuf->media_clock, jbuf->pipeline_clock); + } + g_mutex_unlock (&jbuf->clock_lock); +} + +/** + * rtp_jitter_buffer_set_pipeline_clock: + * @jbuf: an #RTPJitterBuffer + * @clock: pipeline #GstClock + * + * Sets the pipeline clock + * + */ +void +rtp_jitter_buffer_set_pipeline_clock (RTPJitterBuffer * jbuf, GstClock * clock) +{ + g_mutex_lock (&jbuf->clock_lock); + if (jbuf->pipeline_clock) + gst_object_unref (jbuf->pipeline_clock); + jbuf->pipeline_clock = clock ? gst_object_ref (clock) : NULL; + + if (jbuf->pipeline_clock && jbuf->media_clock && + jbuf->pipeline_clock != jbuf->media_clock) { + if (gst_clock_is_synced (jbuf->media_clock)) { + GstClockTime internal, external; + + internal = gst_clock_get_internal_time (jbuf->media_clock); + external = gst_clock_get_time (jbuf->pipeline_clock); + + gst_clock_set_calibration (jbuf->media_clock, internal, external, 1, 1); + } + + gst_clock_set_master (jbuf->media_clock, jbuf->pipeline_clock); + } + g_mutex_unlock (&jbuf->clock_lock); +} + +gboolean +rtp_jitter_buffer_get_rfc7273_sync (RTPJitterBuffer * jbuf) +{ + return jbuf->rfc7273_sync; +} + +void +rtp_jitter_buffer_set_rfc7273_sync (RTPJitterBuffer * jbuf, + gboolean rfc7273_sync) +{ + jbuf->rfc7273_sync = rfc7273_sync; +} + /** * rtp_jitter_buffer_reset_skew: * @jbuf: an #RTPJitterBuffer @@ -215,6 +334,7 @@ rtp_jitter_buffer_reset_skew (RTPJitterBuffer * jbuf) jbuf->base_time = -1; jbuf->base_rtptime = -1; jbuf->base_extrtp = -1; + jbuf->media_clock_base_time = -1; jbuf->ext_rtptime = -1; jbuf->last_rtptime = -1; jbuf->window_pos = 0; @@ -223,6 +343,8 @@ rtp_jitter_buffer_reset_skew (RTPJitterBuffer * jbuf) jbuf->skew = 0; jbuf->prev_send_diff = -1; jbuf->prev_out_time = -1; + jbuf->need_resync = TRUE; + GST_DEBUG ("reset skew correction"); } @@ -244,6 +366,7 @@ rtp_jitter_buffer_resync (RTPJitterBuffer * jbuf, GstClockTime time, GstClockTime gstrtptime, guint64 ext_rtptime, gboolean reset_skew) { jbuf->base_time = time; + jbuf->media_clock_base_time = -1; jbuf->base_rtptime = gstrtptime; jbuf->base_extrtp = ext_rtptime; jbuf->prev_out_time = -1; @@ -255,6 +378,7 @@ rtp_jitter_buffer_resync (RTPJitterBuffer * jbuf, GstClockTime time, jbuf->window_size = 0; jbuf->skew = 0; } + jbuf->need_resync = FALSE; } static guint64 @@ -263,8 +387,8 @@ get_buffer_level (RTPJitterBuffer * jbuf) RTPJitterBufferItem *high_buf = NULL, *low_buf = NULL; guint64 level; - /* first first buffer with timestamp */ - high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets); + /* first buffer with timestamp */ + high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (&jbuf->packets); while (high_buf) { if (high_buf->dts != -1 || high_buf->pts != -1) break; @@ -272,7 +396,7 @@ get_buffer_level (RTPJitterBuffer * jbuf) high_buf = (RTPJitterBufferItem *) g_list_previous (high_buf); } - low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets); + low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (&jbuf->packets); while (low_buf) { if (low_buf->dts != -1 || low_buf->pts != -1) break; @@ -383,7 +507,7 @@ update_buffer_level (RTPJitterBuffer * jbuf, gint * percent) * Cri : The time of the clock at the receiver for packet i * D + ni : The jitter when receiving packet i * - * We see that the network delay is irrelevant here as we can elliminate D: + * We see that the network delay is irrelevant here as we can eliminate D: * * recv_diff(i) = (Cri + ni) - (Cr0 + n0)) * @@ -408,64 +532,22 @@ update_buffer_level (RTPJitterBuffer * jbuf, gint * percent) * Returns: @time adjusted with the clock skew. */ static GstClockTime -calculate_skew (RTPJitterBuffer * jbuf, guint32 rtptime, GstClockTime time) +calculate_skew (RTPJitterBuffer * jbuf, guint64 ext_rtptime, + GstClockTime gstrtptime, GstClockTime time, gint gap, gboolean is_rtx) { - guint64 ext_rtptime; guint64 send_diff, recv_diff; gint64 delta; gint64 old; gint pos, i; - GstClockTime gstrtptime, out_time; + GstClockTime out_time; guint64 slope; - ext_rtptime = gst_rtp_buffer_ext_timestamp (&jbuf->ext_rtptime, rtptime); - - if (jbuf->last_rtptime != -1 && ext_rtptime == jbuf->last_rtptime) - return jbuf->prev_out_time; - - gstrtptime = - gst_util_uint64_scale_int (ext_rtptime, GST_SECOND, jbuf->clock_rate); - - /* keep track of the last extended rtptime */ - jbuf->last_rtptime = ext_rtptime; - - /* first time, lock on to time and gstrtptime */ - if (G_UNLIKELY (jbuf->base_time == -1)) { - jbuf->base_time = time; - jbuf->prev_out_time = -1; - GST_DEBUG ("Taking new base time %" GST_TIME_FORMAT, GST_TIME_ARGS (time)); - } - if (G_UNLIKELY (jbuf->base_rtptime == -1)) { - jbuf->base_rtptime = gstrtptime; - jbuf->base_extrtp = ext_rtptime; - jbuf->prev_send_diff = -1; - GST_DEBUG ("Taking new base rtptime %" GST_TIME_FORMAT, - GST_TIME_ARGS (gstrtptime)); - } - - if (G_LIKELY (gstrtptime >= jbuf->base_rtptime)) - send_diff = gstrtptime - jbuf->base_rtptime; - else if (time != -1) { - /* elapsed time at sender, timestamps can go backwards and thus be smaller - * than our base time, take a new base time in that case. */ - GST_WARNING ("backward timestamps at server, taking new base time"); - rtp_jitter_buffer_resync (jbuf, time, gstrtptime, ext_rtptime, FALSE); - send_diff = 0; - } else { - GST_WARNING ("backward timestamps at server but no timestamps"); - send_diff = 0; - /* at least try to get a new timestamp.. */ - jbuf->base_time = -1; - } - - GST_DEBUG ("extrtp %" G_GUINT64_FORMAT ", gstrtp %" GST_TIME_FORMAT ", base %" - GST_TIME_FORMAT ", send_diff %" GST_TIME_FORMAT, ext_rtptime, - GST_TIME_ARGS (gstrtptime), GST_TIME_ARGS (jbuf->base_rtptime), - GST_TIME_ARGS (send_diff)); + /* elapsed time at sender */ + send_diff = gstrtptime - jbuf->base_rtptime; /* we don't have an arrival timestamp so we can't do skew detection. we * should still apply a timestamp based on RTP timestamp and base_time */ - if (time == -1 || jbuf->base_time == -1) + if (time == -1 || jbuf->base_time == -1 || is_rtx) goto no_skew; /* elapsed time at receiver, includes the jitter */ @@ -495,8 +577,14 @@ calculate_skew (RTPJitterBuffer * jbuf, guint32 rtptime, GstClockTime time) rtp_jitter_buffer_resync (jbuf, time, gstrtptime, ext_rtptime, TRUE); send_diff = 0; delta = 0; + gap = 0; } + /* only do skew calculations if we didn't have a gap. if too much time + * has elapsed despite there being a gap, we resynced already. */ + if (G_UNLIKELY (gap != 0)) + goto no_skew; + pos = jbuf->window_pos; if (G_UNLIKELY (jbuf->window_filling)) { @@ -581,40 +669,9 @@ no_skew: } else { out_time += jbuf->skew; } - /* check if timestamps are not going backwards, we can only check this if we - * have a previous out time and a previous send_diff */ - if (G_LIKELY (jbuf->prev_out_time != -1 && jbuf->prev_send_diff != -1)) { - /* now check for backwards timestamps */ - if (G_UNLIKELY ( - /* if the server timestamps went up and the out_time backwards */ - (send_diff > jbuf->prev_send_diff - && out_time < jbuf->prev_out_time) || - /* if the server timestamps went backwards and the out_time forwards */ - (send_diff < jbuf->prev_send_diff - && out_time > jbuf->prev_out_time) || - /* if the server timestamps did not change */ - send_diff == jbuf->prev_send_diff)) { - GST_DEBUG ("backwards timestamps, using previous time"); - out_time = jbuf->prev_out_time; - } - } - if (time != -1 && out_time + jbuf->delay < time) { - /* if we are going to produce a timestamp that is later than the input - * timestamp, we need to reset the jitterbuffer. Likely the server paused - * temporarily */ - GST_DEBUG ("out %" GST_TIME_FORMAT " + %" G_GUINT64_FORMAT " < time %" - GST_TIME_FORMAT ", reset jitterbuffer", GST_TIME_ARGS (out_time), - jbuf->delay, GST_TIME_ARGS (time)); - rtp_jitter_buffer_resync (jbuf, time, gstrtptime, ext_rtptime, TRUE); - out_time = time; - send_diff = 0; - } } else out_time = -1; - jbuf->prev_out_time = out_time; - jbuf->prev_send_diff = send_diff; - GST_DEBUG ("skew %" G_GINT64_FORMAT ", out %" GST_TIME_FORMAT, jbuf->skew, GST_TIME_ARGS (out_time)); @@ -624,68 +681,349 @@ no_skew: static void queue_do_insert (RTPJitterBuffer * jbuf, GList * list, GList * item) { - GQueue *queue = jbuf->packets; + GQueue *queue = &jbuf->packets; - /* It's more likely that the packet was inserted in the front of the buffer */ + /* It's more likely that the packet was inserted at the tail of the queue */ if (G_LIKELY (list)) { - item->prev = list->prev; - item->next = list; - list->prev = item; - if (item->prev) { - item->prev->next = item; - } else { - queue->head = item; - } + item->prev = list; + item->next = list->next; + list->next = item; } else { - queue->tail = g_list_concat (queue->tail, item); - if (queue->tail->next) - queue->tail = queue->tail->next; - else - queue->head = queue->tail; + item->prev = NULL; + item->next = queue->head; + queue->head = item; } + if (item->next) + item->next->prev = item; + else + queue->tail = item; queue->length++; } +GstClockTime +rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts, + gboolean estimated_dts, guint32 rtptime, GstClockTime base_time, + gint gap, gboolean is_rtx) +{ + guint64 ext_rtptime; + GstClockTime gstrtptime, pts; + GstClock *media_clock, *pipeline_clock; + guint64 media_clock_offset; + gboolean rfc7273_mode; + + /* rtp time jumps are checked for during skew calculation, but bypassed + * in other mode, so mind those here and reset jb if needed. + * Only reset if valid input time, which is likely for UDP input + * where we expect this might happen due to async thread effects + * (in seek and state change cycles), but not so much for TCP input */ + if (GST_CLOCK_TIME_IS_VALID (dts) && !estimated_dts && + jbuf->mode != RTP_JITTER_BUFFER_MODE_SLAVE && + jbuf->base_time != -1 && jbuf->last_rtptime != -1) { + GstClockTime ext_rtptime = jbuf->ext_rtptime; + + ext_rtptime = gst_rtp_buffer_ext_timestamp (&ext_rtptime, rtptime); + if (ext_rtptime > jbuf->last_rtptime + 3 * jbuf->clock_rate || + ext_rtptime + 3 * jbuf->clock_rate < jbuf->last_rtptime) { + if (!is_rtx) { + /* reset even if we don't have valid incoming time; + * still better than producing possibly very bogus output timestamp */ + GST_WARNING ("rtp delta too big, reset skew"); + rtp_jitter_buffer_reset_skew (jbuf); + } else { + GST_WARNING ("rtp delta too big: ignore rtx packet"); + media_clock = NULL; + pipeline_clock = NULL; + pts = GST_CLOCK_TIME_NONE; + goto done; + } + } + } + + /* Return the last time if we got the same RTP timestamp again */ + ext_rtptime = gst_rtp_buffer_ext_timestamp (&jbuf->ext_rtptime, rtptime); + if (jbuf->last_rtptime != -1 && ext_rtptime == jbuf->last_rtptime) { + return jbuf->prev_out_time; + } + + /* keep track of the last extended rtptime */ + jbuf->last_rtptime = ext_rtptime; + + g_mutex_lock (&jbuf->clock_lock); + media_clock = jbuf->media_clock ? gst_object_ref (jbuf->media_clock) : NULL; + pipeline_clock = + jbuf->pipeline_clock ? gst_object_ref (jbuf->pipeline_clock) : NULL; + media_clock_offset = jbuf->media_clock_offset; + g_mutex_unlock (&jbuf->clock_lock); + + gstrtptime = + gst_util_uint64_scale_int (ext_rtptime, GST_SECOND, jbuf->clock_rate); + + if (G_LIKELY (jbuf->base_rtptime != -1)) { + /* check elapsed time in RTP units */ + if (gstrtptime < jbuf->base_rtptime) { + if (!is_rtx) { + /* elapsed time at sender, timestamps can go backwards and thus be + * smaller than our base time, schedule to take a new base time in + * that case. */ + GST_WARNING ("backward timestamps at server, schedule resync"); + jbuf->need_resync = TRUE; + } else { + GST_WARNING ("backward timestamps: ignore rtx packet"); + pts = GST_CLOCK_TIME_NONE; + goto done; + } + } + } + + switch (jbuf->mode) { + case RTP_JITTER_BUFFER_MODE_NONE: + case RTP_JITTER_BUFFER_MODE_BUFFER: + /* send 0 as the first timestamp and -1 for the other ones. This will + * interpolate them from the RTP timestamps with a 0 origin. In buffering + * mode we will adjust the outgoing timestamps according to the amount of + * time we spent buffering. */ + if (jbuf->base_time == -1) + dts = 0; + else + dts = -1; + break; + case RTP_JITTER_BUFFER_MODE_SYNCED: + /* synchronized clocks, take first timestamp as base, use RTP timestamps + * to interpolate */ + if (jbuf->base_time != -1 && !jbuf->need_resync) + dts = -1; + break; + case RTP_JITTER_BUFFER_MODE_SLAVE: + default: + break; + } + + /* need resync, lock on to time and gstrtptime if we can, otherwise we + * do with the previous values */ + if (G_UNLIKELY (jbuf->need_resync && dts != -1)) { + if (is_rtx) { + GST_DEBUG ("not resyncing on rtx packet, discard"); + pts = GST_CLOCK_TIME_NONE; + goto done; + } + GST_INFO ("resync to time %" GST_TIME_FORMAT ", rtptime %" + GST_TIME_FORMAT, GST_TIME_ARGS (dts), GST_TIME_ARGS (gstrtptime)); + rtp_jitter_buffer_resync (jbuf, dts, gstrtptime, ext_rtptime, FALSE); + } + + GST_DEBUG ("extrtp %" G_GUINT64_FORMAT ", gstrtp %" GST_TIME_FORMAT ", base %" + GST_TIME_FORMAT ", send_diff %" GST_TIME_FORMAT, ext_rtptime, + GST_TIME_ARGS (gstrtptime), GST_TIME_ARGS (jbuf->base_rtptime), + GST_TIME_ARGS (gstrtptime - jbuf->base_rtptime)); + + rfc7273_mode = media_clock && pipeline_clock + && gst_clock_is_synced (media_clock); + + if (rfc7273_mode && jbuf->mode == RTP_JITTER_BUFFER_MODE_SLAVE + && (media_clock_offset == -1 || !jbuf->rfc7273_sync)) { + GstClockTime internal, external; + GstClockTime rate_num, rate_denom; + GstClockTime nsrtptimediff, rtpntptime, rtpsystime; + + gst_clock_get_calibration (media_clock, &internal, &external, &rate_num, + &rate_denom); + + /* Slave to the RFC7273 media clock instead of trying to estimate it + * based on receive times and RTP timestamps */ + + if (jbuf->media_clock_base_time == -1) { + if (jbuf->base_time != -1) { + jbuf->media_clock_base_time = + gst_clock_unadjust_with_calibration (media_clock, + jbuf->base_time + base_time, internal, external, rate_num, + rate_denom); + } else { + if (dts != -1) + jbuf->media_clock_base_time = + gst_clock_unadjust_with_calibration (media_clock, dts + base_time, + internal, external, rate_num, rate_denom); + else + jbuf->media_clock_base_time = + gst_clock_get_internal_time (media_clock); + jbuf->base_rtptime = gstrtptime; + } + } + + if (gstrtptime > jbuf->base_rtptime) + nsrtptimediff = gstrtptime - jbuf->base_rtptime; + else + nsrtptimediff = 0; + + rtpntptime = nsrtptimediff + jbuf->media_clock_base_time; + + rtpsystime = + gst_clock_adjust_with_calibration (media_clock, rtpntptime, internal, + external, rate_num, rate_denom); + + if (rtpsystime > base_time) + pts = rtpsystime - base_time; + else + pts = 0; + + GST_DEBUG ("RFC7273 clock time %" GST_TIME_FORMAT ", out %" GST_TIME_FORMAT, + GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (pts)); + } else if (rfc7273_mode && (jbuf->mode == RTP_JITTER_BUFFER_MODE_SLAVE + || jbuf->mode == RTP_JITTER_BUFFER_MODE_SYNCED) + && media_clock_offset != -1 && jbuf->rfc7273_sync) { + GstClockTime ntptime, rtptime_tmp; + GstClockTime ntprtptime, rtpsystime; + GstClockTime internal, external; + GstClockTime rate_num, rate_denom; + + /* Don't do any of the dts related adjustments further down */ + dts = -1; + + /* Calculate the actual clock time on the sender side based on the + * RFC7273 clock and convert it to our pipeline clock + */ + + gst_clock_get_calibration (media_clock, &internal, &external, &rate_num, + &rate_denom); + + ntptime = gst_clock_get_internal_time (media_clock); + + ntprtptime = gst_util_uint64_scale (ntptime, jbuf->clock_rate, GST_SECOND); + ntprtptime += media_clock_offset; + ntprtptime &= 0xffffffff; + + rtptime_tmp = rtptime; + /* Check for wraparounds, we assume that the diff between current RTP + * timestamp and current media clock time can't be bigger than + * 2**31 clock units */ + if (ntprtptime > rtptime_tmp && ntprtptime - rtptime_tmp >= 0x80000000) + rtptime_tmp += G_GUINT64_CONSTANT (0x100000000); + else if (rtptime_tmp > ntprtptime && rtptime_tmp - ntprtptime >= 0x80000000) + ntprtptime += G_GUINT64_CONSTANT (0x100000000); + + if (ntprtptime > rtptime_tmp) + ntptime -= + gst_util_uint64_scale (ntprtptime - rtptime_tmp, GST_SECOND, + jbuf->clock_rate); + else + ntptime += + gst_util_uint64_scale (rtptime_tmp - ntprtptime, GST_SECOND, + jbuf->clock_rate); + + rtpsystime = + gst_clock_adjust_with_calibration (media_clock, ntptime, internal, + external, rate_num, rate_denom); + /* All this assumes that the pipeline has enough additional + * latency to cover for the network delay */ + if (rtpsystime > base_time) + pts = rtpsystime - base_time; + else + pts = 0; + + GST_DEBUG ("RFC7273 clock time %" GST_TIME_FORMAT ", out %" GST_TIME_FORMAT, + GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (pts)); + } else { + /* If we used the RFC7273 clock before and not anymore, + * we need to resync it later again */ + jbuf->media_clock_base_time = -1; + + /* do skew calculation by measuring the difference between rtptime and the + * receive dts, this function will return the skew corrected rtptime. */ + pts = calculate_skew (jbuf, ext_rtptime, gstrtptime, dts, gap, is_rtx); + } + + /* check if timestamps are not going backwards, we can only check this if we + * have a previous out time and a previous send_diff */ + if (G_LIKELY (pts != -1 && jbuf->prev_out_time != -1 + && jbuf->prev_send_diff != -1)) { + /* now check for backwards timestamps */ + if (G_UNLIKELY ( + /* if the server timestamps went up and the out_time backwards */ + (gstrtptime - jbuf->base_rtptime > jbuf->prev_send_diff + && pts < jbuf->prev_out_time) || + /* if the server timestamps went backwards and the out_time forwards */ + (gstrtptime - jbuf->base_rtptime < jbuf->prev_send_diff + && pts > jbuf->prev_out_time) || + /* if the server timestamps did not change */ + gstrtptime - jbuf->base_rtptime == jbuf->prev_send_diff)) { + GST_DEBUG ("backwards timestamps, using previous time"); + pts = jbuf->prev_out_time; + } + } + + if (gap == 0 && dts != -1 && pts + jbuf->delay < dts) { + /* if we are going to produce a timestamp that is later than the input + * timestamp, we need to reset the jitterbuffer. Likely the server paused + * temporarily */ + GST_DEBUG ("out %" GST_TIME_FORMAT " + %" G_GUINT64_FORMAT " < time %" + GST_TIME_FORMAT ", reset jitterbuffer and discard", GST_TIME_ARGS (pts), + jbuf->delay, GST_TIME_ARGS (dts)); + rtp_jitter_buffer_reset_skew (jbuf); + rtp_jitter_buffer_resync (jbuf, dts, gstrtptime, ext_rtptime, TRUE); + pts = dts; + } + + jbuf->prev_out_time = pts; + jbuf->prev_send_diff = gstrtptime - jbuf->base_rtptime; + +done: + if (media_clock) + gst_object_unref (media_clock); + if (pipeline_clock) + gst_object_unref (pipeline_clock); + + return pts; +} + + /** * rtp_jitter_buffer_insert: * @jbuf: an #RTPJitterBuffer * @item: an #RTPJitterBufferItem to insert - * @tail: TRUE when the tail element changed. + * @head: TRUE when the head element changed. * @percent: the buffering percent after insertion * * Inserts @item into the packet queue of @jbuf. The sequence number of the * packet will be used to sort the packets. This function takes ownerhip of * @buf when the function returns %TRUE. * + * When @head is %TRUE, the new packet was added at the head of the queue and + * will be available with the next call to rtp_jitter_buffer_pop() and + * rtp_jitter_buffer_peek(). + * * Returns: %FALSE if a packet with the same number already existed. */ -gboolean +static gboolean rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item, - gboolean * tail, gint * percent) + gboolean * head, gint * percent) { - GList *list = NULL; - guint32 rtptime; + GList *list, *event = NULL; guint16 seqnum; - GstClockTime dts; g_return_val_if_fail (jbuf != NULL, FALSE); g_return_val_if_fail (item != NULL, FALSE); + list = jbuf->packets.tail; + /* no seqnum, simply append then */ - if (item->seqnum == -1) { + if (item->seqnum == -1) goto append; - } seqnum = item->seqnum; - /* loop the list to skip strictly smaller seqnum buffers */ - for (list = jbuf->packets->head; list; list = g_list_next (list)) { + /* loop the list to skip strictly larger seqnum buffers */ + for (; list; list = g_list_previous (list)) { guint16 qseq; gint gap; RTPJitterBufferItem *qitem = (RTPJitterBufferItem *) list; - if (qitem->seqnum == -1) + if (qitem->seqnum == -1) { + /* keep a pointer to the first consecutive event if not already + * set. we will insert the packet after the event if we can't find + * a packet with lower sequence number before the event. */ + if (event == NULL) + event = list; continue; + } qseq = qitem->seqnum; @@ -696,62 +1034,19 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item, if (G_UNLIKELY (gap == 0)) goto duplicate; - /* seqnum < qseq, we can stop looking */ - if (G_LIKELY (gap > 0)) + /* seqnum > qseq, we can stop looking */ + if (G_LIKELY (gap < 0)) break; - } - - dts = item->dts; - if (item->rtptime == -1) - goto append; - - rtptime = item->rtptime; - /* rtp time jumps are checked for during skew calculation, but bypassed - * in other mode, so mind those here and reset jb if needed. - * Only reset if valid input time, which is likely for UDP input - * where we expect this might happen due to async thread effects - * (in seek and state change cycles), but not so much for TCP input */ - if (GST_CLOCK_TIME_IS_VALID (dts) && - jbuf->mode != RTP_JITTER_BUFFER_MODE_SLAVE && - jbuf->base_time != -1 && jbuf->last_rtptime != -1) { - GstClockTime ext_rtptime = jbuf->ext_rtptime; - - ext_rtptime = gst_rtp_buffer_ext_timestamp (&ext_rtptime, rtptime); - if (ext_rtptime > jbuf->last_rtptime + 3 * jbuf->clock_rate || - ext_rtptime + 3 * jbuf->clock_rate < jbuf->last_rtptime) { - /* reset even if we don't have valid incoming time; - * still better than producing possibly very bogus output timestamp */ - GST_WARNING ("rtp delta too big, reset skew"); - rtp_jitter_buffer_reset_skew (jbuf); - } + /* if we've found a packet with greater sequence number, cleanup the + * event pointer as the packet will be inserted before the event */ + event = NULL; } - switch (jbuf->mode) { - case RTP_JITTER_BUFFER_MODE_NONE: - case RTP_JITTER_BUFFER_MODE_BUFFER: - /* send 0 as the first timestamp and -1 for the other ones. This will - * interpollate them from the RTP timestamps with a 0 origin. In buffering - * mode we will adjust the outgoing timestamps according to the amount of - * time we spent buffering. */ - if (jbuf->base_time == -1) - dts = 0; - else - dts = -1; - break; - case RTP_JITTER_BUFFER_MODE_SYNCED: - /* synchronized clocks, take first timestamp as base, use RTP timestamps - * to interpolate */ - if (jbuf->base_time != -1) - dts = -1; - break; - case RTP_JITTER_BUFFER_MODE_SLAVE: - default: - break; - } - /* do skew calculation by measuring the difference between rtptime and the - * receive dts, this function will return the skew corrected rtptime. */ - item->pts = calculate_skew (jbuf, rtptime, dts); + /* if event is set it means that packets before the event had smaller + * sequence number, so we will insert our packet after the event */ + if (event) + list = event; append: queue_do_insert (jbuf, list, (GList *) item); @@ -762,28 +1057,176 @@ append: else if (percent) *percent = -1; - /* tail was changed when we did not find a previous packet, we set the return + /* head was changed when we did not find a previous packet, we set the return * flag when requested. */ - if (G_LIKELY (tail)) - *tail = (list == NULL); + if (G_LIKELY (head)) + *head = (list == NULL); return TRUE; /* ERRORS */ duplicate: { - GST_WARNING ("duplicate packet %d found", (gint) seqnum); + GST_DEBUG ("duplicate packet %d found", (gint) seqnum); + if (G_LIKELY (head)) + *head = FALSE; + if (percent) + *percent = -1; return FALSE; } } /** + * rtp_jitter_buffer_alloc_item: + * @data: The data stored in this item + * @type: User specific item type + * @dts: Decoding Timestamp + * @pts: Presentation Timestamp + * @seqnum: Sequence number + * @count: Number of packet this item represent + * @rtptime: The RTP specific timestamp + * @free_data: A function to free @data (optional) + * + * Create an item that can then be stored in the jitter buffer. + * + * Returns: a newly allocated RTPJitterbufferItem + */ +static RTPJitterBufferItem * +rtp_jitter_buffer_alloc_item (gpointer data, guint type, GstClockTime dts, + GstClockTime pts, guint seqnum, guint count, guint rtptime, + GDestroyNotify free_data) +{ + RTPJitterBufferItem *item; + + item = g_slice_new (RTPJitterBufferItem); + item->data = data; + item->next = NULL; + item->prev = NULL; + item->type = type; + item->dts = dts; + item->pts = pts; + item->seqnum = seqnum; + item->count = count; + item->rtptime = rtptime; + item->free_data = free_data; + + return item; +} + +static inline RTPJitterBufferItem * +alloc_event_item (GstEvent * event) +{ + return rtp_jitter_buffer_alloc_item (event, ITEM_TYPE_EVENT, -1, -1, -1, 0, + -1, (GDestroyNotify) gst_mini_object_unref); +} + +/** + * rtp_jitter_buffer_append_event: + * @jbuf: an #RTPJitterBuffer + * @event: an #GstEvent to insert + + * Inserts @event into the packet queue of @jbuf. + * + * Returns: %TRUE if the event is at the head of the queue + */ +gboolean +rtp_jitter_buffer_append_event (RTPJitterBuffer * jbuf, GstEvent * event) +{ + RTPJitterBufferItem *item = alloc_event_item (event); + gboolean head; + rtp_jitter_buffer_insert (jbuf, item, &head, NULL); + return head; +} + +/** + * rtp_jitter_buffer_append_query: + * @jbuf: an #RTPJitterBuffer + * @query: an #GstQuery to insert + + * Inserts @query into the packet queue of @jbuf. + * + * Returns: %TRUE if the query is at the head of the queue + */ +gboolean +rtp_jitter_buffer_append_query (RTPJitterBuffer * jbuf, GstQuery * query) +{ + RTPJitterBufferItem *item = + rtp_jitter_buffer_alloc_item (query, ITEM_TYPE_QUERY, -1, -1, -1, 0, -1, + NULL); + gboolean head; + rtp_jitter_buffer_insert (jbuf, item, &head, NULL); + return head; +} + +/** + * rtp_jitter_buffer_append_lost_event: + * @jbuf: an #RTPJitterBuffer + * @event: an #GstEvent to insert + * @seqnum: Sequence number + * @lost_packets: Number of lost packet this item represent + + * Inserts @event into the packet queue of @jbuf. + * + * Returns: %TRUE if the event is at the head of the queue + */ +gboolean +rtp_jitter_buffer_append_lost_event (RTPJitterBuffer * jbuf, GstEvent * event, + guint16 seqnum, guint lost_packets) +{ + RTPJitterBufferItem *item = rtp_jitter_buffer_alloc_item (event, + ITEM_TYPE_LOST, -1, -1, seqnum, lost_packets, -1, + (GDestroyNotify) gst_mini_object_unref); + gboolean head; + + if (!rtp_jitter_buffer_insert (jbuf, item, &head, NULL)) { + /* Duplicate */ + rtp_jitter_buffer_free_item (item); + head = FALSE; + } + + return head; +} + +/** + * rtp_jitter_buffer_append_buffer: + * @jbuf: an #RTPJitterBuffer + * @buf: an #GstBuffer to insert + * @seqnum: Sequence number + * @duplicate: TRUE when the packet inserted is a duplicate + * @percent: the buffering percent after insertion + * + * Inserts @buf into the packet queue of @jbuf. + * + * Returns: %TRUE if the buffer is at the head of the queue + */ +gboolean +rtp_jitter_buffer_append_buffer (RTPJitterBuffer * jbuf, GstBuffer * buf, + GstClockTime dts, GstClockTime pts, guint16 seqnum, guint rtptime, + gboolean * duplicate, gint * percent) +{ + RTPJitterBufferItem *item = rtp_jitter_buffer_alloc_item (buf, + ITEM_TYPE_BUFFER, dts, pts, seqnum, 1, rtptime, + (GDestroyNotify) gst_mini_object_unref); + gboolean head; + gboolean inserted; + + inserted = rtp_jitter_buffer_insert (jbuf, item, &head, percent); + if (!inserted) + rtp_jitter_buffer_free_item (item); + + if (duplicate) + *duplicate = !inserted; + + return head; +} + +/** * rtp_jitter_buffer_pop: * @jbuf: an #RTPJitterBuffer * @percent: the buffering percent * * Pops the oldest buffer from the packet queue of @jbuf. The popped buffer will - * have its timestamp adjusted with the incomming running_time and the detected + * have its timestamp adjusted with the incoming running_time and the detected * clock skew. * * Returns: a #GstBuffer or %NULL when there was no packet in the queue. @@ -796,7 +1239,7 @@ rtp_jitter_buffer_pop (RTPJitterBuffer * jbuf, gint * percent) g_return_val_if_fail (jbuf != NULL, NULL); - queue = jbuf->packets; + queue = &jbuf->packets; item = queue->head; if (item) { @@ -814,6 +1257,10 @@ rtp_jitter_buffer_pop (RTPJitterBuffer * jbuf, gint * percent) else if (percent) *percent = -1; + /* let's clear the pointers so we can ensure we don't free items that are + * still in the jitterbuffer */ + item->next = item->prev = NULL; + return (RTPJitterBufferItem *) item; } @@ -821,9 +1268,10 @@ rtp_jitter_buffer_pop (RTPJitterBuffer * jbuf, gint * percent) * rtp_jitter_buffer_peek: * @jbuf: an #RTPJitterBuffer * - * Peek the oldest buffer from the packet queue of @jbuf. Register a callback - * with rtp_jitter_buffer_set_tail_changed() to be notified when an older packet - * was inserted in the queue. + * Peek the oldest buffer from the packet queue of @jbuf. + * + * See rtp_jitter_buffer_insert() to check when an older packet was + * added. * * Returns: a #GstBuffer or %NULL when there was no packet in the queue. */ @@ -832,13 +1280,13 @@ rtp_jitter_buffer_peek (RTPJitterBuffer * jbuf) { g_return_val_if_fail (jbuf != NULL, NULL); - return (RTPJitterBufferItem *) jbuf->packets->head; + return (RTPJitterBufferItem *) jbuf->packets.head; } /** * rtp_jitter_buffer_flush: * @jbuf: an #RTPJitterBuffer - * @free_func: function to free each item + * @free_func: function to free each item (optional) * @user_data: user data passed to @free_func * * Flush all packets from the jitterbuffer. @@ -850,9 +1298,11 @@ rtp_jitter_buffer_flush (RTPJitterBuffer * jbuf, GFunc free_func, GList *item; g_return_if_fail (jbuf != NULL); - g_return_if_fail (free_func != NULL); - while ((item = g_queue_pop_head_link (jbuf->packets))) + if (free_func == NULL) + free_func = (GFunc) rtp_jitter_buffer_free_item; + + while ((item = g_queue_pop_head_link (&jbuf->packets))) free_func ((RTPJitterBufferItem *) item, user_data); } @@ -924,7 +1374,7 @@ rtp_jitter_buffer_num_packets (RTPJitterBuffer * jbuf) { g_return_val_if_fail (jbuf != NULL, 0); - return jbuf->packets->length; + return jbuf->packets.length; } /** @@ -945,8 +1395,8 @@ rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer * jbuf) g_return_val_if_fail (jbuf != NULL, 0); - high_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets); - low_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets); + high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (&jbuf->packets); + low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (&jbuf->packets); if (!high_buf || !low_buf || high_buf == low_buf) return 0; @@ -963,6 +1413,49 @@ rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer * jbuf) return result; } + +/* + * rtp_jitter_buffer_get_seqnum_diff: + * @jbuf: an #RTPJitterBuffer + * + * Get the difference between the seqnum of first and last packet in the + * jitterbuffer. + * + * Returns: The difference expressed in seqnum. + */ +static guint16 +rtp_jitter_buffer_get_seqnum_diff (RTPJitterBuffer * jbuf) +{ + guint32 high_seqnum, low_seqnum; + RTPJitterBufferItem *high_buf, *low_buf; + guint16 result; + + g_return_val_if_fail (jbuf != NULL, 0); + + high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (&jbuf->packets); + low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (&jbuf->packets); + + while (high_buf && high_buf->seqnum == -1) + high_buf = (RTPJitterBufferItem *) high_buf->prev; + + while (low_buf && low_buf->seqnum == -1) + low_buf = (RTPJitterBufferItem *) low_buf->next; + + if (!high_buf || !low_buf || high_buf == low_buf) + return 0; + + high_seqnum = high_buf->seqnum; + low_seqnum = low_buf->seqnum; + + /* it needs to work if ts wraps */ + if (high_seqnum >= low_seqnum) { + result = (guint32) (high_seqnum - low_seqnum); + } else { + result = (guint32) (high_seqnum + G_MAXUINT16 + 1 - low_seqnum); + } + return result; +} + /** * rtp_jitter_buffer_get_sync: * @jbuf: an #RTPJitterBuffer @@ -993,3 +1486,68 @@ rtp_jitter_buffer_get_sync (RTPJitterBuffer * jbuf, guint64 * rtptime, if (last_rtptime) *last_rtptime = jbuf->last_rtptime; } + +/** + * rtp_jitter_buffer_can_fast_start: + * @jbuf: an #RTPJitterBuffer + * @num_packets: Number of consecutive packets needed + * + * Check if in the queue if there is enough packets with consecutive seqnum in + * order to start delivering them. + * + * Returns: %TRUE if the required number of consecutive packets was found. + */ +gboolean +rtp_jitter_buffer_can_fast_start (RTPJitterBuffer * jbuf, gint num_packet) +{ + gboolean ret = TRUE; + RTPJitterBufferItem *last_item = NULL, *item; + gint i; + + if (rtp_jitter_buffer_num_packets (jbuf) < num_packet) + return FALSE; + + item = rtp_jitter_buffer_peek (jbuf); + for (i = 0; i < num_packet; i++) { + if (G_LIKELY (last_item)) { + guint16 expected_seqnum = last_item->seqnum + 1; + + if (expected_seqnum != item->seqnum) { + ret = FALSE; + break; + } + } + + last_item = item; + item = (RTPJitterBufferItem *) last_item->next; + } + + return ret; +} + +gboolean +rtp_jitter_buffer_is_full (RTPJitterBuffer * jbuf) +{ + return rtp_jitter_buffer_get_seqnum_diff (jbuf) >= 32765 && + rtp_jitter_buffer_num_packets (jbuf) > 10000; +} + + +/** + * rtp_jitter_buffer_free_item: + * @item: the item to be freed + * + * Free the jitter buffer item. + */ +void +rtp_jitter_buffer_free_item (RTPJitterBufferItem * item) +{ + g_return_if_fail (item != NULL); + /* needs to be unlinked first */ + g_return_if_fail (item->next == NULL); + g_return_if_fail (item->prev == NULL); + + if (item->data && item->free_data) + item->free_data (item->data); + g_slice_free (RTPJitterBufferItem, item); +}