movenc: Add a flag for indicating a discontinuous fragment
authorMartin Storsjö <martin@martin.st>
Thu, 20 Nov 2014 07:51:05 +0000 (09:51 +0200)
committerMartin Storsjö <martin@martin.st>
Wed, 26 Nov 2014 07:56:56 +0000 (09:56 +0200)
This allows creating a later mp4 fragment without sequentially
writing the earlier ones before (when called from a segmenter).

Normally when writing a fragmented mp4 file sequentially, the
first timestamps of a fragment are adjusted to match the
end of the previous fragment, to make sure the timestamp is the
same, even if it is calculated as the sum of previous fragment
durations. (And for the first packet in a file, the offset of
the first packet is written using an edit list.)

When writing an individual mp4 fragment discontinuously like this
(with potentially writing the earlier fragments separately later),
there's a risk of getting a gap in the timeline if the duration
field of the last packet in the previous fragment doesn't match up
with the start time of the next fragment.

Using this requires setting -avoid_negative_ts make_non_negative
(or -avoid_negative_ts 0).

Signed-off-by: Martin Storsjö <martin@martin.st>
libavformat/movenc.c
libavformat/movenc.h

index 3ba1cc8..80531d0 100644 (file)
@@ -60,6 +60,7 @@ static const AVOption options[] = {
     { "disable_chpl", "Disable Nero chapter atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DISABLE_CHPL}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "default_base_moof", "Set the default-base-is-moof flag in tfhd atoms", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DEFAULT_BASE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "dash", "Write DASH compatible fragmented MP4", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DASH}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+    { "frag_discont", "Signal that the next fragment is discontinuous from earlier ones", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_DISCONT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags),
     { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
     { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM},
@@ -3282,11 +3283,19 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
     trk->cluster[trk->entry].entries          = samples_in_chunk;
     trk->cluster[trk->entry].dts              = pkt->dts;
     if (!trk->entry && trk->start_dts != AV_NOPTS_VALUE) {
-        /* First packet of a new fragment. We already wrote the duration
-         * of the last packet of the previous fragment based on track_duration,
-         * which might not exactly match our dts. Therefore adjust the dts
-         * of this packet to be what the previous packets duration implies. */
-        trk->cluster[trk->entry].dts = trk->start_dts + trk->track_duration;
+        if (!trk->frag_discont) {
+            /* First packet of a new fragment. We already wrote the duration
+             * of the last packet of the previous fragment based on track_duration,
+             * which might not exactly match our dts. Therefore adjust the dts
+             * of this packet to be what the previous packets duration implies. */
+            trk->cluster[trk->entry].dts = trk->start_dts + trk->track_duration;
+        } else {
+            /* New fragment, but discontinuous from previous fragments.
+             * Pretend the duration sum of the earlier fragments is
+             * pkt->dts - trk->start_dts. */
+            trk->frag_start = pkt->dts - trk->start_dts;
+            trk->frag_discont = 0;
+        }
     }
     if (!trk->entry && trk->start_dts == AV_NOPTS_VALUE && !mov->use_editlist &&
         s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) {
@@ -3299,7 +3308,13 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
     }
     if (trk->start_dts == AV_NOPTS_VALUE) {
         trk->start_dts = pkt->dts;
-        if (pkt->dts && mov->flags & FF_MOV_FLAG_EMPTY_MOOV)
+        if (trk->frag_discont) {
+            /* Pretend the whole stream started at dts=0, with earlier framgents
+             * already written, with a duration summing up to pkt->dts. */
+            trk->frag_start   = pkt->dts;
+            trk->start_dts    = 0;
+            trk->frag_discont = 0;
+        } else if (pkt->dts && mov->flags & FF_MOV_FLAG_EMPTY_MOOV)
             av_log(s, AV_LOG_WARNING,
                    "Track %d starts with a nonzero dts %"PRId64". This "
                    "currently isn't handled correctly in combination with "
@@ -3357,6 +3372,13 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
         if (!pkt->size)
             return 0;             /* Discard 0 sized packets */
 
+        if (mov->flags & FF_MOV_FLAG_FRAG_DISCONT) {
+            int i;
+            for (i = 0; i < s->nb_streams; i++)
+                mov->tracks[i].frag_discont = 1;
+            mov->flags &= ~FF_MOV_FLAG_FRAG_DISCONT;
+        }
+
         if (trk->entry)
             frag_duration = av_rescale_q(pkt->dts - trk->cluster[0].dts,
                                          s->streams[pkt->stream_index]->time_base,
index 4483b69..c13a834 100644 (file)
@@ -121,6 +121,7 @@ typedef struct MOVTrack {
     AVIOContext *mdat_buf;
     int64_t     data_offset;
     int64_t     frag_start;
+    int         frag_discont;
 
     int         nb_frag_info;
     MOVFragmentInfo *frag_info;
@@ -182,6 +183,7 @@ typedef struct MOVMuxContext {
 #define FF_MOV_FLAG_DISABLE_CHPL          (1 <<  9)
 #define FF_MOV_FLAG_DEFAULT_BASE_MOOF     (1 << 10)
 #define FF_MOV_FLAG_DASH                  (1 << 11)
+#define FF_MOV_FLAG_FRAG_DISCONT          (1 << 12)
 
 int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt);