Commit 9fb2e234 authored by Clément Bœsch's avatar Clément Bœsch Committed by Clément Bœsch

movenc: add timecode track support.

parent 9846a9c7
......@@ -893,7 +893,7 @@ performance on systems without hardware floating point support).
@item AVI @tab X @tab X
@item DV @tab X @tab X
@item GXF @tab X @tab X
@item MOV @tab X @tab
@item MOV @tab X @tab X
@item MPEG1/2 @tab X @tab X
@item MXF @tab X @tab X
@end multitable
......
......@@ -1095,6 +1095,26 @@ 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 frame_duration = track->enc->time_base.num;
int nb_frames = (track->timescale + frame_duration/2) / frame_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, frame_duration); /* Frame duration */
avio_w8(pb, nb_frames); /* Number of frames */
avio_wb24(pb, 0); /* Reserved */
/* 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);
......@@ -1130,6 +1150,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);
}
......@@ -1262,9 +1284,32 @@ 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)
{
avio_wb32(pb, 0x4C); /* size */
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, 0); /* (unknown, not in the QT specs...) */
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)
{
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 */
......@@ -1295,7 +1340,14 @@ static int mov_write_gmhd_tag(AVIOContext *pb)
avio_wb32(pb, 0x00004000);
avio_wb16(pb, 0x0000);
return 0x4C;
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)
......@@ -1338,6 +1390,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";
......@@ -1389,8 +1444,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);
}
......@@ -2147,6 +2204,14 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
mov->tracks[mov->tracks[i].src_track].track_id;
}
}
for (i = 0; i < mov->nb_streams; i++) {
if (mov->tracks[i].tag == MKTAG('t','m','c','d')) {
int src_trk = mov->tracks[i].src_track;
mov->tracks[src_trk].tref_tag = mov->tracks[i].tag;
mov->tracks[src_trk].tref_id = mov->tracks[i].track_id;
mov->tracks[i].track_duration = mov->tracks[src_trk].track_duration;
}
}
mov_write_mvhd_tag(pb, mov);
if (mov->mode != MODE_MOV && !mov->iods_skip)
......@@ -3151,12 +3216,48 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum)
}
}
static int mov_create_timecode_track(AVFormatContext *s, int index, int src_index, const char *tcstr)
{
MOVMuxContext *mov = s->priv_data;
MOVTrack *track = &mov->tracks[index];
AVStream *src_st = s->streams[src_index];
AVTimecode tc;
AVPacket pkt = {.stream_index = index, .flags = AV_PKT_FLAG_KEY, .size = 4};
AVRational rate = {src_st->codec->time_base.den, src_st->codec->time_base.num};
/* compute the frame number */
int ret = av_timecode_init_from_string(&tc, rate, tcstr, s);
if (ret < 0)
return ret;
/* tmcd track based on video stream */
track->mode = mov->mode;
track->tag = MKTAG('t','m','c','d');
track->src_track = src_index;
track->timescale = src_st->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;
track->enc->time_base = src_st->codec->time_base;
/* the tmcd track just contains one packet with the frame number */
pkt.data = av_malloc(pkt.size);
AV_WB32(pkt.data, tc.start);
ret = ff_mov_write_packet(s, &pkt);
av_free(pkt.data);
return ret;
}
static int mov_write_header(AVFormatContext *s)
{
AVIOContext *pb = s->pb;
MOVMuxContext *mov = s->priv_data;
AVDictionaryEntry *t;
int i, hint_track = 0;
AVDictionaryEntry *t, *global_tcr = av_dict_get(s->metadata, "timecode", NULL, 0);
int i, hint_track = 0, tmcd_track = 0;
/* Set the FRAGMENT flag if any of the fragmentation methods are
* enabled. */
......@@ -3213,6 +3314,17 @@ static int mov_write_header(AVFormatContext *s)
}
}
if (mov->mode == MODE_MOV) {
/* Add a tmcd track for each video stream with a timecode */
tmcd_track = mov->nb_streams;
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
(global_tcr || av_dict_get(st->metadata, "timecode", NULL, 0)))
mov->nb_streams++;
}
}
mov->tracks = av_mallocz(mov->nb_streams*sizeof(*mov->tracks));
if (!mov->tracks)
return AVERROR(ENOMEM);
......@@ -3336,6 +3448,24 @@ static int mov_write_header(AVFormatContext *s)
}
}
if (mov->mode == MODE_MOV) {
/* Initialize the tmcd tracks */
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
t = global_tcr;
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
if (!t)
t = av_dict_get(st->metadata, "timecode", NULL, 0);
if (!t)
continue;
if (mov_create_timecode_track(s, tmcd_track, i, t->value) < 0)
goto error;
tmcd_track++;
}
}
}
avio_flush(pb);
if (mov->flags & FF_MOV_FLAG_ISML)
......@@ -3401,6 +3531,8 @@ static int mov_write_trailer(AVFormatContext *s)
for (i=0; i<mov->nb_streams; i++) {
if (mov->tracks[i].tag == MKTAG('r','t','p',' '))
ff_mov_close_hinting(&mov->tracks[i]);
else if (mov->tracks[i].tag == MKTAG('t','m','c','d'))
av_freep(&mov->tracks[i].enc);
if (mov->flags & FF_MOV_FLAG_FRAGMENT &&
mov->tracks[i].vc1_info.struct_offset && s->pb->seekable) {
int64_t off = avio_tell(pb);
......
......@@ -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
......@@ -102,7 +106,7 @@ typedef struct MOVIndex {
int64_t start_dts;
int hint_track; ///< the track that hints this track, -1 if no hint track is set
int src_track; ///< the track that this hint track describes
int src_track; ///< the track that this hint (or tmcd) track describes
AVFormatContext *rtp_ctx; ///< the format context for the hinting rtp muxer
uint32_t prev_rtp_ts;
int64_t cur_rtp_ts_unwrapped;
......
484aeef3be3eb4deef05c83bdc2dd484 *./tests/data/lavf/lavf.mov
367346 ./tests/data/lavf/lavf.mov
./tests/data/lavf/lavf.mov CRC=0x2f6a9b26
305a68397e3cdb505704841fedcdc352 *./tests/data/lavf/lavf.mov
357845 ./tests/data/lavf/lavf.mov
21b992f6a677f971dfd685cc055a2b0a *./tests/data/lavf/lavf.mov
358463 ./tests/data/lavf/lavf.mov
./tests/data/lavf/lavf.mov CRC=0x2f6a9b26
6e047bce400f2c4a840f783dee1ae030 *./tests/data/lavf/lavf.mov
367275 ./tests/data/lavf/lavf.mov
f1e80a52983775ea27dda0590b46e17a *./tests/data/lavf/lavf.mov
367893 ./tests/data/lavf/lavf.mov
./tests/data/lavf/lavf.mov CRC=0xab307eb9
305a68397e3cdb505704841fedcdc352 *./tests/data/lavf/lavf.mov
357845 ./tests/data/lavf/lavf.mov
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment