From db1ea18276ee255a2654f97f05881ca3b57ae314 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Tue, 7 Apr 2020 20:26:23 +0900 Subject: [PATCH] h264parser: Add support for creating picture timing SEI This new method can make it possible to inject timecode meta into h264 bitstream --- gst-libs/gst/codecparsers/gsth264parser.c | 155 ++++++++++++++++++++++ tests/check/libs/h264parser.c | 101 +++++++++++++- 2 files changed, 255 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/codecparsers/gsth264parser.c b/gst-libs/gst/codecparsers/gsth264parser.c index 83eca656cd..bf454bc314 100644 --- a/gst-libs/gst/codecparsers/gsth264parser.c +++ b/gst-libs/gst/codecparsers/gsth264parser.c @@ -2729,6 +2729,70 @@ error: return FALSE; } +static gboolean +gst_h264_write_sei_pic_timing (NalWriter * nw, GstH264PicTiming * tim) +{ + if (tim->CpbDpbDelaysPresentFlag) { + WRITE_UINT32 (nw, tim->cpb_removal_delay, + tim->cpb_removal_delay_length_minus1 + 1); + WRITE_UINT32 (nw, tim->dpb_output_delay, + tim->dpb_output_delay_length_minus1 + 1); + } + + if (tim->pic_struct_present_flag) { + const guint8 num_clock_ts_table[9] = { + 1, 1, 1, 2, 2, 3, 3, 2, 3 + }; + guint8 num_clock_num_ts; + guint i; + + WRITE_UINT8 (nw, tim->pic_struct, 4); + + num_clock_num_ts = num_clock_ts_table[tim->pic_struct]; + for (i = 0; i < num_clock_num_ts; i++) { + WRITE_UINT8 (nw, tim->clock_timestamp_flag[i], 1); + if (tim->clock_timestamp_flag[i]) { + GstH264ClockTimestamp *timestamp = &tim->clock_timestamp[i]; + + WRITE_UINT8 (nw, timestamp->ct_type, 2); + WRITE_UINT8 (nw, timestamp->nuit_field_based_flag, 1); + WRITE_UINT8 (nw, timestamp->counting_type, 5); + WRITE_UINT8 (nw, timestamp->full_timestamp_flag, 1); + WRITE_UINT8 (nw, timestamp->discontinuity_flag, 1); + WRITE_UINT8 (nw, timestamp->cnt_dropped_flag, 1); + WRITE_UINT8 (nw, timestamp->n_frames, 8); + + if (timestamp->full_timestamp_flag) { + WRITE_UINT8 (nw, timestamp->seconds_value, 6); + WRITE_UINT8 (nw, timestamp->minutes_value, 6); + WRITE_UINT8 (nw, timestamp->hours_value, 5); + } else { + WRITE_UINT8 (nw, timestamp->seconds_flag, 1); + if (timestamp->seconds_flag) { + WRITE_UINT8 (nw, timestamp->seconds_value, 6); + WRITE_UINT8 (nw, timestamp->minutes_flag, 1); + if (timestamp->minutes_flag) { + WRITE_UINT8 (nw, timestamp->minutes_value, 6); + WRITE_UINT8 (nw, timestamp->hours_flag, 1); + if (timestamp->hours_flag) + WRITE_UINT8 (nw, timestamp->hours_value, 5); + } + } + } + + if (tim->time_offset_length > 0) { + WRITE_UINT32 (nw, timestamp->time_offset, tim->time_offset_length); + } + } + } + } + + return TRUE; + +error: + return FALSE; +} + static GstMemory * gst_h264_create_sei_memory_internal (guint8 nal_prefix_size, gboolean packetized, GArray * messages) @@ -2844,6 +2908,89 @@ gst_h264_create_sei_memory_internal (guint8 nal_prefix_size, */ payload_size_data = 4; break; + case GST_H264_SEI_PIC_TIMING:{ + GstH264PicTiming *tim = &msg->payload.pic_timing; + const guint8 num_clock_ts_table[9] = { + 1, 1, 1, 2, 2, 3, 3, 2, 3 + }; + guint8 num_clock_num_ts; + guint i; + + if (!tim->CpbDpbDelaysPresentFlag && !tim->pic_struct_present_flag) { + GST_WARNING + ("Both CpbDpbDelaysPresentFlag and pic_struct_present_flag are zero"); + break; + } + + if (tim->CpbDpbDelaysPresentFlag) { + payload_size_in_bits = tim->cpb_removal_delay_length_minus1 + 1; + payload_size_in_bits += tim->dpb_output_delay_length_minus1 + 1; + } + + if (tim->pic_struct_present_flag) { + /* pic_struct: 4bits */ + payload_size_in_bits += 4; + + num_clock_num_ts = num_clock_ts_table[tim->pic_struct]; + for (i = 0; i < num_clock_num_ts; i++) { + /* clock_timestamp_flag: 1bit */ + payload_size_in_bits++; + + if (tim->clock_timestamp_flag[i]) { + GstH264ClockTimestamp *timestamp = &tim->clock_timestamp[i]; + + /* ct_type: 2bits + * nuit_field_based_flag: 1bit + * counting_type: 5bits + * full_timestamp_flag: 1bit + * discontinuity_flag: 1bit + * cnt_dropped_flag: 1bit + * n_frames: 8bits + */ + payload_size_in_bits += 19; + if (timestamp->full_timestamp_flag) { + /* seconds_value: 6bits + * minutes_value: 6bits + * hours_value: 5bits + */ + payload_size_in_bits += 17; + } else { + /* seconds_flag: 1bit */ + payload_size_in_bits++; + + if (timestamp->seconds_flag) { + /* seconds_value: 6bits + * minutes_flag: 1bit + */ + payload_size_in_bits += 7; + if (timestamp->minutes_flag) { + /* minutes_value: 6bits + * hours_flag: 1bits + */ + payload_size_in_bits += 7; + if (timestamp->hours_flag) { + /* hours_value: 5bits */ + payload_size_in_bits += 5; + } + } + } + } + + /* time_offset_length bits */ + payload_size_in_bits += tim->time_offset_length; + } + } + } + + payload_size_data = payload_size_in_bits >> 3; + + if ((payload_size_in_bits & 0x7) != 0) { + GST_INFO ("Bits for Picture Timing SEI is not byte aligned"); + payload_size_data++; + need_align = TRUE; + } + break; + } default: break; } @@ -2904,6 +3051,14 @@ gst_h264_create_sei_memory_internal (guint8 nal_prefix_size, } have_written_data = TRUE; break; + case GST_H264_SEI_PIC_TIMING: + GST_DEBUG ("Writing \"Picture timing\""); + if (!gst_h264_write_sei_pic_timing (&nw, &msg->payload.pic_timing)) { + GST_WARNING ("Failed to write \"Picture timing\""); + goto error; + } + have_written_data = TRUE; + break; default: break; } diff --git a/tests/check/libs/h264parser.c b/tests/check/libs/h264parser.c index 0d6ed70716..c7c46d9a20 100644 --- a/tests/check/libs/h264parser.c +++ b/tests/check/libs/h264parser.c @@ -237,7 +237,7 @@ static guint8 nalu_sps_with_vui[] = { }; static guint8 nalu_sei_pic_timing[] = { - 0x00, 0x00, 0x01, 0x06, 0x01, 0x01, 0x32, 0x80 + 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x01, 0x32, 0x80 }; static guint8 nalu_chained_sei[] = { @@ -429,6 +429,91 @@ check_sei_cll (const GstH264ContentLightLevel * a, (a->max_pic_average_light_level == b->max_pic_average_light_level); } +static gboolean +check_sei_pic_timing (const GstH264PicTiming * a, const GstH264PicTiming * b) +{ + if (a->CpbDpbDelaysPresentFlag != b->CpbDpbDelaysPresentFlag) + return FALSE; + + if (a->CpbDpbDelaysPresentFlag) { + if (a->cpb_removal_delay != b->cpb_removal_delay || + a->cpb_removal_delay_length_minus1 != b->cpb_removal_delay_length_minus1 + || a->dpb_output_delay != b->dpb_output_delay + || a->dpb_output_delay_length_minus1 != + b->dpb_output_delay_length_minus1) + return FALSE; + } + + if (a->pic_struct_present_flag != b->pic_struct_present_flag) + return FALSE; + + if (a->pic_struct_present_flag) { + const guint8 num_clock_ts_table[9] = { + 1, 1, 1, 2, 2, 3, 3, 2, 3 + }; + guint8 num_clock_num_ts; + guint i; + + if (a->pic_struct != b->pic_struct) + return FALSE; + + if (a->time_offset_length != b->time_offset_length) + return FALSE; + + num_clock_num_ts = num_clock_ts_table[a->pic_struct]; + + for (i = 0; i < num_clock_num_ts; i++) { + if (a->clock_timestamp_flag[i] != b->clock_timestamp_flag[i]) + return FALSE; + + if (a->clock_timestamp_flag[i]) { + const GstH264ClockTimestamp *ta = &a->clock_timestamp[i]; + const GstH264ClockTimestamp *tb = &b->clock_timestamp[i]; + + if (ta->ct_type != tb->ct_type || + ta->nuit_field_based_flag != tb->nuit_field_based_flag || + ta->counting_type != tb->counting_type || + ta->discontinuity_flag != tb->discontinuity_flag || + ta->cnt_dropped_flag != tb->cnt_dropped_flag || + ta->n_frames != tb->n_frames) + return FALSE; + + if (ta->full_timestamp_flag) { + if (ta->seconds_value != tb->seconds_value || + ta->minutes_value != tb->minutes_value || + ta->hours_value != tb->hours_value) + return FALSE; + } else { + if (ta->seconds_flag != tb->seconds_flag) + return FALSE; + + if (ta->seconds_flag) { + if (ta->seconds_value != tb->seconds_value || + ta->minutes_flag != tb->minutes_flag) + return FALSE; + + if (ta->minutes_flag) { + if (ta->minutes_value != tb->minutes_value || + ta->hours_flag != tb->hours_flag) + return FALSE; + + if (ta->hours_flag) { + if (ta->hours_value != tb->hours_value) + return FALSE; + } + } + } + } + + if (ta->time_offset != tb->time_offset) + return FALSE; + } + } + } + + return TRUE; +} + GST_START_TEST (test_h264_create_sei) { GstH264NalParser *parser; @@ -459,11 +544,25 @@ GST_START_TEST (test_h264_create_sei) {h264_sei_cll, G_N_ELEMENTS (h264_sei_cll), GST_H264_SEI_CONTENT_LIGHT_LEVEL, {0,}, (SEICheckFunc) check_sei_cll}, + {nalu_sei_pic_timing, G_N_ELEMENTS (nalu_sei_pic_timing), + GST_H264_SEI_PIC_TIMING, {0,}, + (SEICheckFunc) check_sei_pic_timing}, /* *INDENT-ON* */ }; parser = gst_h264_nal_parser_new (); + /* inject SPS for picture timing sei */ + parse_ret = + gst_h264_parser_identify_nalu_unchecked (parser, nalu_sps_with_vui, 0, + sizeof (nalu_sps_with_vui), &nalu); + assert_equals_int (parse_ret, GST_H264_PARSER_OK); + assert_equals_int (nalu.type, GST_H264_NAL_SPS); + assert_equals_int (nalu.size, 28); + + parse_ret = gst_h264_parser_parse_nal (parser, &nalu); + assert_equals_int (parse_ret, GST_H264_PARSER_OK); + /* test single sei message per sei nal unit */ for (i = 0; i < G_N_ELEMENTS (test_list); i++) { gsize nal_size;