h264parser: Add support for creating picture timing SEI

This new method can make it possible to inject timecode meta into
h264 bitstream
This commit is contained in:
Seungha Yang 2020-04-07 20:26:23 +09:00 committed by GStreamer Merge Bot
parent 72854261bb
commit db1ea18276
2 changed files with 255 additions and 1 deletions

View file

@ -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;
}

View file

@ -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;