diff --git a/doc/general.texi b/doc/general.texi index 5af7822ff3..74092e4ed9 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -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 diff --git a/libavformat/movenc.c b/libavformat/movenc.c index fce6bfc7c8..d5eb25bcc2 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -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; inb_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); diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 574de824ef..830bf377ea 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 @@ -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; diff --git a/tests/ref/lavf/mov b/tests/ref/lavf/mov index b9b020cd80..b23b75dbb6 100644 --- a/tests/ref/lavf/mov +++ b/tests/ref/lavf/mov @@ -1,11 +1,11 @@ 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