[FFmpeg-devel] [PATCH 3/3] avformat: add basic timeline support

Clément Bœsch u at pkh.me
Tue Dec 30 18:25:26 CET 2014


From: Clément Bœsch <clement at stupeflix.com>

---
Another approach this time: exporting a filtergraph from the demuxer
(when requested) and inserting it in the tools automatically.

FIXME: doesn't work when using -ss with ffmpeg, maybe because it's
shifting the timestamps of the frames before feeding them to
libavfilter. I probably need guidance here.

TODO: add -honor_timeline bool option in ffmpeg
---
 ffmpeg_filter.c   | 29 +++++++++++++++++++++
 ffmpeg_opt.c      |  8 +++++-
 ffplay.c          | 46 ++++++++++++++++++++++++++++++++-
 libavformat/mov.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 155 insertions(+), 4 deletions(-)

diff --git a/ffmpeg_filter.c b/ffmpeg_filter.c
index 264840b..83a6473 100644
--- a/ffmpeg_filter.c
+++ b/ffmpeg_filter.c
@@ -623,6 +623,29 @@ static int sub2video_prepare(InputStream *ist)
     return 0;
 }
 
+static int insert_timeline_graph(const AVStream *st, AVFilterContext **last_filter)
+{
+    int ret;
+    AVFilterGraph *graph = (*last_filter)->graph;
+    const AVDictionaryEntry *tl_tag = av_dict_get(st->metadata, "timeline", NULL, 0);
+
+    if (tl_tag) {
+        AVFilterInOut *inputs, *outputs;
+
+        if ((ret = avfilter_graph_parse2(graph, tl_tag->value, &inputs, &outputs)) < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to parse timeline graph\n");
+            return ret;
+        }
+        ret = avfilter_link(*last_filter, 0, inputs[0].filter_ctx, 0);
+        if (ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to link the end of the timeline graph to the last inserted filter\n");
+            return ret;
+        }
+        *last_filter = outputs[0].filter_ctx;
+    }
+    return 0;
+}
+
 static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter,
                                         AVFilterInOut *in)
 {
@@ -676,6 +699,9 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter,
         return ret;
     last_filter = ifilter->filter;
 
+    if ((ret = insert_timeline_graph(ist->st, &last_filter)) < 0)
+        return ret;
+
     if (ist->framerate.num) {
         AVFilterContext *setpts;
 
@@ -764,6 +790,9 @@ static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter,
         return ret;
     last_filter = ifilter->filter;
 
+    if ((ret = insert_timeline_graph(ist->st, &last_filter)) < 0)
+        return ret;
+
 #define AUTO_INSERT_FILTER_INPUT(opt_name, filter_name, arg) do {                 \
     AVFilterContext *filt_ctx;                                              \
                                                                             \
diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c
index ac93eb5..cd7eb46 100644
--- a/ffmpeg_opt.c
+++ b/ffmpeg_opt.c
@@ -804,7 +804,7 @@ static int open_input_file(OptionsContext *o, const char *filename)
     char *   video_codec_name = NULL;
     char *   audio_codec_name = NULL;
     char *subtitle_codec_name = NULL;
-    int scan_all_pmts_set = 0;
+    int scan_all_pmts_set = 0, ignore_editlist_set = 0;
 
     if (o->format) {
         if (!(file_iformat = av_find_input_format(o->format))) {
@@ -879,6 +879,10 @@ static int open_input_file(OptionsContext *o, const char *filename)
         av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
         scan_all_pmts_set = 1;
     }
+    if (!av_dict_get(o->g->format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE)) {
+        av_dict_set(&o->g->format_opts, "ignore_editlist", "lavfi_timeline", AV_DICT_DONT_OVERWRITE);
+        ignore_editlist_set = 1;
+    }
     /* open the input file with generic avformat function */
     err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);
     if (err < 0) {
@@ -887,6 +891,8 @@ static int open_input_file(OptionsContext *o, const char *filename)
     }
     if (scan_all_pmts_set)
         av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
+    if (ignore_editlist_set)
+        av_dict_set(&o->g->format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE);
     remove_avoptions(&o->g->format_opts, o->g->codec_opts);
     assert_avoptions(o->g->format_opts);
 
diff --git a/ffplay.c b/ffplay.c
index 1914a66..52cf686 100644
--- a/ffplay.c
+++ b/ffplay.c
@@ -348,6 +348,7 @@ static int nb_vfilters = 0;
 static char *afilters = NULL;
 #endif
 static int autorotate = 1;
+static int honor_timeline = 1;
 
 /* current context */
 static int is_full_screen;
@@ -1952,6 +1953,36 @@ fail:
     return ret;
 }
 
+static int insert_timeline_graph(const AVStream *st, AVFilterContext **last_filter)
+{
+    // XXX: configure_audio() seems to be called sometimes several times for
+    // the same stream, which might be NULL
+    if (!st)
+        return 0;
+
+    if (honor_timeline) {
+        int ret;
+        AVFilterGraph *graph = (*last_filter)->graph;
+        const AVDictionaryEntry *tl_tag = av_dict_get(st->metadata, "timeline", NULL, 0);
+
+        if (tl_tag) {
+            AVFilterInOut *inputs, *outputs;
+
+            if ((ret = avfilter_graph_parse2(graph, tl_tag->value, &inputs, &outputs)) < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Unable to parse timeline graph\n");
+                return ret;
+            }
+            ret = avfilter_link(outputs[0].filter_ctx, 0, *last_filter, 0);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Unable to link the end of the timeline graph to the last inserted filter\n");
+                return ret;
+            }
+            *last_filter = inputs[0].filter_ctx;
+        }
+    }
+    return 0;
+}
+
 static int configure_video_filters(AVFilterGraph *graph, VideoState *is, const char *vfilters, AVFrame *frame)
 {
     static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
@@ -2031,6 +2062,9 @@ static int configure_video_filters(AVFilterGraph *graph, VideoState *is, const c
         }
     }
 
+    if ((ret = insert_timeline_graph(is->video_st, &last_filter)) < 0)
+        goto fail;
+
     if ((ret = configure_filtergraph(graph, vfilters, filt_src, last_filter)) < 0)
         goto fail;
 
@@ -2104,6 +2138,8 @@ static int configure_audio_filters(VideoState *is, const char *afilters, int for
             goto end;
     }
 
+    if ((ret = insert_timeline_graph(is->audio_st, &filt_asink)) < 0)
+        goto end;
 
     if ((ret = configure_filtergraph(is->agraph, afilters, filt_asrc, filt_asink)) < 0)
         goto end;
@@ -2889,7 +2925,7 @@ static int read_thread(void *arg)
     AVDictionary **opts;
     int orig_nb_streams;
     SDL_mutex *wait_mutex = SDL_CreateMutex();
-    int scan_all_pmts_set = 0;
+    int scan_all_pmts_set = 0, ignore_editlist_set = 0;
 
     memset(st_index, -1, sizeof(st_index));
     is->last_video_stream = is->video_stream = -1;
@@ -2903,6 +2939,11 @@ static int read_thread(void *arg)
         av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
         scan_all_pmts_set = 1;
     }
+    if (CONFIG_AVFILTER && honor_timeline &&
+        !av_dict_get(format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE)) {
+        av_dict_set(&format_opts, "ignore_editlist", "lavfi_timeline", AV_DICT_DONT_OVERWRITE);
+        ignore_editlist_set = 1;
+    }
     err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
     if (err < 0) {
         print_error(is->filename, err);
@@ -2911,6 +2952,8 @@ static int read_thread(void *arg)
     }
     if (scan_all_pmts_set)
         av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
+    if (ignore_editlist_set)
+        av_dict_set(&format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE);
 
     if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
         av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);
@@ -3709,6 +3752,7 @@ static const OptionDef options[] = {
     { "scodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &subtitle_codec_name }, "force subtitle decoder", "decoder_name" },
     { "vcodec", HAS_ARG | OPT_STRING | OPT_EXPERT, {    &video_codec_name }, "force video decoder",    "decoder_name" },
     { "autorotate", OPT_BOOL, { &autorotate }, "automatically rotate video", "" },
+    { "honor_timeline", OPT_BOOL | OPT_EXPERT, { &honor_timeline }, "honor timelines", "" },
     { NULL, },
 };
 
diff --git a/libavformat/mov.c b/libavformat/mov.c
index f2d4fa0..424a894 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -35,6 +35,7 @@
 #include "libavutil/mathematics.h"
 #include "libavutil/time_internal.h"
 #include "libavutil/avstring.h"
+#include "libavutil/bprint.h"
 #include "libavutil/dict.h"
 #include "libavutil/opt.h"
 #include "libavutil/timecode.h"
@@ -2269,6 +2270,74 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
     uint64_t stream_size = 0;
 
     if (sc->elst_count) {
+
+        if (mov->ignore_editlist == -1) {
+            int i;
+            AVBPrint tl;
+            AVBPrint select;
+            AVBPrint setpts;
+            const MOVElst *elst = sc->elst_data;
+
+            av_bprint_init(&tl, 0, AV_BPRINT_SIZE_UNLIMITED);
+            av_bprint_init(&select, 0, AV_BPRINT_SIZE_UNLIMITED);
+            av_bprint_init(&setpts, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+            for (i = 0; i < sc->elst_count; i++) {
+                int64_t gap;
+                const MOVElst *segment = &elst[i];
+                const MOVElst *next    = i < sc->elst_count - 1 ? &elst[i + 1] : NULL;
+                const MOVElst *prev    = i > 0                  ? &elst[i - 1] : NULL;
+                int64_t end = segment->duration ? segment->time + segment->duration : -1;
+
+                if (!segment->duration && next)
+                    end = next->time;
+
+                if (select.str[0])
+                    av_bprintf(&select, "+");
+                if (end == -1)
+                    av_bprintf(&select, "gte(pts,%"PRId64")", segment->time);
+                else
+                    av_bprintf(&select, "between(pts,%"PRId64",%"PRId64"-1)",
+                               segment->time, end);
+
+                if (segment->time == -1)
+                    /* XXX: we are supposed to insert initial silence/emptiness here */
+                    gap = segment->duration;
+                else if (prev)
+                    gap = segment->time - prev->time - prev->duration;
+                else
+                    gap = segment->time;
+                gap *= segment->rate;
+
+                if (gap) {
+                    if (!*setpts.str)
+                        av_bprintf(&setpts, "PTS");
+                    av_bprintf(&setpts, "-if(gte(PTS,%"PRId64"),%"PRId64",0)",
+                               segment->time, gap);
+                }
+            }
+
+            if (select.str[0] && av_bprint_is_complete(&select) &&
+                setpts.str[0] && av_bprint_is_complete(&setpts)) {
+
+                const char *tstr = st->codec->codec_type == AVMEDIA_TYPE_AUDIO ? "a" : "";
+
+                // FIXME: aselect should probably be made sample accurate
+
+                av_bprintf(&tl, "[tl_in] %sselect='%s',%ssetpts='%s' [tl_out]",
+                           tstr, select.str, tstr, setpts.str);
+
+                if (av_bprint_is_complete(&tl))
+                    av_dict_set(&st->metadata, "timeline", tl.str, 0);
+            }
+
+            av_bprint_finalize(&select, NULL);
+            av_bprint_finalize(&setpts, NULL);
+            av_bprint_finalize(&tl, NULL);
+
+        } else {
+            // TODO: reindent
+
         int i, edit_start_index = 0, unsupported = 0;
         int64_t empty_duration = 0; // empty duration of the first edit list entry
         int64_t start_time = 0; // start time of the media
@@ -2303,6 +2372,7 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
                 st->codec->has_b_frames = 1;
             }
         }
+        }
     }
 
     /* only use old uncompressed audio chunk demuxing when stts specifies it */
@@ -3215,7 +3285,7 @@ static int mov_read_elst(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     MOVStreamContext *sc;
     int i, edit_count, version;
 
-    if (c->fc->nb_streams < 1 || c->ignore_editlist)
+    if (c->fc->nb_streams < 1 || c->ignore_editlist == 1)
         return 0;
     sc = c->fc->streams[c->fc->nb_streams-1]->priv_data;
 
@@ -4255,7 +4325,9 @@ static const AVOption mov_options[] = {
         OFFSET(use_absolute_path), FF_OPT_TYPE_INT, {.i64 = 0},
         0, 1, FLAGS},
     {"ignore_editlist", "", OFFSET(ignore_editlist), FF_OPT_TYPE_INT, {.i64 = 0},
-        0, 1, FLAGS},
+        -1, 1, FLAGS, "editlist_mode"},
+        {"lavfi_timeline", "export timelines as libavfilter filtergraphs",
+            0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, FLAGS, "editlist_mode" },
     {"use_mfra_for",
         "use mfra for fragment timestamps",
         OFFSET(use_mfra_for), FF_OPT_TYPE_INT, {.i64 = FF_MOV_FLAG_MFRA_AUTO},
-- 
2.2.1



More information about the ffmpeg-devel mailing list