[FFmpeg-devel,1/3] lavc/h264: create AVFrame side data from H.264 timecodes

Submitted by joshdk@ob-encoder.com on Oct. 9, 2018, 1:32 p.m.

Details

Message ID 20181009133204.29686-2-joshdk@obe.tv
State New
Headers show

Commit Message

joshdk@ob-encoder.com Oct. 9, 2018, 1:32 p.m.
From: Devin Heitmueller <dheitmueller@ltnglobal.com>

Create SMPTE ST 12-1 timecodes based on H.264 SEI picture timing
info.

For framerates > 30 FPS, the field flag is used in conjunction with
pairs of frames which contain the same frame timestamp in S12M.
Ensure the field is properly set per the spec.
---
 fftools/ffprobe.c       |  4 ++++
 libavcodec/h264_sei.c   | 37 ++++++++++++++++++++-----------------
 libavcodec/h264_sei.h   |  9 +++++++++
 libavcodec/h264_slice.c | 38 ++++++++++++++++++++++++++++++++++++++
 libavutil/frame.c       |  1 +
 libavutil/frame.h       |  6 ++++++
 6 files changed, 78 insertions(+), 17 deletions(-)

Patch hide | download patch | download mbox

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index 544786ec72..6cc3a4efc4 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -2199,6 +2199,10 @@  static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream,
                 char tcbuf[AV_TIMECODE_STR_SIZE];
                 av_timecode_make_mpeg_tc_string(tcbuf, *(int64_t *)(sd->data));
                 print_str("timecode", tcbuf);
+            } else if (sd->type == AV_FRAME_DATA_S12M_TIMECODE && sd->size >= 4) {
+                char tcbuf[AV_TIMECODE_STR_SIZE];
+                av_timecode_make_smpte_tc_string(tcbuf, *(uint32_t *)(sd->data), 0);
+                print_str("timecode", tcbuf);
             } else if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) {
                 AVMasteringDisplayMetadata *metadata = (AVMasteringDisplayMetadata *)sd->data;
 
diff --git a/libavcodec/h264_sei.c b/libavcodec/h264_sei.c
index 43593d34d2..275224eabe 100644
--- a/libavcodec/h264_sei.c
+++ b/libavcodec/h264_sei.c
@@ -84,32 +84,35 @@  static int decode_picture_timing(H264SEIPictureTiming *h, GetBitContext *gb,
             return AVERROR_INVALIDDATA;
 
         num_clock_ts = sei_num_clock_ts_table[h->pic_struct];
-
         for (i = 0; i < num_clock_ts; i++) {
-            if (get_bits(gb, 1)) {                /* clock_timestamp_flag */
+            if (get_bits(gb, 1)) {                      /* clock_timestamp_flag */
                 unsigned int full_timestamp_flag;
-
+                unsigned int counting_type, cnt_dropped_flag;
                 h->ct_type |= 1 << get_bits(gb, 2);
-                skip_bits(gb, 1);                 /* nuit_field_based_flag */
-                skip_bits(gb, 5);                 /* counting_type */
+                skip_bits(gb, 1);                       /* nuit_field_based_flag */
+                counting_type = get_bits(gb, 5);        /* counting_type */
                 full_timestamp_flag = get_bits(gb, 1);
-                skip_bits(gb, 1);                 /* discontinuity_flag */
-                skip_bits(gb, 1);                 /* cnt_dropped_flag */
-                skip_bits(gb, 8);                 /* n_frames */
+                skip_bits(gb, 1);                       /* discontinuity_flag */
+                cnt_dropped_flag = get_bits(gb, 1);      /* cnt_dropped_flag */
+                if (cnt_dropped_flag && counting_type > 1 && counting_type < 7)
+                    h->tc_dropframe = 1;
+                h->tc_frames = get_bits(gb, 8);         /* n_frames */
                 if (full_timestamp_flag) {
-                    skip_bits(gb, 6);             /* seconds_value 0..59 */
-                    skip_bits(gb, 6);             /* minutes_value 0..59 */
-                    skip_bits(gb, 5);             /* hours_value 0..23 */
+                    h->fulltc_received = 1;
+                    h->tc_seconds = get_bits(gb, 6); /* seconds_value 0..59 */
+                    h->tc_minutes = get_bits(gb, 6); /* minutes_value 0..59 */
+                    h->tc_hours = get_bits(gb, 5);   /* hours_value 0..23 */
                 } else {
-                    if (get_bits(gb, 1)) {        /* seconds_flag */
-                        skip_bits(gb, 6);         /* seconds_value range 0..59 */
-                        if (get_bits(gb, 1)) {    /* minutes_flag */
-                            skip_bits(gb, 6);     /* minutes_value 0..59 */
-                            if (get_bits(gb, 1))  /* hours_flag */
-                                skip_bits(gb, 5); /* hours_value 0..23 */
+                    if (get_bits(gb, 1)) {             /* seconds_flag */
+                        h->tc_seconds = get_bits(gb, 6);
+                        if (get_bits(gb, 1)) {         /* minutes_flag */
+                            h->tc_minutes = get_bits(gb, 6);
+                            if (get_bits(gb, 1))       /* hours_flag */
+                                h->tc_minutes = get_bits(gb, 5);
                         }
                     }
                 }
+
                 if (sps->time_offset_length > 0)
                     skip_bits(gb,
                               sps->time_offset_length); /* time_offset */
diff --git a/libavcodec/h264_sei.h b/libavcodec/h264_sei.h
index 5b7c8ef9d8..3b8806be0a 100644
--- a/libavcodec/h264_sei.h
+++ b/libavcodec/h264_sei.h
@@ -87,6 +87,15 @@  typedef struct H264SEIPictureTiming {
      * cpb_removal_delay in picture timing SEI message, see H.264 C.1.2
      */
     int cpb_removal_delay;
+
+    /* When not continuously receiving full timecodes, we have to reference
+       the previous timecode received */
+    int fulltc_received;
+    int tc_frames;
+    int tc_seconds;
+    int tc_minutes;
+    int tc_hours;
+    int tc_dropframe;
 } H264SEIPictureTiming;
 
 typedef struct H264SEIAFD {
diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c
index 58e1aaf02f..973f5761ef 100644
--- a/libavcodec/h264_slice.c
+++ b/libavcodec/h264_slice.c
@@ -1287,6 +1287,44 @@  static int h264_export_frame_props(H264Context *h)
         h->avctx->properties |= FF_CODEC_PROPERTY_CLOSED_CAPTIONS;
     }
 
+    if (h->sei.picture_timing.fulltc_received) {
+        uint32_t tc = 0;
+        uint32_t frames;
+
+        AVFrameSideData *tcside = av_frame_new_side_data(cur->f,
+                                                         AV_FRAME_DATA_S12M_TIMECODE,
+                                                         sizeof(uint32_t));
+        if (!tcside)
+            return AVERROR(ENOMEM);
+
+        /* For SMPTE 12-M timecodes, frame count is a special case if > 30 FPS.
+           See SMPTE ST 12-1:2014 Sec 12.1 for more info. */
+        if (av_cmp_q(h->avctx->framerate, (AVRational) {30, 1}) == 1) {
+            frames = h->sei.picture_timing.tc_frames / 2;
+            if (h->sei.picture_timing.tc_frames % 2 == 1) {
+                if (av_cmp_q(h->avctx->framerate, (AVRational) {50, 1}) == 0)
+                    tc |= (1 << 7);
+                else
+                    tc |= (1 << 23);
+            }
+        } else {
+            frames = h->sei.picture_timing.tc_frames;
+        }
+
+        tc |= h->sei.picture_timing.tc_dropframe << 30;
+        tc |= (frames / 10) << 28;
+        tc |= (frames % 10) << 24;
+        tc |= (h->sei.picture_timing.tc_seconds / 10) << 20;
+        tc |= (h->sei.picture_timing.tc_seconds % 10) << 16;
+        tc |= (h->sei.picture_timing.tc_minutes / 10) << 12;
+        tc |= (h->sei.picture_timing.tc_minutes % 10) << 8;
+        tc |= (h->sei.picture_timing.tc_hours / 10) << 4;
+        tc |= (h->sei.picture_timing.tc_hours % 10);
+
+        memcpy(tcside->data, &tc, sizeof(uint32_t));
+        h->sei.picture_timing.fulltc_received = 0;
+    }
+
     if (h->sei.alternative_transfer.present &&
         av_color_transfer_name(h->sei.alternative_transfer.preferred_transfer_characteristics) &&
         h->sei.alternative_transfer.preferred_transfer_characteristics != AVCOL_TRC_UNSPECIFIED) {
diff --git a/libavutil/frame.c b/libavutil/frame.c
index 4460325a9b..92626dccf2 100644
--- a/libavutil/frame.c
+++ b/libavutil/frame.c
@@ -831,6 +831,7 @@  const char *av_frame_side_data_name(enum AVFrameSideDataType type)
     case AV_FRAME_DATA_MASTERING_DISPLAY_METADATA:  return "Mastering display metadata";
     case AV_FRAME_DATA_CONTENT_LIGHT_LEVEL:         return "Content light level metadata";
     case AV_FRAME_DATA_GOP_TIMECODE:                return "GOP timecode";
+    case AV_FRAME_DATA_S12M_TIMECODE:               return "SMPTE 12-1 timecode";
     case AV_FRAME_DATA_SPHERICAL:                   return "Spherical Mapping";
     case AV_FRAME_DATA_ICC_PROFILE:                 return "ICC profile";
 #if FF_API_FRAME_QP
diff --git a/libavutil/frame.h b/libavutil/frame.h
index 9d57d6ce66..0061103894 100644
--- a/libavutil/frame.h
+++ b/libavutil/frame.h
@@ -123,6 +123,12 @@  enum AVFrameSideDataType {
      */
     AV_FRAME_DATA_GOP_TIMECODE,
 
+    /**
+     * Timecode which conforms to SMPTE ST 12-1.  The data is a uint32_t which
+     * can be found described in libavutil/timecode.h.
+     */
+    AV_FRAME_DATA_S12M_TIMECODE,
+
     /**
      * The data represents the AVSphericalMapping structure defined in
      * libavutil/spherical.h.