h265parser: Add a helper method to create SEI nal unit

Add an API to create raw SEI nal unit. This would be useful in case
an user want to create SEI nal data and inject the SEI nal data
into bitstream.
This commit is contained in:
Seungha Yang 2020-03-19 18:25:18 +09:00 committed by GStreamer Merge Bot
parent 45a1070203
commit ce09ceb106
3 changed files with 819 additions and 0 deletions

View file

@ -3371,3 +3371,561 @@ gst_h265_profile_from_string (const gchar * string)
return GST_H265_PROFILE_INVALID;
}
static gboolean
gst_h265_write_sei_registered_user_data (NalWriter * nw,
GstH265RegisteredUserData * rud)
{
WRITE_UINT8 (nw, rud->country_code, 8);
if (rud->country_code == 0xff)
WRITE_UINT8 (nw, rud->country_code_extension, 8);
WRITE_BYTES (nw, rud->data, rud->size);
return TRUE;
error:
return FALSE;
}
static gboolean
gst_h265_write_sei_time_code (NalWriter * nw, GstH265TimeCode * tc)
{
gint i;
WRITE_UINT8 (nw, tc->num_clock_ts, 2);
for (i = 0; i < tc->num_clock_ts; i++) {
WRITE_UINT8 (nw, tc->clock_timestamp_flag[i], 1);
if (tc->clock_timestamp_flag[i]) {
WRITE_UINT8 (nw, tc->units_field_based_flag[i], 1);
WRITE_UINT8 (nw, tc->counting_type[i], 5);
WRITE_UINT8 (nw, tc->full_timestamp_flag[i], 1);
WRITE_UINT8 (nw, tc->discontinuity_flag[i], 1);
WRITE_UINT8 (nw, tc->cnt_dropped_flag[i], 1);
WRITE_UINT16 (nw, tc->n_frames[i], 9);
if (tc->full_timestamp_flag[i]) {
WRITE_UINT8 (nw, tc->seconds_value[i], 6);
WRITE_UINT8 (nw, tc->minutes_value[i], 6);
WRITE_UINT8 (nw, tc->hours_value[i], 5);
} else {
WRITE_UINT8 (nw, tc->seconds_flag[i], 1);
if (tc->seconds_flag[i]) {
WRITE_UINT8 (nw, tc->seconds_value[i], 6);
WRITE_UINT8 (nw, tc->minutes_flag[i], 1);
if (tc->minutes_flag[i]) {
WRITE_UINT8 (nw, tc->minutes_value[i], 6);
WRITE_UINT8 (nw, tc->hours_flag[i], 1);
if (tc->hours_flag[i]) {
WRITE_UINT8 (nw, tc->hours_value[i], 5);
}
}
}
}
}
WRITE_UINT8 (nw, tc->time_offset_length[i], 5);
if (tc->time_offset_length[i] > 0)
WRITE_UINT8 (nw, tc->time_offset_value[i], tc->time_offset_length[i]);
}
return TRUE;
error:
return FALSE;
}
static gboolean
gst_h265_write_sei_mastering_display_colour_volume (NalWriter * nw,
GstH265MasteringDisplayColourVolume * mdcv)
{
gint i;
for (i = 0; i < 3; i++) {
WRITE_UINT16 (nw, mdcv->display_primaries_x[i], 16);
WRITE_UINT16 (nw, mdcv->display_primaries_y[i], 16);
}
WRITE_UINT16 (nw, mdcv->white_point_x, 16);
WRITE_UINT16 (nw, mdcv->white_point_y, 16);
WRITE_UINT32 (nw, mdcv->max_display_mastering_luminance, 32);
WRITE_UINT32 (nw, mdcv->min_display_mastering_luminance, 32);
return TRUE;
error:
return FALSE;
}
static gboolean
gst_h265_write_sei_content_light_level_info (NalWriter * nw,
GstH265ContentLightLevel * cll)
{
WRITE_UINT16 (nw, cll->max_content_light_level, 16);
WRITE_UINT16 (nw, cll->max_pic_average_light_level, 16);
return TRUE;
error:
return FALSE;
}
static GstMemory *
gst_h265_create_sei_memory_internal (guint8 layer_id, guint8 temporal_id_plus1,
guint nal_prefix_size, gboolean packetized, GArray * messages)
{
NalWriter nw;
gint i;
gboolean have_written_data = FALSE;
nal_writer_init (&nw, nal_prefix_size, packetized);
if (messages->len == 0)
goto error;
GST_DEBUG ("Create SEI nal from array, len: %d", messages->len);
/* nal header */
/* forbidden_zero_bit */
WRITE_UINT8 (&nw, 0, 1);
/* nal_unit_type */
WRITE_UINT8 (&nw, GST_H265_NAL_PREFIX_SEI, 6);
/* nuh_layer_id */
WRITE_UINT8 (&nw, layer_id, 6);
/* nuh_temporal_id_plus1 */
WRITE_UINT8 (&nw, temporal_id_plus1, 3);
for (i = 0; i < messages->len; i++) {
GstH265SEIMessage *msg = &g_array_index (messages, GstH265SEIMessage, i);
guint32 payload_size_data = 0;
guint32 payload_size_in_bits = 0;
guint32 payload_type_data = msg->payloadType;
gboolean need_align = FALSE;
switch (payload_type_data) {
case GST_H265_SEI_REGISTERED_USER_DATA:{
GstH265RegisteredUserData *rud = &msg->payload.registered_user_data;
/* itu_t_t35_country_code: 8 bits */
payload_size_data = 1;
if (rud->country_code == 0xff) {
/* itu_t_t35_country_code_extension_byte */
payload_size_data++;
}
payload_size_data += rud->size;
break;
}
case GST_H265_SEI_TIME_CODE:{
gint j;
GstH265TimeCode *tc = &msg->payload.time_code;
/* num_clock_ts: 2 bits */
payload_size_in_bits = 2;
for (j = 0; j < tc->num_clock_ts; j++) {
/* clock_timestamp_flag: 1 bit */
payload_size_in_bits += 1;
if (tc->clock_timestamp_flag[j]) {
/* units_field_based_flag: 1 bit
* counting_type: 5 bits
* full_timestamp_flag: 1 bit
* discontinuity_flag: 1 bit
* cnt_dropped_flag: 1 bit
* n_frames: 9 bit
*/
payload_size_in_bits += 18;
if (tc->full_timestamp_flag[j]) {
/* seconds_value: 6 bits
* minutes_value: 6 bits
* hours_value: 5 bits
*/
payload_size_in_bits += 17;
} else {
/* seconds_flag: 1 bit */
payload_size_in_bits += 1;
if (tc->seconds_flag[j]) {
/* seconds_value: 6 bits
* minutes_flag: 1 bit
*/
payload_size_in_bits += 7;
if (tc->minutes_flag[j]) {
/* minutes_value: 6 bits
* hours_flag: 1 bit
*/
payload_size_in_bits += 7;
if (tc->hours_flag[j]) {
/* hours_value: 5 bits */
payload_size_in_bits += 5;
}
}
}
}
/* time_offset_length: 5bits
* time_offset_value: time_offset_length bits
*/
payload_size_in_bits += (5 + tc->time_offset_length[j]);
}
}
payload_size_data = payload_size_in_bits >> 3;
if ((payload_size_in_bits & 0x7) != 0) {
GST_INFO ("Bits for Time Code SEI is not byte aligned");
payload_size_data++;
need_align = TRUE;
}
break;
}
case GST_H265_SEI_MASTERING_DISPLAY_COLOUR_VOLUME:
/* x, y 16 bits per RGB channel
* x, y 16 bits white point
* max, min luminance 32 bits
*
* (2 * 2 * 3) + (2 * 2) + (4 * 2) = 24 bytes
*/
payload_size_data = 24;
break;
case GST_H265_SEI_CONTENT_LIGHT_LEVEL:
/* maxCLL and maxFALL per 16 bits
*
* 2 * 2 = 4 bytes
*/
payload_size_data = 4;
break;
default:
break;
}
if (payload_size_data == 0) {
GST_FIXME ("Unsupported SEI type %d", msg->payloadType);
continue;
}
/* write payload type bytes */
while (payload_type_data >= 0xff) {
WRITE_UINT8 (&nw, 0xff, 8);
payload_type_data -= -0xff;
}
WRITE_UINT8 (&nw, payload_type_data, 8);
/* write payload size bytes */
while (payload_size_data >= 0xff) {
WRITE_UINT8 (&nw, 0xff, 8);
payload_size_data -= -0xff;
}
WRITE_UINT8 (&nw, payload_size_data, 8);
switch (msg->payloadType) {
case GST_H265_SEI_REGISTERED_USER_DATA:
GST_DEBUG ("Writing \"Registered user data\" done");
if (!gst_h265_write_sei_registered_user_data (&nw,
&msg->payload.registered_user_data)) {
GST_WARNING ("Failed to write \"Registered user data\"");
goto error;
}
have_written_data = TRUE;
break;
case GST_H265_SEI_TIME_CODE:
GST_DEBUG ("Wrtiting \"Time code\"");
if (!gst_h265_write_sei_time_code (&nw, &msg->payload.time_code)) {
GST_WARNING ("Failed to write \"Time code\"");
goto error;
}
have_written_data = TRUE;
break;
case GST_H265_SEI_MASTERING_DISPLAY_COLOUR_VOLUME:
GST_DEBUG ("Wrtiting \"Mastering display colour volume\"");
if (!gst_h265_write_sei_mastering_display_colour_volume (&nw,
&msg->payload.mastering_display_colour_volume)) {
GST_WARNING ("Failed to write \"Mastering display colour volume\"");
goto error;
}
have_written_data = TRUE;
break;
case GST_H265_SEI_CONTENT_LIGHT_LEVEL:
GST_DEBUG ("Writing \"Content light level\" done");
if (!gst_h265_write_sei_content_light_level_info (&nw,
&msg->payload.content_light_level)) {
GST_WARNING ("Failed to write \"Content light level\"");
goto error;
}
have_written_data = TRUE;
break;
default:
break;
}
if (need_align && !nal_writer_do_rbsp_trailing_bits (&nw)) {
GST_WARNING ("Cannot insert traling bits");
goto error;
}
}
if (!have_written_data) {
GST_WARNING ("No written sei data");
goto error;
}
if (!nal_writer_do_rbsp_trailing_bits (&nw)) {
GST_WARNING ("Failed to insert rbsp trailing bits");
goto error;
}
return nal_writer_reset_and_get_memory (&nw);
error:
nal_writer_reset (&nw);
return NULL;
}
/**
* gst_h265_create_sei_memory:
* @layer_id: a nal unit layer id
* @temporal_id_plus1: a nal unit temporal identifier
* @start_code_prefix_length: a length of start code prefix, must be 3 or 4
* @messages: (transfer none): a GArray of #GstH265SEIMessage
*
* Creates raw byte-stream format (a.k.a Annex B type) SEI nal unit data
* from @messages
*
* Returns: a #GstMemory containing a PREFIX SEI nal unit
*
* Since: 1.18
*/
GstMemory *
gst_h265_create_sei_memory (guint8 layer_id, guint8 temporal_id_plus1,
guint8 start_code_prefix_length, GArray * messages)
{
g_return_val_if_fail (start_code_prefix_length == 3
|| start_code_prefix_length == 4, NULL);
g_return_val_if_fail (messages != NULL, NULL);
g_return_val_if_fail (messages->len > 0, NULL);
return gst_h265_create_sei_memory_internal (layer_id, temporal_id_plus1,
start_code_prefix_length, FALSE, messages);
}
/**
* gst_h265_create_sei_memory_hevc:
* @layer_id: a nal unit layer id
* @temporal_id_plus1: a nal unit temporal identifier
* @nal_length_size: a size of nal length field, allowed range is [1, 4]
* @messages: (transfer none): a GArray of #GstH265SEIMessage
*
* Creates raw packetized format SEI nal unit data from @messages
*
* Returns: a #GstMemory containing a PREFIX SEI nal unit
*
* Since: 1.18
*/
GstMemory *
gst_h265_create_sei_memory_hevc (guint8 layer_id, guint8 temporal_id_plus1,
guint8 nal_length_size, GArray * messages)
{
return gst_h265_create_sei_memory_internal (layer_id, temporal_id_plus1,
nal_length_size, TRUE, messages);
}
static GstBuffer *
gst_h265_parser_insert_sei_internal (GstH265Parser * parser,
guint8 nal_prefix_size, gboolean packetized, GstBuffer * au,
GstMemory * sei)
{
GstH265NalUnit nalu;
GstH265NalUnit sei_nalu;
GstMapInfo info;
GstMapInfo sei_info;
GstH265ParserResult pres;
guint offset = 0;
GstBuffer *new_buffer = NULL;
GstMemory *new_mem = NULL;
/* all SEI payload types supported by us need to have the identical
* temporal id to that of slice. Parse SEI first and we will
* update it if it's required */
if (!gst_memory_map (sei, &sei_info, GST_MAP_READ)) {
GST_ERROR ("Cannot map sei memory");
return NULL;
}
if (packetized) {
pres = gst_h265_parser_identify_nalu_hevc (parser,
sei_info.data, 0, sei_info.size, nal_prefix_size, &sei_nalu);
} else {
pres = gst_h265_parser_identify_nalu (parser,
sei_info.data, 0, sei_info.size, &sei_nalu);
}
gst_memory_unmap (sei, &sei_info);
if (pres != GST_H265_PARSER_OK && pres != GST_H265_PARSER_NO_NAL_END) {
GST_DEBUG ("Failed to identify sei nal unit, ret: %d", pres);
return NULL;
}
if (!gst_buffer_map (au, &info, GST_MAP_READ)) {
GST_ERROR ("Cannot map au buffer");
return NULL;
}
/* Find the offset of the first slice */
do {
if (packetized) {
pres = gst_h265_parser_identify_nalu_hevc (parser,
info.data, offset, info.size, nal_prefix_size, &nalu);
} else {
pres = gst_h265_parser_identify_nalu (parser,
info.data, offset, info.size, &nalu);
}
if (pres != GST_H265_PARSER_OK && pres != GST_H265_PARSER_NO_NAL_END) {
GST_DEBUG ("Failed to identify nal unit, ret: %d", pres);
gst_buffer_unmap (au, &info);
return NULL;
}
if ((nalu.type >= GST_H265_NAL_SLICE_TRAIL_N
&& nalu.type <= GST_H265_NAL_SLICE_RASL_R)
|| (nalu.type >= GST_H265_NAL_SLICE_BLA_W_LP
&& nalu.type <= GST_H265_NAL_SLICE_CRA_NUT)) {
GST_DEBUG ("Found slice nal type %d at offset %d", nalu.type,
nalu.sc_offset);
break;
}
offset = nalu.offset + nalu.size;
} while (pres == GST_H265_PARSER_OK);
gst_buffer_unmap (au, &info);
/* found the best position now, create new buffer */
new_buffer = gst_buffer_new ();
/* copy all metadata */
if (!gst_buffer_copy_into (new_buffer, au, GST_BUFFER_COPY_METADATA, 0, -1)) {
GST_ERROR ("Failed to copy metadata into new buffer");
gst_clear_buffer (&new_buffer);
goto out;
}
/* copy non-slice nal */
if (nalu.sc_offset > 0) {
if (!gst_buffer_copy_into (new_buffer, au,
GST_BUFFER_COPY_MEMORY, 0, nalu.sc_offset)) {
GST_ERROR ("Failed to copy buffer");
gst_clear_buffer (&new_buffer);
goto out;
}
}
/* check whether we need to update temporal id and layer id.
* If it's not matched to slice nalu, update it.
*/
if (sei_nalu.layer_id != nalu.layer_id || sei_nalu.temporal_id_plus1 !=
nalu.temporal_id_plus1) {
guint16 nalu_header;
guint16 layer_id_temporal_id = 0;
new_mem = gst_memory_copy (sei, 0, -1);
if (!gst_memory_map (new_mem, &sei_info, GST_MAP_READWRITE)) {
GST_ERROR ("Failed to map new sei memory");
gst_memory_unref (new_mem);
gst_clear_buffer (&new_buffer);
goto out;
}
nalu_header = GST_READ_UINT16_BE (sei_info.data + sei_nalu.offset);
/* clear bits 7 ~ 15
* NOTE:
* bit 0: forbidden_zero_bit
* bits 1 ~ 6: nalu type */
nalu_header &= 0xfe00;
layer_id_temporal_id = ((nalu.layer_id << 3) & 0x1f8);
layer_id_temporal_id |= (nalu.temporal_id_plus1 & 0x7);
nalu_header |= layer_id_temporal_id;
GST_WRITE_UINT16_BE (sei_info.data + sei_nalu.offset, nalu_header);
gst_memory_unmap (new_mem, &sei_info);
} else {
new_mem = gst_memory_ref (sei);
}
/* insert sei */
gst_buffer_append_memory (new_buffer, new_mem);
/* copy the rest */
if (!gst_buffer_copy_into (new_buffer, au,
GST_BUFFER_COPY_MEMORY, nalu.sc_offset, -1)) {
GST_ERROR ("Failed to copy buffer");
gst_clear_buffer (&new_buffer);
goto out;
}
out:
return new_buffer;
}
/**
* gst_h265_parser_insert_sei:
* @parser: a #GstH265Parser
* @au: (transfer none): a #GstBuffer containing AU data
* @sei: (transfer none): a #GstMemory containing a SEI nal
*
* Copy @au into new #GstBuffer and insert @sei into the #GstBuffer.
* The validation for completeness of @au and @sei is caller's responsibility.
* Both @au and @sei must be byte-stream formatted
*
* Returns: (nullable): a SEI inserted #GstBuffer or %NULL
* if cannot figure out proper position to insert a @sei
*
* Since: 1.18
*/
GstBuffer *
gst_h265_parser_insert_sei (GstH265Parser * parser, GstBuffer * au,
GstMemory * sei)
{
g_return_val_if_fail (parser != NULL, NULL);
g_return_val_if_fail (GST_IS_BUFFER (au), NULL);
g_return_val_if_fail (sei != NULL, NULL);
/* the size of start code prefix (3 or 4) is not matter since it will be
* scanned */
return gst_h265_parser_insert_sei_internal (parser, 4, FALSE, au, sei);
}
/**
* gst_h265_parser_insert_sei_hevc:
* @parser: a #GstH265Parser
* @nal_length_size: a size of nal length field, allowed range is [1, 4]
* @au: (transfer none): a #GstBuffer containing AU data
* @sei: (transfer none): a #GstMemory containing a SEI nal
*
* Copy @au into new #GstBuffer and insert @sei into the #GstBuffer.
* The validation for completeness of @au and @sei is caller's responsibility.
* Nal prefix type of both @au and @sei must be packetized, and
* also the size of nal length field must be identical to @nal_length_size
*
* Returns: (nullable): a SEI inserted #GstBuffer or %NULL
* if cannot figure out proper position to insert a @sei
*
* Since: 1.18
*/
GstBuffer *
gst_h265_parser_insert_sei_hevc (GstH265Parser * parser, guint8 nal_length_size,
GstBuffer * au, GstMemory * sei)
{
g_return_val_if_fail (parser != NULL, NULL);
g_return_val_if_fail (nal_length_size > 0 && nal_length_size < 5, NULL);
g_return_val_if_fail (GST_IS_BUFFER (au), NULL);
g_return_val_if_fail (sei != NULL, NULL);
return gst_h265_parser_insert_sei_internal (parser, nal_length_size, TRUE,
au, sei);
}

View file

@ -1669,5 +1669,28 @@ const gchar * gst_h265_profile_to_string (GstH265Profile profile);
GST_CODEC_PARSERS_API
GstH265Profile gst_h265_profile_from_string (const gchar * string);
GST_CODEC_PARSERS_API
GstMemory * gst_h265_create_sei_memory (guint8 layer_id,
guint8 temporal_id_plus1,
guint8 start_code_prefix_length,
GArray * messages);
GST_CODEC_PARSERS_API
GstMemory * gst_h265_create_sei_memory_hevc (guint8 layer_id,
guint8 temporal_id_plus1,
guint8 nal_length_size,
GArray * messages);
GST_CODEC_PARSERS_API
GstBuffer * gst_h265_parser_insert_sei (GstH265Parser * parser,
GstBuffer * au,
GstMemory * sei);
GST_CODEC_PARSERS_API
GstBuffer * gst_h265_parser_insert_sei_hevc (GstH265Parser * parser,
guint8 nal_length_size,
GstBuffer * au,
GstMemory * sei);
G_END_DECLS
#endif

View file

@ -112,6 +112,21 @@ static guint8 h265_sei_user_data_registered[] = {
0xa6, 0xae, 0x5c, 0x83, 0x50, 0xdd, 0xf9, 0x8e, 0xc7, 0xbd, 0x00, 0x80
};
static guint8 h265_sei_time_code[] = {
0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x88, 0x06, 0x60, 0x40, 0x00, 0x00, 0x03,
0x00, 0x10, 0x80
};
static 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
};
static guint8 h265_sei_cll[] = {
0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x90, 0x04, 0x03, 0xe8, 0x01, 0x90, 0x80
};
GST_START_TEST (test_h265_parse_slice_eos_slice_eob)
{
GstH265ParserResult res;
@ -656,6 +671,228 @@ GST_START_TEST (test_h265_sei_registered_user_data)
GST_END_TEST;
typedef gboolean (*SEICheckFunc) (gconstpointer a, gconstpointer b);
static gboolean
check_sei_user_data_registered (const GstH265RegisteredUserData * a,
const GstH265RegisteredUserData * b)
{
if (a->country_code != b->country_code)
return FALSE;
if ((a->country_code == 0xff) &&
(a->country_code_extension != b->country_code_extension))
return FALSE;
if (a->size != b->size)
return FALSE;
return !memcmp (a->data, b->data, a->size);
}
static gboolean
check_sei_time_code (const GstH265TimeCode * a, const GstH265TimeCode * b)
{
gint i;
if (a->num_clock_ts != b->num_clock_ts)
return FALSE;
for (i = 0; i < a->num_clock_ts; i++) {
if (a->clock_timestamp_flag[i] != b->clock_timestamp_flag[i])
return FALSE;
if (a->clock_timestamp_flag[i]) {
if ((a->units_field_based_flag[i] != b->units_field_based_flag[i]) ||
(a->counting_type[i] != b->counting_type[i]) ||
(a->full_timestamp_flag[i] != b->full_timestamp_flag[i]) ||
(a->discontinuity_flag[i] != b->discontinuity_flag[i]) ||
(a->cnt_dropped_flag[i] != b->cnt_dropped_flag[i]) ||
(a->n_frames[i] != b->n_frames[i])) {
return FALSE;
}
if (a->full_timestamp_flag[i]) {
if ((a->seconds_value[i] != b->seconds_value[i]) ||
(a->minutes_value[i] != b->minutes_value[i]) ||
(a->hours_value[i] != b->hours_value[i])) {
return FALSE;
}
} else {
if (a->seconds_flag[i] != b->seconds_flag[i])
return FALSE;
if (a->seconds_flag[i]) {
if ((a->seconds_value[i] != b->seconds_value[i]) ||
(a->minutes_flag[i] != b->minutes_flag[i])) {
return FALSE;
}
if (a->minutes_flag[i]) {
if ((a->minutes_value[i] != b->minutes_value[i]) ||
(a->hours_flag[i] != b->hours_flag[i])) {
return FALSE;
}
if (a->hours_flag[i]) {
if (a->hours_value[i] != b->hours_value[i])
return FALSE;
}
}
}
}
}
}
return TRUE;
}
static gboolean
check_sei_mdcv (const GstH265MasteringDisplayColourVolume * a,
const GstH265MasteringDisplayColourVolume * b)
{
gint i;
for (i = 0; i < 3; i++) {
if (a->display_primaries_x[i] != b->display_primaries_x[i] ||
a->display_primaries_y[i] != b->display_primaries_y[i])
return FALSE;
}
return (a->white_point_x == b->white_point_x) &&
(a->white_point_y == b->white_point_y) &&
(a->max_display_mastering_luminance == b->max_display_mastering_luminance)
&& (a->min_display_mastering_luminance ==
b->min_display_mastering_luminance);
}
static gboolean
check_sei_cll (const GstH265ContentLightLevel * a,
const GstH265ContentLightLevel * b)
{
return (a->max_content_light_level == b->max_content_light_level) &&
(a->max_pic_average_light_level == b->max_pic_average_light_level);
}
GST_START_TEST (test_h265_create_sei)
{
GstH265Parser *parser;
GstH265ParserResult parse_ret;
GstH265NalUnit nalu;
GArray *msg_array = NULL;
GstMemory *mem;
gint i;
GstMapInfo info;
struct
{
guint8 *raw_data;
guint len;
GstH265SEIPayloadType type;
GstH265SEIMessage parsed_message;
SEICheckFunc check_func;
} test_list[] = {
/* *INDENT-OFF* */
{h265_sei_user_data_registered, G_N_ELEMENTS (h265_sei_user_data_registered),
GST_H265_SEI_REGISTERED_USER_DATA, {0,},
(SEICheckFunc) check_sei_user_data_registered},
{h265_sei_time_code, G_N_ELEMENTS (h265_sei_time_code),
GST_H265_SEI_TIME_CODE, {0,}, (
SEICheckFunc) check_sei_time_code},
{h265_sei_mdcv, G_N_ELEMENTS (h265_sei_mdcv),
GST_H265_SEI_MASTERING_DISPLAY_COLOUR_VOLUME, {0,},
(SEICheckFunc) check_sei_mdcv},
{h265_sei_cll, G_N_ELEMENTS (h265_sei_cll),
GST_H265_SEI_CONTENT_LIGHT_LEVEL, {0,},
(SEICheckFunc) check_sei_cll},
/* *INDENT-ON* */
};
parser = gst_h265_parser_new ();
/* test single sei message per sei nal unit */
for (i = 0; i < G_N_ELEMENTS (test_list); i++) {
gsize nal_size;
parse_ret = gst_h265_parser_identify_nalu_unchecked (parser,
test_list[i].raw_data, 0, test_list[i].len, &nalu);
assert_equals_int (parse_ret, GST_H265_PARSER_OK);
assert_equals_int (nalu.type, GST_H265_NAL_PREFIX_SEI);
parse_ret = gst_h265_parser_parse_sei (parser, &nalu, &msg_array);
assert_equals_int (parse_ret, GST_H265_PARSER_OK);
assert_equals_int (msg_array->len, 1);
/* test bytestream */
mem = gst_h265_create_sei_memory (nalu.layer_id,
nalu.temporal_id_plus1, 4, msg_array);
fail_unless (mem != NULL);
fail_unless (gst_memory_map (mem, &info, GST_MAP_READ));
GST_MEMDUMP ("created sei nal", info.data, info.size);
GST_MEMDUMP ("original sei nal", test_list[i].raw_data, test_list[i].len);
assert_equals_int (info.size, test_list[i].len);
fail_if (memcmp (info.data, test_list[i].raw_data, test_list[i].len));
gst_memory_unmap (mem, &info);
gst_memory_unref (mem);
/* test packetized */
mem = gst_h265_create_sei_memory_hevc (nalu.layer_id,
nalu.temporal_id_plus1, 4, msg_array);
fail_unless (mem != NULL);
fail_unless (gst_memory_map (mem, &info, GST_MAP_READ));
assert_equals_int (info.size, test_list[i].len);
fail_if (memcmp (info.data + 4, test_list[i].raw_data + 4,
test_list[i].len - 4));
nal_size = GST_READ_UINT32_BE (info.data);
assert_equals_int (nal_size, info.size - 4);
gst_memory_unmap (mem, &info);
gst_memory_unref (mem);
/* store parsed SEI for following tests */
fail_unless (gst_h265_sei_copy (&test_list[i].parsed_message,
&g_array_index (msg_array, GstH265SEIMessage, 0)));
g_array_unref (msg_array);
}
/* test multiple SEI messages in a nal unit */
msg_array = g_array_new (FALSE, FALSE, sizeof (GstH265SEIMessage));
for (i = 0; i < G_N_ELEMENTS (test_list); i++)
g_array_append_val (msg_array, test_list[i].parsed_message);
mem = gst_h265_create_sei_memory (nalu.layer_id,
nalu.temporal_id_plus1, 4, msg_array);
fail_unless (mem != NULL);
g_array_unref (msg_array);
/* parse sei message from buffer */
fail_unless (gst_memory_map (mem, &info, GST_MAP_READ));
parse_ret = gst_h265_parser_identify_nalu_unchecked (parser,
info.data, 0, info.size, &nalu);
assert_equals_int (parse_ret, GST_H265_PARSER_OK);
assert_equals_int (nalu.type, GST_H265_NAL_PREFIX_SEI);
parse_ret = gst_h265_parser_parse_sei (parser, &nalu, &msg_array);
gst_memory_unmap (mem, &info);
gst_memory_unref (mem);
assert_equals_int (parse_ret, GST_H265_PARSER_OK);
assert_equals_int (msg_array->len, G_N_ELEMENTS (test_list));
for (i = 0; i < msg_array->len; i++) {
GstH265SEIMessage *msg = &g_array_index (msg_array, GstH265SEIMessage, i);
assert_equals_int (msg->payloadType, test_list[i].type);
fail_unless (test_list[i].check_func (&msg->payload,
&test_list[i].parsed_message.payload));
}
/* clean up */
for (i = 0; i < G_N_ELEMENTS (test_list); i++)
gst_h265_sei_free (&test_list[i].parsed_message);
g_array_unref (msg_array);
gst_h265_parser_free (parser);
}
GST_END_TEST;
static Suite *
h265parser_suite (void)
{
@ -675,6 +912,7 @@ h265parser_suite (void)
tcase_add_test (tc_chain, test_h265_parse_pps);
tcase_add_test (tc_chain, test_h265_nal_type_classification);
tcase_add_test (tc_chain, test_h265_sei_registered_user_data);
tcase_add_test (tc_chain, test_h265_create_sei);
return s;
}