mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 17:20:36 +00:00
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:
parent
72854261bb
commit
db1ea18276
2 changed files with 255 additions and 1 deletions
|
@ -2729,6 +2729,70 @@ error:
|
||||||
return FALSE;
|
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 *
|
static GstMemory *
|
||||||
gst_h264_create_sei_memory_internal (guint8 nal_prefix_size,
|
gst_h264_create_sei_memory_internal (guint8 nal_prefix_size,
|
||||||
gboolean packetized, GArray * messages)
|
gboolean packetized, GArray * messages)
|
||||||
|
@ -2844,6 +2908,89 @@ gst_h264_create_sei_memory_internal (guint8 nal_prefix_size,
|
||||||
*/
|
*/
|
||||||
payload_size_data = 4;
|
payload_size_data = 4;
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2904,6 +3051,14 @@ gst_h264_create_sei_memory_internal (guint8 nal_prefix_size,
|
||||||
}
|
}
|
||||||
have_written_data = TRUE;
|
have_written_data = TRUE;
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ static guint8 nalu_sps_with_vui[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static guint8 nalu_sei_pic_timing[] = {
|
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[] = {
|
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);
|
(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)
|
GST_START_TEST (test_h264_create_sei)
|
||||||
{
|
{
|
||||||
GstH264NalParser *parser;
|
GstH264NalParser *parser;
|
||||||
|
@ -459,11 +544,25 @@ GST_START_TEST (test_h264_create_sei)
|
||||||
{h264_sei_cll, G_N_ELEMENTS (h264_sei_cll),
|
{h264_sei_cll, G_N_ELEMENTS (h264_sei_cll),
|
||||||
GST_H264_SEI_CONTENT_LIGHT_LEVEL, {0,},
|
GST_H264_SEI_CONTENT_LIGHT_LEVEL, {0,},
|
||||||
(SEICheckFunc) check_sei_cll},
|
(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* */
|
/* *INDENT-ON* */
|
||||||
};
|
};
|
||||||
|
|
||||||
parser = gst_h264_nal_parser_new ();
|
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 */
|
/* test single sei message per sei nal unit */
|
||||||
for (i = 0; i < G_N_ELEMENTS (test_list); i++) {
|
for (i = 0; i < G_N_ELEMENTS (test_list); i++) {
|
||||||
gsize nal_size;
|
gsize nal_size;
|
||||||
|
|
Loading…
Reference in a new issue