[FFmpeg-devel] [PATCH 2/5] cmdutils: add insert_timeline_graph()

Clément Bœsch u at pkh.me
Tue Jan 6 18:09:58 CET 2015


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

This function will be used in the following commits in ffmpeg and
ffplay.
---
 cmdutils.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 cmdutils.h |  12 +++++
 2 files changed, 182 insertions(+)

diff --git a/cmdutils.c b/cmdutils.c
index b35180e..0e22e57 100644
--- a/cmdutils.c
+++ b/cmdutils.c
@@ -31,7 +31,9 @@
 
 #include "config.h"
 #include "compat/va_copy.h"
+#include "libavcodec/bytestream.h"
 #include "libavformat/avformat.h"
+#include "libavformat/isom.h"
 #include "libavfilter/avfilter.h"
 #include "libavdevice/avdevice.h"
 #include "libavresample/avresample.h"
@@ -2252,3 +2254,171 @@ int show_sinks(void *optctx, const char *opt, const char *arg)
     return ret;
 }
 #endif
+
+static int parse_elst(MOVElst **ret, const uint8_t *buf, int size)
+{
+    GetByteContext gb;
+    int i, edit_count, version;
+    MOVElst *elst_data;
+
+    bytestream2_init(&gb, buf, size);
+
+    version = bytestream2_get_byte(&gb);
+    bytestream2_skip(&gb, 3); /* flags */
+    edit_count = bytestream2_get_be32(&gb);
+
+    if (!edit_count)
+        return 0;
+
+    elst_data = av_malloc_array(edit_count, sizeof(*elst_data));
+    if (!elst_data)
+        return AVERROR(ENOMEM);
+
+    for (i = 0; i < edit_count && bytestream2_get_bytes_left(&gb) > 0; i++) {
+        MOVElst *e = &elst_data[i];
+
+        if (version == 1) {
+            e->duration = bytestream2_get_be64(&gb);
+            e->time     = bytestream2_get_be64(&gb);
+        } else {
+            e->duration = bytestream2_get_be32(&gb);
+            e->time     = (int32_t)bytestream2_get_be32(&gb);
+        }
+        e->rate = bytestream2_get_be32(&gb) / 65536.0;
+    }
+
+    *ret = elst_data;
+    return i;
+}
+
+static int get_elst_lavfi_graph_str(AVBPrint *bp, const AVStream *st, int64_t start_time)
+{
+    int i, elst_count, size;
+    AVBPrint select;
+    AVBPrint setpts;
+    MOVElst *elst;
+    AVRational tb;
+
+    const uint8_t *buf = av_stream_get_side_data(st, AV_PKT_DATA_MOV_TIMELINE, &size);
+    if (!buf || size <= 4)
+        return 0;
+
+    tb = av_make_q(1, AV_RB32(buf));
+
+    elst_count = parse_elst(&elst, buf + 4, size - 4);
+    if (elst_count <= 0)
+        return elst_count;
+
+    av_bprint_init(bp,      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 < elst_count; i++) {
+        int64_t gap;
+        const MOVElst *segment = &elst[i];
+        const MOVElst *next    = i < elst_count - 1 ? &elst[i + 1] : NULL;
+        const MOVElst *prev    = i > 0              ? &elst[i - 1] : NULL;
+        int64_t rescaled_start = av_rescale_q(segment->time, tb, st->time_base);
+        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")", rescaled_start);
+        } else {
+            const int64_t rescaled_end = av_rescale_q(end, tb, st->time_base);
+            av_bprintf(&select, "between(pts,%"PRId64",%"PRId64"-1)",
+                       rescaled_start, rescaled_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");
+            gap = av_rescale_q(gap, tb, st->time_base);
+            av_bprintf(&setpts, "-if(gte(PTS,%"PRId64"),%"PRId64",0)",
+                       segment->time, gap);
+        }
+    }
+
+    av_freep(&elst);
+
+    if (select.str[0] && av_bprint_is_complete(&select) && av_bprint_is_complete(&setpts)) {
+        const char *tstr = st->codec->codec_type == AVMEDIA_TYPE_AUDIO ? "a" : "";
+        int64_t rescaled_start_time = start_time == AV_NOPTS_VALUE ? 0 : av_rescale_q(start_time, AV_TIME_BASE_Q, st->time_base);
+
+        av_bprintf(bp, "[tl_in] ");
+
+        /* make sure the following filters will not take into account the PTS
+         * shift that can occur with ffmpeg (-ss) */
+        if (rescaled_start_time)
+            av_bprintf(bp, "%ssetpts=PTS+%"PRId64", ", tstr, rescaled_start_time);
+
+        /* select the time ranges
+         * FIXME: aselect should be replaced with a sample accurate filter */
+        av_bprintf(bp, "%sselect='%s'", tstr, select.str);
+
+        /* insert the time adjustment filter if there are time time gaps (often
+         * the case if there is more than one entry) */
+        if (setpts.str[0])
+            av_bprintf(bp, ", %ssetpts='%s'", tstr, setpts.str);
+
+        /* restore the time shift introduced previously */
+        if (rescaled_start_time)
+            av_bprintf(bp, ", %ssetpts=PTS-%"PRId64, tstr, rescaled_start_time);
+
+        av_bprintf(bp, " [tl_out]");
+    }
+
+    av_bprint_finalize(&select, NULL);
+    av_bprint_finalize(&setpts, NULL);
+
+    return 0;
+}
+
+int insert_timeline_graph(const AVStream *st, AVFilterContext **last_filter,
+                          int64_t start_time, int reverse)
+{
+    AVBPrint bp;
+    AVFilterInOut *inputs, *outputs;
+    AVFilterGraph *graph = (*last_filter)->graph;
+
+    int ret = get_elst_lavfi_graph_str(&bp, st, start_time);
+    if (ret < 0)
+        goto end;
+
+    if (!av_bprint_is_complete(&bp) || !bp.str[0])
+        goto end;
+
+    if ((ret = avfilter_graph_parse2(graph, bp.str, &inputs, &outputs)) < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Unable to parse timeline graph\n");
+        goto end;
+    }
+
+    if (reverse) ret = avfilter_link(outputs[0].filter_ctx, 0, *last_filter, 0);
+    else         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: %s\n", av_err2str(ret));
+        goto end;
+    }
+
+    if (reverse) *last_filter =  inputs[0].filter_ctx;
+    else         *last_filter = outputs[0].filter_ctx;
+
+end:
+    av_bprint_finalize(&bp, NULL);
+    return ret;
+}
diff --git a/cmdutils.h b/cmdutils.h
index f6ad44c..7b140fd 100644
--- a/cmdutils.h
+++ b/cmdutils.h
@@ -597,4 +597,16 @@ void *grow_array(void *array, int elem_size, int *size, int new_size);
     char name[128];\
     av_get_channel_layout_string(name, sizeof(name), 0, ch_layout);
 
+/**
+ * Get the MOV timeline from the stream side data, construct a libavfilter
+ * filtergraph, and insert it after the last filter.
+ *
+ * @param st          the stream with the timeline
+ * @param last_filter pointer to last filter to stick the filtergraph (will be updated)
+ * @param start_time  initial timestamp offset in AV_TIME_BASE_Q time base
+ * @param reverse     if set, prepend the timeline filtergraph instead of appending it
+ */
+int insert_timeline_graph(const AVStream *st, AVFilterContext **last_filter,
+                          int64_t start_time, int reverse);
+
 #endif /* CMDUTILS_H */
-- 
2.2.1



More information about the ffmpeg-devel mailing list