diff --git a/gst-libs/gst/codecparsers/gsth264parser.c b/gst-libs/gst/codecparsers/gsth264parser.c index 83b675baaf..9e664faa31 100644 --- a/gst-libs/gst/codecparsers/gsth264parser.c +++ b/gst-libs/gst/codecparsers/gsth264parser.c @@ -2639,3 +2639,483 @@ gst_h264_video_calculate_framerate (const GstH264SPS * sps, *fps_num = num; *fps_den = den; } + +static gboolean +gst_h264_write_sei_registered_user_data (NalWriter * nw, + GstH264RegisteredUserData * 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_h264_write_sei_frame_packing (NalWriter * nw, + GstH264FramePacking * frame_packing) +{ + WRITE_UE (nw, frame_packing->frame_packing_id); + WRITE_UINT8 (nw, frame_packing->frame_packing_cancel_flag, 1); + + if (!frame_packing->frame_packing_cancel_flag) { + WRITE_UINT8 (nw, frame_packing->frame_packing_type, 7); + WRITE_UINT8 (nw, frame_packing->quincunx_sampling_flag, 1); + WRITE_UINT8 (nw, frame_packing->content_interpretation_type, 6); + WRITE_UINT8 (nw, frame_packing->spatial_flipping_flag, 1); + WRITE_UINT8 (nw, frame_packing->frame0_flipped_flag, 1); + WRITE_UINT8 (nw, frame_packing->field_views_flag, 1); + WRITE_UINT8 (nw, frame_packing->current_frame_is_frame0_flag, 1); + WRITE_UINT8 (nw, frame_packing->frame0_self_contained_flag, 1); + WRITE_UINT8 (nw, frame_packing->frame1_self_contained_flag, 1); + + if (!frame_packing->quincunx_sampling_flag && + frame_packing->frame_packing_type != + GST_H264_FRAME_PACKING_TEMPORAL_INTERLEAVING) { + WRITE_UINT8 (nw, frame_packing->frame0_grid_position_x, 4); + WRITE_UINT8 (nw, frame_packing->frame0_grid_position_y, 4); + WRITE_UINT8 (nw, frame_packing->frame1_grid_position_x, 4); + WRITE_UINT8 (nw, frame_packing->frame1_grid_position_y, 4); + } + + /* frame_packing_arrangement_reserved_byte */ + WRITE_UINT8 (nw, 0, 8); + WRITE_UE (nw, frame_packing->frame_packing_repetition_period); + } + + /* frame_packing_arrangement_extension_flag */ + WRITE_UINT8 (nw, 0, 1); + + return TRUE; + +error: + return FALSE; +} + +static gboolean +gst_h264_write_sei_mastering_display_colour_volume (NalWriter * nw, + GstH264MasteringDisplayColourVolume * 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_h264_write_sei_content_light_level_info (NalWriter * nw, + GstH264ContentLightLevel * 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_h264_create_sei_memory_internal (guint8 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_ref_idc, zero for sei nalu */ + WRITE_UINT8 (&nw, 0, 2); + /* nal_unit_type */ + WRITE_UINT8 (&nw, GST_H264_NAL_SEI, 5); + + for (i = 0; i < messages->len; i++) { + GstH264SEIMessage *msg = &g_array_index (messages, GstH264SEIMessage, 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_H264_SEI_REGISTERED_USER_DATA:{ + GstH264RegisteredUserData *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_H264_SEI_FRAME_PACKING:{ + GstH264FramePacking *frame_packing = &msg->payload.frame_packing; + guint leading_zeros, rest; + + /* frame_packing_arrangement_id: exp-golomb bits */ + count_exp_golomb_bits (frame_packing->frame_packing_id, + &leading_zeros, &rest); + payload_size_in_bits = leading_zeros + rest; + + /* frame_packing_arrangement_cancel_flag: 1 bit */ + payload_size_in_bits++; + if (!frame_packing->frame_packing_cancel_flag) { + /* frame_packing_arrangement_type: 7 bits + * quincunx_sampling_flag: 1 bit + * content_interpretation_type: 6 bit + * spatial_flipping_flag: 1 bit + * frame0_flipped_flag: 1 bit + * field_views_flag: 1 bit + * current_frame_is_frame0_flag: 1 bit + * frame0_self_contained_flag: 1 bit + * frame1_self_contained_flag: 1 bit + */ + payload_size_in_bits += 20; + + if (!frame_packing->quincunx_sampling_flag && + frame_packing->frame_packing_type != + GST_H264_FRAME_PACKING_TEMPORAL_INTERLEAVING) { + /* frame0_grid_position_x: 4bits + * frame0_grid_position_y: 4bits + * frame1_grid_position_x: 4bits + * frame1_grid_position_y: 4bits + */ + payload_size_in_bits += 16; + } + + /* frame_packing_arrangement_reserved_byte: 8 bits */ + payload_size_in_bits += 8; + + /* frame_packing_arrangement_repetition_period: exp-golomb bits */ + count_exp_golomb_bits (frame_packing->frame_packing_repetition_period, + &leading_zeros, &rest); + payload_size_in_bits += (leading_zeros + rest); + } + /* frame_packing_arrangement_extension_flag: 1 bit */ + payload_size_in_bits++; + + payload_size_data = payload_size_in_bits >> 3; + + if ((payload_size_in_bits & 0x7) != 0) { + GST_INFO ("Bits for Frame Packing SEI is not byte aligned"); + payload_size_data++; + need_align = TRUE; + } + break; + } + case GST_H264_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_H264_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_H264_SEI_REGISTERED_USER_DATA: + GST_DEBUG ("Writing \"Registered user data\" done"); + if (!gst_h264_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_H264_SEI_FRAME_PACKING: + GST_DEBUG ("Writing \"Frame packing\" done"); + if (!gst_h264_write_sei_frame_packing (&nw, + &msg->payload.frame_packing)) { + GST_WARNING ("Failed to write \"Frame packing\""); + goto error; + } + have_written_data = TRUE; + break; + case GST_H264_SEI_MASTERING_DISPLAY_COLOUR_VOLUME: + GST_DEBUG ("Wrtiting \"Mastering display colour volume\""); + if (!gst_h264_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_H264_SEI_CONTENT_LIGHT_LEVEL: + GST_DEBUG ("Writing \"Content light level\" done"); + if (!gst_h264_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_h264_create_sei_memory: + * @start_code_prefix_length: a length of start code prefix, must be 3 or 4 + * @messages: (transfer none): a GArray of #GstH264SEIMessage + * + * Creates raw byte-stream format (a.k.a Annex B type) SEI nal unit data + * from @messages + * + * Returns: a #GstMemory containing a SEI nal unit + * + * Since: 1.18 + */ +GstMemory * +gst_h264_create_sei_memory (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_h264_create_sei_memory_internal (start_code_prefix_length, + FALSE, messages); +} + +/** + * gst_h264_create_sei_memory_avc: + * @nal_length_size: a size of nal length field, allowed range is [1, 4] + * @messages: (transfer none): a GArray of #GstH264SEIMessage + * + * Creates raw packetized format SEI nal unit data from @messages + * + * Returns: a #GstMemory containing a SEI nal unit + * + * Since: 1.18 + */ +GstMemory * +gst_h264_create_sei_memory_avc (guint8 nal_length_size, GArray * messages) +{ + g_return_val_if_fail (nal_length_size > 0 && nal_length_size < 5, NULL); + g_return_val_if_fail (messages != NULL, NULL); + g_return_val_if_fail (messages->len > 0, NULL); + + return gst_h264_create_sei_memory_internal (nal_length_size, TRUE, messages); +} + +static GstBuffer * +gst_h264_parser_insert_sei_internal (GstH264NalParser * nalparser, + guint8 nal_prefix_size, gboolean packetized, GstBuffer * au, + GstMemory * sei) +{ + GstH264NalUnit nalu; + GstMapInfo info; + GstH264ParserResult pres; + guint offset = 0; + GstBuffer *new_buffer = 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_h264_parser_identify_nalu_avc (nalparser, + info.data, offset, info.size, nal_prefix_size, &nalu); + } else { + pres = gst_h264_parser_identify_nalu (nalparser, + info.data, offset, info.size, &nalu); + } + + if (pres != GST_H264_PARSER_OK && pres != GST_H264_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_H264_NAL_SLICE && nalu.type <= GST_H264_NAL_SLICE_IDR) + || (nalu.type >= GST_H264_NAL_SLICE_AUX + && nalu.type <= GST_H264_NAL_SLICE_DEPTH)) { + GST_DEBUG ("Found slice nal type %d at offset %d", + nalu.type, nalu.sc_offset); + break; + } + + offset = nalu.offset + nalu.size; + } while (pres == GST_H264_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; + } + } + + /* insert sei */ + gst_buffer_append_memory (new_buffer, gst_memory_ref (sei)); + + /* 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_h264_parser_insert_sei: + * @nalparser: a #GstH264NalParser + * @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_h264_parser_insert_sei (GstH264NalParser * nalparser, GstBuffer * au, + GstMemory * sei) +{ + g_return_val_if_fail (nalparser != 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_h264_parser_insert_sei_internal (nalparser, 4, FALSE, au, sei); +} + +/** + * gst_h264_parser_insert_sei_avc: + * @nalparser: a #GstH264NalParser + * @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_h264_parser_insert_sei_avc (GstH264NalParser * nalparser, + guint8 nal_length_size, GstBuffer * au, GstMemory * sei) +{ + g_return_val_if_fail (nalparser != 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); + + /* the size of start code prefix (3 or 4) is not matter since it will be + * scanned */ + return gst_h264_parser_insert_sei_internal (nalparser, nal_length_size, TRUE, + au, sei); +} diff --git a/gst-libs/gst/codecparsers/gsth264parser.h b/gst-libs/gst/codecparsers/gsth264parser.h index 7a8b4bc478..bce09051fe 100644 --- a/gst-libs/gst/codecparsers/gsth264parser.h +++ b/gst-libs/gst/codecparsers/gsth264parser.h @@ -1223,6 +1223,25 @@ GST_CODEC_PARSERS_API void gst_h264_video_calculate_framerate (const GstH264SPS * sps, guint field_pic_flag, guint pic_struct, gint * fps_num, gint * fps_den); +GST_CODEC_PARSERS_API +GstMemory * gst_h264_create_sei_memory (guint8 start_code_prefix_size, + GArray * messages); + +GST_CODEC_PARSERS_API +GstMemory * gst_h264_create_sei_memory_avc (guint8 nal_length_size, + GArray * messages); + +GST_CODEC_PARSERS_API +GstBuffer * gst_h264_parser_insert_sei (GstH264NalParser * nalparser, + GstBuffer * au, + GstMemory * sei); + +GST_CODEC_PARSERS_API +GstBuffer * gst_h264_parser_insert_sei_avc (GstH264NalParser * nalparser, + guint8 nal_length_size, + GstBuffer * au, + GstMemory * sei); + G_END_DECLS #endif diff --git a/tests/check/libs/h264parser.c b/tests/check/libs/h264parser.c index 06bda1b0e7..e9a8fd361c 100644 --- a/tests/check/libs/h264parser.c +++ b/tests/check/libs/h264parser.c @@ -245,6 +245,40 @@ static guint8 nalu_chained_sei[] = { 0x06, 0x01, 0xc4, 0x80 }; +/* Content light level information SEI message */ +static guint8 h264_sei_cll[] = { + 0x00, 0x00, 0x00, 0x01, 0x06, 0x90, 0x04, 0x03, 0xe8, 0x01, 0x90, 0x80 +}; + +/* Mastering display colour volume information SEI message */ +static guint8 h264_sei_mdcv[] = { + 0x00, 0x00, 0x00, 0x01, 0x06, 0x89, 0x18, 0x84, + 0xd0, 0x3e, 0x80, 0x33, 0x90, 0x86, 0xc4, 0x1d, + 0x4c, 0x0b, 0xb8, 0x3d, 0x13, 0x40, 0x42, 0x00, + 0x98, 0x96, 0x80, 0x00, 0x00, 0x03, 0x00, 0x01, + 0x80 +}; + +/* closed caption data */ +static guint8 h264_sei_user_data_registered[] = { + 0x00, 0x00, 0x00, 0x01, 0x06, 0x04, 0x47, 0xb5, 0x00, 0x31, 0x47, 0x41, + 0x39, 0x34, 0x03, 0xd4, + 0xff, 0xfc, 0x80, 0x80, 0xfd, 0x80, 0x80, 0xfa, 0x00, 0x00, 0xfa, 0x00, + 0x00, 0xfa, 0x00, 0x00, + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, + 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, + 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, + 0x00, 0xff, 0x80 +}; + +/* frame packing, side-by-side */ +static guint8 h264_sei_frame_packing[] = { + 0x00, 0x00, 0x00, 0x01, 0x06, 0x2d, 0x07, 0x81, 0x81, 0x00, 0x00, 0x03, + 0x00, 0x01, 0x20, 0x80 +}; + GST_START_TEST (test_h264_parse_invalid_sei) { GstH264ParserResult res; @@ -313,6 +347,215 @@ GST_START_TEST (test_h264_parse_invalid_sei) GST_END_TEST; +typedef gboolean (*SEICheckFunc) (gconstpointer a, gconstpointer b); + +static gboolean +check_sei_user_data_registered (const GstH264RegisteredUserData * a, + const GstH264RegisteredUserData * 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_frame_packing (const GstH264FramePacking * a, + const GstH264FramePacking * b) +{ + if ((a->frame_packing_id != b->frame_packing_id) || + (a->frame_packing_cancel_flag != b->frame_packing_cancel_flag)) + return FALSE; + + if (!a->frame_packing_cancel_flag) { + if ((a->frame_packing_type != b->frame_packing_type) || + (a->quincunx_sampling_flag != b->quincunx_sampling_flag) || + (a->content_interpretation_type != b->content_interpretation_type) || + (a->spatial_flipping_flag != b->spatial_flipping_flag) || + (a->frame0_flipped_flag != b->frame0_flipped_flag) || + (a->field_views_flag != b->field_views_flag) || + (a->current_frame_is_frame0_flag != b->current_frame_is_frame0_flag) || + (a->frame0_self_contained_flag != b->frame0_self_contained_flag) || + (a->frame1_self_contained_flag != b->frame1_self_contained_flag)) + return FALSE; + + if (!a->quincunx_sampling_flag && + a->frame_packing_type != GST_H264_FRAME_PACKING_TEMPORAL_INTERLEAVING) { + if ((a->frame0_grid_position_x != b->frame0_grid_position_x) || + (a->frame0_grid_position_y != b->frame0_grid_position_y) || + (a->frame1_grid_position_x != b->frame1_grid_position_x) || + (a->frame1_grid_position_y != b->frame1_grid_position_y)) + return FALSE; + } + + if (a->frame_packing_repetition_period != + b->frame_packing_repetition_period) + return FALSE; + } + + return TRUE; +} + +static gboolean +check_sei_mdcv (const GstH264MasteringDisplayColourVolume * a, + const GstH264MasteringDisplayColourVolume * 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 GstH264ContentLightLevel * a, + const GstH264ContentLightLevel * 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_h264_create_sei) +{ + GstH264NalParser *parser; + GstH264ParserResult parse_ret; + GstH264NalUnit nalu; + GArray *msg_array = NULL; + GstMemory *mem; + gint i; + GstMapInfo info; + struct + { + guint8 *raw_data; + guint len; + GstH264SEIPayloadType type; + GstH264SEIMessage parsed_message; + SEICheckFunc check_func; + } test_list[] = { + /* *INDENT-OFF* */ + {h264_sei_user_data_registered, G_N_ELEMENTS (h264_sei_user_data_registered), + GST_H264_SEI_REGISTERED_USER_DATA, {0,}, + (SEICheckFunc) check_sei_user_data_registered}, + {h264_sei_frame_packing, G_N_ELEMENTS (h264_sei_frame_packing), + GST_H264_SEI_FRAME_PACKING, {0,}, + (SEICheckFunc) check_sei_frame_packing}, + {h264_sei_mdcv, G_N_ELEMENTS (h264_sei_mdcv), + GST_H264_SEI_MASTERING_DISPLAY_COLOUR_VOLUME, {0,}, + (SEICheckFunc) check_sei_mdcv}, + {h264_sei_cll, G_N_ELEMENTS (h264_sei_cll), + GST_H264_SEI_CONTENT_LIGHT_LEVEL, {0,}, + (SEICheckFunc) check_sei_cll}, + /* *INDENT-ON* */ + }; + + parser = gst_h264_nal_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_h264_parser_identify_nalu_unchecked (parser, + test_list[i].raw_data, 0, test_list[i].len, &nalu); + assert_equals_int (parse_ret, GST_H264_PARSER_OK); + assert_equals_int (nalu.type, GST_H264_NAL_SEI); + + parse_ret = gst_h264_parser_parse_sei (parser, &nalu, &msg_array); + assert_equals_int (parse_ret, GST_H264_PARSER_OK); + assert_equals_int (msg_array->len, 1); + + /* test bytestream */ + mem = gst_h264_create_sei_memory (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_h264_create_sei_memory_avc (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 */ + test_list[i].parsed_message = + g_array_index (msg_array, GstH264SEIMessage, 0); + if (test_list[i].type == GST_H264_SEI_REGISTERED_USER_DATA) { + GstH264RegisteredUserData *dst_rud = + &test_list[i].parsed_message.payload.registered_user_data; + const GstH264SEIMessage *src_msg = + &g_array_index (msg_array, GstH264SEIMessage, 0); + const GstH264RegisteredUserData *src_rud = + &src_msg->payload.registered_user_data; + + dst_rud->data = g_malloc (src_rud->size); + memcpy ((guint8 *) dst_rud->data, src_rud->data, src_rud->size); + } + g_array_unref (msg_array); + } + + /* test multiple SEI messages in a nal unit */ + msg_array = g_array_new (FALSE, FALSE, sizeof (GstH264SEIMessage)); + for (i = 0; i < G_N_ELEMENTS (test_list); i++) + g_array_append_val (msg_array, test_list[i].parsed_message); + + mem = gst_h264_create_sei_memory (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_h264_parser_identify_nalu_unchecked (parser, + info.data, 0, info.size, &nalu); + assert_equals_int (parse_ret, GST_H264_PARSER_OK); + assert_equals_int (nalu.type, GST_H264_NAL_SEI); + parse_ret = gst_h264_parser_parse_sei (parser, &nalu, &msg_array); + gst_memory_unmap (mem, &info); + gst_memory_unref (mem); + + assert_equals_int (parse_ret, GST_H264_PARSER_OK); + assert_equals_int (msg_array->len, G_N_ELEMENTS (test_list)); + for (i = 0; i < msg_array->len; i++) { + GstH264SEIMessage *msg = &g_array_index (msg_array, GstH264SEIMessage, 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_h264_sei_clear (&test_list[i].parsed_message); + + g_array_unref (msg_array); + gst_h264_nal_parser_free (parser); +} + +GST_END_TEST; + static Suite * h264parser_suite (void) { @@ -325,6 +568,7 @@ h264parser_suite (void) tcase_add_test (tc_chain, test_h264_parse_slice_eoseq_slice); tcase_add_test (tc_chain, test_h264_parse_slice_5bytes); tcase_add_test (tc_chain, test_h264_parse_invalid_sei); + tcase_add_test (tc_chain, test_h264_create_sei); return s; }