if (trk->start_dts == AV_NOPTS_VALUE)
trk->start_dts = pkt->dts;
trk->track_duration = pkt->dts - trk->start_dts + pkt->duration;
+ trk->last_sample_is_subtitle_end = 0;
if (pkt->pts == AV_NOPTS_VALUE) {
av_log(s, AV_LOG_WARNING, "pts has no value\n");
return 0;
}
-static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
+static int mov_write_single_packet(AVFormatContext *s, AVPacket *pkt)
{
- if (!pkt) {
- mov_flush_fragment(s);
- return 1;
- } else {
MOVMuxContext *mov = s->priv_data;
MOVTrack *trk = &mov->tracks[pkt->stream_index];
AVCodecContext *enc = trk->enc;
}
return ff_mov_write_packet(s, pkt);
+}
+
+static int mov_write_subtitle_end_packet(AVFormatContext *s,
+ int stream_index,
+ int64_t dts) {
+ AVPacket end;
+ short data = 0;
+ int ret;
+
+ av_init_packet(&end);
+ end.size = sizeof (short);
+ end.data = (char *)&data;
+ end.pts = dts;
+ end.dts = dts;
+ end.duration = 0;
+ end.stream_index = stream_index;
+
+ ret = mov_write_single_packet(s, &end);
+ av_free_packet(&end);
+
+ return ret;
+}
+
+static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ if (!pkt) {
+ mov_flush_fragment(s);
+ return 1;
+ } else {
+ int i;
+ MOVMuxContext *mov = s->priv_data;
+
+ if (!pkt->size) return 0; /* Discard 0 sized packets */
+
+ /*
+ * Subtitles require special handling.
+ *
+ * 1) For full complaince, every track must have a sample at
+ * dts == 0, which is rarely true for subtitles. So, as soon
+ * as we see any packet with dts > 0, write an empty subtitle
+ * at dts == 0 for any subtitle track with no samples in it.
+ *
+ * 2) For each subtitle track, check if the current packet's
+ * dts is past the duration of the last subtitle sample. If
+ * so, we now need to write an end sample for that subtitle.
+ *
+ * This must be done conditionally to allow for subtitles that
+ * immediately replace each other, in which case an end sample
+ * is not needed, and is, in fact, actively harmful.
+ *
+ * 3) See mov_write_trailer for how the final end sample is
+ * handled.
+ */
+ for (i = 0; i < mov->nb_streams; i++) {
+ MOVTrack *trk = &mov->tracks[i];
+ int ret;
+
+ if (trk->enc->codec_id == CODEC_ID_MOV_TEXT &&
+ trk->track_duration < pkt->dts &&
+ (trk->entry == 0 || !trk->last_sample_is_subtitle_end)) {
+ ret = mov_write_subtitle_end_packet(s, i, trk->track_duration);
+ if (ret < 0) return ret;
+ trk->last_sample_is_subtitle_end = 1;
+ }
+ }
+
+ return mov_write_single_packet(s, pkt);
}
}
}
if (mov->mode == MODE_MOV) {
- /* Add a tmcd track for each video stream with a timecode */
tmcd_track = mov->nb_streams;
+
+ /* +1 tmcd track for each video stream with a timecode */
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->nb_meta_tmcd++;
}
+
+ /* check if there is already a tmcd track to remux */
+ if (mov->nb_meta_tmcd) {
+ for (i = 0; i < s->nb_streams; i++) {
+ AVStream *st = s->streams[i];
+ if (st->codec->codec_tag == MKTAG('t','m','c','d')) {
+ av_log(s, AV_LOG_WARNING, "You requested a copy of the original timecode track "
+ "so timecode metadata are now ignored\n");
+ mov->nb_meta_tmcd = 0;
+ }
+ }
+ }
+
+ mov->nb_streams += mov->nb_meta_tmcd;
}
mov->tracks = av_mallocz(mov->nb_streams*sizeof(*mov->tracks));
}
}
- if (mov->mode == MODE_MOV) {
+ if (mov->nb_meta_tmcd) {
/* Initialize the tmcd tracks */
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
{
MOVMuxContext *mov = s->priv_data;
AVIOContext *pb = s->pb;
+ int64_t moov_pos;
int res = 0;
int i;
- int64_t moov_pos = avio_tell(pb);
+ /*
+ * Before actually writing the trailer, make sure that there are no
+ * dangling subtitles, that need a terminating sample.
+ */
+ for (i = 0; i < mov->nb_streams; i++) {
+ MOVTrack *trk = &mov->tracks[i];
+ if (trk->enc->codec_id == CODEC_ID_MOV_TEXT &&
+ !trk->last_sample_is_subtitle_end) {
+ mov_write_subtitle_end_packet(s, i, trk->track_duration);
+ trk->last_sample_is_subtitle_end = 1;
+ }
+ }
+
+ moov_pos = avio_tell(pb);
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
/* Write size of mdat tag */
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'))
+ else if (mov->tracks[i].tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd)
av_freep(&mov->tracks[i].enc);
if (mov->flags & FF_MOV_FLAG_FRAGMENT &&
mov->tracks[i].vc1_info.struct_offset && s->pb->seekable) {
MOV_CLASS(mov)
AVOutputFormat ff_mov_muxer = {
.name = "mov",
- .long_name = NULL_IF_CONFIG_SMALL("MOV format"),
+ .long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
.extensions = "mov",
.priv_data_size = sizeof(MOVMuxContext),
.audio_codec = CODEC_ID_AAC,
MOV_CLASS(tgp)
AVOutputFormat ff_tgp_muxer = {
.name = "3gp",
- .long_name = NULL_IF_CONFIG_SMALL("3GP format"),
+ .long_name = NULL_IF_CONFIG_SMALL("3GP (3GPP file format)"),
.extensions = "3gp",
.priv_data_size = sizeof(MOVMuxContext),
.audio_codec = CODEC_ID_AMR_NB,
MOV_CLASS(mp4)
AVOutputFormat ff_mp4_muxer = {
.name = "mp4",
- .long_name = NULL_IF_CONFIG_SMALL("MP4 format"),
+ .long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),
.mime_type = "application/mp4",
.extensions = "mp4",
.priv_data_size = sizeof(MOVMuxContext),
MOV_CLASS(psp)
AVOutputFormat ff_psp_muxer = {
.name = "psp",
- .long_name = NULL_IF_CONFIG_SMALL("PSP MP4 format"),
+ .long_name = NULL_IF_CONFIG_SMALL("PSP MP4 (MPEG-4 Part 14)"),
.extensions = "mp4,psp",
.priv_data_size = sizeof(MOVMuxContext),
.audio_codec = CODEC_ID_AAC,
MOV_CLASS(tg2)
AVOutputFormat ff_tg2_muxer = {
.name = "3g2",
- .long_name = NULL_IF_CONFIG_SMALL("3GP2 format"),
+ .long_name = NULL_IF_CONFIG_SMALL("3GP2 (3GPP2 file format)"),
.extensions = "3g2",
.priv_data_size = sizeof(MOVMuxContext),
.audio_codec = CODEC_ID_AMR_NB,
MOV_CLASS(ipod)
AVOutputFormat ff_ipod_muxer = {
.name = "ipod",
- .long_name = NULL_IF_CONFIG_SMALL("iPod H.264 MP4 format"),
+ .long_name = NULL_IF_CONFIG_SMALL("iPod H.264 MP4 (MPEG-4 Part 14)"),
.mime_type = "application/mp4",
.extensions = "m4v,m4a",
.priv_data_size = sizeof(MOVMuxContext),
MOV_CLASS(ismv)
AVOutputFormat ff_ismv_muxer = {
.name = "ismv",
- .long_name = NULL_IF_CONFIG_SMALL("ISMV/ISMA (Smooth Streaming) format"),
+ .long_name = NULL_IF_CONFIG_SMALL("ISMV/ISMA (Smooth Streaming)"),
.mime_type = "application/mp4",
.extensions = "ismv,isma",
.priv_data_size = sizeof(MOVMuxContext),