self->adapter = gst_adapter_new ();
self->started = FALSE;
self->wait_for_keyframe = DEFAULT_WAIT_FOR_KEYFRAME;
+ self->last_pushed_was_lost_event = FALSE;
}
static void
->packet_lost (GST_RTP_BASE_DEPAYLOAD_CAST (self),
self->last_lost_event);
gst_event_replace (&self->last_lost_event, NULL);
+ self->last_pushed_was_lost_event = TRUE;
}
}
static void
+send_new_lost_event (GstRtpVP8Depay * self, GstClockTime timestamp,
+ guint new_picture_id, const gchar * reason)
+{
+ GstEvent *event;
+
+ if (!GST_CLOCK_TIME_IS_VALID (timestamp)) {
+ GST_WARNING_OBJECT (self, "Can't create lost event with invalid timestmap");
+ return;
+ }
+
+ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+ gst_structure_new ("GstRTPPacketLost",
+ "timestamp", G_TYPE_UINT64, timestamp,
+ "duration", G_TYPE_UINT64, 0, NULL));
+
+ GST_DEBUG_OBJECT (self, "Pushing lost event "
+ "(picids 0x%x 0x%x, reason \"%s\"): %" GST_PTR_FORMAT,
+ self->last_picture_id, new_picture_id, reason, event);
+
+ GST_RTP_BASE_DEPAYLOAD_CLASS (gst_rtp_vp8_depay_parent_class)
+ ->packet_lost (GST_RTP_BASE_DEPAYLOAD_CAST (self), event);
+
+ gst_event_unref (event);
+}
+
+static void
send_last_lost_event_if_needed (GstRtpVP8Depay * self, guint new_picture_id)
{
if (self->last_picture_id == PICTURE_ID_NONE)
guint hdrsize = 1;
guint picture_id = PICTURE_ID_NONE;
guint size = gst_rtp_buffer_get_payload_len (rtp);
+ guint s_bit;
+ guint part_id;
+ gboolean frame_start;
+ gboolean sent_lost_event = FALSE;
if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (rtp->buffer))) {
GST_DEBUG_OBJECT (self, "Discontinuity, flushing adapter");
goto too_small;
data = gst_rtp_buffer_get_payload (rtp);
+
+ s_bit = (data[0] >> 4) & 0x1;
+ part_id = (data[0] >> 0) & 0x7;
+
/* Check X optional header */
if ((data[0] & 0x80) != 0) {
hdrsize++;
if ((data[1] & 0x20) != 0 || (data[1] & 0x10) != 0)
hdrsize++;
}
- GST_LOG_OBJECT (depay, "hdrsize %u, size %u, picture id 0x%x", hdrsize,
- size, picture_id);
+ GST_LOG_OBJECT (depay,
+ "hdrsize %u, size %u, picture id 0x%x, s %u, part_id %u", hdrsize, size,
+ picture_id, s_bit, part_id);
if (G_UNLIKELY (hdrsize >= size))
goto too_small;
- if (G_UNLIKELY (!self->started)) {
- /* Check if this is the start of a VP8 frame, otherwise bail */
- /* S=1 and PartID= 0 */
- if ((data[0] & 0x17) != 0x10) {
+ frame_start = (s_bit == 1) && (part_id == 0);
+ if (frame_start) {
+ if (G_UNLIKELY (self->started)) {
+ GST_DEBUG_OBJECT (depay, "Incomplete frame, flushing adapter");
+ gst_adapter_clear (self->adapter);
+ self->started = FALSE;
+
+ send_new_lost_event (self, GST_BUFFER_PTS (rtp->buffer), picture_id,
+ "Incomplete frame detected");
+ sent_lost_event = TRUE;
+ }
+ }
+
+ if (!self->started) {
+ if (G_UNLIKELY (!frame_start)) {
GST_DEBUG_OBJECT (depay,
- "The frame is missing the first packets, ignoring the packet");
- if (self->stop_lost_events) {
+ "The frame is missing the first packet, ignoring the packet");
+ if (self->stop_lost_events && !sent_lost_event) {
send_last_lost_event (self);
self->stop_lost_events = FALSE;
}
GST_LOG_OBJECT (depay, "Found the start of the frame");
- if (self->stop_lost_events) {
+ if (self->stop_lost_events && !sent_lost_event) {
send_last_lost_event_if_needed (self, picture_id);
self->stop_lost_events = FALSE;
}
if (picture_id != PICTURE_ID_NONE)
self->stop_lost_events = TRUE;
+
+ self->last_pushed_was_lost_event = FALSE;
+
return out;
}
GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (depay);
const GstStructure *s;
gboolean might_have_been_fec;
+ gboolean unref_event = FALSE;
+ gboolean ret;
s = gst_event_get_structure (event);
return TRUE;
}
} else if (self->last_picture_id != PICTURE_ID_NONE) {
- GstStructure *s = gst_event_writable_structure (self->last_lost_event);
+ GstStructure *s;
+
+ if (!gst_event_is_writable (event)) {
+ event = gst_event_copy (event);
+ unref_event = TRUE;
+ }
+
+ s = gst_event_writable_structure (event);
/* We are currently processing a picture, let's make sure the
* base depayloader doesn't drop this lost event */
gst_structure_remove_field (s, "might-have-been-fec");
}
- return
+ self->last_pushed_was_lost_event = TRUE;
+
+ ret =
GST_RTP_BASE_DEPAYLOAD_CLASS
(gst_rtp_vp8_depay_parent_class)->packet_lost (depay, event);
+
+ if (unref_event)
+ gst_event_unref (event);
+
+ return ret;
}
gboolean
};
static GstBuffer *
-create_rtp_vp8_buffer (guint seqnum, guint picid, gint picid_bits,
- GstClockTime buf_pts)
+create_rtp_vp8_buffer_full (guint seqnum, guint picid, gint picid_bits,
+ GstClockTime buf_pts, gboolean s_bit, gboolean marker_bit)
{
GstBuffer *ret;
guint8 *packet;
packet[2] = (seqnum >> 8) & 0xff;
packet[3] = (seqnum >> 0) & 0xff;
+ if (marker_bit)
+ packet[1] |= 0x80;
+ else
+ packet[1] &= ~0x80;
+
+ if (s_bit)
+ packet[12] |= 0x10;
+ else
+ packet[12] &= ~0x10;
+
ret = gst_buffer_new_wrapped (packet, size);
GST_BUFFER_PTS (ret) = buf_pts;
return ret;
}
+static GstBuffer *
+create_rtp_vp8_buffer (guint seqnum, guint picid, guint picid_bits,
+ GstClockTime buf_pts)
+{
+ return create_rtp_vp8_buffer_full (seqnum, picid, picid_bits, buf_pts, TRUE,
+ TRUE);
+}
+
static void
add_vp8_meta (GstBuffer * buffer, gboolean use_temporal_scaling,
gboolean layer_sync, guint layer_id, guint tl0picidx)
gint picid_bits;
} DepayGapEventTestData;
+typedef struct
+{
+ gint seq_num;
+ gint picid;
+ guint buffer_type;
+ gboolean s_bit;
+ gboolean marker_bit;
+} DepayGapEventTestDataFull;
+
static void
test_depay_gap_event_base (const DepayGapEventTestData * data,
gboolean send_lost_event, gboolean expect_gap_event)
GST_END_TEST;
+GST_START_TEST (test_depay_send_gap_event_when_marker_bit_missing_and_picid_gap)
+{
+ gboolean send_lost_event = __i__ == 0;
+ GstClockTime pts = 0;
+ gint seqnum = 100;
+ gint i;
+ GstEvent *event;
+
+ GstHarness *h = gst_harness_new_parse ("rtpvp8depay");
+ gst_harness_set_src_caps_str (h, RTP_VP8_CAPS_STR);
+
+ /* Push a complete frame to avoid depayloader to suppress gap events */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (seqnum, 23,
+ 7, pts, TRUE, TRUE)));
+ pts += 33 * GST_MSECOND;
+ seqnum++;
+
+ for (i = 0; i < 3; i++)
+ gst_event_unref (gst_harness_pull_event (h));
+ fail_unless_equals_int (gst_harness_events_in_queue (h), 0);
+
+ /* Push packet with start bit set, but no marker bit */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (seqnum, 24,
+ 7, pts, TRUE, FALSE)));
+ pts += 33 * GST_MSECOND;
+ seqnum++;
+
+ if (send_lost_event) {
+ gst_harness_push_event (h,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+ gst_structure_new ("GstRTPPacketLost", "timestamp", G_TYPE_UINT64,
+ pts, "duration", G_TYPE_UINT64, 33 * GST_MSECOND, NULL)));
+ pts += 33 * GST_MSECOND;
+ seqnum++;
+ }
+
+ /* Push packet with gap in picid */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (seqnum, 26,
+ 7, pts, TRUE, TRUE)));
+
+ /* Expect only 2 output frames since the one frame was incomplete */
+ fail_unless_equals_int (2, gst_harness_buffers_received (h));
+
+ /* There should be a gap event, either triggered by the loss or the picid
+ * gap */
+
+ /* Making sure the GAP event was pushed downstream */
+ event = gst_harness_pull_event (h);
+ fail_unless_equals_string ("gap",
+ gst_event_type_get_name (GST_EVENT_TYPE (event)));
+ gst_event_unref (event);
+
+ fail_unless_equals_int (gst_harness_events_in_queue (h), 0);
+
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST
+ (test_depay_send_gap_event_when_marker_bit_missing_and_no_picid_gap) {
+ GstClockTime pts = 0;
+ gint seqnum = 100;
+ gint i;
+ GstEvent *event;
+
+ GstHarness *h = gst_harness_new_parse ("rtpvp8depay");
+ gst_harness_set_src_caps_str (h, RTP_VP8_CAPS_STR);
+
+ /* Push a complete frame to avoid depayloader to suppress gap events */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (seqnum, 23,
+ 7, pts, TRUE, TRUE)));
+ pts += 33 * GST_MSECOND;
+ seqnum++;
+
+ for (i = 0; i < 3; i++)
+ gst_event_unref (gst_harness_pull_event (h));
+ fail_unless_equals_int (gst_harness_events_in_queue (h), 0);
+
+ /* Push packet with start bit set, but no marker bit */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (seqnum, 24,
+ 7, pts, TRUE, FALSE)));
+ pts += 33 * GST_MSECOND;
+ seqnum++;
+
+ /* Push packet for next picid, without having sent a packet with marker bit
+ * foor the previous picid */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (seqnum, 25,
+ 7, pts, TRUE, TRUE)));
+
+ /* Expect only 2 output frames since one was incomplete */
+ fail_unless_equals_int (2, gst_harness_buffers_received (h));
+
+ /* Making sure the GAP event was pushed downstream */
+ event = gst_harness_pull_event (h);
+ gst_event_unref (event);
+
+ fail_unless_equals_int (gst_harness_events_in_queue (h), 0);
+
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_depay_no_gap_event_when_partial_frames_with_no_picid_gap)
+{
+ gint i;
+ GstClockTime pts = 0;
+ GstHarness *h = gst_harness_new ("rtpvp8depay");
+ gst_harness_set_src_caps_str (h, RTP_VP8_CAPS_STR);
+
+ /* start with complete frame to make sure depayloader will not drop
+ * potential gap events */
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (100, 24,
+ 7, pts, TRUE, TRUE)));
+ fail_unless_equals_int (1, gst_harness_buffers_received (h));
+
+ /* drop setup events to more easily check for gap events */
+ for (i = 0; i < 3; i++)
+ gst_event_unref (gst_harness_pull_event (h));
+ fail_unless_equals_int (gst_harness_events_in_queue (h), 0);
+
+ /* Next frame is split in two packets */
+ pts += 33 * GST_MSECOND;
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (101, 25,
+ 7, pts, TRUE, FALSE)));
+ fail_unless_equals_int (GST_FLOW_OK,
+ gst_harness_push (h, create_rtp_vp8_buffer_full (102, 25,
+ 7, pts, FALSE, TRUE)));
+ fail_unless_equals_int (2, gst_harness_buffers_received (h));
+
+ /* there must be no gap events */
+ fail_unless_equals_int (gst_harness_events_in_queue (h), 0);
+
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
/* Packet loss + lost picture ids */
static const DepayGapEventTestData resend_gap_event_test_data[][2] = {
/* 7bit picture ids */
G_N_ELEMENTS (stop_gap_events_test_data));
tcase_add_loop_test (tc_chain, test_depay_resend_gap_event, 0,
G_N_ELEMENTS (resend_gap_event_test_data));
+ tcase_add_loop_test (tc_chain,
+ test_depay_send_gap_event_when_marker_bit_missing_and_picid_gap, 0, 2);
+ tcase_add_test (tc_chain,
+ test_depay_send_gap_event_when_marker_bit_missing_and_no_picid_gap);
+ tcase_add_test (tc_chain,
+ test_depay_no_gap_event_when_partial_frames_with_no_picid_gap);
return s;
}