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);
+}
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;
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)
{
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;
}