Stefan Andersson 38f5ab75fd h26xparse: Drop NAL units that can't be parsed using AU alignment
Change so that the handling of NAL unit that can't be parsed when using
AU alignment is the same as when using NAL alignment, ie drop the data
if it can't be parsed.

If the AU contains more than one NAL unit any correctly parsed NAL unit
in the AU is kept.

Fixes #4436

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8976>
2025-06-10 13:00:20 +00:00

1431 lines
47 KiB
C

/*
* GStreamer
*
* unit test for h265parse
*
* Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.com>
* Copyright (C) 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.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/video/video-sei.h>
#include "parser.h"
#define SRC_CAPS_TMPL "video/x-h265, parsed=(boolean)false"
#define SINK_CAPS_TMPL "video/x-h265, parsed=(boolean)true"
GstStaticPadTemplate sinktemplate_bs_au = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (SINK_CAPS_TMPL
", stream-format = (string) byte-stream, alignment = (string) au")
);
GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (SRC_CAPS_TMPL)
);
/* Data generated with:
*
* gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,width=16,height=16 ! x265enc option-string="max-cll=1000,400:master-display=G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)" ! h265parse ! fakesink
*
* x265enc SEI has been dropped.
*
*/
static const guint8 h265_hvcc_codec_data[] = {
0x01, 0x04, 0x08, 0x00, 0x00, 0x00, 0x98, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3f,
0xf0, 0x00, 0xfc, 0xff, 0xfc, 0xfc, 0x00, 0x00, 0x0f, 0x03, 0x20, 0x00, 0x01,
0x00, 0x17, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x04, 0x08, 0x00, 0x00, 0x03,
0x00, 0x98, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x3f, 0x95, 0x98, 0x09, 0x21,
0x00, 0x01, 0x00, 0x2f, 0x42, 0x01, 0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00,
0x98, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x3f, 0x90, 0x11, 0x08, 0x8a, 0x52,
0xca, 0xcd, 0x57, 0x95, 0xff, 0xe0, 0x00, 0x20, 0x00, 0x2d, 0x41, 0x81, 0x81,
0x81, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x1e, 0x08, 0x22,
0x00, 0x01, 0x00, 0x06, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89
};
static const guint8 h265_vps[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00,
0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0x95,
0x98, 0x09
};
static const guint8 h265_sps[] = {
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00,
0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0xa0, 0x88, 0x45, 0x96,
0x56, 0x6a, 0xbc, 0xaf, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a, 0x0c, 0x02, 0x0c,
0x08, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x40
};
static const guint8 h265_pps[] = {
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89
};
static const guint8 h265_idr[] = {
0x00, 0x00, 0x00, 0x01, 0x26, 0x01, 0xaf, 0x06, 0xb8, 0xcf, 0xbc, 0x65, 0x85,
0x3b, 0x49, 0xff, 0xd0, 0x2c, 0xff, 0x3b, 0x61, 0x6d, 0x1b, 0xae, 0xf1, 0xf4,
0x96, 0x15, 0xef, 0x3e, 0xc6, 0x67, 0x3c, 0x0a, 0xd0, 0x6a, 0xb9, 0xfb, 0xf8,
0xb4, 0xb8, 0x4a, 0x4c, 0x4e, 0xe2, 0xf6, 0xb0, 0x29, 0x41, 0x4e, 0x14, 0xe8,
0x1f, 0x41, 0x58, 0xcb, 0x7a, 0x94, 0xdc, 0xba, 0x3d, 0x2e, 0xe0, 0x83, 0x4d,
0x3c, 0x3d, 0x2d, 0x70, 0xd1, 0xc4, 0x3d, 0x65, 0xf8, 0x3a, 0xe3, 0xdf, 0xb1,
0xf1, 0x1c, 0x48, 0x45, 0x63, 0x5b, 0x55, 0x0e, 0x0d, 0xef, 0xfc, 0x07, 0xd3,
0xce, 0x14, 0xc2, 0xac, 0x79, 0xd6, 0x1c, 0x44, 0x2c, 0xbd, 0x00, 0xff, 0xe5,
0x0c, 0x09, 0x3a, 0x3b, 0x53, 0xa8, 0x58, 0xb5, 0xb0, 0x29, 0xe6, 0x64, 0x14,
0x3a, 0xec, 0x8c, 0x7d, 0xd9, 0x19, 0xb4, 0xc2, 0x75, 0x37, 0xa2, 0x64, 0xa3,
0x1f, 0x26, 0x78, 0xe0, 0xa4, 0xde, 0xed, 0xb1, 0x52, 0x67, 0x90, 0xf1, 0x8e,
0xf9, 0x99, 0xa8, 0x9e, 0xfa, 0x55, 0xfc, 0x92, 0x3d, 0xd1, 0x03, 0xff, 0xff,
0xf7, 0x79, 0xaf, 0xa5, 0x90, 0x72, 0x35, 0x4e, 0x64, 0x16, 0x48, 0xa8, 0x28,
0xc4, 0xcf, 0x51, 0x83, 0x78, 0x6d, 0x90, 0x3a, 0xdf, 0xff, 0xb1, 0x1b, 0xb4,
0x3e, 0xa5, 0xd3, 0xc9, 0x2b, 0x75, 0x16, 0x01, 0x16, 0xa6, 0xc5, 0x1d, 0x1e,
0xd6, 0x63, 0x0c, 0xba, 0x2f, 0x77, 0x58, 0x5a, 0x4c, 0xb6, 0x49, 0x63, 0xb4,
0xa5, 0xb3, 0x25, 0x1b, 0xfd, 0xea, 0x13, 0x8b, 0xb3, 0x8f, 0x42, 0x81, 0xa1,
0x89, 0xe1, 0x36, 0x80, 0x11, 0x3c, 0x88, 0x84, 0x29, 0x51, 0x59, 0x2c, 0xb2,
0x9c, 0x90, 0xa5, 0x12, 0x80, 0x2d, 0x16, 0x61, 0x8e, 0xf1, 0x28, 0xba, 0x0f,
0x71, 0xdf, 0x7b, 0xdb, 0xd7, 0xb0, 0x3d, 0xa1, 0xbe, 0x4f, 0x7c, 0xcf, 0x09,
0x73, 0xe1, 0x10, 0xea, 0x64, 0x96, 0x89, 0x5d, 0x7e, 0x7f, 0x26, 0x18, 0x43,
0xbb, 0x0d, 0x2c, 0x95, 0xaa, 0xec, 0x03, 0x9d, 0x55, 0x56, 0xdf, 0xd3, 0x7e,
0x4f, 0xf7, 0x47, 0x60, 0x89, 0x35, 0x6e, 0x08, 0x9a, 0xcf, 0x11, 0x26, 0xc3,
0xec, 0x31, 0x23, 0xca, 0x51, 0x10, 0x80
};
/* Content light level information SEI message */
static const guint8 h265_sei_clli[] = {
0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x90, 0x04, 0x03, 0xe8, 0x01, 0x90, 0x80
};
/* Mastering display colour volume information SEI message */
static const guint8 h265_sei_mdcv[] = {
0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x89, 0x18, 0x33, 0xc2, 0x86, 0xc4, 0x1d,
0x4c, 0x0b, 0xb8, 0x84, 0xd0, 0x3e, 0x80, 0x3d, 0x13, 0x40, 0x42, 0x00, 0x98,
0x96, 0x80, 0x00, 0x00, 0x03, 0x00, 0x01, 0x80
};
/* single-sliced data, generated with:
* gst-launch-1.0 videotestsrc num-buffers=1 pattern=green \
* ! video/x-raw,width=128,height=128 \
* ! x265enc
* ! fakesink dump=1
*/
static const guint8 h265_128x128_vps[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01,
0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00,
0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
0x3f, 0x95, 0x98, 0x09
};
static const guint8 h265_128x128_sps[] = {
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01,
0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00,
0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0xa0, 0x10,
0x20, 0x20, 0x59, 0x65, 0x66, 0x92, 0x4c, 0xaf,
0xff, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00,
0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x1e,
0x08
};
static const guint8 h265_128x128_pps[] = {
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x72,
0xb4, 0x22, 0x40
};
static const guint8 h265_128x128_slice_idr_n_lp[] = {
0x00, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x0e,
0xe0, 0x34, 0x82, 0x15, 0x84, 0xf4, 0x70, 0x4f,
0xff, 0xed, 0x41, 0x3f, 0xff, 0xe4, 0xcd, 0xc4,
0x7c, 0x03, 0x0c, 0xc2, 0xbb, 0xb0, 0x74, 0xe5,
0xef, 0x4f, 0xe1, 0xa3, 0xd4, 0x00, 0x02, 0xc2
};
/* multi-sliced data, generated on zynqultrascaleplus with:
* gst-launch-1.0 videotestsrc num-buffers=1 pattern=green \
* ! video/x-raw,width=128,height=128 \
* ! omxh265enc num-slices=2 \
* ! fakesink dump=1
*/
static const guint8 h265_128x128_sliced_vps[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01,
0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00,
0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
0x1e, 0x25, 0x02, 0x40
};
static const guint8 h265_128x128_sliced_sps[] = {
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01,
0x40, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00,
0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x10,
0x20, 0x20, 0x59, 0xe9, 0x6e, 0x44, 0xa1, 0x73,
0x50, 0x60, 0x20, 0x2e, 0x10, 0x00, 0x00, 0x03,
0x00, 0x10, 0x00, 0x00, 0x03, 0x01, 0xe5, 0x1a,
0xff, 0xff, 0x10, 0x3e, 0x80, 0x5d, 0xf7, 0xc2,
0x01, 0x04
};
static const guint8 h265_128x128_sliced_pps[] = {
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0x71,
0x81, 0x8d, 0xb2
};
static const guint8 h265_128x128_slice_1_idr_n_lp[] = {
0x00, 0x00, 0x00, 0x01, 0x28, 0x01, 0xac, 0x46,
0x13, 0xb6, 0x45, 0x43, 0xaf, 0xee, 0x3d, 0x3f,
0x76, 0xe5, 0x73, 0x2f, 0xee, 0xd2, 0xeb, 0xbf,
0x80
};
static const guint8 h265_128x128_slice_2_idr_n_lp[] = {
0x00, 0x00, 0x00, 0x01, 0x28, 0x01, 0x30, 0xc4,
0x60, 0x13, 0xb6, 0x45, 0x43, 0xaf, 0xee, 0x3d,
0x3f, 0x76, 0xe5, 0x73, 0x2f, 0xee, 0xd2, 0xeb,
0xbf, 0x80
};
static const gchar *ctx_suite;
static gboolean ctx_codec_data;
/* A single access unit comprising of VPS, SPS, PPS and IDR frame */
static gboolean
verify_buffer_bs_au (buffer_verify_data_s * vdata, GstBuffer * buffer)
{
GstMapInfo map;
fail_unless (ctx_sink_template == &sinktemplate_bs_au);
gst_buffer_map (buffer, &map, GST_MAP_READ);
fail_unless (map.size > 4);
if (vdata->buffer_counter == 0) {
guint8 *data = map.data;
/* VPS, SPS, PPS */
fail_unless (map.size == vdata->data_to_verify_size +
ctx_headers[0].size + ctx_headers[1].size + ctx_headers[2].size);
fail_unless (memcmp (data, ctx_headers[0].data, ctx_headers[0].size) == 0);
data += ctx_headers[0].size;
fail_unless (memcmp (data, ctx_headers[1].data, ctx_headers[1].size) == 0);
data += ctx_headers[1].size;
fail_unless (memcmp (data, ctx_headers[2].data, ctx_headers[2].size) == 0);
data += ctx_headers[2].size;
/* IDR frame */
fail_unless (memcmp (data, vdata->data_to_verify,
vdata->data_to_verify_size) == 0);
} else {
/* IDR frame */
fail_unless (map.size == vdata->data_to_verify_size);
fail_unless (memcmp (map.data, vdata->data_to_verify, map.size) == 0);
}
gst_buffer_unmap (buffer, &map);
return TRUE;
}
GST_START_TEST (test_parse_normal)
{
gst_parser_test_normal (h265_idr, sizeof (h265_idr));
}
GST_END_TEST;
GST_START_TEST (test_parse_drain_single)
{
gst_parser_test_drain_single (h265_idr, sizeof (h265_idr));
}
GST_END_TEST;
GST_START_TEST (test_parse_split)
{
gst_parser_test_split (h265_idr, sizeof (h265_idr));
}
GST_END_TEST;
#define structure_get_int(s,f) \
(g_value_get_int(gst_structure_get_value(s,f)))
#define fail_unless_structure_field_int_equals(s,field,num) \
fail_unless_equals_int (structure_get_int(s,field), num)
#define structure_get_string(s,f) \
(g_value_get_string(gst_structure_get_value(s,f)))
#define fail_unless_structure_field_string_equals(s,field,name) \
fail_unless_equals_string (structure_get_string(s,field), name)
GST_START_TEST (test_parse_detect_stream)
{
GstCaps *caps;
GstStructure *s;
caps = gst_parser_test_get_output_caps (h265_idr, sizeof (h265_idr), NULL);
fail_unless (caps != NULL);
/* Check that the negotiated caps are as expected */
GST_DEBUG ("output caps: %" GST_PTR_FORMAT, caps);
s = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_has_name (s, "video/x-h265"));
fail_unless_structure_field_int_equals (s, "width", 16);
fail_unless_structure_field_int_equals (s, "height", 16);
fail_unless_structure_field_string_equals (s, "stream-format", "byte-stream");
fail_unless_structure_field_string_equals (s, "alignment", "au");
fail_unless_structure_field_string_equals (s, "profile", "main");
fail_unless_structure_field_string_equals (s, "tier", "main");
fail_unless_structure_field_string_equals (s, "level", "2.1");
gst_caps_unref (caps);
}
GST_END_TEST;
GST_START_TEST (test_parse_detect_stream_with_hdr_sei)
{
GstCaps *caps;
GstStructure *s;
guint8 *h265_idr_plus_sei;
gsize h265_idr_plus_sei_size =
sizeof (h265_sei_clli) + sizeof (h265_sei_mdcv) + sizeof (h265_idr);
h265_idr_plus_sei = malloc (h265_idr_plus_sei_size);
memcpy (h265_idr_plus_sei, h265_sei_clli, sizeof (h265_sei_clli));
memcpy (h265_idr_plus_sei + sizeof (h265_sei_clli), h265_sei_mdcv,
sizeof (h265_sei_mdcv));
memcpy (h265_idr_plus_sei + sizeof (h265_sei_clli) + sizeof (h265_sei_mdcv),
h265_idr, sizeof (h265_idr));
caps =
gst_parser_test_get_output_caps (h265_idr_plus_sei,
h265_idr_plus_sei_size, NULL);
fail_unless (caps != NULL);
/* Check that the negotiated caps are as expected */
GST_DEBUG ("output caps: %" GST_PTR_FORMAT, caps);
s = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_has_name (s, "video/x-h265"));
fail_unless_structure_field_int_equals (s, "width", 16);
fail_unless_structure_field_int_equals (s, "height", 16);
fail_unless_structure_field_string_equals (s, "stream-format", "byte-stream");
fail_unless_structure_field_string_equals (s, "alignment", "au");
fail_unless_structure_field_string_equals (s, "profile", "main");
fail_unless_structure_field_string_equals (s, "tier", "main");
fail_unless_structure_field_string_equals (s, "level", "2.1");
fail_unless_structure_field_string_equals (s, "mastering-display-info",
"34000:16000:13250:34500:7500:3000:15635:16450:10000000:1");
fail_unless_structure_field_string_equals (s, "content-light-level",
"1000:400");
g_free (h265_idr_plus_sei);
gst_caps_unref (caps);
}
GST_END_TEST;
/* 8bits 4:4:4 encoded stream, and profile-level-tier is not spec compliant.
* extracted from the file reported at
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1009
*/
static const guint8 broken_profile_codec_data[] = {
0x01, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x99, 0xf0, 0x00, 0xfc, 0xff, 0xf8, 0xf8, 0x00, 0x00, 0x0f, 0x03, 0x20,
0x00, 0x01, 0x00, 0x18, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x24, 0x08,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
0x00, 0x99, 0xac, 0x09, 0x21, 0x00, 0x01, 0x00, 0x2c, 0x42, 0x01, 0x01,
0x24, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
0x00, 0x03, 0x00, 0x99, 0x90, 0x00, 0x3c, 0x04, 0x00, 0x44, 0x0f, 0x84,
0x72, 0xd6, 0x94, 0x84, 0xb2, 0x5c, 0x40, 0x20, 0x00, 0x00, 0x03, 0x00,
0x20, 0x00, 0x00, 0x07, 0x81, 0x22, 0x00, 0x01, 0x00, 0x08, 0x44, 0x01,
0xc0, 0xf7, 0x18, 0x30, 0x0c, 0xc9
};
GST_START_TEST (test_parse_fallback_profile)
{
GstHarness *h = gst_harness_new ("h265parse");
GstCaps *caps;
GstBuffer *codec_data;
GstEvent *event;
codec_data = gst_buffer_new_memdup (broken_profile_codec_data,
sizeof (broken_profile_codec_data));
caps = gst_caps_from_string ("video/x-h265, stream-format=(string)hvc1, "
"alignment=(string)au");
gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, codec_data, NULL);
gst_buffer_unref (codec_data);
gst_harness_set_src_caps (h, caps);
while ((event = gst_harness_pull_event (h)) != NULL) {
GstStructure *s;
const gchar *profile;
if (GST_EVENT_TYPE (event) != GST_EVENT_CAPS) {
gst_event_unref (event);
continue;
}
gst_event_parse_caps (event, &caps);
s = gst_caps_get_structure (caps, 0);
profile = gst_structure_get_string (s, "profile");
/* h265parse must provide profile */
fail_unless (profile);
/* must not be main profile at least.
* main-444 is expected but we might update the profile parsing
* logic later. At least it should not be main profile
*/
fail_if (g_strcmp0 (profile, "main") == 0);
gst_event_unref (event);
break;
}
gst_harness_teardown (h);
}
GST_END_TEST;
static Suite *
h265parse_suite (void)
{
Suite *s = suite_create (ctx_suite);
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_parse_normal);
tcase_add_test (tc_chain, test_parse_drain_single);
tcase_add_test (tc_chain, test_parse_split);
tcase_add_test (tc_chain, test_parse_detect_stream);
tcase_add_test (tc_chain, test_parse_detect_stream_with_hdr_sei);
tcase_add_test (tc_chain, test_parse_fallback_profile);
return s;
}
/* helper methods for GstHasness based tests */
static inline GstBuffer *
wrap_buffer (const guint8 * buf, gsize size, GstClockTime pts,
GstBufferFlags flags)
{
GstBuffer *buffer;
buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
(gpointer) buf, size, 0, size, NULL, NULL);
GST_BUFFER_PTS (buffer) = pts;
GST_BUFFER_FLAGS (buffer) |= flags;
return buffer;
}
static inline GstBuffer *
composite_buffer (GstClockTime pts, GstBufferFlags flags, gint count, ...)
{
va_list vl;
gint i;
const guint8 *data;
gsize size;
GstBuffer *buffer;
va_start (vl, count);
buffer = gst_buffer_new ();
for (i = 0; i < count; i++) {
data = va_arg (vl, guint8 *);
size = va_arg (vl, gsize);
buffer = gst_buffer_append (buffer, wrap_buffer (data, size, 0, 0));
}
GST_BUFFER_PTS (buffer) = pts;
GST_BUFFER_FLAGS (buffer) |= flags;
va_end (vl);
return buffer;
}
static inline void
pull_and_check_full (GstHarness * h, const guint8 * data, gsize size,
GstClockTime pts, GstBufferFlags flags)
{
GstBuffer *b = gst_harness_pull (h);
gst_check_buffer_data (b, data, size);
fail_unless_equals_clocktime (GST_BUFFER_PTS (b), pts);
if (flags)
fail_unless (GST_BUFFER_FLAG_IS_SET (b, flags));
gst_buffer_unref (b);
}
#define pull_and_check(h, data, pts, flags) \
pull_and_check_full (h, data, sizeof (data), pts, flags)
#define pull_and_check_composite(h, pts, flags, ...) \
G_STMT_START { \
GstMapInfo info; \
GstBuffer *cb; \
\
cb = composite_buffer (0, 0, __VA_ARGS__); \
gst_buffer_map (cb, &info, GST_MAP_READ); \
\
pull_and_check_full (h, info.data, info.size, pts, flags); \
\
gst_buffer_unmap (cb, &info); \
gst_buffer_unref (cb); \
} G_STMT_END
#define pull_and_drop(h) \
G_STMT_START { \
GstBuffer *b = gst_harness_pull (h); \
gst_buffer_unref (b); \
} G_STMT_END
#define HEADER_DATA \
const guint8 * const vps = sliced ? h265_128x128_sliced_vps : h265_128x128_vps; \
const guint8 * const sps = sliced ? h265_128x128_sliced_sps : h265_128x128_sps; \
const guint8 * const pps = sliced ? h265_128x128_sliced_pps : h265_128x128_pps; \
const gsize vps_size = sliced ? sizeof (h265_128x128_sliced_vps) : sizeof (h265_128x128_vps); \
const gsize sps_size = sliced ? sizeof (h265_128x128_sliced_sps) : sizeof (h265_128x128_sps); \
const gsize pps_size = sliced ? sizeof (h265_128x128_sliced_pps) : sizeof (h265_128x128_pps)
#define SLICE_DATA \
const guint8 * const slice_1 = sliced ? h265_128x128_slice_1_idr_n_lp : h265_128x128_slice_idr_n_lp; \
const guint8 * const slice_2 = sliced ? h265_128x128_slice_2_idr_n_lp : NULL; \
const gsize slice_1_size = sliced ? sizeof (h265_128x128_slice_1_idr_n_lp) : sizeof (h265_128x128_slice_idr_n_lp); \
const gsize slice_2_size = sliced ? sizeof (h265_128x128_slice_2_idr_n_lp) : 0
#define bytestream_set_caps(h, in_align, out_align) \
gst_harness_set_caps_str (h, \
"video/x-h265, parsed=(boolean)false, stream-format=byte-stream, alignment=" in_align ", framerate=30/1", \
"video/x-h265, parsed=(boolean)true, stream-format=byte-stream, alignment=" out_align)
static inline void
bytestream_push_first_au_inalign_nal (GstHarness * h, gboolean sliced)
{
HEADER_DATA;
SLICE_DATA;
GstBuffer *buf;
buf = wrap_buffer (vps, vps_size, 10, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
buf = wrap_buffer (sps, sps_size, 10, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
buf = wrap_buffer (pps, pps_size, 10, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
buf = wrap_buffer (slice_1, slice_1_size, 10, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
if (sliced) {
buf = wrap_buffer (slice_2, slice_2_size, 10, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
}
}
static inline void
bytestream_push_first_au_inalign_au (GstHarness * h, gboolean sliced)
{
HEADER_DATA;
SLICE_DATA;
GstBuffer *buf;
buf = composite_buffer (10, 0, sliced ? 5 : 4,
vps, vps_size, sps, sps_size, pps, pps_size,
slice_1, slice_1_size, slice_2, slice_2_size);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
}
/* tests */
static void
test_flow_outalign_nal (GstHarness * h)
{
GstBuffer *buf;
/* drop the first AU - tested separately */
fail_unless (gst_harness_buffers_in_queue (h) > 0);
while (gst_harness_buffers_in_queue (h) > 0)
pull_and_drop (h);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 100, 0);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 200, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 200, 0);
}
static void
test_flow_outalign_au (GstHarness * h)
{
GstBuffer *buf;
/* drop the first AU - tested separately */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_drop (h);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 100, 0);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 200, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 200, 0);
}
GST_START_TEST (test_flow_nal_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "nal", "nal");
bytestream_push_first_au_inalign_nal (h, FALSE);
test_flow_outalign_nal (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_flow_au_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "nal");
bytestream_push_first_au_inalign_au (h, FALSE);
test_flow_outalign_nal (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_flow_nal_au)
{
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf;
bytestream_set_caps (h, "nal", "au");
bytestream_push_first_au_inalign_nal (h, FALSE);
/* special case because we have latency */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* drop the first AU - tested separately */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_drop (h);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 200, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 100, 0);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_flow_au_au)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "au");
bytestream_push_first_au_inalign_au (h, FALSE);
test_flow_outalign_au (h);
gst_harness_teardown (h);
}
GST_END_TEST;
static void
test_headers_outalign_nal (GstHarness * h)
{
/* 5 -> AUD + VPS + SPS + PPS + slice */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 4);
/* parser must have inserted AUD before the headers, with the same PTS */
pull_and_check (h, h265_128x128_vps, 10, 0);
pull_and_check (h, h265_128x128_sps, 10, 0);
pull_and_check (h, h265_128x128_pps, 10, 0);
/* FIXME The timestamp should be 10 really, but base parse refuse to repeat
* the same TS for two consecutive calls to _finish_frame(), see [0] for
* more details. It's not a huge issue, the decoder can fix it for now.
*
* [0] https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/287
*/
pull_and_check (h, h265_128x128_slice_idr_n_lp, -1, 0);
}
static void
test_headers_outalign_au (GstHarness * h)
{
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check_composite (h, 10, 0, 4,
h265_128x128_vps, sizeof (h265_128x128_vps),
h265_128x128_sps, sizeof (h265_128x128_sps),
h265_128x128_pps, sizeof (h265_128x128_pps),
h265_128x128_slice_idr_n_lp, sizeof (h265_128x128_slice_idr_n_lp));
}
GST_START_TEST (test_headers_nal_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "nal", "nal");
bytestream_push_first_au_inalign_nal (h, FALSE);
test_headers_outalign_nal (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_headers_au_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "nal");
bytestream_push_first_au_inalign_au (h, FALSE);
test_headers_outalign_nal (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_headers_au_au)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "au");
bytestream_push_first_au_inalign_au (h, FALSE);
test_headers_outalign_au (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_latency_nal_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "nal", "nal");
bytestream_push_first_au_inalign_nal (h, FALSE);
fail_unless_equals_clocktime (gst_harness_query_latency (h), 0);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_latency_au_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "nal");
bytestream_push_first_au_inalign_au (h, FALSE);
fail_unless_equals_clocktime (gst_harness_query_latency (h), 0);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_latency_nal_au)
{
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf;
bytestream_set_caps (h, "nal", "au");
bytestream_push_first_au_inalign_nal (h, FALSE);
/* special case because we have latency;
* the first buffer needs to be pushed out
* before we can correctly query the latency */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* our input caps declare framerate=30fps, so the latency must be 1/30 sec */
fail_unless_equals_clocktime (gst_harness_query_latency (h),
gst_util_uint64_scale (GST_SECOND, 1, 30));
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_latency_au_au)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "au");
bytestream_push_first_au_inalign_au (h, FALSE);
fail_unless_equals_clocktime (gst_harness_query_latency (h), 0);
gst_harness_teardown (h);
}
GST_END_TEST;
static void
test_discont_outalign_nal (GstHarness * h)
{
GstBuffer *buf;
/* drop the first AU - tested separately */
fail_unless (gst_harness_buffers_in_queue (h) > 0);
while (gst_harness_buffers_in_queue (h) > 0)
pull_and_drop (h);
/* FIXME: I think the AUD ought to have DISCONT */
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 1000, GST_BUFFER_FLAG_DISCONT);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 1000,
GST_BUFFER_FLAG_DISCONT);
}
static void
test_discont_outalign_au (GstHarness * h)
{
GstBuffer *buf;
/* drop the first AU - tested separately */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_drop (h);
buf = wrap_buffer (h265_128x128_slice_idr_n_lp,
sizeof (h265_128x128_slice_idr_n_lp), 1000, GST_BUFFER_FLAG_DISCONT);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_idr_n_lp, 1000,
GST_BUFFER_FLAG_DISCONT);
}
GST_START_TEST (test_discont_nal_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "nal", "nal");
bytestream_push_first_au_inalign_nal (h, FALSE);
test_discont_outalign_nal (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_discont_au_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "nal");
bytestream_push_first_au_inalign_au (h, FALSE);
test_discont_outalign_nal (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_discont_au_au)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "au", "au");
bytestream_push_first_au_inalign_au (h, FALSE);
test_discont_outalign_au (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_sliced_nal_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf;
bytestream_set_caps (h, "nal", "nal");
bytestream_push_first_au_inalign_nal (h, TRUE);
/* drop the header buffers */
fail_unless (gst_harness_buffers_in_queue (h) > 2);
while (gst_harness_buffers_in_queue (h) > 2)
pull_and_drop (h);
/* but expect 2 slices */
pull_and_check (h, h265_128x128_slice_1_idr_n_lp, -1, 0);
pull_and_check (h, h265_128x128_slice_2_idr_n_lp, -1, 0);
/* push some more */
buf = wrap_buffer (h265_128x128_slice_1_idr_n_lp,
sizeof (h265_128x128_slice_1_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_1_idr_n_lp, 100, 0);
buf = wrap_buffer (h265_128x128_slice_2_idr_n_lp,
sizeof (h265_128x128_slice_2_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check (h, h265_128x128_slice_2_idr_n_lp, -1, 0);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_sliced_au_nal)
{
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf;
bytestream_set_caps (h, "au", "nal");
bytestream_push_first_au_inalign_au (h, TRUE);
/* drop the header buffers */
fail_unless (gst_harness_buffers_in_queue (h) > 2);
while (gst_harness_buffers_in_queue (h) > 2)
pull_and_drop (h);
/* but expect 2 slices */
pull_and_check (h, h265_128x128_slice_1_idr_n_lp, -1, 0);
pull_and_check (h, h265_128x128_slice_2_idr_n_lp, -1, 0);
/* push some more */
buf = composite_buffer (100, 0, 2,
h265_128x128_slice_1_idr_n_lp, sizeof (h265_128x128_slice_1_idr_n_lp),
h265_128x128_slice_2_idr_n_lp, sizeof (h265_128x128_slice_2_idr_n_lp));
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 2);
pull_and_check (h, h265_128x128_slice_1_idr_n_lp, 100, 0);
pull_and_check (h, h265_128x128_slice_2_idr_n_lp, -1, 0);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_sliced_nal_au)
{
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf;
bytestream_set_caps (h, "nal", "au");
bytestream_push_first_au_inalign_nal (h, TRUE);
/* nal -> au has latency; we need to start the next AU to get output */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
/* push some more */
buf = wrap_buffer (h265_128x128_slice_1_idr_n_lp,
sizeof (h265_128x128_slice_1_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* now we can see the initial AU on the output */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check_composite (h, 10, 0, 5,
h265_128x128_sliced_vps, sizeof (h265_128x128_sliced_vps),
h265_128x128_sliced_sps, sizeof (h265_128x128_sliced_sps),
h265_128x128_sliced_pps, sizeof (h265_128x128_sliced_pps),
h265_128x128_slice_1_idr_n_lp, sizeof (h265_128x128_slice_1_idr_n_lp),
h265_128x128_slice_2_idr_n_lp, sizeof (h265_128x128_slice_2_idr_n_lp));
buf = wrap_buffer (h265_128x128_slice_2_idr_n_lp,
sizeof (h265_128x128_slice_2_idr_n_lp), 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
buf = wrap_buffer (h265_128x128_slice_1_idr_n_lp,
sizeof (h265_128x128_slice_1_idr_n_lp), 200, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check_composite (h, 100, 0, 2,
h265_128x128_slice_1_idr_n_lp, sizeof (h265_128x128_slice_1_idr_n_lp),
h265_128x128_slice_2_idr_n_lp, sizeof (h265_128x128_slice_2_idr_n_lp));
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_sliced_au_au)
{
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf;
bytestream_set_caps (h, "au", "au");
bytestream_push_first_au_inalign_au (h, TRUE);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check_composite (h, 10, 0, 5,
h265_128x128_sliced_vps, sizeof (h265_128x128_sliced_vps),
h265_128x128_sliced_sps, sizeof (h265_128x128_sliced_sps),
h265_128x128_sliced_pps, sizeof (h265_128x128_sliced_pps),
h265_128x128_slice_1_idr_n_lp, sizeof (h265_128x128_slice_1_idr_n_lp),
h265_128x128_slice_2_idr_n_lp, sizeof (h265_128x128_slice_2_idr_n_lp));
/* push some more */
buf = composite_buffer (100, 0, 2,
h265_128x128_slice_1_idr_n_lp, sizeof (h265_128x128_slice_1_idr_n_lp),
h265_128x128_slice_2_idr_n_lp, sizeof (h265_128x128_slice_2_idr_n_lp));
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_check_composite (h, 100, 0, 2,
h265_128x128_slice_1_idr_n_lp, sizeof (h265_128x128_slice_1_idr_n_lp),
h265_128x128_slice_2_idr_n_lp, sizeof (h265_128x128_slice_2_idr_n_lp));
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_parse_skip_to_4bytes_sc)
{
GstHarness *h;
GstBuffer *buf1, *buf2;
const guint8 initial_bytes[] = { 0x00, 0x00, 0x00, 0x00, 0x01, h265_vps[4] };
GstMapInfo map;
h = gst_harness_new ("h265parse");
gst_harness_set_caps_str (h, "video/x-h265, stream-format=byte-stream",
"video/x-h265, stream-format=byte-stream, alignment=nal");
/* padding bytes, four bytes start code and 1 of the two identification
* bytes. */
buf1 = wrap_buffer (initial_bytes, sizeof (initial_bytes), 100, 0);
/* The second contains the an VPS, starting from second NAL identification
* byte and is followed by an SPS, IDR to ensure that the NAL end can be
* found */
buf2 = composite_buffer (100, 0, 4, h265_vps + 5, sizeof (h265_vps) - 5,
h265_sps, sizeof (h265_sps), h265_pps, sizeof (h265_pps),
h265_idr, sizeof (h265_idr));
fail_unless_equals_int (gst_harness_push (h, buf1), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
fail_unless_equals_int (gst_harness_push (h, buf2), GST_FLOW_OK);
/* The parser will deliver VPS, SPS, PPS as it now have complete cpas */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 3);
buf1 = gst_harness_pull (h);
gst_buffer_map (buf1, &map, GST_MAP_READ);
fail_unless_equals_int (gst_buffer_get_size (buf1), sizeof (h265_vps));
gst_buffer_unmap (buf1, &map);
gst_buffer_unref (buf1);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_parse_sc_with_half_header)
{
GstHarness *h;
GstBuffer *buf1, *buf2;
GstMapInfo map;
h = gst_harness_new ("h265parse");
gst_harness_set_caps_str (h, "video/x-h265, stream-format=byte-stream",
"video/x-h265, stream-format=byte-stream, alignment=nal");
buf1 = composite_buffer (100, 0, 4, h265_vps, sizeof (h265_vps),
h265_sps, sizeof (h265_sps), h265_pps, sizeof (h265_pps), h265_idr, 5);
buf2 = wrap_buffer (h265_idr + 5, sizeof (h265_idr) - 5, 100, 0);
fail_unless_equals_int (gst_harness_push (h, buf1), GST_FLOW_OK);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
fail_unless_equals_int (gst_harness_push (h, buf2), GST_FLOW_OK);
/* The parser will deliver VPS, SPS, PPS as it now have complete cpas */
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 3);
buf1 = gst_harness_pull (h);
gst_buffer_map (buf1, &map, GST_MAP_READ);
fail_unless_equals_int (gst_buffer_get_size (buf1), sizeof (h265_vps));
gst_buffer_unmap (buf1, &map);
gst_buffer_unref (buf1);
gst_harness_teardown (h);
}
GST_END_TEST;
/* nal->au has latency, but EOS should force the last AU out */
GST_START_TEST (test_drain)
{
GstHarness *h = gst_harness_new ("h265parse");
bytestream_set_caps (h, "nal", "au");
bytestream_push_first_au_inalign_nal (h, FALSE);
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 0);
gst_harness_push_event (h, gst_event_new_eos ());
fail_unless_equals_int (gst_harness_buffers_in_queue (h), 1);
pull_and_drop (h);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_parse_sei_userdefinedunregistered)
{
GstVideoSEIUserDataUnregisteredMeta *meta;
GstHarness *h;
GstBuffer *buf;
const guint8 bytestream[] = {
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x04, 0x08, 0x00, 0x00, 0x03,
0x00, 0x9e, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0x90, 0x11, 0x08,
0xb2, 0xca, 0xcd, 0x57, 0x95, 0xcd, 0xc0, 0x80, 0x80, 0x01, 0x00, 0x00,
0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x19, 0x08, 0x00, 0x00, 0x00,
0x01, 0x44, 0x01, 0xc1, 0x73, 0x18, 0x31, 0x08, 0x90,
// SEI
0x00, 0x00, 0x01, 0x4e, 0x01,
0x05, // SEI Type.
0x18, // SEI Payload size (16 UUID size + 8 payload size = 24).
// SEI User Data Unregistered UUID.
0xee, 0x2c, 0xa2, 0xde, 0x09, 0xb5, 0x17, 0x47, 0xdb, 0xbb, 0x55, 0xa4,
0xfe, 0x7f, 0xc2, 0xfc,
// SEI User Data Unregistered Payload.
0x4e, 0x78, 0x32, 0x36, 0x35, 0x20, 0x28, 0x62,
};
const gsize bytestream_size = sizeof (bytestream);
const guint8 uuid[] = {
0xee, 0x2c, 0xa2, 0xde, 0x09, 0xb5, 0x17, 0x47, 0xdb, 0xbb, 0x55, 0xa4,
0xfe, 0x7f, 0xc2, 0xfc
};
const guint8 payload[] = { 0x4e, 0x78, 0x32, 0x36, 0x35, 0x20, 0x28, 0x62 };
h = gst_harness_new ("h265parse");
gst_harness_set_src_caps_str (h, "video/x-h265, stream-format=byte-stream");
buf = gst_buffer_new_and_alloc (bytestream_size);
gst_buffer_fill (buf, 0, bytestream, bytestream_size);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
gst_harness_push_event (h, gst_event_new_eos ());
buf = gst_harness_pull (h);
meta = gst_buffer_get_video_sei_user_data_unregistered_meta (buf);
fail_unless (meta != NULL);
fail_unless (memcmp (meta->uuid, uuid, 16) == 0);
fail_unless_equals_int (meta->size, G_N_ELEMENTS (payload));
fail_unless (memcmp (meta->data, payload, meta->size) == 0);
gst_buffer_unref (buf);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_invalid_sei_in_hvcc)
{
GstHarness *h;
GstCaps *caps;
GstBuffer *codec_data;
/* Consists of 4 arrays (VPS, SPS, PPS, SEI -> broken) and each array contains
* single nalu
* Captured from the log at
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2905
*/
static const guint8 hvcc_data[] = {
0x01, 0x01, 0x01, 0x01, 0x60, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0x00, 0xfc, 0xfd, 0xf8, 0xf8, 0x00, 0x00, 0x0f, 0x04, 0x20,
0x00, 0x01, 0x00, 0x17, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
0x7b, 0xac, 0x09, 0x21, 0x00, 0x01, 0x00, 0x42, 0x42, 0x01, 0x01, 0x01,
0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
0x00, 0x7b, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x1f, 0xe3, 0x6b, 0xbb, 0x53,
0x77, 0x72, 0x5d, 0x60, 0x2d, 0xc0, 0x40, 0x40, 0x41, 0x00, 0x00, 0x03,
0x03, 0xe8, 0x00, 0x00, 0x4e, 0x20, 0x72, 0x1d, 0xee, 0x51, 0x00, 0x05,
0xdc, 0x00, 0x00, 0x1a, 0x5e, 0x00, 0x00, 0x2e, 0xe0, 0x00, 0x00, 0xd2,
0xf0, 0x08, 0x22, 0x00, 0x01, 0x00, 0x0b, 0x44, 0x01, 0xc1, 0x72, 0xb0,
0x9c, 0x38, 0x77, 0x06, 0x0c, 0x24, 0x27, 0x00, 0x01, 0x00, 0x00, 0x00
};
caps = gst_caps_new_simple ("video/x-h265", "stream-format", G_TYPE_STRING,
"hvc1", "alignment", G_TYPE_STRING, "au", NULL);
codec_data = gst_buffer_new_memdup (hvcc_data, sizeof (hvcc_data));
gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, codec_data, NULL);
gst_buffer_unref (codec_data);
h = gst_harness_new ("h265parse");
gst_harness_set_src_caps (h, caps);
gst_harness_push_event (h, gst_event_new_eos ());
while (TRUE) {
GstEvent *event = gst_harness_pull_event (h);
fail_unless (event);
if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
GstStructure *s;
gint width, height;
gst_event_parse_caps (event, &caps);
s = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_get_int (s, "width", &width));
fail_unless_equals_int (width, 1280);
fail_unless (gst_structure_get_int (s, "height", &height));
fail_unless_equals_int (height, 720);
gst_event_unref (event);
break;
}
gst_event_unref (event);
}
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_packetized_hvcc_drop_corrupt)
{
GstBuffer *cdata;
GstCaps *in_caps, *out_caps;
GstHarness *h = gst_harness_new ("h265parse");
GstBuffer *buf, *bufout;
GstMapInfo mapout;
const gchar *in_caps_str =
"video/x-h265, stream-format=(string)hvc1, alignment=(string)au";
const gchar *out_caps_str =
"video/x-h265, stream-format=(string)hvc1, alignment=(string)au";
in_caps = gst_caps_from_string (in_caps_str);
cdata = gst_buffer_new_memdup (h265_hvcc_codec_data,
sizeof (h265_hvcc_codec_data));
gst_caps_set_simple (in_caps, "codec_data", GST_TYPE_BUFFER, cdata, NULL);
gst_buffer_unref (cdata);
out_caps = gst_caps_from_string (out_caps_str);
gst_harness_set_caps (h, in_caps, out_caps);
/* hvcc idr frame nal */
static guint8 *h265_idr_hvcc;
/* make hvcc frame NAL */
h265_idr_hvcc = g_malloc (sizeof (h265_idr));
GST_WRITE_UINT32_BE (h265_idr_hvcc, sizeof (h265_idr) - 4);
memcpy (h265_idr_hvcc + 4, h265_idr + 4, sizeof (h265_idr) - 4);
static guint8 h265_garbage_hvcc[] = {
0x00, 0x00, 0x00, 0x00, 0x05
};
/* Send all => drop garbage end but keep correct frame. */
buf = composite_buffer (100, 0, 2, h265_idr_hvcc, sizeof (h265_idr),
h265_garbage_hvcc, sizeof (h265_garbage_hvcc));
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* Send 3 IDR frames => all should be kept. */
buf = composite_buffer (200, 0, 3, h265_idr_hvcc, sizeof (h265_idr),
h265_idr_hvcc, sizeof (h265_idr), h265_idr_hvcc, sizeof (h265_idr));
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* Send 2 IDR and one garbage => keep the first two and drop garabage. */
buf = composite_buffer (300, 0, 3, h265_idr_hvcc, sizeof (h265_idr),
h265_idr_hvcc, sizeof (h265_idr), h265_garbage_hvcc,
sizeof (h265_garbage_hvcc));
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* Only send part of correct frame => drop everything */
buf = wrap_buffer (h265_idr_hvcc, sizeof (h265_idr) - 10, 400, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* Send garbage frame => drop everything */
buf = wrap_buffer (h265_garbage_hvcc, sizeof (h265_garbage_hvcc), 500, 0);
fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
/* EOS for pending buffers to be drained if any */
gst_harness_push_event (h, gst_event_new_eos ());
fail_unless_equals_int (gst_harness_buffers_received (h), 3);
bufout = gst_harness_pull (h);
fail_unless (bufout != NULL);
/* Verify IDR + garbage. */
gst_buffer_map (bufout, &mapout, GST_MAP_READ);
fail_unless_equals_int (mapout.size, sizeof (h265_idr));
fail_unless (memcmp (mapout.data, h265_idr_hvcc, sizeof (h265_idr)) == 0);
gst_buffer_unmap (bufout, &mapout);
gst_buffer_unref (bufout);
/* Verify 3 * IDR. */
bufout = gst_harness_pull (h);
fail_unless (bufout != NULL);
gst_buffer_map (bufout, &mapout, GST_MAP_READ);
fail_unless_equals_int (mapout.size, 3 * sizeof (h265_idr));
fail_unless (memcmp (mapout.data, h265_idr_hvcc, sizeof (h265_idr)) == 0);
fail_unless (memcmp (mapout.data + sizeof (h265_idr), h265_idr_hvcc,
sizeof (h265_idr)) == 0);
fail_unless (memcmp (mapout.data + 2 * sizeof (h265_idr), h265_idr_hvcc,
sizeof (h265_idr)) == 0);
gst_buffer_unmap (bufout, &mapout);
gst_buffer_unref (bufout);
/* Verify 2 * IDR + garbage. */
bufout = gst_harness_pull (h);
fail_unless (bufout != NULL);
gst_buffer_map (bufout, &mapout, GST_MAP_READ);
fail_unless_equals_int (mapout.size, 2 * sizeof (h265_idr));
fail_unless (memcmp (mapout.data, h265_idr_hvcc, sizeof (h265_idr)) == 0);
fail_unless (memcmp (mapout.data + sizeof (h265_idr), h265_idr_hvcc,
sizeof (h265_idr)) == 0);
gst_buffer_unmap (bufout, &mapout);
gst_buffer_unref (bufout);
g_free (h265_idr_hvcc);
gst_harness_teardown (h);
}
GST_END_TEST;
static Suite *
h265parse_harnessed_suite (void)
{
Suite *s = suite_create ("h265parse");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_flow_nal_nal);
tcase_add_test (tc_chain, test_flow_au_nal);
tcase_add_test (tc_chain, test_flow_nal_au);
tcase_add_test (tc_chain, test_flow_au_au);
tcase_add_test (tc_chain, test_headers_nal_nal);
tcase_add_test (tc_chain, test_headers_au_nal);
tcase_add_test (tc_chain, test_headers_au_au);
tcase_add_test (tc_chain, test_latency_nal_nal);
tcase_add_test (tc_chain, test_latency_au_nal);
tcase_add_test (tc_chain, test_latency_nal_au);
tcase_add_test (tc_chain, test_latency_au_au);
tcase_add_test (tc_chain, test_discont_nal_nal);
tcase_add_test (tc_chain, test_discont_au_nal);
tcase_add_test (tc_chain, test_discont_au_au);
tcase_add_test (tc_chain, test_sliced_nal_nal);
tcase_add_test (tc_chain, test_sliced_au_nal);
tcase_add_test (tc_chain, test_sliced_nal_au);
tcase_add_test (tc_chain, test_sliced_au_au);
tcase_add_test (tc_chain, test_parse_skip_to_4bytes_sc);
tcase_add_test (tc_chain, test_parse_sc_with_half_header);
tcase_add_test (tc_chain, test_drain);
tcase_add_test (tc_chain, test_parse_sei_userdefinedunregistered);
tcase_add_test (tc_chain, test_invalid_sei_in_hvcc);
tcase_add_test (tc_chain, test_packetized_hvcc_drop_corrupt);
return s;
}
int
main (int argc, char **argv)
{
int nf = 0;
Suite *s;
gst_check_init (&argc, &argv);
/* init test context */
ctx_factory = "h265parse";
ctx_sink_template = &sinktemplate_bs_au;
ctx_src_template = &srctemplate;
/* no timing info to parse */
ctx_headers[0].data = h265_vps;
ctx_headers[0].size = sizeof (h265_vps);
ctx_headers[1].data = h265_sps;
ctx_headers[1].size = sizeof (h265_sps);
ctx_headers[2].data = h265_pps;
ctx_headers[2].size = sizeof (h265_pps);
ctx_verify_buffer = verify_buffer_bs_au;
/* discard initial vps/sps/pps buffers */
ctx_discard = 0;
/* no timing info to parse */
ctx_no_metadata = TRUE;
ctx_codec_data = FALSE;
ctx_suite = "h265parse_to_bs_au";
s = h265parse_suite ();
nf += gst_check_run_suite (s, ctx_suite, __FILE__ "_to_bs_au.c");
ctx_suite = "h265parse_harnessed";
s = h265parse_harnessed_suite ();
nf += gst_check_run_suite (s, ctx_suite, __FILE__ "_harnessed.c");
return nf;
}