diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1c32b6fd19..9f10aed193 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -77,12 +77,15 @@ typedef struct MOVIndex { MOVIentry *cluster; int audio_vbr; int height; ///< active picture (w/o VBI) height for D-10/IMX + uint32_t tref_tag; + int tref_id; ///< trackID of the referenced track } MOVTrack; typedef struct MOVMuxContext { int mode; int64_t time; int nb_streams; + int chapter_track; ///< qt chapter track number int64_t mdat_pos; uint64_t mdat_size; MOVTrack *tracks; @@ -1183,8 +1186,8 @@ static int mov_write_tkhd_tag(ByteIOContext *pb, MOVTrack *track, AVStream *st) put_be32(pb, 0x40000000); /* reserved */ /* Track width and height, for visual only */ - if(track->enc->codec_type == AVMEDIA_TYPE_VIDEO || - track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) { + if(st && (track->enc->codec_type == AVMEDIA_TYPE_VIDEO || + track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE)) { double sample_aspect_ratio = av_q2d(st->sample_aspect_ratio); if(!sample_aspect_ratio || track->height != track->enc->height) sample_aspect_ratio = 1; @@ -1217,6 +1220,16 @@ static int mov_write_edts_tag(ByteIOContext *pb, MOVTrack *track) return 0x24; } +static int mov_write_tref_tag(ByteIOContext *pb, MOVTrack *track) +{ + put_be32(pb, 20); // size + put_tag(pb, "tref"); + put_be32(pb, 12); // size (subatom) + put_le32(pb, track->tref_tag); + put_be32(pb, track->tref_id); + return 20; +} + // goes at the end of each track! ... Critical for PSP playback ("Incompatible data" without it) static int mov_write_uuid_tag_psp(ByteIOContext *pb, MOVTrack *mov) { @@ -1244,6 +1257,8 @@ static int mov_write_trak_tag(ByteIOContext *pb, MOVTrack *track, AVStream *st) mov_write_tkhd_tag(pb, track, st); if (track->mode == MODE_PSP || track->flags & MOV_TRACK_CTTS) mov_write_edts_tag(pb, track); // PSP Movies require edts box + if (track->tref_tag) + mov_write_tref_tag(pb, track); mov_write_mdia_tag(pb, track); if (track->mode == MODE_PSP) mov_write_uuid_tag_psp(pb,track); // PSP Movies require this uuid box @@ -1656,11 +1671,17 @@ static int mov_write_moov_tag(ByteIOContext *pb, MOVMuxContext *mov, mov->tracks[i].trackID = i+1; } + if (mov->chapter_track) + for (i=0; inb_streams; i++) { + mov->tracks[i].tref_tag = MKTAG('c','h','a','p'); + mov->tracks[i].tref_id = mov->tracks[mov->chapter_track].trackID; + } + mov_write_mvhd_tag(pb, mov); //mov_write_iods_tag(pb, mov); for (i=0; inb_streams; i++) { if(mov->tracks[i].entry > 0) { - mov_write_trak_tag(pb, &(mov->tracks[i]), s->streams[i]); + mov_write_trak_tag(pb, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL); } } @@ -1918,6 +1939,41 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) return 0; } +// QuickTime chapters involve an additional text track with the chapter names +// as samples, and a tref pointing from the other tracks to the chapter one. +static void mov_create_chapter_track(AVFormatContext *s, int tracknum) +{ + MOVMuxContext *mov = s->priv_data; + MOVTrack *track = &mov->tracks[tracknum]; + AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY }; + int i, len; + + track->mode = mov->mode; + track->tag = MKTAG('t','e','x','t'); + track->timescale = MOV_TIMESCALE; + track->enc = avcodec_alloc_context(); + track->enc->codec_type = AVMEDIA_TYPE_SUBTITLE; + + for (i = 0; i < s->nb_chapters; i++) { + AVChapter *c = s->chapters[i]; + AVMetadataTag *t; + + int64_t end = av_rescale_q(c->end, c->time_base, (AVRational){1,MOV_TIMESCALE}); + pkt.pts = pkt.dts = av_rescale_q(c->start, c->time_base, (AVRational){1,MOV_TIMESCALE}); + pkt.duration = end - pkt.dts; + + if ((t = av_metadata_get(c->metadata, "title", NULL, 0))) { + len = strlen(t->value); + pkt.size = len+2; + pkt.data = av_malloc(pkt.size); + AV_WB16(pkt.data, len); + memcpy(pkt.data+2, t->value, len); + mov_write_packet(s, &pkt); + av_freep(&pkt.data); + } + } +} + static int mov_write_header(AVFormatContext *s) { ByteIOContext *pb = s->pb; @@ -1949,7 +2005,11 @@ static int mov_write_header(AVFormatContext *s) } } - mov->tracks = av_mallocz(s->nb_streams*sizeof(*mov->tracks)); + mov->nb_streams = s->nb_streams; + if (mov->mode & (MODE_MOV|MODE_IPOD) && s->nb_chapters) + mov->chapter_track = mov->nb_streams++; + + mov->tracks = av_mallocz(mov->nb_streams*sizeof(*mov->tracks)); if (!mov->tracks) return AVERROR(ENOMEM); @@ -2019,7 +2079,9 @@ static int mov_write_header(AVFormatContext *s) mov_write_mdat_tag(pb, mov); mov->time = s->timestamp + 0x7C25B080; //1970 based -> 1904 based - mov->nb_streams = s->nb_streams; + + if (mov->chapter_track) + mov_create_chapter_track(s, mov->chapter_track); put_flush_packet(pb); @@ -2053,6 +2115,9 @@ static int mov_write_trailer(AVFormatContext *s) mov_write_moov_tag(pb, mov, s); + if (mov->chapter_track) + av_freep(&mov->tracks[mov->chapter_track].enc); + for (i=0; inb_streams; i++) { av_freep(&mov->tracks[i].cluster);