[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