/** * rtp_jitter_buffer_insert: * @jbuf: an #RTPJitterBuffer * @buf: a buffer * @time: a running_time when this buffer was received in nanoseconds * @clock_rate: the clock-rate of the payload of @buf * @max_delay: the maximum lateness of @buf * @tail: TRUE when the tail element changed. * * Inserts @buf 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. * @buf should have writable metadata when calling this function. * * Returns: %FALSE if a packet with the same number already existed. */ gboolean rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, GstBuffer * buf, GstClockTime time, guint32 clock_rate, GstClockTime max_delay, gboolean * tail) { GList *list; guint32 rtptime; guint16 seqnum; g_return_val_if_fail (jbuf != NULL, FALSE); g_return_val_if_fail (buf != NULL, FALSE); seqnum = gst_rtp_buffer_get_seq (buf); /* loop the list to skip strictly smaller seqnum buffers */ for (list = jbuf->packets->head; list; list = g_list_next (list)) { guint16 qseq; gint gap; qseq = gst_rtp_buffer_get_seq (GST_BUFFER_CAST (list->data)); /* compare the new seqnum to the one in the buffer */ gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq); /* we hit a packet with the same seqnum, notify a duplicate */ if (G_UNLIKELY (gap == 0)) goto duplicate; /* seqnum > qseq, we can stop looking */ if (G_LIKELY (gap < 0)) break; } /* do skew calculation by measuring the difference between rtptime and the * receive time, this function will retimestamp @buf with the skew corrected * running time. */ rtptime = gst_rtp_buffer_get_timestamp (buf); time = calculate_skew (jbuf, rtptime, time, clock_rate, max_delay); GST_BUFFER_TIMESTAMP (buf) = time; /* It's more likely that the packet was inserted in the front of the buffer */ if (G_LIKELY (list)) g_queue_insert_before (jbuf->packets, list, buf); else g_queue_push_tail (jbuf->packets, buf); /* tail was changed when we did not find a previous packet, we set the return * flag when requested. */ if (G_LIKELY (tail)) *tail = (list == NULL); return TRUE; /* ERRORS */ duplicate: { GST_WARNING ("duplicate packet %d found", (gint) seqnum); return FALSE; } }
static gint _compare_fec_map_info (gconstpointer a, gconstpointer b, gpointer userdata) { guint16 aseq = gst_rtp_buffer_get_seq (&RTP_FEC_MAP_INFO_NTH (userdata, a)->rtp); guint16 bseq = gst_rtp_buffer_get_seq (&RTP_FEC_MAP_INFO_NTH (userdata, b)->rtp); return gst_rtp_buffer_compare_seqnum (bseq, aseq); }
static GstFlowReturn gst_base_rtp_depayload_chain (GstPad * pad, GstBuffer * in) { GstBaseRTPDepayload *filter; GstBaseRTPDepayloadPrivate *priv; GstBaseRTPDepayloadClass *bclass; GstFlowReturn ret = GST_FLOW_OK; GstBuffer *out_buf; GstClockTime timestamp; guint16 seqnum; guint32 rtptime; gboolean reset_seq, discont; gint gap; filter = GST_BASE_RTP_DEPAYLOAD (GST_OBJECT_PARENT (pad)); priv = filter->priv; /* we must have a setcaps first */ if (G_UNLIKELY (!priv->negotiated)) goto not_negotiated; /* we must validate, it's possible that this element is plugged right after a * network receiver and we don't want to operate on invalid data */ if (G_UNLIKELY (!gst_rtp_buffer_validate (in))) goto invalid_buffer; priv->discont = GST_BUFFER_IS_DISCONT (in); timestamp = GST_BUFFER_TIMESTAMP (in); /* convert to running_time and save the timestamp, this is the timestamp * we put on outgoing buffers. */ timestamp = gst_segment_to_running_time (&filter->segment, GST_FORMAT_TIME, timestamp); priv->timestamp = timestamp; priv->duration = GST_BUFFER_DURATION (in); seqnum = gst_rtp_buffer_get_seq (in); rtptime = gst_rtp_buffer_get_timestamp (in); reset_seq = TRUE; discont = FALSE; GST_LOG_OBJECT (filter, "discont %d, seqnum %u, rtptime %u, timestamp %" GST_TIME_FORMAT, priv->discont, seqnum, rtptime, GST_TIME_ARGS (timestamp)); /* Check seqnum. This is a very simple check that makes sure that the seqnums * are striclty increasing, dropping anything that is out of the ordinary. We * can only do this when the next_seqnum is known. */ if (G_LIKELY (priv->next_seqnum != -1)) { gap = gst_rtp_buffer_compare_seqnum (seqnum, priv->next_seqnum); /* if we have no gap, all is fine */ if (G_UNLIKELY (gap != 0)) { GST_LOG_OBJECT (filter, "got packet %u, expected %u, gap %d", seqnum, priv->next_seqnum, gap); if (gap < 0) { /* seqnum > next_seqnum, we are missing some packets, this is always a * DISCONT. */ GST_LOG_OBJECT (filter, "%d missing packets", gap); discont = TRUE; } else { /* seqnum < next_seqnum, we have seen this packet before or the sender * could be restarted. If the packet is not too old, we throw it away as * a duplicate, otherwise we mark discont and continue. 100 misordered * packets is a good threshold. See also RFC 4737. */ if (gap < 100) goto dropping; GST_LOG_OBJECT (filter, "%d > 100, packet too old, sender likely restarted", gap); discont = TRUE; } } } priv->next_seqnum = (seqnum + 1) & 0xffff; if (G_UNLIKELY (discont && !priv->discont)) { GST_LOG_OBJECT (filter, "mark DISCONT on input buffer"); /* we detected a seqnum discont but the buffer was not flagged with a discont, * set the discont flag so that the subclass can throw away old data. */ priv->discont = TRUE; GST_BUFFER_FLAG_SET (in, GST_BUFFER_FLAG_DISCONT); } bclass = GST_BASE_RTP_DEPAYLOAD_GET_CLASS (filter); if (G_UNLIKELY (bclass->process == NULL)) goto no_process; /* let's send it out to processing */ out_buf = bclass->process (filter, in); if (out_buf) { /* we pass rtptime as backward compatibility, in reality, the incomming * buffer timestamp is always applied to the outgoing packet. */ ret = gst_base_rtp_depayload_push_ts (filter, rtptime, out_buf); } gst_buffer_unref (in); return ret; /* ERRORS */ not_negotiated: { /* this is not fatal but should be filtered earlier */ GST_ELEMENT_ERROR (filter, CORE, NEGOTIATION, (NULL), ("Not RTP format was negotiated")); gst_buffer_unref (in); return GST_FLOW_NOT_NEGOTIATED; } invalid_buffer: { /* this is not fatal but should be filtered earlier */ GST_ELEMENT_WARNING (filter, STREAM, DECODE, (NULL), ("Received invalid RTP payload, dropping")); gst_buffer_unref (in); return GST_FLOW_OK; } dropping: { GST_WARNING_OBJECT (filter, "%d <= 100, dropping old packet", gap); gst_buffer_unref (in); return GST_FLOW_OK; } no_process: { /* this is not fatal but should be filtered earlier */ GST_ELEMENT_ERROR (filter, STREAM, NOT_IMPLEMENTED, (NULL), ("The subclass does not have a process method")); gst_buffer_unref (in); return GST_FLOW_ERROR; } }
/** * rtp_jitter_buffer_insert: * @jbuf: an #RTPJitterBuffer * @item: an #RTPJitterBufferItem to insert * @tail: TRUE when the tail 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. * * Returns: %FALSE if a packet with the same number already existed. */ gboolean rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item, gboolean * tail, gint * percent) { GList *list = NULL; guint32 rtptime; guint16 seqnum; GstClockTime dts; g_return_val_if_fail (jbuf != NULL, FALSE); g_return_val_if_fail (item != NULL, FALSE); /* no seqnum, simply append then */ 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)) { guint16 qseq; gint gap; RTPJitterBufferItem *qitem = (RTPJitterBufferItem *) list; if (qitem->seqnum == -1) continue; qseq = qitem->seqnum; /* compare the new seqnum to the one in the buffer */ gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq); /* we hit a packet with the same seqnum, notify a duplicate */ if (G_UNLIKELY (gap == 0)) goto duplicate; /* 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); } } 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); append: queue_do_insert (jbuf, list, (GList *) item); /* buffering mode, update buffer stats */ if (jbuf->mode == RTP_JITTER_BUFFER_MODE_BUFFER) update_buffer_level (jbuf, percent); else if (percent) *percent = -1; /* tail was changed when we did not find a previous packet, we set the return * flag when requested. */ if (G_LIKELY (tail)) *tail = (list == NULL); return TRUE; /* ERRORS */ duplicate: { GST_WARNING ("duplicate packet %d found", (gint) seqnum); return FALSE; } }
static GstFlowReturn gst_rtp_jitter_buffer_chain (GstPad * pad, GstBuffer * buffer) { GstRtpJitterBuffer *jitterbuffer; GstRtpJitterBufferPrivate *priv; guint16 seqnum; GstFlowReturn ret = GST_FLOW_OK; GstClockTime timestamp; guint64 latency_ts; gboolean tail; jitterbuffer = GST_RTP_JITTER_BUFFER (gst_pad_get_parent (pad)); if (!gst_rtp_buffer_validate (buffer)) goto invalid_buffer; priv = jitterbuffer->priv; if (priv->last_pt != gst_rtp_buffer_get_payload_type (buffer)) { GstCaps *caps; priv->last_pt = gst_rtp_buffer_get_payload_type (buffer); /* reset clock-rate so that we get a new one */ priv->clock_rate = -1; /* Try to get the clock-rate from the caps first if we can. If there are no * caps we must fire the signal to get the clock-rate. */ if ((caps = GST_BUFFER_CAPS (buffer))) { gst_jitter_buffer_sink_parse_caps (jitterbuffer, caps); } } if (priv->clock_rate == -1) { guint8 pt; /* no clock rate given on the caps, try to get one with the signal */ pt = gst_rtp_buffer_get_payload_type (buffer); gst_rtp_jitter_buffer_get_clock_rate (jitterbuffer, pt); if (priv->clock_rate == -1) goto not_negotiated; } /* take the timestamp of the buffer. This is the time when the packet was * received and is used to calculate jitter and clock skew. We will adjust * this timestamp with the smoothed value after processing it in the * jitterbuffer. */ timestamp = GST_BUFFER_TIMESTAMP (buffer); /* bring to running time */ timestamp = gst_segment_to_running_time (&priv->segment, GST_FORMAT_TIME, timestamp); seqnum = gst_rtp_buffer_get_seq (buffer); GST_DEBUG_OBJECT (jitterbuffer, "Received packet #%d at time %" GST_TIME_FORMAT, seqnum, GST_TIME_ARGS (timestamp)); JBUF_LOCK_CHECK (priv, out_flushing); /* don't accept more data on EOS */ if (priv->eos) goto have_eos; /* let's check if this buffer is too late, we can only accept packets with * bigger seqnum than the one we last pushed. */ if (priv->last_popped_seqnum != -1) { gint gap; gap = gst_rtp_buffer_compare_seqnum (priv->last_popped_seqnum, seqnum); if (gap <= 0) { /* priv->last_popped_seqnum >= seqnum, this packet is too late or the * sender might have been restarted with different seqnum. */ if (gap < -100) { GST_DEBUG_OBJECT (jitterbuffer, "reset: buffer too old %d", gap); priv->last_popped_seqnum = -1; priv->next_seqnum = -1; } else { goto too_late; } } else { /* priv->last_popped_seqnum < seqnum, this is a new packet */ if (gap > 3000) { GST_DEBUG_OBJECT (jitterbuffer, "reset: too many dropped packets %d", gap); priv->last_popped_seqnum = -1; priv->next_seqnum = -1; } } } /* let's drop oldest packet if the queue is already full and drop-on-latency * is set. We can only do this when there actually is a latency. When no * latency is set, we just pump it in the queue and let the other end push it * out as fast as possible. */ if (priv->latency_ms && priv->drop_on_latency) { latency_ts = gst_util_uint64_scale_int (priv->latency_ms, priv->clock_rate, 1000); if (rtp_jitter_buffer_get_ts_diff (priv->jbuf) >= latency_ts) { GstBuffer *old_buf; GST_DEBUG_OBJECT (jitterbuffer, "Queue full, dropping old packet #%d", seqnum); old_buf = rtp_jitter_buffer_pop (priv->jbuf); gst_buffer_unref (old_buf); } } /* now insert the packet into the queue in sorted order. This function returns * FALSE if a packet with the same seqnum was already in the queue, meaning we * have a duplicate. */ if (!rtp_jitter_buffer_insert (priv->jbuf, buffer, timestamp, priv->clock_rate, &tail)) goto duplicate; /* signal addition of new buffer when the _loop is waiting. */ if (priv->waiting) JBUF_SIGNAL (priv); /* let's unschedule and unblock any waiting buffers. We only want to do this * when the tail buffer changed */ if (priv->clock_id && tail) { GST_DEBUG_OBJECT (jitterbuffer, "Unscheduling waiting buffer, new tail buffer"); gst_clock_id_unschedule (priv->clock_id); } GST_DEBUG_OBJECT (jitterbuffer, "Pushed packet #%d, now %d packets", seqnum, rtp_jitter_buffer_num_packets (priv->jbuf)); finished: JBUF_UNLOCK (priv); gst_object_unref (jitterbuffer); return ret; /* ERRORS */ invalid_buffer: { /* this is not fatal but should be filtered earlier */ GST_ELEMENT_WARNING (jitterbuffer, STREAM, DECODE, (NULL), ("Received invalid RTP payload, dropping")); gst_buffer_unref (buffer); gst_object_unref (jitterbuffer); return GST_FLOW_OK; } not_negotiated: { GST_WARNING_OBJECT (jitterbuffer, "No clock-rate in caps!"); gst_buffer_unref (buffer); gst_object_unref (jitterbuffer); return GST_FLOW_OK; } out_flushing: { ret = priv->srcresult; GST_DEBUG_OBJECT (jitterbuffer, "flushing %s", gst_flow_get_name (ret)); gst_buffer_unref (buffer); goto finished; } have_eos: { ret = GST_FLOW_UNEXPECTED; GST_WARNING_OBJECT (jitterbuffer, "we are EOS, refusing buffer"); gst_buffer_unref (buffer); goto finished; } too_late: { GST_WARNING_OBJECT (jitterbuffer, "Packet #%d too late as #%d was already" " popped, dropping", seqnum, priv->last_popped_seqnum); priv->num_late++; gst_buffer_unref (buffer); goto finished; } duplicate: { GST_WARNING_OBJECT (jitterbuffer, "Duplicate packet #%d detected, dropping", seqnum); priv->num_duplicates++; gst_buffer_unref (buffer); goto finished; } }
/** * This funcion will push out buffers on the source pad. * * For each pushed buffer, the seqnum is recorded, if the next buffer B has a * different seqnum (missing packets before B), this function will wait for the * missing packet to arrive up to the timestamp of buffer B. */ static void gst_rtp_jitter_buffer_loop (GstRtpJitterBuffer * jitterbuffer) { GstRtpJitterBufferPrivate *priv; GstBuffer *outbuf; GstFlowReturn result; guint16 seqnum; guint32 next_seqnum; GstClockTime timestamp, out_time; gboolean discont = FALSE; gint gap; priv = jitterbuffer->priv; JBUF_LOCK_CHECK (priv, flushing); again: GST_DEBUG_OBJECT (jitterbuffer, "Peeking item"); while (TRUE) { /* always wait if we are blocked */ if (!priv->blocked) { /* if we have a packet, we can exit the loop and grab it */ if (rtp_jitter_buffer_num_packets (priv->jbuf) > 0) break; /* no packets but we are EOS, do eos logic */ if (priv->eos) goto do_eos; } /* underrun, wait for packets or flushing now */ priv->waiting = TRUE; JBUF_WAIT_CHECK (priv, flushing); priv->waiting = FALSE; } /* peek a buffer, we're just looking at the timestamp and the sequence number. * If all is fine, we'll pop and push it. If the sequence number is wrong we * wait on the timestamp. In the chain function we will unlock the wait when a * new buffer is available. The peeked buffer is valid for as long as we hold * the jitterbuffer lock. */ outbuf = rtp_jitter_buffer_peek (priv->jbuf); /* get the seqnum and the next expected seqnum */ seqnum = gst_rtp_buffer_get_seq (outbuf); next_seqnum = priv->next_seqnum; /* get the timestamp, this is already corrected for clock skew by the * jitterbuffer */ timestamp = GST_BUFFER_TIMESTAMP (outbuf); GST_DEBUG_OBJECT (jitterbuffer, "Peeked buffer #%d, expect #%d, timestamp %" GST_TIME_FORMAT ", now %d left", seqnum, next_seqnum, GST_TIME_ARGS (timestamp), rtp_jitter_buffer_num_packets (priv->jbuf)); /* apply our timestamp offset to the incomming buffer, this will be our output * timestamp. */ out_time = apply_offset (jitterbuffer, timestamp); /* get the gap between this and the previous packet. If we don't know the * previous packet seqnum assume no gap. */ if (next_seqnum != -1) { gap = gst_rtp_buffer_compare_seqnum (next_seqnum, seqnum); /* if we have a packet that we already pushed or considered dropped, pop it * off and get the next packet */ if (gap < 0) { GST_DEBUG_OBJECT (jitterbuffer, "Old packet #%d, next #%d dropping", seqnum, next_seqnum); outbuf = rtp_jitter_buffer_pop (priv->jbuf); gst_buffer_unref (outbuf); goto again; } } else { GST_DEBUG_OBJECT (jitterbuffer, "no next seqnum known, first packet"); gap = -1; } /* If we don't know what the next seqnum should be (== -1) we have to wait * because it might be possible that we are not receiving this buffer in-order, * a buffer with a lower seqnum could arrive later and we want to push that * earlier buffer before this buffer then. * If we know the expected seqnum, we can compare it to the current seqnum to * determine if we have missing a packet. If we have a missing packet (which * must be before this packet) we can wait for it until the deadline for this * packet expires. */ if (gap != 0 && out_time != -1) { GstClockID id; GstClockTime sync_time; GstClockReturn ret; GstClock *clock; GstClockTime duration = GST_CLOCK_TIME_NONE; if (gap > 0) { /* we have a gap */ GST_WARNING_OBJECT (jitterbuffer, "Sequence number GAP detected: expected %d instead of %d (%d missing)", next_seqnum, seqnum, gap); if (priv->last_out_time != -1) { GST_DEBUG_OBJECT (jitterbuffer, "out_time %" GST_TIME_FORMAT ", last %" GST_TIME_FORMAT, GST_TIME_ARGS (out_time), GST_TIME_ARGS (priv->last_out_time)); /* interpolate between the current time and the last time based on * number of packets we are missing, this is the estimated duration * for the missing packet based on equidistant packet spacing. Also make * sure we never go negative. */ if (out_time > priv->last_out_time) duration = (out_time - priv->last_out_time) / (gap + 1); else goto lost; GST_DEBUG_OBJECT (jitterbuffer, "duration %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); /* add this duration to the timestamp of the last packet we pushed */ out_time = (priv->last_out_time + duration); } } else { /* we don't know what the next_seqnum should be, wait for the last * possible moment to push this buffer, maybe we get an earlier seqnum * while we wait */ GST_DEBUG_OBJECT (jitterbuffer, "First buffer %d, do sync", seqnum); } GST_OBJECT_LOCK (jitterbuffer); clock = GST_ELEMENT_CLOCK (jitterbuffer); if (!clock) { GST_OBJECT_UNLOCK (jitterbuffer); /* let's just push if there is no clock */ goto push_buffer; } GST_DEBUG_OBJECT (jitterbuffer, "sync to timestamp %" GST_TIME_FORMAT, GST_TIME_ARGS (out_time)); /* prepare for sync against clock */ sync_time = out_time + GST_ELEMENT_CAST (jitterbuffer)->base_time; /* add latency, this includes our own latency and the peer latency. */ sync_time += (priv->latency_ms * GST_MSECOND); sync_time += priv->peer_latency; /* create an entry for the clock */ id = priv->clock_id = gst_clock_new_single_shot_id (clock, sync_time); GST_OBJECT_UNLOCK (jitterbuffer); /* release the lock so that the other end can push stuff or unlock */ JBUF_UNLOCK (priv); ret = gst_clock_id_wait (id, NULL); JBUF_LOCK (priv); /* and free the entry */ gst_clock_id_unref (id); priv->clock_id = NULL; /* at this point, the clock could have been unlocked by a timeout, a new * tail element was added to the queue or because we are shutting down. Check * for shutdown first. */ if (priv->srcresult != GST_FLOW_OK) goto flushing; /* if we got unscheduled and we are not flushing, it's because a new tail * element became available in the queue. Grab it and try to push or sync. */ if (ret == GST_CLOCK_UNSCHEDULED) { GST_DEBUG_OBJECT (jitterbuffer, "Wait got unscheduled, will retry to push with new buffer"); goto again; } lost: /* we now timed out, this means we lost a packet or finished synchronizing * on the first buffer. */ if (gap > 0) { GstEvent *event; /* we had a gap and thus we lost a packet. Create an event for this. */ GST_DEBUG_OBJECT (jitterbuffer, "Packet #%d lost", next_seqnum); priv->num_late++; discont = TRUE; if (priv->do_lost) { /* create paket lost event */ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new ("GstRTPPacketLost", "seqnum", G_TYPE_UINT, (guint) next_seqnum, "timestamp", G_TYPE_UINT64, out_time, "duration", G_TYPE_UINT64, duration, NULL)); gst_pad_push_event (priv->srcpad, event); } /* update our expected next packet */ priv->last_popped_seqnum = next_seqnum; priv->last_out_time = out_time; priv->next_seqnum = (next_seqnum + 1) & 0xffff; /* look for next packet */ goto again; } /* there was no known gap,just the first packet, exit the loop and push */ GST_DEBUG_OBJECT (jitterbuffer, "First packet #%d synced", seqnum); /* get new timestamp, latency might have changed */ out_time = apply_offset (jitterbuffer, timestamp); } push_buffer: /* when we get here we are ready to pop and push the buffer */ outbuf = rtp_jitter_buffer_pop (priv->jbuf); if (discont || priv->discont) { /* set DISCONT flag when we missed a packet. */ outbuf = gst_buffer_make_metadata_writable (outbuf); GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); priv->discont = FALSE; } /* apply timestamp with offset to buffer now */ GST_BUFFER_TIMESTAMP (outbuf) = out_time; /* now we are ready to push the buffer. Save the seqnum and release the lock * so the other end can push stuff in the queue again. */ priv->last_popped_seqnum = seqnum; priv->last_out_time = out_time; priv->next_seqnum = (seqnum + 1) & 0xffff; JBUF_UNLOCK (priv); /* push buffer */ GST_DEBUG_OBJECT (jitterbuffer, "Pushing buffer %d, timestamp %" GST_TIME_FORMAT, seqnum, GST_TIME_ARGS (out_time)); result = gst_pad_push (priv->srcpad, outbuf); if (result != GST_FLOW_OK) goto pause; return; /* ERRORS */ do_eos: { /* store result, we are flushing now */ GST_DEBUG_OBJECT (jitterbuffer, "We are EOS, pushing EOS downstream"); priv->srcresult = GST_FLOW_UNEXPECTED; gst_pad_pause_task (priv->srcpad); gst_pad_push_event (priv->srcpad, gst_event_new_eos ()); JBUF_UNLOCK (priv); return; } flushing: { GST_DEBUG_OBJECT (jitterbuffer, "we are flushing"); gst_pad_pause_task (priv->srcpad); JBUF_UNLOCK (priv); return; } pause: { const gchar *reason = gst_flow_get_name (result); GST_DEBUG_OBJECT (jitterbuffer, "pausing task, reason %s", reason); JBUF_LOCK (priv); /* store result */ priv->srcresult = result; /* we don't post errors or anything because upstream will do that for us * when we pass the return value upstream. */ gst_pad_pause_task (priv->srcpad); JBUF_UNLOCK (priv); return; } }
/* takes ownership of the input buffer */ static GstFlowReturn gst_rtp_base_depayload_handle_buffer (GstRTPBaseDepayload * filter, GstRTPBaseDepayloadClass * bclass, GstBuffer * in) { GstBuffer *(*process_rtp_packet_func) (GstRTPBaseDepayload * base, GstRTPBuffer * rtp_buffer); GstBuffer *(*process_func) (GstRTPBaseDepayload * base, GstBuffer * in); GstRTPBaseDepayloadPrivate *priv; GstFlowReturn ret = GST_FLOW_OK; GstBuffer *out_buf; guint32 ssrc; guint16 seqnum; guint32 rtptime; gboolean discont, buf_discont; gint gap; GstRTPBuffer rtp = { NULL }; priv = filter->priv; process_func = bclass->process; process_rtp_packet_func = bclass->process_rtp_packet; /* we must have a setcaps first */ if (G_UNLIKELY (!priv->negotiated)) goto not_negotiated; if (G_UNLIKELY (!gst_rtp_buffer_map (in, GST_MAP_READ, &rtp))) goto invalid_buffer; buf_discont = GST_BUFFER_IS_DISCONT (in); priv->pts = GST_BUFFER_PTS (in); priv->dts = GST_BUFFER_DTS (in); priv->duration = GST_BUFFER_DURATION (in); ssrc = gst_rtp_buffer_get_ssrc (&rtp); seqnum = gst_rtp_buffer_get_seq (&rtp); rtptime = gst_rtp_buffer_get_timestamp (&rtp); priv->last_seqnum = seqnum; priv->last_rtptime = rtptime; discont = buf_discont; GST_LOG_OBJECT (filter, "discont %d, seqnum %u, rtptime %u, pts %" GST_TIME_FORMAT ", dts %" GST_TIME_FORMAT, buf_discont, seqnum, rtptime, GST_TIME_ARGS (priv->pts), GST_TIME_ARGS (priv->dts)); /* Check seqnum. This is a very simple check that makes sure that the seqnums * are strictly increasing, dropping anything that is out of the ordinary. We * can only do this when the next_seqnum is known. */ if (G_LIKELY (priv->next_seqnum != -1)) { if (ssrc != priv->last_ssrc) { GST_LOG_OBJECT (filter, "New ssrc %u (current ssrc %u), sender restarted", ssrc, priv->last_ssrc); discont = TRUE; } else { gap = gst_rtp_buffer_compare_seqnum (seqnum, priv->next_seqnum); /* if we have no gap, all is fine */ if (G_UNLIKELY (gap != 0)) { GST_LOG_OBJECT (filter, "got packet %u, expected %u, gap %d", seqnum, priv->next_seqnum, gap); if (gap < 0) { /* seqnum > next_seqnum, we are missing some packets, this is always a * DISCONT. */ GST_LOG_OBJECT (filter, "%d missing packets", gap); discont = TRUE; } else { /* seqnum < next_seqnum, we have seen this packet before, have a * reordered packet or the sender could be restarted. If the packet * is not too old, we throw it away as a duplicate. Otherwise we * mark discont and continue assuming the sender has restarted. See * also RFC 4737. */ GST_WARNING ("gap %d <= priv->max_reorder %d -> dropping %d", gap, priv->max_reorder, gap <= priv->max_reorder); if (gap <= priv->max_reorder) goto dropping; GST_LOG_OBJECT (filter, "%d > %d, packet too old, sender likely restarted", gap, priv->max_reorder); discont = TRUE; } } } } priv->next_seqnum = (seqnum + 1) & 0xffff; priv->last_ssrc = ssrc; if (G_UNLIKELY (discont)) { priv->discont = TRUE; if (!buf_discont) { gpointer old_inbuf = in; /* we detected a seqnum discont but the buffer was not flagged with a discont, * set the discont flag so that the subclass can throw away old data. */ GST_LOG_OBJECT (filter, "mark DISCONT on input buffer"); in = gst_buffer_make_writable (in); GST_BUFFER_FLAG_SET (in, GST_BUFFER_FLAG_DISCONT); /* depayloaders will check flag on rtpbuffer->buffer, so if the input * buffer was not writable already we need to remap to make our * newly-flagged buffer current on the rtpbuffer */ if (in != old_inbuf) { gst_rtp_buffer_unmap (&rtp); if (G_UNLIKELY (!gst_rtp_buffer_map (in, GST_MAP_READ, &rtp))) goto invalid_buffer; } } } /* prepare segment event if needed */ if (filter->need_newsegment) { priv->segment_event = create_segment_event (filter, rtptime, GST_BUFFER_PTS (in)); filter->need_newsegment = FALSE; } priv->input_buffer = in; if (process_rtp_packet_func != NULL) { out_buf = process_rtp_packet_func (filter, &rtp); gst_rtp_buffer_unmap (&rtp); } else if (process_func != NULL) { gst_rtp_buffer_unmap (&rtp); out_buf = process_func (filter, in); } else { goto no_process; } /* let's send it out to processing */ if (out_buf) { ret = gst_rtp_base_depayload_push (filter, out_buf); } gst_buffer_unref (in); priv->input_buffer = NULL; return ret; /* ERRORS */ not_negotiated: { /* this is not fatal but should be filtered earlier */ GST_ELEMENT_ERROR (filter, CORE, NEGOTIATION, ("No RTP format was negotiated."), ("Input buffers need to have RTP caps set on them. This is usually " "achieved by setting the 'caps' property of the upstream source " "element (often udpsrc or appsrc), or by putting a capsfilter " "element before the depayloader and setting the 'caps' property " "on that. Also see http://cgit.freedesktop.org/gstreamer/" "gst-plugins-good/tree/gst/rtp/README")); gst_buffer_unref (in); return GST_FLOW_NOT_NEGOTIATED; } invalid_buffer: { /* this is not fatal but should be filtered earlier */ GST_ELEMENT_WARNING (filter, STREAM, DECODE, (NULL), ("Received invalid RTP payload, dropping")); gst_buffer_unref (in); return GST_FLOW_OK; } dropping: { gst_rtp_buffer_unmap (&rtp); GST_WARNING_OBJECT (filter, "%d <= 100, dropping old packet", gap); gst_buffer_unref (in); return GST_FLOW_OK; } no_process: { gst_rtp_buffer_unmap (&rtp); /* this is not fatal but should be filtered earlier */ GST_ELEMENT_ERROR (filter, STREAM, NOT_IMPLEMENTED, (NULL), ("The subclass does not have a process or process_rtp_packet method")); gst_buffer_unref (in); return GST_FLOW_ERROR; } }
/** * rtp_jitter_buffer_insert: * @jbuf: an #RTPJitterBuffer * @buf: a buffer * @time: a running_time when this buffer was received in nanoseconds * @clock_rate: the clock-rate of the payload of @buf * @max_delay: the maximum lateness of @buf * @tail: TRUE when the tail element changed. * * Inserts @buf 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. * @buf should have writable metadata when calling this function. * * Returns: %FALSE if a packet with the same number already existed. */ gboolean rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, GstBuffer * buf, GstClockTime time, guint32 clock_rate, gboolean * tail, gint * percent) { GList *list; guint32 rtptime; guint16 seqnum; GstRTPBuffer rtp = {NULL}; g_return_val_if_fail (jbuf != NULL, FALSE); g_return_val_if_fail (buf != NULL, FALSE); gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp); seqnum = gst_rtp_buffer_get_seq (&rtp); /* loop the list to skip strictly smaller seqnum buffers */ for (list = jbuf->packets->head; list; list = g_list_next (list)) { guint16 qseq; gint gap; GstRTPBuffer rtpb = {NULL}; gst_rtp_buffer_map (GST_BUFFER_CAST (list->data), GST_MAP_READ, &rtpb); qseq = gst_rtp_buffer_get_seq (&rtpb); gst_rtp_buffer_unmap (&rtpb); /* compare the new seqnum to the one in the buffer */ gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq); /* we hit a packet with the same seqnum, notify a duplicate */ if (G_UNLIKELY (gap == 0)) goto duplicate; /* seqnum > qseq, we can stop looking */ if (G_LIKELY (gap < 0)) break; } rtptime = gst_rtp_buffer_get_timestamp (&rtp); /* 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 (time) && 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 * clock_rate || ext_rtptime + 3 * 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); } } 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) time = 0; else time = -1; break; case RTP_JITTER_BUFFER_MODE_SLAVE: default: break; } /* do skew calculation by measuring the difference between rtptime and the * receive time, this function will retimestamp @buf with the skew corrected * running time. */ time = calculate_skew (jbuf, rtptime, time, clock_rate); GST_BUFFER_TIMESTAMP (buf) = time; /* It's more likely that the packet was inserted in the front of the buffer */ if (G_LIKELY (list)) g_queue_insert_before (jbuf->packets, list, buf); else g_queue_push_tail (jbuf->packets, buf); /* buffering mode, update buffer stats */ if (jbuf->mode == RTP_JITTER_BUFFER_MODE_BUFFER) update_buffer_level (jbuf, percent); else *percent = -1; /* tail was changed when we did not find a previous packet, we set the return * flag when requested. */ if (G_LIKELY (tail)) *tail = (list == NULL); gst_rtp_buffer_unmap (&rtp); return TRUE; /* ERRORS */ duplicate: { gst_rtp_buffer_unmap (&rtp); GST_WARNING ("duplicate packet %d found", (gint) seqnum); return FALSE; } }
/** * rtp_jitter_buffer_insert: * @jbuf: an #RTPJitterBuffer * @buf: a buffer * @time: a running_time when this buffer was received in nanoseconds * @clock_rate: the clock-rate of the payload of @buf * @max_delay: the maximum lateness of @buf * @tail: TRUE when the tail element changed. * * Inserts @buf 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. * @buf should have writable metadata when calling this function. * * Returns: %FALSE if a packet with the same number already existed. */ gboolean rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, GstBuffer * buf, GstClockTime time, guint32 clock_rate, gboolean * tail, gint * percent) { GList *list; guint32 rtptime; guint16 seqnum; g_return_val_if_fail (jbuf != NULL, FALSE); g_return_val_if_fail (buf != NULL, FALSE); seqnum = gst_rtp_buffer_get_seq (buf); /* loop the list to skip strictly smaller seqnum buffers */ for (list = jbuf->packets->head; list; list = g_list_next (list)) { guint16 qseq; gint gap; qseq = gst_rtp_buffer_get_seq (GST_BUFFER_CAST (list->data)); /* compare the new seqnum to the one in the buffer */ gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq); /* we hit a packet with the same seqnum, notify a duplicate */ if (G_UNLIKELY (gap == 0)) goto duplicate; /* seqnum > qseq, we can stop looking */ if (G_LIKELY (gap < 0)) break; } rtptime = gst_rtp_buffer_get_timestamp (buf); 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) time = 0; else time = -1; break; case RTP_JITTER_BUFFER_MODE_SLAVE: default: break; } /* do skew calculation by measuring the difference between rtptime and the * receive time, this function will retimestamp @buf with the skew corrected * running time. */ time = calculate_skew (jbuf, rtptime, time, clock_rate); GST_BUFFER_TIMESTAMP (buf) = time; /* It's more likely that the packet was inserted in the front of the buffer */ if (G_LIKELY (list)) g_queue_insert_before (jbuf->packets, list, buf); else g_queue_push_tail (jbuf->packets, buf); /* buffering mode, update buffer stats */ if (jbuf->mode == RTP_JITTER_BUFFER_MODE_BUFFER) update_buffer_level (jbuf, percent); else *percent = -1; /* tail was changed when we did not find a previous packet, we set the return * flag when requested. */ if (G_LIKELY (tail)) *tail = (list == NULL); return TRUE; /* ERRORS */ duplicate: { GST_WARNING ("duplicate packet %d found", (gint) seqnum); return FALSE; } }