h264parser: Add support for creating picture timing SEI
authorSeungha Yang <seungha@centricular.com>
Tue, 7 Apr 2020 11:26:23 +0000 (20:26 +0900)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Wed, 8 Apr 2020 15:39:12 +0000 (15:39 +0000)
This new method can make it possible to inject timecode meta into
h264 bitstream

gst-libs/gst/codecparsers/gsth264parser.c
tests/check/libs/h264parser.c

index 83eca65..bf454bc 100644 (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;
     }
index 0d6ed70..c7c46d9 100644 (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;