/* GStreamer * * Copyright (C) 2016 Pexip AS * @author Stian Selnes * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #define RTP_VP8_CAPS_STR \ "application/x-rtp,media=video,encoding-name=VP8,clock-rate=90000,payload=96" /* FIXME: array argument is unused! */ #define gst_buffer_new_from_array(array) \ gst_buffer_new_memdup (vp8_bitstream_payload, sizeof (vp8_bitstream_payload)) static guint8 intra_picid6336_seqnum0[] = { 0x80, 0xe0, 0x00, 0x00, 0x9a, 0xbb, 0xe3, 0xb3, 0x8b, 0xe9, 0x1d, 0x61, 0x90, 0x80, 0x98, 0xc0, 0xf0, 0x07, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, }; static guint8 intra_picid24_seqnum0[] = { 0x80, 0xe0, 0x00, 0x00, 0x9a, 0xbb, 0xe3, 0xb3, 0x8b, 0xe9, 0x1d, 0x61, 0x90, 0x80, 0x18, 0xf0, 0x07, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, }; static guint8 intra_nopicid_seqnum0[] = { 0x80, 0xe0, 0x00, 0x00, 0x9a, 0xbb, 0xe3, 0xb3, 0x8b, 0xe9, 0x1d, 0x61, 0x90, 0x00, 0xf0, 0x07, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, }; static GstBuffer * 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; gsize size; g_assert (picid_bits == 0 || picid_bits == 7 || picid_bits == 15); if (picid_bits == 0) { size = sizeof (intra_nopicid_seqnum0); packet = g_memdup2 (intra_nopicid_seqnum0, size); } else if (picid_bits == 7) { size = sizeof (intra_picid24_seqnum0); packet = g_memdup2 (intra_picid24_seqnum0, size); packet[14] = picid & 0x7f; } else { size = sizeof (intra_picid6336_seqnum0); packet = g_memdup2 (intra_picid6336_seqnum0, size); packet[14] = ((picid >> 8) & 0xff) | 0x80; packet[15] = (picid >> 0) & 0xff; } 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) { GstCustomMeta *meta; meta = gst_buffer_add_custom_meta (buffer, "GstVP8Meta"); fail_unless (meta != NULL); gst_structure_set (meta->structure, "use-temporal-scaling", G_TYPE_BOOLEAN, use_temporal_scaling, "layer-sync", G_TYPE_BOOLEAN, layer_sync, "layer-id", G_TYPE_UINT, layer_id, "tl0picidx", G_TYPE_UINT, tl0picidx, NULL); } /* PictureID emum is not exported */ enum PictureID { VP8_PAY_NO_PICTURE_ID = 0, VP8_PAY_PICTURE_ID_7BITS = 1, VP8_PAY_PICTURE_ID_15BITS = 2, }; static const struct no_meta_test_data { /* control inputs */ enum PictureID pid; /* picture ID type of test */ gboolean vp8_payload_header_m_flag; /* expected outputs */ guint vp8_payload_header_size; guint vp8_payload_control_value; } no_meta_test_data[] = { { VP8_PAY_NO_PICTURE_ID, FALSE, 1, 0x10}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, 3, 0x90}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, 4, 0x90}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ /* repeated with non reference frame */ { VP8_PAY_NO_PICTURE_ID, FALSE, 1, 0x30}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, 3, 0xB0}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, 4, 0xB0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ }; GST_START_TEST (test_pay_no_meta) { guint8 vp8_bitstream_payload[] = { 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00 }; const struct no_meta_test_data *test_data = &no_meta_test_data[__i__]; GstBuffer *buffer; GstMapInfo map = GST_MAP_INFO_INIT; GstHarness *h = gst_harness_new ("rtpvp8pay"); gst_harness_set_src_caps_str (h, "video/x-vp8"); /* check unknown picture id enum value */ fail_unless (test_data->pid <= VP8_PAY_PICTURE_ID_15BITS); g_object_set (h->element, "picture-id-mode", test_data->pid, "picture-id-offset", 0x5A5A, NULL); buffer = gst_buffer_new_memdup (vp8_bitstream_payload, sizeof (vp8_bitstream_payload)); /* set droppable if N flag set */ if ((test_data->vp8_payload_control_value & 0x20) != 0) { GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DROPPABLE); } buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless (map.data != NULL); /* check buffer size and content */ fail_unless_equals_int (map.size, 12 + test_data->vp8_payload_header_size + sizeof (vp8_bitstream_payload)); fail_unless_equals_int (test_data->vp8_payload_control_value, map.data[12]); if (test_data->vp8_payload_header_size > 2) { /* vp8 header extension byte must have I set */ fail_unless_equals_int (0x80, map.data[13]); /* check picture id */ if (test_data->pid == VP8_PAY_PICTURE_ID_7BITS) { fail_unless_equals_int (0x5a, map.data[14]); } else if (test_data->pid == VP8_PAY_PICTURE_ID_15BITS) { fail_unless_equals_int (0xDA, map.data[14]); fail_unless_equals_int (0x5A, map.data[15]); } } gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); gst_harness_teardown (h); } GST_END_TEST; static const struct with_meta_test_data { /* control inputs */ enum PictureID pid; /* picture ID type of test */ gboolean vp8_payload_header_m_flag; gboolean use_temporal_scaling; gboolean y_flag; /* expected outputs */ guint vp8_payload_header_size; guint vp8_payload_control_value; guint vp8_payload_extended_value; } with_meta_test_data[] = { { VP8_PAY_NO_PICTURE_ID, FALSE, FALSE, FALSE, 1, 0x10, 0x80}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, FALSE, FALSE, 3, 0x90, 0x80}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, FALSE, FALSE, 4, 0x90, 0x80}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ { VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, FALSE, 4, 0x90, 0x60}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, FALSE, 5, 0x90, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, FALSE, 6, 0x90, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ { VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, TRUE, 4, 0x90, 0x60}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, TRUE, 5, 0x90, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, TRUE, 6, 0x90, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ /* repeated with non reference frame */ { VP8_PAY_NO_PICTURE_ID, FALSE, FALSE, FALSE, 1, 0x30, 0x80}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, FALSE, FALSE, 3, 0xB0, 0x80}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, FALSE, FALSE, 4, 0xB0, 0x80}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ { VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, FALSE, 4, 0xB0, 0x60}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, FALSE, 5, 0xB0, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, FALSE, 6, 0xB0, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ { VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, TRUE, 4, 0xB0, 0x60}, /* no picture ID single byte header, S set */ { VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, TRUE, 5, 0xB0, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */ { VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, TRUE, 6, 0xB0, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */ }; GST_START_TEST (test_pay_with_meta) { guint8 vp8_bitstream_payload[] = { 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00 }; const struct with_meta_test_data *test_data = &with_meta_test_data[__i__]; GstBuffer *buffer; GstCustomMeta *meta; GstMapInfo map = GST_MAP_INFO_INIT; GstHarness *h = gst_harness_new ("rtpvp8pay"); gst_harness_set_src_caps_str (h, "video/x-vp8"); /* check for unknown picture id enum value */ fail_unless (test_data->pid <= VP8_PAY_PICTURE_ID_15BITS); g_object_set (h->element, "picture-id-mode", test_data->pid, "picture-id-offset", 0x5A5A, NULL); /* Push a buffer in */ buffer = gst_buffer_new_memdup (vp8_bitstream_payload, sizeof (vp8_bitstream_payload)); add_vp8_meta (buffer, test_data->use_temporal_scaling, test_data->y_flag, 2, 255); /* set droppable if N flag set */ if ((test_data->vp8_payload_control_value & 0x20) != 0) { GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DROPPABLE); } buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless (map.data != NULL); meta = gst_buffer_get_custom_meta (buffer, "GstVP8Meta"); fail_unless (meta == NULL); /* check buffer size and content */ fail_unless_equals_int (map.size, 12 + test_data->vp8_payload_header_size + sizeof (vp8_bitstream_payload)); fail_unless_equals_int (test_data->vp8_payload_control_value, map.data[12]); if (test_data->vp8_payload_header_size > 1) { int hdridx = 13; fail_unless_equals_int (test_data->vp8_payload_extended_value, map.data[hdridx++]); /* check picture ID */ if (test_data->pid == VP8_PAY_PICTURE_ID_7BITS) { fail_unless_equals_int (0x5A, map.data[hdridx++]); } else if (test_data->pid == VP8_PAY_PICTURE_ID_15BITS) { fail_unless_equals_int (0xDA, map.data[hdridx++]); fail_unless_equals_int (0x5A, map.data[hdridx++]); } if (test_data->use_temporal_scaling) { /* check temporal layer 0 picture ID value */ fail_unless_equals_int (255, map.data[hdridx++]); /* check temporal layer ID value */ fail_unless_equals_int (2, (map.data[hdridx] >> 6) & 0x3); if (test_data->y_flag) { fail_unless_equals_int (1, (map.data[hdridx] >> 5) & 1); } else { fail_unless_equals_int (0, (map.data[hdridx] >> 5) & 1); } } } gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); gst_harness_teardown (h); } GST_END_TEST; GST_START_TEST (test_pay_continuous_picture_id_and_tl0picidx) { guint8 vp8_bitstream_payload[] = { 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00 }; GstHarness *h = gst_harness_new ("rtpvp8pay"); const gint header_len_without_tl0picidx = 3; const gint header_len_with_tl0picidx = 5; const gint packet_len_without_tl0picidx = 12 + header_len_without_tl0picidx + sizeof (vp8_bitstream_payload); const gint packet_len_with_tl0picidx = 12 + header_len_with_tl0picidx + sizeof (vp8_bitstream_payload); const gint picid_offset = 14; const gint tl0picidx_offset = 15; GstBuffer *buffer; GstMapInfo map; g_object_set (h->element, "picture-id-mode", VP8_PAY_PICTURE_ID_7BITS, "picture-id-offset", 0, NULL); gst_harness_set_src_caps_str (h, "video/x-vp8"); /* First, push a frame without temporal scalability meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len_without_tl0picidx); fail_unless_equals_int (map.data[picid_offset], 0x00); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* Push a frame for temporal layer 0 with meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 0, 0); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len_with_tl0picidx); fail_unless_equals_int (map.data[picid_offset], 0x01); fail_unless_equals_int (map.data[tl0picidx_offset], 0x00); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* Push a frame for temporal layer 1 with meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 1, 0); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len_with_tl0picidx); fail_unless_equals_int (map.data[picid_offset], 0x02); fail_unless_equals_int (map.data[tl0picidx_offset], 0x00); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* Push next frame for temporal layer 0 with meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 0, 1); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len_with_tl0picidx); fail_unless_equals_int (map.data[picid_offset], 0x03); fail_unless_equals_int (map.data[tl0picidx_offset], 0x01); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* Another frame for temporal layer 0, but now the meta->tl0picidx has been * reset to 0 (simulating an encoder reset). Payload must ensure tl0picidx * is increasing. */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 0, 0); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len_with_tl0picidx); fail_unless_equals_int (map.data[picid_offset], 0x04); fail_unless_equals_int (map.data[tl0picidx_offset], 0x02); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* If we receive a frame without meta, we should continue to increase and * add tl0picidx (assuming TID=0) in order to maximize interop. */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len_with_tl0picidx); fail_unless_equals_int (map.data[picid_offset], 0x05); fail_unless_equals_int (map.data[tl0picidx_offset], 0x03); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); gst_harness_teardown (h); } GST_END_TEST; GST_START_TEST (test_pay_tl0picidx_split_buffer) { guint8 vp8_bitstream_payload[] = { 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00 }; GstHarness *h = gst_harness_new_parse ("rtpvp8pay mtu=28 picture-id-mode=1 picture-id-offset=0"); const gint header_len = 12 + 5; /* RTP + VP8 payload header */ const gint picid_offset = 14; const gint tl0picidx_offset = 15; guint output_bytes_left; GstBuffer *buffer; GstMapInfo map; gst_harness_set_src_caps_str (h, "video/x-vp8"); /* Push a frame for temporal layer 0 with meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 0, 0); gst_harness_push (h, buffer); /* Expect it to be split into multiple buffers to fit the MTU */ output_bytes_left = sizeof (vp8_bitstream_payload); while (output_bytes_left > 0) { const gint expected = MIN (output_bytes_left, 28 - header_len); const gint packet_len = header_len + expected; output_bytes_left -= expected; buffer = gst_harness_pull (h); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len); fail_unless_equals_int (map.data[picid_offset], 0x00); fail_unless_equals_int (map.data[tl0picidx_offset], 0x00); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); } /* Push a frame for temporal layer 1 with meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 1, 0); gst_harness_push (h, buffer); /* Expect it to be split into multiple buffers to fit the MTU */ output_bytes_left = sizeof (vp8_bitstream_payload); while (output_bytes_left > 0) { const gint expected = MIN (output_bytes_left, 28 - header_len); const gint packet_len = header_len + expected; output_bytes_left -= expected; buffer = gst_harness_pull (h); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len); fail_unless_equals_int (map.data[picid_offset], 0x01); fail_unless_equals_int (map.data[tl0picidx_offset], 0x00); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); } /* Push another frame for temporal layer 0 with meta */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); add_vp8_meta (buffer, TRUE, TRUE, 0, 0); gst_harness_push (h, buffer); /* Expect it to be split into multiple buffers to fit the MTU */ output_bytes_left = sizeof (vp8_bitstream_payload); while (output_bytes_left > 0) { const gint expected = MIN (output_bytes_left, 28 - header_len); const gint packet_len = header_len + expected; output_bytes_left -= expected; buffer = gst_harness_pull (h); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_int (map.size, packet_len); fail_unless_equals_int (map.data[picid_offset], 0x02); fail_unless_equals_int (map.data[tl0picidx_offset], 0x01); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); } gst_harness_teardown (h); } GST_END_TEST; GST_START_TEST (test_pay_continuous_picture_id_on_flush) { guint8 vp8_bitstream_payload[] = { 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00 }; GstHarness *h = gst_harness_new ("rtpvp8pay"); const gint header_len = 3; const gint packet_len = 12 + header_len + sizeof (vp8_bitstream_payload); const gint picid_offset = 14; GstBuffer *buffer; GstMapInfo map; g_object_set (h->element, "picture-id-mode", VP8_PAY_PICTURE_ID_7BITS, "picture-id-offset", 0, NULL); gst_harness_set_src_caps_str (h, "video/x-vp8"); /* First, push a frame */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_uint64 (map.size, packet_len); fail_unless_equals_int (map.data[picid_offset], 0x00); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* Push another one and expect the PictureID to increment */ buffer = gst_buffer_new_from_array (vp8_bitstream_payload); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_uint64 (map.size, packet_len); fail_unless_equals_int (map.data[picid_offset], 0x01); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); /* Yet another frame followed by a FLUSH of the pipeline should result * on an increase rather than a reset to maximize interop. */ fail_unless (gst_harness_push_event (h, gst_event_new_flush_start ())); fail_unless (gst_harness_push_event (h, gst_event_new_flush_stop (FALSE))); gst_harness_set_src_caps_str (h, "video/x-vp8"); buffer = gst_buffer_new_from_array (vp8_bitstream_payload); buffer = gst_harness_push_and_pull (h, buffer); fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); fail_unless_equals_uint64 (map.size, packet_len); /* PictureID should increment by 2 * One due to the FLUSH_START, and another one due to the new frame */ fail_unless_equals_int (map.data[picid_offset], 0x03); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); gst_harness_teardown (h); } GST_END_TEST; typedef struct _DepayGapEventTestData { gint seq_num; gint picid; 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) { GstEvent *event; GstClockTime pts = 0; GstHarness *h = gst_harness_new ("rtpvp8depay"); gst_harness_set_src_caps_str (h, RTP_VP8_CAPS_STR); gst_harness_push (h, create_rtp_vp8_buffer (data[0].seq_num, data[0].picid, data[0].picid_bits, pts)); pts += 33 * GST_MSECOND; /* Preparation before pushing gap event. Getting rid of all events which * came by this point - segment, caps, etc */ for (gint i = 0; i < 3; i++) gst_event_unref (gst_harness_pull_event (h)); fail_unless_equals_int (gst_harness_events_in_queue (h), 0); 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, "might-have-been-fec", G_TYPE_BOOLEAN, TRUE, NULL))); pts += 33 * GST_MSECOND; } gst_harness_push (h, create_rtp_vp8_buffer (data[1].seq_num, data[1].picid, data[1].picid_bits, pts)); fail_unless_equals_int (2, gst_harness_buffers_received (h)); if (expect_gap_event) { /* 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); } /* Packet loss + no loss in picture ids */ static const DepayGapEventTestData stop_gap_events_test_data[][2] = { /* 7bit picture ids */ {{100, 24, 7}, {102, 25, 7}}, /* 15bit picture ids */ {{100, 250, 15}, {102, 251, 15}}, /* 7bit picture ids wrap */ {{100, 127, 7}, {102, 0, 7}}, /* 15bit picture ids wrap */ {{100, 32767, 15}, {102, 0, 15}}, /* 7bit to 15bit picture id */ {{100, 127, 7}, {102, 128, 15}}, }; GST_START_TEST (test_depay_stop_gap_events) { test_depay_gap_event_base (&stop_gap_events_test_data[__i__][0], TRUE, FALSE); } 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 */ {{100, 24, 7}, {102, 26, 7}}, /* 15bit picture ids */ {{100, 250, 15}, {102, 252, 15}}, /* 7bit picture ids wrap */ {{100, 127, 7}, {102, 1, 7}}, /* 15bit picture ids wrap */ {{100, 32767, 15}, {102, 1, 15}}, /* 7bit to 15bit picture id */ {{100, 126, 7}, {102, 129, 15}}, }; GST_START_TEST (test_depay_resend_gap_event) { test_depay_gap_event_base (&resend_gap_event_test_data[__i__][0], TRUE, TRUE); } GST_END_TEST; GST_START_TEST (test_pay_delta_unit_flag) { guint8 vp8_bitstream_payload[] = { 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47, 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00 }; /* set mtu so that the buffer is split into multiple packets */ GstHarness *h = gst_harness_new_parse ("rtpvp8pay mtu=28"); GstFlowReturn ret; GstBuffer *buffer; gst_harness_set_src_caps_str (h, "video/x-vp8"); buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, vp8_bitstream_payload, sizeof (vp8_bitstream_payload), 0, sizeof (vp8_bitstream_payload), NULL, NULL); ret = gst_harness_push (h, buffer); fail_unless_equals_int (ret, GST_FLOW_OK); /* the input buffer should be split into two buffers and pushed as a buffer * list, only the first buffer of the first buffer list should be marked as a * non-delta unit */ buffer = gst_harness_pull (h); fail_unless (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)); gst_buffer_unref (buffer); buffer = gst_harness_pull (h); fail_unless (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)); gst_buffer_unref (buffer); gst_harness_teardown (h); } GST_END_TEST; static Suite * rtpvp8_suite (void) { Suite *s = suite_create ("rtpvp8"); TCase *tc_chain; /* Register custom GstVP8Meta manually */ gst_meta_register_custom_simple ("GstVP8Meta"); suite_add_tcase (s, (tc_chain = tcase_create ("vp8pay"))); tcase_add_loop_test (tc_chain, test_pay_no_meta, 0, G_N_ELEMENTS (no_meta_test_data)); tcase_add_loop_test (tc_chain, test_pay_with_meta, 0, G_N_ELEMENTS (with_meta_test_data)); tcase_add_test (tc_chain, test_pay_continuous_picture_id_and_tl0picidx); tcase_add_test (tc_chain, test_pay_tl0picidx_split_buffer); tcase_add_test (tc_chain, test_pay_continuous_picture_id_on_flush); tcase_add_test (tc_chain, test_pay_delta_unit_flag); suite_add_tcase (s, (tc_chain = tcase_create ("vp8depay"))); tcase_add_loop_test (tc_chain, test_depay_stop_gap_events, 0, 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; } GST_CHECK_MAIN (rtpvp8);