[FFmpeg-devel] [PATCH 2/2] movenc: add timecode track support.

Clément Bœsch ubitux at gmail.com
Tue Mar 20 09:00:24 CET 2012


On Tue, Mar 20, 2012 at 07:39:01AM +0000, Tim Nicholson wrote:
> On 09/03/12 16:51, Clément Bœsch wrote:
> > From: Clément Bœsch <clement.boesch at smartjog.com>
> > 
> > The example in the QT specs shows a likely invalid gmin atom size, which
> > suggests tmcd atom is contained in the gmin one, while it is actually in
> > the gmhd atom, following gmin (according to the given layout on the same
> > page, and various samples):
> > 
> > [..]
> 
> 
> ping.
> 

Patch rebased for review, no functional changes from previously (IIRC).

-- 
Clément B.
-------------- next part --------------
From 82b7c450f6b7fcf6fcb986d21027103697b00a59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= <clement.boesch at smartjog.com>
Date: Mon, 5 Mar 2012 08:51:08 +0100
Subject: [PATCH 2/2] movenc: add timecode track support.

The example in the QT specs shows a likely invalid gmin atom size, which
suggests tmcd atom is contained in the gmin one, while it is actually in
the gmhd atom, following gmin (according to the given layout on the same
page, and various samples):

Suggested by qtff-2001.pdf (p223) atom sizes:
  [gmhd]
    [gmin]
      [tmcd]
        [tmci]

Reality (samples) and tree view in qtff-2001.pdf (p223):
  [gmhd]
    [gmin]
    [tmcd]
      [tmci]

Generally speaking, the specs isn't clear about the timecode, so I just
followed the layout I found in some samples.

The choosen font, size and colors in tmci tag are choosen to match the
most common ones I expected in various samples. Feel free to propose
something better.

Concerning the Handler chunk, it seems common to have "Time Code Media
Handler", but to be consistent with the a/v/s code around (which has the
same issue), "TimeCodeHandler" is choosen instead. This atom seems
deprecated anyway.
---
 Changelog            |    1 +
 doc/general.texi     |    2 +-
 libavformat/movenc.c |  143 ++++++++++++++++++++++++++++++++++++++++++++++++--
 libavformat/movenc.h |    8 +++
 tests/ref/lavf/mov   |    8 ++--
 5 files changed, 153 insertions(+), 9 deletions(-)

diff --git a/Changelog b/Changelog
index e6569ac..208c024 100644
--- a/Changelog
+++ b/Changelog
@@ -18,6 +18,7 @@ version next:
 - XBM encoder
 - RealAudio Lossless decoder
 - ZeroCodec decoder
+- timecode track writting in MOV
 
 
 version 0.10:
diff --git a/doc/general.texi b/doc/general.texi
index 1acdc24..3579e36 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -886,7 +886,7 @@ performance on systems without hardware floating point support).
 @item Codec/format      @tab Read   @tab Write
 @item DV                @tab X      @tab X
 @item GXF               @tab X      @tab X
- at item MOV               @tab X      @tab
+ at item MOV               @tab X      @tab X
 @item MPEG1/2           @tab X      @tab X
 @item MXF               @tab X      @tab X
 @end multitable
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 7f970be..c9ddcdd 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -59,6 +59,7 @@ static const AVOption options[] = {
     { "frag_duration", "Maximum fragment duration", offsetof(MOVMuxContext, max_fragment_duration), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
     { "frag_size", "Maximum fragment size", offsetof(MOVMuxContext, max_fragment_size), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
     { "ism_lookahead", "Number of lookahead entries for ISM files", offsetof(MOVMuxContext, ism_lookahead), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
+    { AV_TIMECODE_OPTION(MOVMuxContext, tc_opt_str, AV_OPT_FLAG_ENCODING_PARAM)},
     { NULL },
 };
 
@@ -1090,6 +1091,24 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track)
     return update_size(pb, pos);
 }
 
+static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+    int nb_frames = (track->timescale + track->track_duration/2) / track->track_duration;
+
+    avio_wb32(pb, 0); /* size */
+    ffio_wfourcc(pb, "tmcd");               /* Data format */
+    avio_wb32(pb, 0);                       /* Reserved */
+    avio_wb32(pb, 1);                       /* Data reference index */
+    avio_wb32(pb, 0);                       /* Flags */
+    avio_wb32(pb, track->timecode_flags);   /* Flags (timecode) */
+    avio_wb32(pb, track->timescale);        /* Timescale */
+    avio_wb32(pb, track->track_duration);   /* Frame duration */
+    avio_w8(pb, nb_frames);                 /* Number of frames */
+    avio_w8(pb, 0);                         /* TODO: source reference string */
+    return update_size(pb, pos);
+}
+
 static int mov_write_rtp_tag(AVIOContext *pb, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
@@ -1125,6 +1144,8 @@ static int mov_write_stsd_tag(AVIOContext *pb, MOVTrack *track)
         mov_write_subtitle_tag(pb, track);
     else if (track->enc->codec_tag == MKTAG('r','t','p',' '))
         mov_write_rtp_tag(pb, track);
+    else if (track->enc->codec_tag == MKTAG('t','m','c','d'))
+        mov_write_tmcd_tag(pb, track);
     return update_size(pb, pos);
 }
 
@@ -1257,9 +1278,31 @@ static int mov_write_nmhd_tag(AVIOContext *pb)
     return 12;
 }
 
-static int mov_write_gmhd_tag(AVIOContext *pb)
+static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+    const char *font = "Lucida Grande";
+    avio_wb32(pb, 0);                   /* size */
+    ffio_wfourcc(pb, "tcmi");           /* timecode media information atom */
+    avio_wb32(pb, 0);                   /* version & flags */
+    avio_wb16(pb, 0);                   /* text font */
+    avio_wb16(pb, 0);                   /* text face */
+    avio_wb16(pb, 12);                  /* text size */
+    avio_wb16(pb, 0x0000);              /* text color (red) */
+    avio_wb16(pb, 0x0000);              /* text color (green) */
+    avio_wb16(pb, 0x0000);              /* text color (blue) */
+    avio_wb16(pb, 0xffff);              /* background color (red) */
+    avio_wb16(pb, 0xffff);              /* background color (green) */
+    avio_wb16(pb, 0xffff);              /* background color (blue) */
+    avio_w8(pb, strlen(font));          /* font len (part of the pascal string) */
+    avio_write(pb, font, strlen(font)); /* font name */
+    return update_size(pb, pos);
+}
+
+static int mov_write_gmhd_tag(AVIOContext *pb, MOVTrack *track)
 {
-    avio_wb32(pb, 0x20);   /* size */
+    int64_t pos = avio_tell(pb);
+    avio_wb32(pb, 0);      /* size */
     ffio_wfourcc(pb, "gmhd");
     avio_wb32(pb, 0x18);   /* gmin size */
     ffio_wfourcc(pb, "gmin");/* generic media info */
@@ -1270,7 +1313,15 @@ static int mov_write_gmhd_tag(AVIOContext *pb)
     avio_wb16(pb, 0x8000); /* opColor (b?) */
     avio_wb16(pb, 0);      /* balance */
     avio_wb16(pb, 0);      /* reserved */
-    return 0x20;
+
+    if (track->enc->codec_tag == MKTAG('t','m','c','d')) {
+        int64_t tmcd_pos = avio_tell(pb);
+        avio_wb32(pb, 0); /* size */
+        ffio_wfourcc(pb, "tmcd");
+        mov_write_tcmi_tag(pb, track);
+        update_size(pb, tmcd_pos);
+    }
+    return update_size(pb, pos);
 }
 
 static int mov_write_smhd_tag(AVIOContext *pb)
@@ -1313,6 +1364,9 @@ static int mov_write_hdlr_tag(AVIOContext *pb, MOVTrack *track)
             if (track->tag == MKTAG('t','x','3','g')) hdlr_type = "sbtl";
             else                                      hdlr_type = "text";
             descr = "SubtitleHandler";
+        } else if (track->enc->codec_tag == MKTAG('t','m','c','d')) {
+            hdlr_type = "tmcd";
+            descr = "TimeCodeHandler";
         } else if (track->enc->codec_tag == MKTAG('r','t','p',' ')) {
             hdlr_type = "hint";
             descr = "HintHandler";
@@ -1360,8 +1414,10 @@ static int mov_write_minf_tag(AVIOContext *pb, MOVTrack *track)
     else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO)
         mov_write_smhd_tag(pb);
     else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) {
-        if (track->tag == MKTAG('t','e','x','t')) mov_write_gmhd_tag(pb);
+        if (track->tag == MKTAG('t','e','x','t')) mov_write_gmhd_tag(pb, track);
         else                                      mov_write_nmhd_tag(pb);
+    } else if (track->tag == MKTAG('t','m','c','d')) {
+        mov_write_gmhd_tag(pb, track);
     } else if (track->tag == MKTAG('r','t','p',' ')) {
         mov_write_hmhd_tag(pb);
     }
@@ -2118,6 +2174,11 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
                 mov->tracks[mov->tracks[i].src_track].track_id;
         }
     }
+    if (mov->tmcd_track) {
+        i = mov->tmcd_video_track + 1;
+        mov->tracks[i].tref_tag = MKTAG('t','m','c','d');
+        mov->tracks[i].tref_id  = mov->tracks[mov->tmcd_track].track_id;
+    }
 
     mov_write_mvhd_tag(pb, mov);
     if (mov->mode != MODE_MOV && !mov->iods_skip)
@@ -3087,6 +3148,57 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum)
     }
 }
 
+static int mov_create_timecode_track(AVFormatContext *s, int tracknum, int vst_idx)
+{
+    MOVMuxContext *mov = s->priv_data;
+    MOVTrack *track = &mov->tracks[tracknum];
+    AVTimecode tc;
+    AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY };
+    const AVStream *vst = s->streams[vst_idx];
+    AVRational rate = {vst->codec->time_base.den, vst->codec->time_base.num};
+
+    /* compute the frame number internally */
+    int ret = av_timecode_init_from_string(&tc, rate, mov->tc_opt_str, s);
+    if (ret < 0)
+        return ret;
+
+    /* tmcd track based on video stream */
+    track->mode = mov->mode;
+    track->tag  = MKTAG('t','m','c','d');
+    track->timescale = vst->codec->time_base.den;
+    if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
+        track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME;
+
+    /* encode context: tmcd data stream */
+    track->enc = avcodec_alloc_context3(NULL);
+    track->enc->codec_type = AVMEDIA_TYPE_DATA;
+    track->enc->codec_tag  = track->tag;
+
+    /* the timecode stream just contains one packet with the frame number */
+    pkt.size = 4;
+    pkt.data = av_malloc(pkt.size);
+    pkt.duration = vst->codec->time_base.num; // for track->track_duration
+    AV_WB32(pkt.data, tc.start);
+    ret = ff_mov_write_packet(s, &pkt);
+    av_free(pkt.data);
+    return ret;
+}
+
+/* select the video with the highest resolution, just like ffmpeg.c. */
+static int select_best_video_stream(AVFormatContext *s)
+{
+    int i, vst = -1;
+    unsigned best_res = 0;
+
+    for (i = 0; i < s->nb_streams; i++) {
+        unsigned res = s->streams[i]->codec->width *
+                       s->streams[i]->codec->height;
+        if (res > best_res)
+            best_res = res, vst = i;
+    }
+    return vst;
+}
+
 static int mov_write_header(AVFormatContext *s)
 {
     AVIOContext *pb = s->pb;
@@ -3149,6 +3261,22 @@ static int mov_write_header(AVFormatContext *s)
         }
     }
 
+    /* Add a stream for the timecode track if -timecode option is specified */
+    if (mov->tc_opt_str) {
+        if (mov->mode != MODE_MOV) {
+            av_log(s, AV_LOG_ERROR, "Timecode is only supported with MOV.\n");
+            goto error;
+        }
+        mov->tmcd_track = mov->nb_streams++;
+        /* TODO: add an option to select the video stream to associate timecode with */
+        mov->tmcd_video_track = select_best_video_stream(s);
+        if (mov->tmcd_video_track < 0) {
+            av_log(s, AV_LOG_ERROR,
+                   "No video stream found to associate the timecode with.\n");
+            goto error;
+        }
+    }
+
     mov->tracks = av_mallocz(mov->nb_streams*sizeof(*mov->tracks));
     if (!mov->tracks)
         return AVERROR(ENOMEM);
@@ -3276,6 +3404,10 @@ static int mov_write_header(AVFormatContext *s)
         }
     }
 
+    if (mov->tmcd_track &&
+        mov_create_timecode_track(s, mov->tmcd_track, mov->tmcd_video_track) < 0)
+        goto error;
+
     avio_flush(pb);
 
     if (mov->flags & FF_MOV_FLAG_ISML)
@@ -3359,6 +3491,9 @@ static int mov_write_trailer(AVFormatContext *s)
 
     }
 
+    if (mov->tmcd_track)
+        av_freep(&mov->tracks[mov->tmcd_track].enc);
+
     avio_flush(pb);
 
     av_freep(&mov->tracks);
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index 4cf40f2..34e649e 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -87,6 +87,10 @@ typedef struct MOVIndex {
 #define MOV_TRACK_CTTS         0x0001
 #define MOV_TRACK_STPS         0x0002
     uint32_t    flags;
+#define MOV_TIMECODE_FLAG_DROPFRAME     0x0001
+#define MOV_TIMECODE_FLAG_24HOURSMAX    0x0002
+#define MOV_TIMECODE_FLAG_ALLOWNEGATIVE 0x0004
+    uint32_t    timecode_flags;
     int         language;
     int         track_id;
     int         tag; ///< stsd fourcc
@@ -157,6 +161,10 @@ typedef struct MOVMuxContext {
     int max_fragment_size;
     int ism_lookahead;
     AVIOContext *mdat_buf;
+
+    char *tc_opt_str;       ///< timecode option string
+    int tmcd_track;         ///< qt timecode track number
+    int tmcd_video_track;   ///< video stream id to associate the timecode with
 } MOVMuxContext;
 
 #define FF_MOV_FLAG_RTP_HINT 1
diff --git a/tests/ref/lavf/mov b/tests/ref/lavf/mov
index 610a759..b640738 100644
--- a/tests/ref/lavf/mov
+++ b/tests/ref/lavf/mov
@@ -1,8 +1,8 @@
-9a0b239ff596da58debcf210dece3985 *./tests/data/lavf/lavf.mov
-357821 ./tests/data/lavf/lavf.mov
+67b44249786dc7aa2c54f9e40c215ffd *./tests/data/lavf/lavf.mov
+358391 ./tests/data/lavf/lavf.mov
 ./tests/data/lavf/lavf.mov CRC=0x2f6a9b26
-cea874222a6d40b1761d75ea11ebe681 *./tests/data/lavf/lavf.mov
-367251 ./tests/data/lavf/lavf.mov
+809cdd372f29dc8383a1183febf13ddb *./tests/data/lavf/lavf.mov
+367821 ./tests/data/lavf/lavf.mov
 ./tests/data/lavf/lavf.mov CRC=0xab307eb9
 9a0b239ff596da58debcf210dece3985 *./tests/data/lavf/lavf.mov
 357821 ./tests/data/lavf/lavf.mov
-- 
1.7.9.1

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20120320/8fcb3f9f/attachment.asc>


More information about the ffmpeg-devel mailing list