diff options
author | Philip Langdale <philipl@overt.org> | 2012-06-25 02:05:33 +0400 |
---|---|---|
committer | Philip Langdale <philipl@overt.org> | 2012-08-04 23:01:24 +0400 |
commit | 18175ca9b2d9dc3b66e63b3cc461dfde788943a0 (patch) | |
tree | 544a825f68e5fa60e27a967d2a95999dd8f79e9d | |
parent | 2daaf77698c9adc9b390e94b755d743c135a469f (diff) |
movenc: Handle need for extra packets to express subtitle duration.
As packet duration is not stored inherently in MPEG4 containers,
subtitles have their duration expressed by storing an additional
empty packet with a pts matching the desired end time of the real
subtitle. Additionally, it is generally expected that all streams
start at time = 0, so an empty packet needs to be inserted at the
beginning of the stream, before the first real subtitle.
Unfortunately, ffmpeg lacks a proper way to express that a subtitle
might map to multiple packets, so the muxer is the only place we
can handle this.
Signed-off-by: Philip Langdale <philipl@overt.org>
-rw-r--r-- | libavformat/movenc.c | 89 | ||||
-rw-r--r-- | libavformat/movenc.h | 1 |
2 files changed, 84 insertions, 6 deletions
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; |