diff mbox

[FFmpeg-devel] Patch for memory optimization with QuickTime/MP4

Message ID ade3661cdf374486b5e3049010c929a3@scisys.com
State Superseded
Headers show

Commit Message

Jörg Beckmann Dec. 2, 2019, 9:05 a.m. UTC
Hi,

this patch invents a new option "discard_fragments" for the MP4/Quicktime/MOV decoder. If the option is not set, nothing changes at all. If it is set, old fragments are discarded as far as possible on each call to switch_root. For pure audio streams, the memory usage is now constant. For video streams, the memory usage is reduced. I've tested it 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>". Does someone know sources for MP4 video streams? I could not find any with more than a few minutes. That's not sufficient to test memory usage.

Cheers,
Jörg
---
libavformat/isom.h |  1 +
libavformat/mov.c  | 52 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 52 insertions(+), 1 deletion(-)

+    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;
+            }
+        }
+
+        if (!st) {
+            av_log(mov->fc, AV_LOG_WARNING, "could not find corresponding track id %u\n", frag->track_id);
+        } else {
+            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_data = NULL;
+                    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;
@@ -7975,6 +8018,9 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti
     int sample;
     int i;
+    if (mc->discard_fragments)  // Seeking is not possible if fragments are discarded.
+        return AVERROR(ENOTSUP);
+
     if (stream_index >= s->nb_streams)
         return AVERROR_INVALIDDATA;
@@ -8063,6 +8109,10 @@ 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",
+            "Discards fragments after they have been read to support live streams.",
+            OFFSET(discard_fragments), AV_OPT_TYPE_BOOL, { .i64 = 0 },
+            0, 1, FLAGS },
     { NULL },
};

Comments

Carl Eugen Hoyos Dec. 2, 2019, 9:14 a.m. UTC | #1
Am Mo., 2. Dez. 2019 um 10:05 Uhr schrieb Jörg Beckmann
<Joerg.Beckmann@scisys.com>:
>
> Hi,
>
> this patch invents a new option "discard_fragments" for the MP4/Quicktime/MOV decoder. If the option is not set, nothing changes at all. If it is set, old fragments are discarded as far as possible on each call to switch_root. For pure audio streams, the memory usage is now constant. For video streams, the memory usage is reduced. I've tested it 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>". Does someone know sources for MP4 video streams? I could not find any with more than a few minutes. That's not sufficient to test memory usage.
>
> Cheers,
> Jörg
> ---
> libavformat/isom.h |  1 +
> libavformat/mov.c  | 52 +++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 52 insertions(+), 1 deletion(-)
>
> 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 7553a7fdfc..3a5cfa23f8 100644
> --- a/libavformat/mov.c
> +++ b/libavformat/mov.c
> @@ -7698,8 +7698,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;
> @@ -7721,6 +7724,46 @@ 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;
> +            }
> +        }
> +

> +        if (!st) {
> +            av_log(mov->fc, AV_LOG_WARNING, "could not find corresponding track id %u\n", frag->track_id);

How can this happen?

> +        } else {
> +            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_data = NULL;
> +                    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;
> @@ -7975,6 +8018,9 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti
>      int sample;
>      int i;
> +    if (mc->discard_fragments)  // Seeking is not possible if fragments are discarded.
> +        return AVERROR(ENOTSUP);

I guess this is only true if the stream actually is fragmented.

> +
>      if (stream_index >= s->nb_streams)
>          return AVERROR_INVALIDDATA;
> @@ -8063,6 +8109,10 @@ 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",
> +            "Discards fragments after they have been read to support live streams.",
> +            OFFSET(discard_fragments), AV_OPT_TYPE_BOOL, { .i64 = 0 },
> +            0, 1, FLAGS },

Carl Eugen
Jörg Beckmann Dec. 2, 2019, 10:05 a.m. UTC | #2
The for-loop and the warning are copied from somewhere else in the decoder. I'm also quite sure that it cannot really happen. 

One cannot set the option to discard old frames and also try to seek. I did not manage to create an example with calls to switch_root while reading from a file. But if it is possible somehow and the option is set, there are no old fragments to seek in. Therefore I think, seek should be forbidden if the option is set.

Cheers,
Jörg

-----Ursprüngliche Nachricht-----
Von: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> Im Auftrag von Carl Eugen Hoyos
Gesendet: Montag, 2. Dezember 2019 10:15
An: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Betreff: [SCISYS Possible Spam] Re: [FFmpeg-devel] [PATCH] Patch for memory optimization with QuickTime/MP4

Am Mo., 2. Dez. 2019 um 10:05 Uhr schrieb Jörg Beckmann
<Joerg.Beckmann@scisys.com>:
>

> Hi,

>

> this patch invents a new option "discard_fragments" for the MP4/Quicktime/MOV decoder. If the option is not set, nothing changes at all. If it is set, old fragments are discarded as far as possible on each call to switch_root. For pure audio streams, the memory usage is now constant. For video streams, the memory usage is reduced. I've tested it 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>". Does someone know sources for MP4 video streams? I could not find any with more than a few minutes. That's not sufficient to test memory usage.

>

> Cheers,

> Jörg

> ---

> libavformat/isom.h |  1 +

> libavformat/mov.c  | 52 +++++++++++++++++++++++++++++++++++++++++++++-

> 2 files changed, 52 insertions(+), 1 deletion(-)

>

> 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 

> 7553a7fdfc..3a5cfa23f8 100644

> --- a/libavformat/mov.c

> +++ b/libavformat/mov.c

> @@ -7698,8 +7698,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;

> @@ -7721,6 +7724,46 @@ 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;

> +            }

> +        }

> +


> +        if (!st) {

> +            av_log(mov->fc, AV_LOG_WARNING, "could not find 

> + corresponding track id %u\n", frag->track_id);


How can this happen?

> +        } else {

> +            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_data = NULL;

> +                    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;

> @@ -7975,6 +8018,9 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti

>      int sample;

>      int i;

> +    if (mc->discard_fragments)  // Seeking is not possible if fragments are discarded.

> +        return AVERROR(ENOTSUP);


I guess this is only true if the stream actually is fragmented.

> +

>      if (stream_index >= s->nb_streams)

>          return AVERROR_INVALIDDATA;

> @@ -8063,6 +8109,10 @@ 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",

> +            "Discards fragments after they have been read to support live streams.",

> +            OFFSET(discard_fragments), AV_OPT_TYPE_BOOL, { .i64 = 0 },

> +            0, 1, FLAGS },


Carl Eugen
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Carl Eugen Hoyos Dec. 2, 2019, 10:29 a.m. UTC | #3
Am Mo., 2. Dez. 2019 um 11:06 Uhr schrieb Jörg Beckmann
<Joerg.Beckmann@scisys.com>:

> The for-loop and the warning are copied from somewhere else in the decoder. I'm also quite sure that it cannot really happen.

Then use an assert().

> One cannot set the option to discard old frames and also try to seek. I did not manage to create an example with calls to switch_root while reading from a file. But if it is possible somehow and the option is set, there are no old fragments to seek in. Therefore I think, seek should be forbidden if the option is set.

But if you read from a file and use the option, seeking does not work
although it would be possible or do I misunderstand?

Please find out what top-posting means and avoid it here.

Carl Eugen
diff mbox

Patch

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 7553a7fdfc..3a5cfa23f8 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -7698,8 +7698,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;
@@ -7721,6 +7724,46 @@  static int mov_switch_root(AVFormatContext *s, int64_t target, int index)
     mov->found_mdat = 0;