diff --git a/gst-libs/gst/codecparsers/gsth265parser.c b/gst-libs/gst/codecparsers/gsth265parser.c index 41d1d8516f..6217ca13b4 100644 --- a/gst-libs/gst/codecparsers/gsth265parser.c +++ b/gst-libs/gst/codecparsers/gsth265parser.c @@ -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); +} diff --git a/gst-libs/gst/codecparsers/gsth265parser.h b/gst-libs/gst/codecparsers/gsth265parser.h index 2efa9596e5..5b6a9a2eed 100644 --- a/gst-libs/gst/codecparsers/gsth265parser.h +++ b/gst-libs/gst/codecparsers/gsth265parser.h @@ -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 diff --git a/tests/check/libs/h265parser.c b/tests/check/libs/h265parser.c index 61853cc2b7..ca31eee9ae 100644 --- a/tests/check/libs/h265parser.c +++ b/tests/check/libs/h265parser.c @@ -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; }