mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-11 20:01:35 +00:00
d77fcf251b
This means the previous frame was incomplete. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/796>
804 lines
28 KiB
C
804 lines
28 KiB
C
/* GStreamer
|
|
*
|
|
* Copyright (C) 2016 Pexip AS
|
|
* @author Stian Selnes <stian@pexip.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <gst/check/check.h>
|
|
#include <gst/check/gstharness.h>
|
|
|
|
#define RTP_VP8_CAPS_STR \
|
|
"application/x-rtp,media=video,encoding-name=VP8,clock-rate=90000,payload=96"
|
|
|
|
#define gst_buffer_new_from_array(array) gst_buffer_new_wrapped ( \
|
|
g_memdup (vp8_bitstream_payload, sizeof (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_memdup (intra_nopicid_seqnum0, size);
|
|
} else if (picid_bits == 7) {
|
|
size = sizeof (intra_picid24_seqnum0);
|
|
packet = g_memdup (intra_picid24_seqnum0, size);
|
|
packet[14] = picid & 0x7f;
|
|
} else {
|
|
size = sizeof (intra_picid6336_seqnum0);
|
|
packet = g_memdup (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;
|
|
GstStructure *s;
|
|
|
|
meta = gst_buffer_add_custom_meta (buffer, "GstVP8Meta");
|
|
fail_unless (meta != NULL);
|
|
s = gst_custom_meta_get_structure (meta);
|
|
gst_structure_set (s,
|
|
"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_wrapped (g_memdup (vp8_bitstream_payload,
|
|
sizeof (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_wrapped (g_memdup (vp8_bitstream_payload,
|
|
sizeof (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;
|
|
|
|
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;
|
|
|
|
static Suite *
|
|
rtpvp8_suite (void)
|
|
{
|
|
Suite *s = suite_create ("rtpvp8");
|
|
TCase *tc_chain;
|
|
static const gchar *tags[] = { NULL };
|
|
|
|
/* Register custom GstVP8Meta manually */
|
|
gst_meta_register_custom ("GstVP8Meta", tags, NULL, NULL, NULL);
|
|
|
|
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_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);
|