diff --git a/libavformat/movenc.c b/libavformat/movenc.c index e086b3a3a4..1d24383241 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -3086,6 +3086,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) 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"); @@ -3122,12 +3123,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) 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; @@ -3151,6 +3148,72 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) } return ff_mov_write_packet(s, pkt); +} + +static int mov_write_subtitle_end_packet(AVFormatContext *s, + int stream_index, + int64_t dts) { + AVPacket end; + int ret; + + av_init_packet(&end); + end.size = sizeof (short); + end.data = av_mallocz(end.size); + 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); } } @@ -3521,10 +3584,24 @@ static int mov_write_trailer(AVFormatContext *s) { 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 */ diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 557bcda636..72427d8cfd 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -80,6 +80,7 @@ typedef struct MOVIndex { unsigned timescale; uint64_t time; int64_t track_duration; + int last_sample_is_subtitle_end; long sample_count; long sample_size; long chunkCount;