mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
rtpvp8pay: payload temporally scaled bitstreams.
Co-Authored-By: Vincent Sanders <vince@pexip.com> Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/728>
This commit is contained in:
parent
29d5936749
commit
3348c5ceae
4 changed files with 617 additions and 16 deletions
|
@ -131,12 +131,22 @@ gst_rtp_vp8_pay_picture_id_increment (GstRtpVP8Pay * obj)
|
|||
obj->picture_id &= (1 << nbits) - 1;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtp_vp8_pay_reset (GstRtpVP8Pay * obj)
|
||||
{
|
||||
gst_rtp_vp8_pay_picture_id_reset (obj);
|
||||
/* tl0picidx MAY start at a random value, but there's no point. Initialize
|
||||
* so that first packet will use 0 for convenience */
|
||||
obj->tl0picidx = -1;
|
||||
obj->temporal_scalability_fields_present = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtp_vp8_pay_init (GstRtpVP8Pay * obj)
|
||||
{
|
||||
obj->picture_id_mode = DEFAULT_PICTURE_ID_MODE;
|
||||
gst_rtp_vp8_pay_picture_id_reset (obj);
|
||||
obj->picture_id_offset = DEFAULT_PICTURE_ID_OFFSET;
|
||||
gst_rtp_vp8_pay_reset (obj);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -418,21 +428,38 @@ gst_rtp_vp8_offset_to_partition (GstRtpVP8Pay * self, guint offset)
|
|||
static gsize
|
||||
gst_rtp_vp8_calc_header_len (GstRtpVP8Pay * self)
|
||||
{
|
||||
gsize len;
|
||||
|
||||
switch (self->picture_id_mode) {
|
||||
case VP8_PAY_PICTURE_ID_7BITS:
|
||||
return 3;
|
||||
len = 1;
|
||||
break;
|
||||
case VP8_PAY_PICTURE_ID_15BITS:
|
||||
return 4;
|
||||
len = 2;
|
||||
break;
|
||||
case VP8_PAY_NO_PICTURE_ID:
|
||||
default:
|
||||
return 1;
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (self->temporal_scalability_fields_present) {
|
||||
/* Add on space for TL0PICIDX and TID/Y/KEYIDX */
|
||||
len += 2;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
/* All fields above are extension, so allocate space for the ECB field */
|
||||
len++;
|
||||
}
|
||||
|
||||
return len + 1; /* computed + fixed size header */
|
||||
}
|
||||
|
||||
/* When growing the vp8 header keep max payload len calculation in sync */
|
||||
static GstBuffer *
|
||||
gst_rtp_vp8_create_header_buffer (GstRtpVP8Pay * self, guint8 partid,
|
||||
gboolean start, gboolean mark, GstBuffer * in)
|
||||
gboolean start, gboolean mark, GstBuffer * in, GstCustomMeta * meta)
|
||||
{
|
||||
GstBuffer *out;
|
||||
guint8 *p;
|
||||
|
@ -443,20 +470,73 @@ gst_rtp_vp8_create_header_buffer (GstRtpVP8Pay * self, guint8 partid,
|
|||
(self), gst_rtp_vp8_calc_header_len (self), 0, 0);
|
||||
gst_rtp_buffer_map (out, GST_MAP_READWRITE, &rtpbuffer);
|
||||
p = gst_rtp_buffer_get_payload (&rtpbuffer);
|
||||
|
||||
/* X=0,R=0,N=0,S=start,PartID=partid */
|
||||
p[0] = (start << 4) | partid;
|
||||
if (self->picture_id_mode != VP8_PAY_NO_PICTURE_ID) {
|
||||
if (GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_DROPPABLE)) {
|
||||
/* Enable N=1 */
|
||||
p[0] |= 0x20;
|
||||
}
|
||||
|
||||
if (self->picture_id_mode != VP8_PAY_NO_PICTURE_ID ||
|
||||
self->temporal_scalability_fields_present) {
|
||||
gint index;
|
||||
|
||||
/* Enable X=1 */
|
||||
p[0] |= 0x80;
|
||||
/* X: I=1,L=0,T=0,K=0,RSV=0 */
|
||||
p[1] = 0x80;
|
||||
|
||||
/* X: I=0,L=0,T=0,K=0,RSV=0 */
|
||||
p[1] = 0x00;
|
||||
if (self->picture_id_mode != VP8_PAY_NO_PICTURE_ID) {
|
||||
/* Set I bit */
|
||||
p[1] |= 0x80;
|
||||
}
|
||||
if (self->temporal_scalability_fields_present) {
|
||||
/* Set L and T bits */
|
||||
p[1] |= 0x60;
|
||||
}
|
||||
|
||||
/* Insert picture ID */
|
||||
if (self->picture_id_mode == VP8_PAY_PICTURE_ID_7BITS) {
|
||||
/* I: 7 bit picture_id */
|
||||
p[2] = self->picture_id & 0x7F;
|
||||
} else {
|
||||
index = 3;
|
||||
} else if (self->picture_id_mode == VP8_PAY_PICTURE_ID_15BITS) {
|
||||
/* I: 15 bit picture_id */
|
||||
p[2] = 0x80 | ((self->picture_id & 0x7FFF) >> 8);
|
||||
p[3] = self->picture_id & 0xFF;
|
||||
index = 4;
|
||||
} else {
|
||||
index = 2;
|
||||
}
|
||||
|
||||
/* Insert TL0PICIDX and TID/Y/KEYIDX */
|
||||
if (self->temporal_scalability_fields_present) {
|
||||
/* The meta contains tl0picidx from the encoder, but we need to ensure
|
||||
* that tl0picidx is increasing correctly. The encoder may reset it's
|
||||
* state and counter, but we cannot. Therefore, we cannot simply copy
|
||||
* the value into the header.*/
|
||||
guint temporal_layer = 0;
|
||||
gboolean layer_sync = FALSE;
|
||||
gboolean use_temporal_scaling = FALSE;
|
||||
|
||||
if (meta) {
|
||||
GstStructure *s = gst_custom_meta_get_structure (meta);
|
||||
gst_structure_get_boolean (s, "use-temporal-scaling",
|
||||
&use_temporal_scaling);
|
||||
|
||||
if (use_temporal_scaling)
|
||||
gst_structure_get (s, "layer-id", G_TYPE_UINT, &temporal_layer,
|
||||
"layer-sync", G_TYPE_BOOLEAN, &layer_sync, NULL);
|
||||
}
|
||||
|
||||
/* FIXME: Support a prediction structure where higher layers don't
|
||||
* necessarily refer to the last base layer frame, ie they use an older
|
||||
* tl0picidx as signalled in the meta */
|
||||
if (temporal_layer == 0 && start)
|
||||
self->tl0picidx++;
|
||||
p[index] = self->tl0picidx & 0xFF;
|
||||
p[index + 1] = ((temporal_layer << 6) | (layer_sync << 5)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,15 +550,38 @@ gst_rtp_vp8_create_header_buffer (GstRtpVP8Pay * self, guint8 partid,
|
|||
return out;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
foreach_metadata_drop (GstBuffer * buf, GstMeta ** meta, gpointer user_data)
|
||||
{
|
||||
GstElement *element = user_data;
|
||||
const GstMetaInfo *info = (*meta)->info;
|
||||
|
||||
if (gst_meta_info_is_custom (info) &&
|
||||
gst_custom_meta_has_name ((GstCustomMeta *) * meta, "GstVP8Meta")) {
|
||||
GST_DEBUG_OBJECT (element, "dropping GstVP8Meta");
|
||||
*meta = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtp_vp8_drop_vp8_meta (gpointer element, GstBuffer * buf)
|
||||
{
|
||||
gst_buffer_foreach_meta (buf, foreach_metadata_drop, element);
|
||||
}
|
||||
|
||||
static guint
|
||||
gst_rtp_vp8_payload_next (GstRtpVP8Pay * self, GstBufferList * list,
|
||||
guint offset, GstBuffer * buffer, gsize buffer_size, gsize max_payload_len)
|
||||
guint offset, GstBuffer * buffer, gsize buffer_size, gsize max_payload_len,
|
||||
GstCustomMeta * meta)
|
||||
{
|
||||
guint partition;
|
||||
GstBuffer *header;
|
||||
GstBuffer *sub;
|
||||
GstBuffer *out;
|
||||
gboolean mark;
|
||||
gboolean start;
|
||||
gsize remaining;
|
||||
gsize available;
|
||||
|
||||
|
@ -487,16 +590,27 @@ gst_rtp_vp8_payload_next (GstRtpVP8Pay * self, GstBufferList * list,
|
|||
if (available > remaining)
|
||||
available = remaining;
|
||||
|
||||
partition = gst_rtp_vp8_offset_to_partition (self, offset);
|
||||
g_assert (partition < self->n_partitions);
|
||||
if (meta) {
|
||||
/* If meta is present, then we have no partition offset information,
|
||||
* so always emit PID 0 and set the start bit for the first packet
|
||||
* of a frame only (c.f. RFC7741 $4.4)
|
||||
*/
|
||||
partition = 0;
|
||||
start = (offset == 0);
|
||||
} else {
|
||||
partition = gst_rtp_vp8_offset_to_partition (self, offset);
|
||||
g_assert (partition < self->n_partitions);
|
||||
start = (offset == self->partition_offset[partition]);
|
||||
}
|
||||
|
||||
mark = (remaining == available);
|
||||
/* whole set of partitions, payload them and done */
|
||||
header = gst_rtp_vp8_create_header_buffer (self, partition,
|
||||
offset == self->partition_offset[partition], mark, buffer);
|
||||
start, mark, buffer, meta);
|
||||
sub = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, offset, available);
|
||||
|
||||
gst_rtp_copy_video_meta (self, header, buffer);
|
||||
gst_rtp_vp8_drop_vp8_meta (self, header);
|
||||
|
||||
out = gst_buffer_append (header, sub);
|
||||
|
||||
|
@ -512,17 +626,31 @@ gst_rtp_vp8_pay_handle_buffer (GstRTPBasePayload * payload, GstBuffer * buffer)
|
|||
GstRtpVP8Pay *self = GST_RTP_VP8_PAY (payload);
|
||||
GstFlowReturn ret;
|
||||
GstBufferList *list;
|
||||
GstCustomMeta *meta;
|
||||
gsize size, max_paylen;
|
||||
guint offset, mtu, vp8_hdr_len;
|
||||
|
||||
size = gst_buffer_get_size (buffer);
|
||||
|
||||
meta = gst_buffer_get_custom_meta (buffer, "GstVP8Meta");
|
||||
if (G_UNLIKELY (!gst_rtp_vp8_pay_parse_frame (self, buffer, size))) {
|
||||
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
|
||||
("Failed to parse VP8 frame"));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
if (meta) {
|
||||
GstStructure *s = gst_custom_meta_get_structure (meta);
|
||||
gboolean use_temporal_scaling;
|
||||
/* For interop it's most likely better to keep the temporal scalability
|
||||
* fields present if the stream previously had them present. Alternating
|
||||
* whether these fields are present or not may confuse the receiver. */
|
||||
|
||||
gst_structure_get_boolean (s, "use-temporal-scaling",
|
||||
&use_temporal_scaling);
|
||||
if (use_temporal_scaling)
|
||||
self->temporal_scalability_fields_present = TRUE;
|
||||
}
|
||||
|
||||
mtu = GST_RTP_BASE_PAYLOAD_MTU (payload);
|
||||
vp8_hdr_len = gst_rtp_vp8_calc_header_len (self);
|
||||
max_paylen = gst_rtp_buffer_calc_payload_len (mtu - vp8_hdr_len, 0,
|
||||
|
@ -533,7 +661,8 @@ gst_rtp_vp8_pay_handle_buffer (GstRTPBasePayload * payload, GstBuffer * buffer)
|
|||
offset = 0;
|
||||
while (offset < size) {
|
||||
offset +=
|
||||
gst_rtp_vp8_payload_next (self, list, offset, buffer, size, max_paylen);
|
||||
gst_rtp_vp8_payload_next (self, list, offset, buffer, size,
|
||||
max_paylen, meta);
|
||||
}
|
||||
|
||||
ret = gst_rtp_base_payload_push_list (payload, list);
|
||||
|
@ -551,7 +680,7 @@ gst_rtp_vp8_pay_sink_event (GstRTPBasePayload * payload, GstEvent * event)
|
|||
GstRtpVP8Pay *self = GST_RTP_VP8_PAY (payload);
|
||||
|
||||
if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_START) {
|
||||
gst_rtp_vp8_pay_picture_id_reset (self);
|
||||
gst_rtp_vp8_pay_reset (self);
|
||||
}
|
||||
|
||||
return GST_RTP_BASE_PAYLOAD_CLASS (gst_rtp_vp8_pay_parent_class)->sink_event
|
||||
|
|
|
@ -64,6 +64,8 @@ struct _GstRtpVP8Pay
|
|||
PictureIDMode picture_id_mode;
|
||||
gint picture_id_offset;
|
||||
guint16 picture_id;
|
||||
gboolean temporal_scalability_fields_present;
|
||||
guint8 tl0picidx;
|
||||
};
|
||||
|
||||
GType gst_rtp_vp8_pay_get_type (void);
|
||||
|
|
469
tests/check/elements/rtpvp8.c
Normal file
469
tests/check/elements/rtpvp8.c
Normal file
|
@ -0,0 +1,469 @@
|
|||
/* 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 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;
|
||||
|
||||
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);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
GST_CHECK_MAIN (rtpvp8);
|
|
@ -66,6 +66,7 @@ good_tests = [
|
|||
[ 'elements/rtph264' ],
|
||||
[ 'elements/rtph265' ],
|
||||
[ 'elements/rtpopus' ],
|
||||
[ 'elements/rtpvp8' ],
|
||||
[ 'elements/rtpvp9' ],
|
||||
[ 'elements/rtpbin' ],
|
||||
[ 'elements/rtpbin_buffer_list' ],
|
||||
|
|
Loading…
Reference in a new issue