From: Clément Bœsch <clem...@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel