[FFmpeg-devel] [PATCH v6] avformat/mov: Memory optimization with QuickTime/MP4

Jörg Beckmann Joerg.Beckmann at scisys.com
Wed Jan 8 15:26:35 EET 2020


Invents a new option "discard_fragments" for the MP4/Quicktime/MOV decoder.

If this option is set to "on", old fragments are discarded as far as possible
on each call to switch_root(). If set to "off", nothing changes at all. If set
to "auto" (the default), this function is turned on for streams containing
only audio.

For pure audio streams, the memory usage is now constant. For video streams,
the memory usage is reduced. It's tested with audio streams received from a
professional DAB+ receiver and with video streams created on my own with
"ffmpeg -i <video>.m4v -c:a:0 copy -c:v copy -c:s copy -f ismv -movflags \
frag_keyframe -movflags faststart tcp://localhost:1234?listen" and
"ffmpeg -i tcp://localhost:1234 -c:a copy -c:v copy -c:s copy -y <output>".

For pure audio streams, this function is intensively tested and in production
use for several weeks. For video streams, it is tested with some examples, but is
more or less experimental. Therefore "auto" turns it on for pure audio streams
only.

Signed-off-by: Jörg Beckmann mailto:joerg.beckmann at scisys.com
---
 libavformat/isom.h |  1 +
 libavformat/mov.c  | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 59 insertions(+), 2 deletions(-)

diff --git a/libavformat/isom.h b/libavformat/isom.h
index 4943b80ccf..9b4753f4d7 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -268,6 +268,7 @@ typedef struct MOVContext {
     int advanced_editlist;
     int ignore_chapters;
     int seek_individually;
+    int discard_fragments;
     int64_t next_root_atom; ///< offset of the next root atom
     int export_all;
     int export_xmp;
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 890c6e85b8..a3289dda20 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -66,6 +66,10 @@
 
 #include "qtpalette.h"
 
+#define MOV_DISCARD_AUTO (-1)
+#define MOV_DISCARD_OFF   0
+#define MOV_DISCARD_ON    1
+
 /* those functions parse an atom */
 /* links atom IDs to parse functions */
 typedef struct MOVParseTableEntry {
@@ -7454,6 +7458,7 @@ static int mov_read_header(AVFormatContext *s)
     int j, err;
     MOVAtom atom = { AV_RL32("root") };
     int i;
+    int audio_only = 1;
 
     if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) {
         av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n",
@@ -7522,6 +7527,9 @@ static int mov_read_header(AVFormatContext *s)
         AVStream *st = s->streams[i];
         MOVStreamContext *sc = st->priv_data;
         fix_timescale(mov, sc);
+        if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
+            audio_only = 0;
+        }
         if(st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && st->codecpar->codec_id == AV_CODEC_ID_AAC) {
             st->skip_samples = sc->start_pad;
         }
@@ -7547,6 +7555,10 @@ static int mov_read_header(AVFormatContext *s)
         }
     }
 
+    if (mov->discard_fragments == MOV_DISCARD_AUTO) {
+        mov->discard_fragments = audio_only;
+    }
+
     if (mov->trex_data) {
         for (i = 0; i < s->nb_streams; i++) {
             AVStream *st = s->streams[i];
@@ -7692,8 +7704,11 @@ static int should_retry(AVIOContext *pb, int error_code) {
 
 static int mov_switch_root(AVFormatContext *s, int64_t target, int index)
 {
-    int ret;
+    int ret, i;
     MOVContext *mov = s->priv_data;
+    AVStream *st = NULL;
+    MOVStreamContext *sc;
+    MOVFragment *frag;
 
     if (index >= 0 && index < mov->frag_index.nb_items)
         target = mov->frag_index.item[index].moof_offset;
@@ -7715,6 +7730,43 @@ static int mov_switch_root(AVFormatContext *s, int64_t target, int index)
 
     mov->found_mdat = 0;
 
+    if (mov->discard_fragments) {
+        frag = &mov->fragment;
+
+        for (i = 0; i < mov->fc->nb_streams; i++) {
+            if (mov->fc->streams[i]->id == frag->track_id) {
+                st = mov->fc->streams[i];
+                break;
+            }
+        }
+
+        av_assert0(st);
+
+        sc = st->priv_data;
+
+        switch (st->codecpar->codec_type) {
+            case AVMEDIA_TYPE_AUDIO:
+            case AVMEDIA_TYPE_SUBTITLE:
+                /* Freeing VIDEO tables leads to corrupted video when writing to eg. MKV */
+                av_freep(&st->index_entries);
+                st->nb_index_entries = 0;
+                st->index_entries_allocated_size = 0;
+
+                sc->current_index = 0;
+                sc->current_sample = 0;
+
+                av_freep(&sc->ctts_data);
+                sc->ctts_allocated_size = 0;
+                sc->ctts_count = 0;
+                break;
+        }
+
+        av_free(mov->frag_index.item->stream_info);
+        av_freep(&mov->frag_index.item);
+        mov->frag_index.allocated_size = 0;
+        mov->frag_index.nb_items = 0;
+    }
+
     ret = mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX });
     if (ret < 0)
         return ret;
@@ -8057,7 +8109,11 @@ static const AVOption mov_options[] = {
     { "decryption_key", "The media decryption key (hex)", OFFSET(decryption_key), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM },
     { "enable_drefs", "Enable external track support.", OFFSET(enable_drefs), AV_OPT_TYPE_BOOL,
         {.i64 = 0}, 0, 1, FLAGS },
-
+    { "discard_fragments", "Discard fragments after they have been read to support live streams.", OFFSET(discard_fragments),
+        AV_OPT_TYPE_INT, { .i64 = MOV_DISCARD_AUTO }, MOV_DISCARD_AUTO, MOV_DISCARD_ON, FLAGS, "discard_fragments"},
+        { "auto", "Switch on for audio only streams", 0, AV_OPT_TYPE_CONST, {.i64 = MOV_DISCARD_AUTO}, INT_MIN, INT_MAX, FLAGS, "discard_fragments" },
+        { "off",  "Switch off",                       0, AV_OPT_TYPE_CONST, {.i64 = MOV_DISCARD_OFF},  INT_MIN, INT_MAX, FLAGS, "discard_fragments" },
+        { "on",   "Switch on",                        0, AV_OPT_TYPE_CONST, {.i64 = MOV_DISCARD_ON},   INT_MIN, INT_MAX, FLAGS, "discard_fragments" },
     { NULL },
 };



More information about the ffmpeg-devel mailing list