movenc: Add an option for skipping writing the mfra/tfra/mfro trailer

Message ID 1467113620-50547-1-git-send-email-martin@martin.st
State Committed
Commit 4f7723cb3b913c577842a5bb088c804ddacac8df
Headers show

Commit Message

Martin Storsjö June 28, 2016, 11:33 a.m.
When writing a fragmented file, we by default write an index pointing
to all the fragments at the end of the file. This causes constantly
increasing memory usage during the muxing. For live streams, the
index might not be useful at all.

A similar fragment index is written (but at the start of the file) if
the global_sidx flag is set. If ism_lookahead is set, we need to keep
data about the last ism_lookahead+1 fragments.

If no fragment index is to be written, we don't need to store information
about all fragments, avoiding increasing the memory consumption
linearly with the muxing runtime.

This fixes out of memory situations with long live mp4 streams.
---
 libavformat/movenc.c | 29 ++++++++++++++++++++++++++---
 libavformat/movenc.h |  1 +
 2 files changed, 27 insertions(+), 3 deletions(-)

Comments

Vittorio Giovara June 29, 2016, 3:36 p.m. | #1
On Tue, Jun 28, 2016 at 7:33 AM, Martin Storsjö <martin@martin.st> wrote:
> When writing a fragmented file, we by default write an index pointing
> to all the fragments at the end of the file. This causes constantly
> increasing memory usage during the muxing. For live streams, the
> index might not be useful at all.
>
> A similar fragment index is written (but at the start of the file) if
> the global_sidx flag is set. If ism_lookahead is set, we need to keep
> data about the last ism_lookahead+1 fragments.
>
> If no fragment index is to be written, we don't need to store information
> about all fragments, avoiding increasing the memory consumption
> linearly with the muxing runtime.
>
> This fixes out of memory situations with long live mp4 streams.
> ---
>  libavformat/movenc.c | 29 ++++++++++++++++++++++++++---
>  libavformat/movenc.h |  1 +
>  2 files changed, 27 insertions(+), 3 deletions(-)
>
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 393442c..31b940a 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -60,6 +60,7 @@ static const AVOption options[] = {
>      { "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" },
>      { "delay_moov", "Delay writing the initial moov until the first fragment is cut, or until the first fragment flush", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DELAY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
>      { "global_sidx", "Write a global sidx index at the start of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_GLOBAL_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
> +    { "skip_trailer", "Skip writing the mfra/tfra/mfro trailer for fragmented files", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SKIP_TRAILER}, 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},
> @@ -2732,6 +2733,20 @@ static int mov_add_tfra_entries(AVIOContext *pb, MOVMuxContext *mov, int tracks,
>      return 0;
>  }
>
> +static void mov_prune_frag_info(MOVMuxContext *mov, int tracks, int max)
> +{
> +    int i;
> +    for (i = 0; i < mov->nb_streams; i++) {
> +        MOVTrack *track = &mov->tracks[i];
> +        if ((tracks >= 0 && i != tracks) || !track->entry)
> +            continue;
> +        if (track->nb_frag_info > max) {
> +            memmove(track->frag_info, track->frag_info + (track->nb_frag_info - max), max * sizeof(*track->frag_info));
> +            track->nb_frag_info = max;
> +        }
> +    }
> +}
> +
>  static int mov_write_tfdt_tag(AVIOContext *pb, MOVTrack *track)
>  {
>      int64_t pos = avio_tell(pb);
> @@ -2917,8 +2932,16 @@ static int mov_write_moof_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks,
>      if (mov->flags & FF_MOV_FLAG_DASH && !(mov->flags & FF_MOV_FLAG_GLOBAL_SIDX))
>          mov_write_sidx_tags(pb, mov, tracks, moof_size + 8 + mdat_size);
>
> -    if ((ret = mov_add_tfra_entries(pb, mov, tracks, moof_size + 8 + mdat_size)) < 0)
> -        return ret;
> +    if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX ||
> +        !(mov->flags & FF_MOV_FLAG_SKIP_TRAILER) ||
> +        mov->ism_lookahead) {
> +        if ((ret = mov_add_tfra_entries(pb, mov, tracks, moof_size + 8 + mdat_size)) < 0)
> +            return ret;
> +        if (!(mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) &&
> +            mov->flags & FF_MOV_FLAG_SKIP_TRAILER) {
> +            mov_prune_frag_info(mov, tracks, mov->ism_lookahead + 1);
> +        }
> +    }
>
>      return mov_write_moof_tag_internal(pb, mov, tracks, moof_size);
>  }
> @@ -4389,7 +4412,7 @@ static int mov_write_trailer(AVFormatContext *s)
>                  avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
>                  mov_write_mfra_tag(pb, mov);
>              }
> -        } else {
> +        } else if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) {
>              avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
>              mov_write_mfra_tag(pb, mov);
>          }
> diff --git a/libavformat/movenc.h b/libavformat/movenc.h
> index 6c922fb..f4ed188 100644
> --- a/libavformat/movenc.h
> +++ b/libavformat/movenc.h
> @@ -194,6 +194,7 @@ typedef struct MOVMuxContext {
>  #define FF_MOV_FLAG_FRAG_DISCONT          (1 << 12)
>  #define FF_MOV_FLAG_DELAY_MOOV            (1 << 13)
>  #define FF_MOV_FLAG_GLOBAL_SIDX           (1 << 14)
> +#define FF_MOV_FLAG_SKIP_TRAILER          (1 << 15)
>
>  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt);

I think this is ok (and useful!)

Patch

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 393442c..31b940a 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -60,6 +60,7 @@  static const AVOption options[] = {
     { "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" },
     { "delay_moov", "Delay writing the initial moov until the first fragment is cut, or until the first fragment flush", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DELAY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "global_sidx", "Write a global sidx index at the start of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_GLOBAL_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+    { "skip_trailer", "Skip writing the mfra/tfra/mfro trailer for fragmented files", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SKIP_TRAILER}, 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},
@@ -2732,6 +2733,20 @@  static int mov_add_tfra_entries(AVIOContext *pb, MOVMuxContext *mov, int tracks,
     return 0;
 }
 
+static void mov_prune_frag_info(MOVMuxContext *mov, int tracks, int max)
+{
+    int i;
+    for (i = 0; i < mov->nb_streams; i++) {
+        MOVTrack *track = &mov->tracks[i];
+        if ((tracks >= 0 && i != tracks) || !track->entry)
+            continue;
+        if (track->nb_frag_info > max) {
+            memmove(track->frag_info, track->frag_info + (track->nb_frag_info - max), max * sizeof(*track->frag_info));
+            track->nb_frag_info = max;
+        }
+    }
+}
+
 static int mov_write_tfdt_tag(AVIOContext *pb, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
@@ -2917,8 +2932,16 @@  static int mov_write_moof_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks,
     if (mov->flags & FF_MOV_FLAG_DASH && !(mov->flags & FF_MOV_FLAG_GLOBAL_SIDX))
         mov_write_sidx_tags(pb, mov, tracks, moof_size + 8 + mdat_size);
 
-    if ((ret = mov_add_tfra_entries(pb, mov, tracks, moof_size + 8 + mdat_size)) < 0)
-        return ret;
+    if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX ||
+        !(mov->flags & FF_MOV_FLAG_SKIP_TRAILER) ||
+        mov->ism_lookahead) {
+        if ((ret = mov_add_tfra_entries(pb, mov, tracks, moof_size + 8 + mdat_size)) < 0)
+            return ret;
+        if (!(mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) &&
+            mov->flags & FF_MOV_FLAG_SKIP_TRAILER) {
+            mov_prune_frag_info(mov, tracks, mov->ism_lookahead + 1);
+        }
+    }
 
     return mov_write_moof_tag_internal(pb, mov, tracks, moof_size);
 }
@@ -4389,7 +4412,7 @@  static int mov_write_trailer(AVFormatContext *s)
                 avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
                 mov_write_mfra_tag(pb, mov);
             }
-        } else {
+        } else if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) {
             avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
             mov_write_mfra_tag(pb, mov);
         }
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index 6c922fb..f4ed188 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -194,6 +194,7 @@  typedef struct MOVMuxContext {
 #define FF_MOV_FLAG_FRAG_DISCONT          (1 << 12)
 #define FF_MOV_FLAG_DELAY_MOOV            (1 << 13)
 #define FF_MOV_FLAG_GLOBAL_SIDX           (1 << 14)
+#define FF_MOV_FLAG_SKIP_TRAILER          (1 << 15)
 
 int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt);