This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 32a256f619a344a7a351b89709f4a7de732268eb
Author:     James Almer <[email protected]>
AuthorDate: Wed Jun 24 08:24:01 2026 -0300
Commit:     James Almer <[email protected]>
CommitDate: Mon Jul 6 13:26:02 2026 -0300

    avcodec: add a graph based bitstream filtering API
    
    Tagged experimental for now, as it may see modifications depending on
    requirements.
    
    Signed-off-by: James Almer <[email protected]>
---
 configure                      |   7 +-
 doc/APIchanges                 |  13 +
 libavcodec/Makefile            |   2 +
 libavcodec/bitstream_filters.c |   6 +
 libavcodec/bitstreamfilter.c   | 906 +++++++++++++++++++++++++++++++++++++++++
 libavcodec/bsf.c               |   8 +-
 libavcodec/bsf.h               | 376 +++++++++++++++++
 libavcodec/bsf/Makefile        |   3 +
 libavcodec/bsf/filters.h       | 215 ++++++++++
 libavcodec/bsf/sink.c          | 148 +++++++
 libavcodec/bsf/source.c        | 222 ++++++++++
 libavcodec/bsf_internal.h      | 192 +++++++++
 libavcodec/bsfgraph.c          | 404 ++++++++++++++++++
 libavcodec/version.h           |   2 +-
 14 files changed, 2497 insertions(+), 7 deletions(-)

diff --git a/configure b/configure
index 070dd3f40f..80954aa826 100755
--- a/configure
+++ b/configure
@@ -4569,7 +4569,7 @@ find_things_extern(){
     pattern=$2
     file=$source_path/$3
     out=${4:-$thing}
-    sed -n "s/^[^#]*extern.*$pattern *ff_\([^ ]*\)_$thing;/\1_$out/p" "$file"
+    sed -n "s/^[^#]*extern const.*$pattern *ff_\([^ ]*\)_$thing;/\1_$out/p" 
"$file"
 }
 
 find_filters_extern(){
@@ -8983,6 +8983,11 @@ print_enabled_components(){
             printf "    &ff_%s,\n" $c >> $TMPH
         done
     fi
+    if [ "$name" = "bitstream_filters" ]; then
+        for c in source_bsf sink_bsf; do
+            printf "    &ff_%s,\n" $c >> $TMPH
+        done
+    fi
     echo "    NULL };" >> $TMPH
     cp_if_changed $TMPH $file
 }
diff --git a/doc/APIchanges b/doc/APIchanges
index 02481f31ac..4582e369e2 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,19 @@ The last version increases of all libraries were on 2026-06-23.
 
 API changes, most recent first:
 
+2026-07-06 - xxxxxxxxxxx - lavc 63.4.100 - bsf.h
+  Added graph based experimental bitstream filter API.
+  Add AVBitStreamFilterContext, AVBitStreamFilterGraph.
+  Add AVBitStreamFilterLink, AVBitStreamFilterPad.
+  Add av_bsf_pad_get_name(), av_bsf_pad_get_codec_ids(), av_bsf_link().
+  Add av_bsf_init_str(), av_bsf_init_dict().
+  Add av_bsf_graph_alloc(), av_bsf_graph_alloc_filter(), 
av_bsf_graph_create_filter().
+  Add av_bsf_graph_get_filter(), av_bsf_graph_config().
+  Add av_bsf_graph_source_needs_input(), av_bsf_graph_free().
+  Add av_bsf_source_add_packet(), av_bsf_source_close().
+  Add av_bsf_source_parameters_set(), av_bsf_source_get_status().
+  Add av_bsf_sink_get_packet(), av_bsf_sink_get_time_base(), 
av_bsf_sink_get_parameters().
+
 2026-06-23 - 61693f6c35f - lavu 60.34.100 - hwcontext_vulkan.h
   Switch AVVkFrame.access to VkAccessFlagBits2.
 
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b0e9e4f17b..5730463f3e 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -34,8 +34,10 @@ OBJS = ac3_parser.o                                          
           \
        avdct.o                                                          \
        packet.o                                                         \
        bitstream.o                                                      \
+       bitstreamfilter.o                                                \
        bitstream_filters.o                                              \
        bsf.o                                                            \
+       bsfgraph.o                                                       \
        codec_desc.o                                                     \
        codec_par.o                                                      \
        d3d11va.o                                                        \
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index 9c9443e5b1..bcc3011526 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -76,6 +76,12 @@ extern const FFBitStreamFilter ff_vp9_superframe_split_bsf;
 extern const FFBitStreamFilter ff_vvc_metadata_bsf;
 extern const FFBitStreamFilter ff_vvc_mp4toannexb_bsf;
 
+/* these filters are part of public or internal API,
+ * they are formatted to not be found by the grep
+ * as they are manually added */
+extern  const FFBitStreamFilter ff_source_bsf;
+extern  const FFBitStreamFilter ff_sink_bsf;
+
 #include "libavcodec/bsf_list.c"
 
 const AVBitStreamFilter *av_bsf_iterate(void **opaque)
diff --git a/libavcodec/bitstreamfilter.c b/libavcodec/bitstreamfilter.c
new file mode 100644
index 0000000000..ae445143fe
--- /dev/null
+++ b/libavcodec/bitstreamfilter.c
@@ -0,0 +1,906 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+
+#include "bsf.h"
+#include "bsf_internal.h"
+
+static const char *default_bsf_name(void *filter_ctx)
+{
+    AVBitStreamFilterContext *ctx = filter_ctx;
+    return ctx->name ? ctx->name : ctx->filter->name;
+}
+
+static void *bsf_child_next(void *obj, void *prev)
+{
+    AVBitStreamFilterContext *ctx = obj;
+    if (!prev && ctx->filter && ctx->filter->priv_class && ctx->priv_data)
+        return ctx->priv_data;
+    return NULL;
+}
+
+static const AVClass bitstreamfilter_class = {
+    .class_name = "AVBitStreamFilterContext",
+    .item_name  = default_bsf_name,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .category   = AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+    .child_next = bsf_child_next,
+    .child_class_iterate = ff_bsf_child_class_iterate,
+    .state_flags_offset = offsetof(FFBitStreamFilterContext, state_flags),
+};
+
+int ff_bsf_alloc(const AVBitStreamFilter *filter, const char *inst_name, 
AVBitStreamFilterContext **pctx)
+{
+    FFBitStreamFilterContext *ctxi;
+    AVBitStreamFilterContext *ctx;
+    const FFBitStreamFilter *const fi = ff_bsf(filter);
+    int ret = AVERROR(ENOMEM), preinited = 0;
+
+    av_assert0(filter);
+
+    ctxi = av_mallocz(sizeof(*ctxi));
+    if (!ctxi)
+        return AVERROR(ENOMEM);
+    *pctx = ctx = &ctxi->p;
+
+    ctx->av_class = &bitstreamfilter_class;
+    ctx->filter   = filter;
+    ctx->name     = inst_name ? av_strdup(inst_name) : NULL;
+    if (fi->priv_data_size) {
+        ctx->priv_data     = av_mallocz(fi->priv_data_size);
+        if (!ctx->priv_data)
+            goto err;
+    }
+    if (fi->preinit) {
+        ret = fi->preinit(ctx);
+        if (ret < 0)
+            goto err;
+        preinited = 1;
+    }
+
+    av_opt_set_defaults(ctx);
+    if (filter->priv_class) {
+        *(const AVClass**)ctx->priv_data = filter->priv_class;
+        av_opt_set_defaults(ctx->priv_data);
+    }
+
+    ctx->nb_inputs  = fi->nb_inputs;
+    if (ctx->nb_inputs ) {
+        ctx->input_pads = av_memdup(fi->inputs, ctx->nb_inputs * 
sizeof(*fi->inputs));
+        if (!ctx->input_pads)
+            goto err;
+        ctx->inputs     = av_calloc(ctx->nb_inputs, sizeof(*ctx->inputs));
+        if (!ctx->inputs)
+            goto err;
+    }
+
+    ctx->nb_outputs = fi->nb_outputs;
+    if (ctx->nb_outputs) {
+        ctx->output_pads = av_memdup(fi->outputs, ctx->nb_outputs * 
sizeof(*fi->outputs));
+        if (!ctx->output_pads)
+            goto err;
+        ctx->outputs     = av_calloc(ctx->nb_outputs, sizeof(*ctx->outputs));
+        if (!ctx->outputs)
+            goto err;
+    }
+
+    return 0;
+
+err:
+    if (preinited)
+        fi->uninit(ctx);
+    av_freep(&ctx->name);
+    av_freep(&ctx->inputs);
+    av_freep(&ctx->input_pads);
+    ctx->nb_inputs = 0;
+    av_freep(&ctx->outputs);
+    av_freep(&ctx->output_pads);
+    ctx->nb_outputs = 0;
+    av_freep(&ctx->priv_data);
+    av_freep(pctx);
+    return ret;
+}
+
+int av_bsf_link(AVBitStreamFilterContext *src, unsigned srcpad,
+                AVBitStreamFilterContext *dst, unsigned dstpad)
+{
+    BitStreamFilterLinkInternal *li;
+    AVBitStreamFilterLink *link;
+
+    av_assert0(src->graph);
+    av_assert0(dst->graph);
+    av_assert0(src->graph == dst->graph);
+
+    if (src->nb_outputs <= srcpad || dst->nb_inputs <= dstpad ||
+        src->outputs[srcpad]      || dst->inputs[dstpad])
+        return AVERROR(EINVAL);
+
+    if (!(ffbsfctx(src)->state_flags & AV_CLASS_STATE_INITIALIZED) ||
+        !(ffbsfctx(dst)->state_flags & AV_CLASS_STATE_INITIALIZED)) {
+        av_log(src, AV_LOG_ERROR, "Filters must be initialized before 
linking.\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (src->output_pads[srcpad].codec_ids && 
dst->input_pads[dstpad].codec_ids) {
+        int i;
+        for (i = 0; src->output_pads[srcpad].codec_ids[i] != AV_CODEC_ID_NONE; 
i++)
+            for (int j = 0; dst->input_pads[dstpad].codec_ids[j] != 
AV_CODEC_ID_NONE; j++)
+                if (src->output_pads[srcpad].codec_ids[i] == 
dst->input_pads[dstpad].codec_ids[j])
+                    break;
+
+        if (src->output_pads->codec_ids[i] == AV_CODEC_ID_NONE) {
+            av_log(src, AV_LOG_ERROR, "No common codec id between source and 
dest pads\n");
+                       av_log(src, AV_LOG_ERROR, "Supported input pad codecs 
are: ");
+            for (i = 0; dst->input_pads->codec_ids[i] != AV_CODEC_ID_NONE; 
i++) {
+                enum AVCodecID codec_id = dst->input_pads->codec_ids[i];
+                av_log(src, AV_LOG_ERROR, "%s (%d) ",
+                       avcodec_get_name(codec_id), codec_id);
+            }
+            av_log(src, AV_LOG_ERROR, "\n");
+                       av_log(src, AV_LOG_ERROR, "Supported output pad codecs 
are: ");
+            for (i = 0; src->output_pads->codec_ids[i] != AV_CODEC_ID_NONE; 
i++) {
+                enum AVCodecID codec_id = src->output_pads->codec_ids[i];
+                av_log(src, AV_LOG_ERROR, "%s (%d) ",
+                       avcodec_get_name(codec_id), codec_id);
+            }
+            av_log(src, AV_LOG_ERROR, "\n");
+
+            return AVERROR(EINVAL);
+        }
+    }
+
+    li = av_mallocz(sizeof(*li));
+    if (!li)
+        return AVERROR(ENOMEM);
+    link = &li->l;
+
+    src->outputs[srcpad] = dst->inputs[dstpad] = link;
+
+    link->src     = src;
+    link->dst     = dst;
+    link->srcpad  = &src->output_pads[srcpad];
+    link->dstpad  = &dst->input_pads[dstpad];
+    link->graph   = src->graph;
+    li->fifo      = av_container_fifo_alloc_avpacket(0);
+    if (!li->fifo)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static void link_free(AVBitStreamFilterLink **plink)
+{
+    AVBitStreamFilterLink *link = *plink;
+    BitStreamFilterLinkInternal *li;
+
+    if (!link)
+        return;
+    li = ff_link_internal(link);
+
+    avcodec_parameters_free(&link->par);
+    av_container_fifo_free(&li->fifo);
+
+    av_freep(plink);
+}
+
+static void free_link(AVBitStreamFilterLink *link)
+{
+    if (!link)
+        return;
+
+    if (link->src)
+        link->src->outputs[link->srcpad - link->src->output_pads] = NULL;
+    if (link->dst)
+        link->dst->inputs[link->dstpad - link->dst->input_pads] = NULL;
+
+    link_free(&link);
+}
+
+void ff_bsf_free(AVBitStreamFilterContext *ctx)
+{
+    int i;
+
+    if (!ctx)
+        return;
+
+    if (ctx->graph)
+        ff_bsf_graph_remove_filter(ctx->graph, ctx);
+
+    if (ff_bsf(ctx->filter)->uninit)
+        ff_bsf(ctx->filter)->uninit(ctx);
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        free_link(ctx->inputs[i]);
+        if (ctx->input_pads[i].flags  & FF_BSF_PAD_FLAG_FREE_NAME)
+            av_freep(&ctx->input_pads[i].name);
+    }
+    for (i = 0; i < ctx->nb_outputs; i++) {
+        free_link(ctx->outputs[i]);
+        if (ctx->output_pads[i].flags & FF_BSF_PAD_FLAG_FREE_NAME)
+            av_freep(&ctx->output_pads[i].name);
+    }
+
+    if (ctx->filter->priv_class)
+        av_opt_free(ctx->priv_data);
+
+    av_freep(&ctx->name);
+    av_freep(&ctx->input_pads);
+    av_freep(&ctx->output_pads);
+    av_freep(&ctx->inputs);
+    av_freep(&ctx->outputs);
+    av_freep(&ctx->priv_data);
+    av_opt_free(ctx);
+    av_free(ctx);
+}
+
+/**
+ * Parse filter options into a dictionary.
+ *
+ * @param logctx context for logging
+ * @param priv_class a filter's private class for shorthand options or NULL
+ * @param options dictionary to store parsed options in
+ * @param args options string to parse
+ *
+ * @return a non-negative number on success, a negative error code on failure
+ */
+static int bsf_opt_parse(void *logctx, const AVClass *priv_class,
+                         AVDictionary **options, const char *args)
+{
+    const AVOption *o = NULL;
+    int ret;
+    int offset= -1;
+
+    if (!args)
+        return 0;
+
+    while (*args) {
+        char *parsed_key, *value;
+        const char *key;
+        const char *shorthand = NULL;
+        int additional_flags  = 0;
+
+        if (priv_class && (o = av_opt_next(&priv_class, o))) {
+            if (o->type == AV_OPT_TYPE_CONST || o->offset == offset)
+                continue;
+            offset = o->offset;
+            shorthand = o->name;
+        }
+
+        ret = av_opt_get_key_value(&args, "=", ":",
+                                   shorthand ? AV_OPT_FLAG_IMPLICIT_KEY : 0,
+                                   &parsed_key, &value);
+        if (ret < 0) {
+            if (ret == AVERROR(EINVAL))
+                av_log(logctx, AV_LOG_ERROR, "No option name near '%s'\n", 
args);
+            else
+                av_log(logctx, AV_LOG_ERROR, "Unable to parse '%s': %s\n", 
args,
+                       av_err2str(ret));
+            return ret;
+        }
+        if (*args)
+            args++;
+        if (parsed_key) {
+            key = parsed_key;
+            additional_flags = AV_DICT_DONT_STRDUP_KEY;
+            priv_class = NULL; /* reject all remaining shorthand */
+        } else {
+            key = shorthand;
+        }
+
+        av_log(logctx, AV_LOG_DEBUG, "Setting '%s' to value '%s'\n", key, 
value);
+
+        av_dict_set(options, key, value,
+                    additional_flags | AV_DICT_DONT_STRDUP_VAL | 
AV_DICT_MULTIKEY);
+    }
+
+    return 0;
+}
+
+int av_bsf_init_dict(AVBitStreamFilterContext *ctx, AVDictionary **options)
+{
+    FFBitStreamFilterContext *ctxi = ffbsfctx(ctx);
+    int ret = 0;
+
+    if (ctxi->state_flags & AV_CLASS_STATE_INITIALIZED) {
+        av_log(ctx, AV_LOG_ERROR, "Filter already initialized\n");
+        return AVERROR(EINVAL);
+    }
+
+    ret = av_opt_set_dict2(ctx, options, AV_OPT_SEARCH_CHILDREN);
+    if (ret < 0) {
+        av_log(ctx, AV_LOG_ERROR, "Error applying generic filter options.\n");
+        return ret;
+    }
+
+    if (ff_bsf(ctx->filter)->init2)
+        ret = ff_bsf(ctx->filter)->init2(ctx);
+    if (ret < 0)
+        return ret;
+
+    ctxi->state_flags |= AV_CLASS_STATE_INITIALIZED;
+
+    return 0;
+}
+
+int av_bsf_init_str(AVBitStreamFilterContext *ctx, const char *args)
+{
+    AVDictionary *options = NULL;
+    const AVDictionaryEntry *e;
+    int ret = 0;
+
+    if (args && *args) {
+        ret = bsf_opt_parse(ctx, ctx->filter->priv_class, &options, args);
+        if (ret < 0)
+            goto fail;
+    }
+
+    ret = av_bsf_init_dict(ctx, &options);
+    if (ret < 0)
+        goto fail;
+
+    if ((e = av_dict_iterate(options, NULL))) {
+        av_log(ctx, AV_LOG_ERROR, "No such option: %s.\n", e->key);
+        ret = AVERROR_OPTION_NOT_FOUND;
+        goto fail;
+    }
+
+fail:
+    av_dict_free(&options);
+
+    return ret;
+}
+
+const char *av_bsf_pad_get_name(const AVBitStreamFilterPad *pads, int pad_idx)
+{
+    return pads[pad_idx].name;
+}
+
+const enum AVCodecID *av_bsf_pad_get_codec_ids(const AVBitStreamFilterPad 
*pads, int pad_idx)
+{
+    return pads[pad_idx].codec_ids;
+}
+
+static void update_link_current_pts(BitStreamFilterLinkInternal *li, int64_t 
pts)
+{
+    if (pts == AV_NOPTS_VALUE)
+        return;
+    li->l.current_pts = pts;
+    li->l.current_pts_us = av_rescale_q(pts, li->l.time_base, AV_TIME_BASE_Q);
+    if (li->l.graph && li->age_index >= 0)
+        ff_bsf_graph_update_heap(li->l.graph, li);
+}
+
+void ff_bsf_set_ready(AVBitStreamFilterContext *filter, unsigned priority)
+{
+    FFBitStreamFilterContext *ctxi = ffbsfctx(filter);
+    ctxi->ready = FFMAX(ctxi->ready, priority);
+}
+
+/**
+ * Clear packet_blocked_in on all outputs.
+ * This is necessary whenever something changes on input.
+ */
+static void filter_unblock(AVBitStreamFilterContext *filter)
+{
+    unsigned i;
+
+    for (i = 0; i < filter->nb_outputs; i++) {
+        BitStreamFilterLinkInternal * const li = 
ff_link_internal(filter->outputs[i]);
+        li->packet_blocked_in = 0;
+    }
+}
+
+void ff_bsf_link_set_in_status(AVBitStreamFilterLink *link, int status, 
int64_t pts)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+
+    if (li->status_in == status)
+        return;
+    av_assert0(!li->status_in);
+    li->status_in = status;
+    li->status_in_pts = pts;
+    li->packet_wanted_out = 0;
+    li->packet_blocked_in = 0;
+    filter_unblock(link->dst);
+    ff_bsf_set_ready(link->dst, 200);
+}
+
+/**
+ * Set the status field of a link from the destination filter.
+ * The pts should probably be left unset (AV_NOPTS_VALUE).
+ */
+static void link_set_out_status(AVBitStreamFilterLink *link, int status, 
int64_t pts)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+
+    av_assert0(!li->packet_wanted_out);
+    av_assert0(!li->status_out);
+    li->status_out = status;
+    if (pts != AV_NOPTS_VALUE)
+        update_link_current_pts(li, pts);
+    filter_unblock(link->dst);
+    ff_bsf_set_ready(link->src, 200);
+}
+
+int ff_bsf_config_links(AVBitStreamFilterContext *filter)
+{
+    int (*config_link)(AVBitStreamFilterLink *);
+    unsigned i;
+    int ret;
+
+    for (i = 0; i < filter->nb_inputs; i ++) {
+        AVBitStreamFilterLink *link = filter->inputs[i];
+        AVBitStreamFilterLink *inlink;
+        BitStreamFilterLinkInternal *li = ff_link_internal(link);
+
+        if (!link) continue;
+        if (!link->src || !link->dst) {
+            av_log(filter, AV_LOG_ERROR,
+                   "Not all input and output are properly linked (%d).\n", i);
+            return AVERROR(EINVAL);
+        }
+
+        inlink = link->src->nb_inputs ? link->src->inputs[0] : NULL;
+        link->current_pts =
+        link->current_pts_us = AV_NOPTS_VALUE;
+
+        switch (li->init_state) {
+        case AVLINK_INIT:
+            continue;
+        case AVLINK_STARTINIT:
+            av_log(filter, AV_LOG_INFO, "circular filter chain detected\n");
+            return 0;
+        case AVLINK_UNINIT:
+            li->init_state = AVLINK_STARTINIT;
+
+            if ((ret = ff_bsf_config_links(link->src)) < 0)
+                return ret;
+
+            if (!(config_link = link->srcpad->config_props)) {
+                if (link->src->nb_inputs != 1) {
+                    av_log(link->src, AV_LOG_ERROR, "Source filters and 
filters "
+                                                    "with more than one input "
+                                                    "must set config_props() "
+                                                    "callbacks on all 
outputs\n");
+                    return AVERROR(EINVAL);
+                }
+            }
+
+            if (inlink) {
+                av_assert0(!link->par && inlink->par);
+                link->par = avcodec_parameters_alloc();
+                if (!link->par)
+                    return AVERROR(ENOMEM);
+                ret = avcodec_parameters_copy(link->par, inlink->par);
+                if (ret < 0)
+                    return ret;
+            } else {
+                av_assert0(!link->par);
+                link->par = avcodec_parameters_alloc();
+                if (!link->par)
+                    return AVERROR(ENOMEM);
+            }
+
+            if (config_link && (ret = config_link(link)) < 0) {
+                av_log(link->src, AV_LOG_ERROR,
+                       "Failed to configure output pad on %s\n",
+                       link->src->name);
+                return ret;
+            }
+
+            switch (link->par->codec_type) {
+            case AVMEDIA_TYPE_VIDEO:
+                if (!link->time_base.num && !link->time_base.den)
+                    link->time_base = inlink ? inlink->time_base : 
AV_TIME_BASE_Q;
+                break;
+
+            case AVMEDIA_TYPE_AUDIO:
+                if (inlink) {
+                    if (!link->time_base.num && !link->time_base.den)
+                        link->time_base = inlink->time_base;
+                }
+
+                if (!link->time_base.num && !link->time_base.den)
+                    link->time_base = (AVRational) {1, link->par->sample_rate};
+                break;
+            }
+
+            if (link->dstpad->codec_ids) {
+                int j;
+                for (j = 0; link->dstpad->codec_ids[j] != AV_CODEC_ID_NONE; 
j++)
+                    if (link->par->codec_id == link->dstpad->codec_ids[j])
+                        break;
+                if (link->dstpad->codec_ids[j] == AV_CODEC_ID_NONE) {
+                               av_log(link->dst, AV_LOG_ERROR, "Unsupported 
input codec id %s (%d). Supported input pad codecs are: ",
+                           avcodec_get_name(link->par->codec_id), 
link->par->codec_id);
+                    for (j = 0; link->dstpad->codec_ids[j] != 
AV_CODEC_ID_NONE; j++) {
+                        enum AVCodecID codec_id = link->dstpad->codec_ids[j];
+                        av_log(link->dst, AV_LOG_ERROR, "%s (%d) ",
+                               avcodec_get_name(codec_id), codec_id);
+                    }
+                    av_log(link->dst, AV_LOG_ERROR, "\n");
+                    return AVERROR(EINVAL);
+                }
+            }
+
+            if ((config_link = link->dstpad->config_props))
+                if ((ret = config_link(link)) < 0) {
+                    av_log(link->dst, AV_LOG_ERROR,
+                           "Failed to configure input pad on %s\n",
+                           link->dst->name);
+                    return ret;
+                }
+
+            li->init_state = AVLINK_INIT;
+        }
+    }
+
+    return 0;
+}
+
+int ff_bsf_request_packet(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+
+    av_assert1(!ff_bsf(link->dst->filter)->activate);
+    if (li->status_out)
+        return li->status_out;
+    if (li->status_in) {
+        if (av_container_fifo_can_read(li->fifo)) {
+            av_assert1(!li->packet_wanted_out);
+            av_assert1(ffbsfctx(link->dst)->ready >= 300);
+            return 0;
+        } else {
+            /* Acknowledge status change. Filters using 
ff_bsf_request_packet() will
+               handle the change automatically. Filters can also check the
+               status directly but none do yet. */
+            link_set_out_status(link, li->status_in, li->status_in_pts);
+            return li->status_out;
+        }
+    }
+    li->packet_wanted_out = 1;
+    ff_bsf_set_ready(link->src, 100);
+    return 0;
+}
+
+static int64_t guess_status_pts(AVBitStreamFilterContext *ctx, int status, 
AVRational link_time_base)
+{
+    unsigned i;
+    int64_t r = INT64_MAX;
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        AVBitStreamFilterLink * const link = ctx->inputs[i];
+        BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+        if (li->status_out == status)
+            r = FFMIN(r, av_rescale_q(link->current_pts, link->time_base, 
link_time_base));
+    }
+    if (r < INT64_MAX)
+        return r;
+    av_log(ctx, AV_LOG_WARNING, "EOF timestamp not reliable\n");
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        AVBitStreamFilterLink * const link = ctx->inputs[i];
+        BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+        r = FFMIN(r, av_rescale_q(li->status_in_pts, link->time_base, 
link_time_base));
+    }
+    if (r < INT64_MAX)
+        return r;
+    return AV_NOPTS_VALUE;
+}
+
+static int request_packet_to_filter(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    int ret = -1;
+
+    /* Assume the filter is blocked, let the method clear it if not */
+    li->packet_blocked_in = 1;
+    if (link->srcpad->request_packet)
+        ret = link->srcpad->request_packet(link);
+    else if (link->src->inputs[0])
+        ret = ff_bsf_request_packet(link->src->inputs[0]);
+    if (ret < 0) {
+        if (ret != AVERROR(EAGAIN) && ret != li->status_in)
+            ff_bsf_link_set_in_status(link, ret, guess_status_pts(link->src, 
ret, link->time_base));
+        if (ret == AVERROR_EOF)
+            ret = 0;
+    }
+    return ret;
+}
+
+static int default_filter_packet(AVBitStreamFilterLink *link, AVPacket *pkt)
+{
+    return ff_bsf_filter_packet(link->dst->outputs[0], pkt);
+}
+
+static int filter_packet_framed(AVBitStreamFilterLink *link, AVPacket *pkt)
+{
+    int (*filter)(AVBitStreamFilterLink *, AVPacket *);
+    AVBitStreamFilterPad *dst = link->dstpad;
+    int ret;
+
+    if (!(filter = dst->filter))
+        filter = default_filter_packet;
+
+    if (dst->flags & FF_BSF_PAD_FLAG_NEEDS_WRITABLE) {
+        ret = av_packet_make_writable(pkt);
+        if (ret < 0)
+            goto fail;
+    }
+
+    ret = filter(link, pkt);
+    link->packet_count_out++;
+    return ret;
+
+fail:
+    av_packet_free(&pkt);
+    return ret;
+}
+
+int ff_bsf_filter_packet(AVBitStreamFilterLink *link, AVPacket *pkt)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    FFBitStreamFilterGraph * const graphi = ffbsffiltergraph(link->graph);
+    int ret;
+
+    li->packet_blocked_in = li->packet_wanted_out = 0;
+    link->packet_count_in++;
+    filter_unblock(link->dst);
+    if (av_container_fifo_can_read(li->fifo) >= graphi->max_packet_queue)
+        return AVERROR(ENOMEM);
+    ret = av_container_fifo_write(li->fifo, pkt, 0);
+    av_packet_free(&pkt);
+    if (ret < 0)
+        return ret;
+    av_assert1(graphi->packets_queued < graphi->max_packet_queue);
+    graphi->packets_queued++;
+    ff_bsf_set_ready(link->dst, 300);
+    return 0;
+}
+
+static int filter_packet_to_filter(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    AVPacket *pkt = NULL;
+    AVBitStreamFilterContext *dst = link->dst;
+    int ret;
+
+    av_assert1(av_container_fifo_can_read(li->fifo));
+    ret = ff_bsf_inlink_consume_packet(link, &pkt);
+    av_assert1(ret);
+    if (ret < 0) {
+        av_assert1(!pkt);
+        return ret;
+    }
+
+    /* The filter will soon have received a new packet, that may allow it to
+       produce one or more: unblock its outputs. */
+    filter_unblock(dst);
+    /* AVBitStreamFilterPad.filter_packet() expect packet_count_out to have 
the value
+       before the packet; filter_packet_framed() will re-increment it. */
+    link->packet_count_out--;
+    ret = filter_packet_framed(link, pkt);
+    if (ret < 0 && ret != li->status_out) {
+        link_set_out_status(link, ret, AV_NOPTS_VALUE);
+    } else {
+        /* Run once again, to see if several packets were available, or if
+           the input status has also changed, or any other reason. */
+        ff_bsf_set_ready(dst, 300);
+    }
+    return ret;
+}
+
+static int forward_status_change(AVBitStreamFilterContext *filter, 
BitStreamFilterLinkInternal *li_in)
+{
+    AVBitStreamFilterLink *in = &li_in->l;
+    unsigned out = 0, progress = 0;
+    int ret;
+
+    av_assert0(!li_in->status_out);
+    if (!filter->nb_outputs) {
+        /* not necessary with the current API and sinks */
+        return 0;
+    }
+    while (!li_in->status_out) {
+        BitStreamFilterLinkInternal *li_out = 
ff_link_internal(filter->outputs[out]);
+
+        if (!li_out->status_in) {
+            progress++;
+            ret = request_packet_to_filter(filter->outputs[out]);
+            if (ret < 0)
+                return ret;
+        }
+        if (++out == filter->nb_outputs) {
+            if (!progress) {
+                /* Every output already closed: input no longer interesting. */
+                link_set_out_status(in, li_in->status_in, 
li_in->status_in_pts);
+                return 0;
+            }
+            progress = 0;
+            out = 0;
+        }
+    }
+    ff_bsf_set_ready(filter, 200);
+    return 0;
+}
+
+static int filter_activate_default(AVBitStreamFilterContext *ctx)
+{
+    int i, nb_eofs = 0;
+
+    for (i = 0; i < ctx->nb_outputs; i++)
+        nb_eofs += ff_bsf_outlink_get_status(ctx->outputs[i]) == AVERROR_EOF;
+    if (ctx->nb_outputs && nb_eofs == ctx->nb_outputs) {
+        for (int j = 0; j < ctx->nb_inputs; j++)
+            ff_bsf_inlink_set_status(ctx->inputs[j], AVERROR_EOF);
+        return 0;
+    }
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        BitStreamFilterLinkInternal *li = ff_link_internal(ctx->inputs[i]);
+        if (av_container_fifo_can_read(li->fifo)) {
+            return filter_packet_to_filter(ctx->inputs[i]);
+        }
+    }
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        BitStreamFilterLinkInternal * const li = 
ff_link_internal(ctx->inputs[i]);
+        if (li->status_in && !li->status_out) {
+            av_assert1(!av_container_fifo_can_read(li->fifo));
+            return forward_status_change(ctx, li);
+        }
+    }
+    for (i = 0; i < ctx->nb_outputs; i++) {
+        BitStreamFilterLinkInternal * const li = 
ff_link_internal(ctx->outputs[i]);
+        if (li->packet_wanted_out &&
+            !li->packet_blocked_in) {
+            return request_packet_to_filter(ctx->outputs[i]);
+        }
+    }
+    for (i = 0; i < ctx->nb_outputs; i++) {
+        BitStreamFilterLinkInternal * const li = 
ff_link_internal(ctx->outputs[i]);
+        if (li->packet_wanted_out)
+            return request_packet_to_filter(ctx->outputs[i]);
+    }
+    if (!ctx->nb_outputs) {
+        ff_bsf_inlink_request_packet(ctx->inputs[0]);
+        return 0;
+    }
+    return FFERROR_BSF_NOT_READY;
+}
+
+int ff_bsf_activate(AVBitStreamFilterContext *ctx)
+{
+    FFBitStreamFilterContext *ctxi = ffbsfctx(ctx);
+    const FFBitStreamFilter *const fi = ff_bsf(ctx->filter);
+    int ret;
+
+    ctxi->ready = 0;
+    ret = fi->activate ? fi->activate(ctx) : filter_activate_default(ctx);
+    if (ret == FFERROR_BSF_NOT_READY)
+        ret = 0;
+    return ret;
+}
+
+int ff_bsf_inlink_acknowledge_status(AVBitStreamFilterLink *link, int 
*rstatus, int64_t *rpts)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    *rpts = link->current_pts;
+    if (av_container_fifo_can_read(li->fifo))
+        return *rstatus = 0;
+    if (li->status_out)
+        return *rstatus = li->status_out;
+    if (!li->status_in)
+        return *rstatus = 0;
+    *rstatus = li->status_out = li->status_in;
+    update_link_current_pts(li, li->status_in_pts);
+    *rpts = link->current_pts;
+    return 1;
+}
+
+size_t ff_bsf_inlink_queued_packets(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    return av_container_fifo_can_read(li->fifo);
+}
+
+int ff_bsf_inlink_check_available_packet(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    return av_container_fifo_can_read(li->fifo) > 0;
+}
+
+static void consume_update(BitStreamFilterLinkInternal *li, const AVPacket 
*pkt)
+{
+    update_link_current_pts(li, pkt->pts);
+    li->l.packet_count_out++;
+}
+
+int ff_bsf_inlink_consume_packet(AVBitStreamFilterLink *link, AVPacket **rpkt)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    FFBitStreamFilterGraph * const graphi = ffbsffiltergraph(link->graph);
+    AVPacket *pkt;
+
+    *rpkt = NULL;
+    if (!av_container_fifo_can_read(li->fifo))
+        return 0;
+
+    pkt = av_packet_alloc();
+    if (!pkt)
+        return AVERROR(ENOMEM);
+
+    av_container_fifo_read(li->fifo, pkt, 0);
+    consume_update(li, pkt);
+    av_assert1(graphi->packets_queued > 0);
+    graphi->packets_queued--;
+    *rpkt = pkt;
+    return 1;
+}
+
+void ff_bsf_inlink_request_packet(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal *li = ff_link_internal(link);
+    av_assert1(!li->status_in);
+    av_assert1(!li->status_out);
+    li->packet_wanted_out = 1;
+    ff_bsf_set_ready(link->src, 100);
+}
+
+void ff_bsf_inlink_set_status(AVBitStreamFilterLink *link, int status)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    FFBitStreamFilterGraph * const graphi = ffbsffiltergraph(link->graph);
+    if (li->status_out)
+        return;
+    li->packet_wanted_out = 0;
+    li->packet_blocked_in = 0;
+    link_set_out_status(link, status, AV_NOPTS_VALUE);
+    while (av_container_fifo_can_read(li->fifo)) {
+        AVPacket pkt;
+        av_container_fifo_read(li->fifo, &pkt, 0);
+        av_packet_unref(&pkt);
+        av_assert1(graphi->packets_queued > 0);
+        graphi->packets_queued--;
+    }
+    if (!li->status_in)
+        li->status_in = status;
+}
+
+int ff_bsf_outlink_get_status(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    return li->status_in;
+}
+
+int ff_bsf_outlink_packet_wanted(AVBitStreamFilterLink *link)
+{
+    BitStreamFilterLinkInternal * const li = ff_link_internal(link);
+    return li->packet_wanted_out;
+}
+
+const AVBitStreamFilterPad ff_default_bsf_pad[1] = {
+    {
+        .name = "default",
+    }
+};
diff --git a/libavcodec/bsf.c b/libavcodec/bsf.c
index 1e710f7d4a..74d767ee7c 100644
--- a/libavcodec/bsf.c
+++ b/libavcodec/bsf.c
@@ -33,11 +33,6 @@
 #include "codec_par.h"
 #include "packet_internal.h"
 
-static av_always_inline const FFBitStreamFilter *ff_bsf(const 
AVBitStreamFilter *bsf)
-{
-    return (const FFBitStreamFilter*)bsf;
-}
-
 typedef struct FFBSFContext {
     AVBSFContext pub;
     AVPacket *buffer_pkt;
@@ -107,6 +102,9 @@ int av_bsf_alloc(const AVBitStreamFilter *filter, 
AVBSFContext **pctx)
     FFBSFContext *bsfi;
     int ret;
 
+    if (!ff_bsf(filter)->filter)
+        return AVERROR(ENOTSUP);
+
     bsfi = av_mallocz(sizeof(*bsfi));
     if (!bsfi)
         return AVERROR(ENOMEM);
diff --git a/libavcodec/bsf.h b/libavcodec/bsf.h
index a09c69f242..9bde9a41b8 100644
--- a/libavcodec/bsf.h
+++ b/libavcodec/bsf.h
@@ -326,6 +326,382 @@ int av_bsf_list_parse_str(const char *str, AVBSFContext 
**bsf);
 int av_bsf_get_null_filter(AVBSFContext **bsf);
 
 /**
+ * @defgroup lavc_bsfgraph Bitstream filter graph
+ * Experimental graph-based API for bitstream filters.
+ * @{
+ */
+
+/**
+ * A link between two filters. This contains pointers to the source and
+ * destination filters between which this link exists, and the indexes of
+ * the pads involved.
+ */
+typedef struct AVBitStreamFilterLink AVBitStreamFilterLink;
+
+/**
+ * A filter pad used for either input or output.
+ */
+typedef struct AVBitStreamFilterPad AVBitStreamFilterPad;
+
+/** An instance of a filter */
+typedef struct AVBitStreamFilterContext {
+    /**
+     * A class for logging and AVOptions
+     */
+    const AVClass *av_class;
+
+    /**
+     * The bitstream filter this context is an instance of.
+     */
+    const struct AVBitStreamFilter *filter;
+
+    /**
+     * name of this filter instance
+     */
+    char *name;
+
+    AVBitStreamFilterPad  *input_pads; ///< array of input pads
+    AVBitStreamFilterLink    **inputs; ///< array of pointers to input links
+    unsigned                nb_inputs; ///< number of input pads
+
+    AVBitStreamFilterPad *output_pads; ///< array of output pads
+    AVBitStreamFilterLink   **outputs; ///< array of pointers to output links
+    unsigned               nb_outputs; ///< number of output pads
+
+    /**
+     * Opaque filter-specific private data. If filter->priv_class is non-NULL,
+     * this is an AVOptions-enabled struct.
+     */
+    void *priv_data;
+
+    /**
+     * filtergraph this filter belongs to
+     */
+    struct AVBitStreamFilterGraph *graph;
+} AVBitStreamFilterContext;
+
+/**
+ * The number of the filter inputs is not determined just by the filter's 
static
+ * inputs. The filter might add additional inputs during initialization 
depending
+ * on the options supplied to it.
+ */
+#define AV_BSF_FLAG_DYNAMIC_INPUTS        (1 << 0)
+/**
+ * The number of the filter outputs is not determined just by the filter's 
static
+ * outputs. The filter might add additional outputs during initialization 
depending
+ * on the options supplied to it.
+ */
+#define AV_BSF_FLAG_DYNAMIC_OUTPUTS       (1 << 1)
+/**
+ * The filter is a "metadata" filter - it does not modify the packet data in 
any
+ * way. It may only affect the metadata (i.e. those fields copied by
+ * av_packet_copy_props()).
+ *
+ * More precisely, this means that the data of any packet output by the filter
+ * must be exactly equal to some packet that is received on one of its inputs.
+ * Furthermore, all packets produced on a given output must correspond to 
packet
+ * received on the same input and their order must be unchanged.
+ * Note that the filter may still drop or duplicate the frames.
+ */
+#define AV_BSF_FLAG_METADATA_ONLY         (1 << 2)
+
+/**
+ * Get the name of an AVBitStreamFilterPad.
+ *
+ * @param pads an array of AVBitStreamFilterPads
+ * @param pad_idx index of the pad in the array; it is the caller's
+ *                responsibility to ensure the index is valid
+ *
+ * @return name of the pad_idx'th pad in pads
+ */
+const char *av_bsf_pad_get_name(const AVBitStreamFilterPad *pads, int pad_idx);
+
+/**
+ * Get the codec ids supported by an AVBitStreamFilterPad.
+ *
+ * @param pads an array of AVBitStreamFilterPads
+ * @param pad_idx index of the pad in the array; it is the caller's
+ *                responsibility to ensure the index is valid
+ *
+ * @return an array of AVCodecID terminated by AV_CODEC_ID_NONE, or NULL
+ *         if the pad has no codec id constrains.
+ */
+const enum AVCodecID *av_bsf_pad_get_codec_ids(const AVBitStreamFilterPad 
*pads, int pad_idx);
+
+/**
+ * Link two filters together.
+ *
+ * @param src    the source filter
+ * @param srcpad index of the output pad on the source filter
+ * @param dst    the destination filter
+ * @param dstpad index of the input pad on the destination filter
+ * @return       zero on success
+ */
+int av_bsf_link(AVBitStreamFilterContext *src, unsigned srcpad,
+                AVBitStreamFilterContext *dst, unsigned dstpad);
+
+/**
+ * Initialize a filter with the supplied parameters.
+ *
+ * @param ctx  uninitialized filter context to initialize
+ * @param args Options to initialize the filter with. This must be a
+ *             ':'-separated list of options in the 'key=value' form.
+ *             May be NULL if the options have been set directly using the
+ *             AVOptions API or there are no options that need to be set.
+ * @return 0 on success, a negative AVERROR on failure
+ */
+int av_bsf_init_str(AVBitStreamFilterContext *ctx, const char *args);
+
+/**
+ * Initialize a filter with the supplied dictionary of options.
+ *
+ * @param ctx     uninitialized filter context to initialize
+ * @param options An AVDictionary filled with options for this filter. On
+ *                return this parameter will be destroyed and replaced with
+ *                a dict containing options that were not found. This 
dictionary
+ *                must be freed by the caller.
+ *                May be NULL, then this function is equivalent to
+ *                av_bsf_init_str() with the second parameter set to NULL.
+ * @return 0 on success, a negative AVERROR on failure
+ *
+ * @note This function and av_bsf_init_str() do essentially the same thing,
+ * the difference is in manner in which the options are passed. It is up to the
+ * calling code to choose whichever is more preferable. The two functions also
+ * behave differently when some of the provided options are not declared as
+ * supported by the filter. In such a case, av_bsf_init_str() will fail, but
+ * this function will leave those extra options in the options AVDictionary and
+ * continue as usual.
+ */
+int av_bsf_init_dict(AVBitStreamFilterContext *ctx, AVDictionary **options);
+
+typedef struct AVBitStreamFilterGraph {
+    const AVClass *av_class;
+
+    AVBitStreamFilterContext **filters;
+
+    unsigned nb_filters;
+
+    /**
+     * Sets the maximum number of buffered packets in the filtergraph combined.
+     *
+     * Zero means no limit. This field must be set before calling
+     * av_bsf_graph_config().
+     */
+    unsigned max_buffered_packets;
+} AVBitStreamFilterGraph;
+
+/**
+ * Allocate a filter graph.
+ *
+ * @return the allocated filter graph on success or NULL.
+ */
+AVBitStreamFilterGraph *av_bsf_graph_alloc(void);
+
+/**
+ * Create a new filter instance in a filter graph.
+ *
+ * @param[out] filt_ctx A pointer into which the pointer to the 
newly-allocated context
+ *                      will be written on success. May be NULL. Note that it 
is also
+ *                      retrievable directly through 
AVBitStreamFilterGraph.filters or
+ *                      with @ref av_bsf_graph_get_filter().
+ * @param[in] filter the filter to create an instance of
+ * @param[in] name Name to give to the new instance (will be copied to
+ *                 AVBitStreamFilterContext.name). This may be used by the 
caller to
+ *                 identify different filters, libavcodec itself assigns no 
semantics
+ *                 to this parameter. May be NULL.
+ * @param[in] graph graph in which the new filter will be used
+ *
+ * @note On failure and if filt_ctx is not NULL, *filt_ctx will be set to NULL.
+ * @return a negative AVERROR error code in case of failure, a non negative 
value otherwise
+ */
+int av_bsf_graph_alloc_filter(AVBitStreamFilterContext **filt_ctx,
+                              const AVBitStreamFilter *filter,
+                              const char *name,
+                              AVBitStreamFilterGraph *graph);
+
+/**
+ * A convenience wrapper that allocates and initializes a filter in a single
+ * step. The filter instance is created from the filter filt and inited with 
the
+ * parameter args.
+ *
+ * @param[out] filt_ctx A pointer into which the pointer to the 
newly-allocated context
+ *                      will be written on success. May be NULL. Note that it 
is also
+ *                      retrievable directly through 
AVBitStreamFilterGraph.filters or
+ *                      with @ref av_bsf_graph_get_filter().
+ * @param[in] name the instance name to give to the created filter instance
+ * @param[in] graph_ctx the filter graph
+ * @return a negative AVERROR error code in case of failure, a non negative 
value otherwise
+ *
+ * @note On failure and if filt_ctx is not NULL, *filt_ctx will be set to NULL.
+ * @warning Since the filter is initialized after this function successfully
+ *          returns, you MUST NOT set any further options on it. If you need to
+ *          do that, call ::av_bsf_graph_alloc_filter(), followed by setting
+ *          the options, followed by ::av_bsf_init_dict() instead of this
+ *          function.
+ */
+int av_bsf_graph_create_filter(AVBitStreamFilterContext **filt_ctx,
+                               const AVBitStreamFilter *filt,
+                               const char *name, AVDictionary **options,
+                               AVBitStreamFilterGraph *graph_ctx);
+
+/**
+ * Get a filter instance identified by instance name from graph.
+ *
+ * @param graph filter graph to search through.
+ * @param name filter instance name (should be unique in the graph).
+ * @return the pointer to the found filter instance or NULL if it
+ * cannot be found.
+ */
+AVBitStreamFilterContext *av_bsf_graph_get_filter(AVBitStreamFilterGraph 
*graph, const char *name);
+
+/**
+ * Check validity and configure all the links and formats in the graph.
+ *
+ * @param graphctx the filter graph
+ * @param log_ctx context used for logging
+ * @return >= 0 in case of success, a negative AVERROR code otherwise
+ */
+int av_bsf_graph_config(AVBitStreamFilterGraph *graphctx, void *log_ctx);
+
+/**
+ * Get the index of the source filter in the filtergraph that reported needing
+ * input more urgently.
+ *
+ * @return the index value of a source filter in the filtergraph, or 
AVERROR(EOF)
+ *         if no source is accepting more packets.
+ */
+int av_bsf_graph_source_needs_input(const AVBitStreamFilterGraph *graph);
+
+/**
+ * Free a graph, destroy its links, and set *graph to NULL.
+ * If *graph is NULL, do nothing.
+ */
+void av_bsf_graph_free(AVBitStreamFilterGraph **graph);
+
+/**
+ * @defgroup lavc_bsfgraph_source Packet source API
+ *
+ * The source filter is there to connect filter graphs to applications
+ * They have a single output, connected to the graph, and no input.
+ * Packets must be fed to it using av_bsf_source_add_packet().
+ * @{
+ */
+
+enum {
+    /**
+     * Immediately push the packet to the output.
+     */
+    AV_BSF_SOURCE_FLAG_PUSH = 1 << 0,
+
+    /**
+     * Keep a reference to the packet.
+     */
+    AV_BSF_SOURCE_FLAG_KEEP_REF = 1 << 1,
+};
+
+/**
+ * Initialize the source filter with the provided parameters.
+ * This function may be called multiple times, the later calls override the
+ * previous ones. Some of the parameters may also be set through AVOptions, 
then
+ * whatever method is used last takes precedence.
+ *
+ * @param ctx an instance of the source filter
+ * @param param the stream parameters. The packet later passed to this filter
+ *              must conform to those parameters. All the allocated fields in
+ *              param remain owned by the caller, libavcodec will make internal
+ *              copies or references when necessary.
+ * @return 0 on success, a negative AVERROR code on failure.
+ */
+int av_bsf_source_parameters_set(AVBitStreamFilterContext *ctx, const 
AVCodecParameters *par);
+
+/**
+ * Add a packet to the buffer source.
+ *
+ * By default, this function will take ownership of the reference(s) and reset
+ * the packet. This can be controlled using the flags.
+ *
+ * If this function returns an error, the input packet is not touched.
+ *
+ * @param buffer_src  pointer to a source filter context
+ * @param packet      a packet, or NULL to mark EOF
+ * @param flags       a combination of AV_BSF_FLAG_*
+ * @return            >= 0 in case of success, a negative AVERROR code
+ *                    in case of failure
+ */
+av_warn_unused_result
+int av_bsf_source_add_packet(AVBitStreamFilterContext *ctx, AVPacket *pkt, int 
flags);
+
+/**
+ * Returns 0 or a negative AVERROR code. Currently, this will only ever
+ * return AVERROR(EOF), to indicate that the buffer source has been closed,
+ * either as a result of av_bsf_source_close(), or because the downstream
+ * filter is no longer accepting new data.
+ */
+int av_bsf_source_get_status(AVBitStreamFilterContext *ctx);
+
+/**
+ * Close the source after EOF.
+ *
+ * This is similar to passing NULL to av_bsf_source_add_packet()
+ * except it takes the timestamp of the EOF, i.e. the timestamp of the end
+ * of the last packet.
+ */
+int av_bsf_source_close(AVBitStreamFilterContext *ctx, int64_t pts, unsigned 
flags);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavc_bsfgraph_sink Packet sink API
+ * @{
+ *
+ * The sink filter is there to connect filter graphs to applications
+ * They have a single input, connected to the graph, and no output.
+ * Packets must be extracted using av_bsf_sink_get_packet().
+ */
+
+enum {
+    /**
+     * Tell av_buffersink_get_buffer_ref() to read video/samples buffer
+     * reference, but not remove it from the buffer. This is useful if you
+     * need only to read a video/samples buffer, without to fetch it.
+     */
+    AV_BSF_SINK_FLAG_PEEK = 1 << 0,
+
+    /**
+     * Tell av_bsf_sink_get_packet() not to request a packet from its input.
+     * If a packet is already buffered, it is read (and removed from the 
buffer),
+     * but if no packet is present, return AVERROR(EAGAIN).
+     */
+    AV_BSF_SINK_FLAG_NO_REQUEST = 1 << 1,
+};
+
+/**
+ * Get a packet with filtered data from sink and put it in packet.
+ *
+ * @param ctx    pointer to a sink filter context.
+ * @param packet pointer to an allocated packet that will be filled with data.
+ *               The data must be freed using av_packet_unref() / 
av_packet_free()
+ * @param flags  a combination of AV_BSF_SINK_FLAG_* flags
+ *
+ * @retval AVERROR(EAGAIN) output could not be produced.
+ *                         if AV_BSF_SINK_FLAG_NO_REQUEST was not set,
+ *                         @ref av_bsf_graph_needs_input can be called to
+ *                         know which source needs input more urgently.
+ * @retval >= 0            success
+ * @retval "another negative error code" legitimate error
+ */
+int av_bsf_sink_get_packet(AVBitStreamFilterContext *ctx, AVPacket *pkt, int 
flags);
+
+AVRational av_bsf_sink_get_time_base(const AVBitStreamFilterContext *ctx);
+const AVCodecParameters *av_bsf_sink_get_parameters(const 
AVBitStreamFilterContext *ctx);
+
+/**
+ * @}
+ *
+ * @}
+ *
  * @}
  */
 
diff --git a/libavcodec/bsf/Makefile b/libavcodec/bsf/Makefile
index 981bb8276b..a1c7adea38 100644
--- a/libavcodec/bsf/Makefile
+++ b/libavcodec/bsf/Makefile
@@ -1,6 +1,9 @@
 clean::
        $(RM) $(CLEANSUFFIXES:%=libavcodec/bsf/%)
 
+OBJS +=                                      bsf/sink.o       \
+                                             bsf/source.o
+
 OBJS-$(CONFIG_AAC_ADTSTOASC_BSF)          += bsf/aac_adtstoasc.o
 OBJS-$(CONFIG_AHX_TO_MP2_BSF)             += bsf/ahx_to_mp2.o
 OBJS-$(CONFIG_APV_METADATA_BSF)           += bsf/apv_metadata.o
diff --git a/libavcodec/bsf/filters.h b/libavcodec/bsf/filters.h
new file mode 100644
index 0000000000..84c969c04f
--- /dev/null
+++ b/libavcodec/bsf/filters.h
@@ -0,0 +1,215 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_BSF_FILTERS_H
+#define AVCODEC_BSF_FILTERS_H
+
+#include "libavutil/log.h"
+#include "libavutil/rational.h"
+
+#include "libavcodec/bsf.h"
+#include "libavcodec/codec_par.h"
+#include "libavcodec/packet.h"
+
+/**
+ * Special return code when activate() did not do anything.
+ */
+#define FFERROR_BSF_NOT_READY FFERRTAG('N','R','D','Y')
+#define FFERROR_SOURCE_EMPTY FFERRTAG('M','P','T','Y')
+
+struct AVBitStreamFilterPad {
+    /**
+     * Pad name. The name is unique among inputs and among outputs, but an
+     * input may have the same name as an output. This may be NULL if this
+     * pad has no need to ever be referenced by name.
+     */
+    const char *name;
+
+    /**
+     * A list of codec ids supported by the pad, terminated by
+     * AV_CODEC_ID_NONE.
+     * May be NULL, in that case the pad works with any codec id.
+     */
+    const enum AVCodecID *codec_ids;
+
+    /**
+     * The filter expects writable packets from its input link,
+     * duplicating data buffers if needed.
+     *
+     * input pads only.
+     */
+#define FF_BSF_PAD_FLAG_NEEDS_WRITABLE                  (1 << 0)
+
+    /**
+     * The pad's name is allocated and should be freed generically.
+     */
+#define FF_BSF_PAD_FLAG_FREE_NAME                       (1 << 1)
+
+    /**
+     * A combination of FF_BSF_PAD_FLAG_* flags.
+     */
+    int flags;
+
+    /**
+     * Filtering callback. This is where a filter receives a packet with
+     * audio/video data and should do its processing.
+     *
+     * Input pads only.
+     *
+     * @return >= 0 on success, a negative AVERROR on error. This function
+     * must ensure that packet is properly unreferenced on error if it
+     * hasn't been passed on to another filter.
+     */
+    int (*filter)(AVBitStreamFilterLink *link, AVPacket *pkt);
+
+    /**
+     * Packet request callback. A call to this should result in some progress
+     * towards producing output over the given link. This should return zero
+     * on success, and another value on error.
+     *
+     * Output pads only.
+     */
+    int (*request_packet)(AVBitStreamFilterLink *link);
+
+    /**
+     * Link configuration callback.
+     *
+     * For output pads, this should set the link properties such as
+     * width/height.
+     *
+     * For input pads, this should check the properties of the link, and update
+     * the filter's internal state as necessary.
+     *
+     * For both input and output filters, this should return zero on success,
+     * and another value on error.
+     */
+    int (*config_props)(AVBitStreamFilterLink *link);
+};
+
+/**
+ * Link properties exposed to filter code, but not external callers.
+ */
+typedef struct AVBitStreamFilterLink {
+    AVBitStreamFilterContext *src; ///< source filter
+    AVBitStreamFilterPad  *srcpad; ///< output pad on the source filter
+
+    AVBitStreamFilterContext *dst; ///< dest filter
+    AVBitStreamFilterPad  *dstpad; ///< input pad on the dest filter
+
+    /**
+     * Graph the filter belongs to.
+     */
+    struct AVBitStreamFilterGraph *graph;
+
+
+    AVCodecParameters *par;
+
+    AVRational time_base;
+
+    /**
+     * Current timestamp of the link, as defined by the most recent
+     * packet(s), in link time_base units.
+     */
+    int64_t current_pts;
+
+    /**
+     * Current timestamp of the link, as defined by the most recent
+     * packet(s), in AV_TIME_BASE units.
+     */
+    int64_t current_pts_us;
+
+    /**
+     * Number of past packets sent through the link.
+     */
+    int64_t packet_count_in, packet_count_out;
+} AVBitStreamFilterLink;
+
+#define BSFILTER_INOUTPADS(inout, array) \
+       .inout        = array, \
+       .nb_ ## inout = FF_ARRAY_ELEMS(array)
+#define BSFILTER_INPUTS(array)  BSFILTER_INOUTPADS(inputs,  (array))
+#define BSFILTER_OUTPUTS(array) BSFILTER_INOUTPADS(outputs, (array))
+
+extern const AVBitStreamFilterPad ff_default_bsf_pad[1];
+
+#define BSF_DEFINE_CLASS_EXT(name, desc, options) \
+    static const AVClass name##_class = {       \
+        .class_name = desc,                     \
+        .item_name  = av_default_item_name,     \
+        .option     = options,                  \
+        .version    = LIBAVUTIL_VERSION_INT,    \
+        .category   = AV_CLASS_CATEGORY_BITSTREAM_FILTER, \
+    }
+#define BSF_DEFINE_CLASS(fname) \
+    BSF_DEFINE_CLASS_EXT(fname, #fname, fname##_options)
+
+/**
+ * Mark a filter ready and schedule it for activation.
+ *
+ * This is automatically done when something happens to the filter (queued
+ * packet, status change, request on output).
+ * Filters implementing the activate callback can call it directly to
+ * perform one more round of processing later.
+ * It is also useful for filters reacting to external or asynchronous
+ * events.
+ */
+void ff_bsf_set_ready(AVBitStreamFilterContext *filter, unsigned priority);
+
+/**
+ * Send a packet of data to the next filter.
+ *
+ * @param link   the output link over which the data is being sent
+ * @param pkt   a reference to the buffer of data being sent. The
+ *              receiving filter will free this reference when it no longer
+ *              needs it or pass it on to the next filter.
+ *
+ * @return >= 0 on success, a negative AVERROR on error. The receiving filter
+ * is responsible for unreferencing pkt in case of error.
+ */
+int ff_bsf_filter_packet(AVBitStreamFilterLink *link, AVPacket *pkt);
+
+int ff_bsf_request_packet(AVBitStreamFilterLink *link);
+
+int ff_bsf_inlink_consume_packet(AVBitStreamFilterLink *link, AVPacket **pkt);
+
+void ff_bsf_inlink_request_packet(AVBitStreamFilterLink *link);
+
+int ff_bsf_inlink_acknowledge_status(AVBitStreamFilterLink *link, int 
*rstatus, int64_t *rpts);
+
+size_t ff_bsf_inlink_queued_packets(AVBitStreamFilterLink *link);
+
+int ff_bsf_inlink_check_available_packet(AVBitStreamFilterLink *link);
+
+void ff_bsf_inlink_set_status(AVBitStreamFilterLink *link, int status);
+
+void ff_bsf_link_set_in_status(AVBitStreamFilterLink *link, int status, 
int64_t pts);
+
+int ff_bsf_outlink_get_status(AVBitStreamFilterLink *link);
+
+int ff_bsf_outlink_packet_wanted(AVBitStreamFilterLink *link);
+
+/**
+ * Get the number of failed requests.
+ *
+ * A failed request is when the request_packet method is called while no
+ * packet is present in the buffer.
+ * The number is reset when a packet is added.
+ */
+unsigned ff_bsf_source_get_nb_failed_requests(const AVBitStreamFilterContext 
*src);
+
+#endif /* AVCODEC_BSF_FILTERS_H */
diff --git a/libavcodec/bsf/sink.c b/libavcodec/bsf/sink.c
new file mode 100644
index 0000000000..cc8c9cda01
--- /dev/null
+++ b/libavcodec/bsf/sink.c
@@ -0,0 +1,148 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Packet sink. Heavily based on libavfilter/buffersink.c
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/opt.h"
+
+#include "libavcodec/bsf.h"
+#include "libavcodec/bsf_internal.h"
+#include "libavcodec/packet.h"
+#include "libavcodec/packet_internal.h"
+
+typedef struct BufferSinkContext {
+    const AVClass *class;
+    unsigned warning_limit;
+
+    AVPacket *peeked_pkt;
+} BufferSinkContext;
+
+static int return_or_keep_packet(BufferSinkContext *buf, AVPacket *out, 
AVPacket *in, int flags)
+{
+    if ((flags & AV_BSF_SINK_FLAG_PEEK)) {
+        buf->peeked_pkt = in;
+        return av_packet_ref(out, in);
+    } else {
+        buf->peeked_pkt = NULL;
+        av_packet_move_ref(out, in);
+        av_packet_free(&in);
+        return 0;
+    }
+}
+
+int attribute_align_arg av_bsf_sink_get_packet(AVBitStreamFilterContext *ctx, 
AVPacket *pkt, int flags)
+{
+    BufferSinkContext *buf = ctx->priv_data;
+    AVBitStreamFilterLink *inlink = ctx->inputs[0];
+    BitStreamFilterLinkInternal *li = ff_link_internal(inlink);
+    int status, ret;
+    AVPacket *cur_pkt;
+    int64_t pts;
+    int buffersrc_empty = 0;
+
+    if (buf->peeked_pkt)
+        return return_or_keep_packet(buf, pkt, buf->peeked_pkt, flags);
+
+    while (1) {
+        ret = ff_bsf_inlink_consume_packet(inlink, &cur_pkt);
+        if (ret < 0) {
+            return ret;
+        } else if (ret) {
+            return return_or_keep_packet(buf, pkt, cur_pkt, flags);
+        } else if (ff_bsf_inlink_acknowledge_status(inlink, &status, &pts)) {
+            return status;
+        } else if ((flags & AV_BSF_SINK_FLAG_NO_REQUEST)) {
+            return AVERROR(EAGAIN);
+        } else if (li->packet_wanted_out) {
+            ret = ff_bsf_graph_run_once(ctx->graph);
+            if (ret == FFERROR_SOURCE_EMPTY) {
+                buffersrc_empty = 1;
+            } else if (ret == AVERROR(EAGAIN)) {
+                if (buffersrc_empty)
+                    return ret;
+                ff_bsf_inlink_request_packet(inlink);
+            } else if (ret < 0) {
+                return ret;
+            }
+        } else {
+            ff_bsf_inlink_request_packet(inlink);
+        }
+    }
+}
+
+static int init(AVBitStreamFilterContext *ctx)
+{
+    BufferSinkContext *s = ctx->priv_data;
+
+    s->warning_limit = 100;
+
+    return 0;
+}
+
+static void uninit(AVBitStreamFilterContext *ctx)
+{
+    BufferSinkContext *buf = ctx->priv_data;
+
+    av_packet_free(&buf->peeked_pkt);
+}
+
+static int activate(AVBitStreamFilterContext *ctx)
+{
+    BufferSinkContext *buf = ctx->priv_data;
+    BitStreamFilterLinkInternal * const li = ff_link_internal(ctx->inputs[0]);
+
+    if (buf->warning_limit &&
+        av_container_fifo_can_read(li->fifo) >= buf->warning_limit) {
+        av_log(ctx, AV_LOG_WARNING,
+               "%d buffers queued in %s, something may be wrong.\n",
+               buf->warning_limit,
+               (char *)av_x_if_null(ctx->name, ctx->filter->name));
+        buf->warning_limit *= 10;
+    }
+
+    /* The packet is queued, the rest is up to av_bsf_sink_get_packet */
+    return 0;
+}
+
+AVRational av_bsf_sink_get_time_base(const AVBitStreamFilterContext *ctx)
+{
+    av_assert0(ff_bsf(ctx->filter)->activate == activate);
+    return ctx->inputs[0]->time_base;
+}
+
+const AVCodecParameters *av_bsf_sink_get_parameters(const 
AVBitStreamFilterContext *ctx)
+{
+    av_assert0(ff_bsf(ctx->filter)->activate == activate);
+    return ctx->inputs[0]->par;
+}
+
+BSF_DEFINE_CLASS_EXT(sink, "sink", NULL);
+
+const FFBitStreamFilter ff_sink_bsf = {
+    .p.name        = "sink",
+    .p.priv_class  = &sink_class,
+    .priv_data_size = sizeof(BufferSinkContext),
+    .init2         = init,
+    .uninit        = uninit,
+    .activate      = activate,
+    BSFILTER_INPUTS(ff_default_bsf_pad),
+};
diff --git a/libavcodec/bsf/source.c b/libavcodec/bsf/source.c
new file mode 100644
index 0000000000..896538e5c9
--- /dev/null
+++ b/libavcodec/bsf/source.c
@@ -0,0 +1,222 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Packet source. Heavily based on libavfilter/buffersrc.c
+ */
+
+#include <float.h>
+
+#include "libavutil/container_fifo.h"
+#include "libavutil/opt.h"
+
+#include "libavcodec/bsf.h"
+#include "libavcodec/bsf_internal.h"
+#include "libavcodec/packet.h"
+#include "libavcodec/packet_internal.h"
+
+typedef struct SourceContext {
+    const AVClass    *class;
+    AVCodecParameters *par;
+    AVRational        time_base;     ///< time_base to set in the output link
+
+    unsigned          nb_failed_requests;
+    unsigned          warning_limit;
+
+    int eof;
+    int64_t last_pts;
+} SourceContext;
+
+int av_bsf_source_parameters_set(AVBitStreamFilterContext *ctx, const 
AVCodecParameters *par)
+{
+    SourceContext *s = ctx->priv_data;
+
+    return avcodec_parameters_copy(s->par, par);
+}
+
+static int push_packet(AVBitStreamFilterGraph *graph)
+{
+    int ret;
+
+    while (1) {
+        ret = ff_bsf_graph_run_once(graph);
+        if (ret == AVERROR(EAGAIN))
+            break;
+        if (ret < 0 && ret != FFERROR_SOURCE_EMPTY)
+            return ret;
+    }
+    return 0;
+}
+
+int attribute_align_arg av_bsf_source_add_packet(AVBitStreamFilterContext 
*ctx, AVPacket *pkt, int flags)
+{
+    SourceContext *s = ctx->priv_data;
+    AVPacket *copy;
+    int ret;
+
+    s->nb_failed_requests = 0;
+
+    if (!pkt || AVPACKET_IS_EMPTY(pkt))
+        return av_bsf_source_close(ctx, s->last_pts, flags);
+    if (s->eof)
+        return AVERROR_EOF;
+
+    s->last_pts = pkt->pts + pkt->duration;
+
+    copy = av_packet_alloc();
+    if (!copy)
+        return AVERROR(ENOMEM);
+
+    if ((flags & AV_BSF_SOURCE_FLAG_KEEP_REF)) {
+        ret = av_packet_ref(copy, pkt);
+        if (ret < 0)
+            return ret;
+    } else
+        av_packet_move_ref(copy, pkt);
+
+    ret = ff_bsf_filter_packet(ctx->outputs[0], copy);
+    if (ret < 0)
+        return ret;
+
+    if ((flags & AV_BSF_SOURCE_FLAG_PUSH)) {
+        ret = push_packet(ctx->graph);
+        if (ret < 0)
+            return ret;
+    }
+
+    BitStreamFilterLinkInternal *const li = ff_link_internal(ctx->outputs[0]);
+    if (s->warning_limit &&
+        av_container_fifo_can_read(li->fifo) >= s->warning_limit) {
+        av_log(s, AV_LOG_WARNING,
+               "%d buffers queued in %s, something may be wrong.\n",
+               s->warning_limit,
+               (char *)av_x_if_null(ctx->name, ctx->filter->name));
+        s->warning_limit *= 10;
+    }
+
+    return 0;
+}
+
+int av_bsf_source_close(AVBitStreamFilterContext *ctx, int64_t pts, unsigned 
flags)
+{
+    SourceContext *s = ctx->priv_data;
+
+    s->eof = 1;
+    ff_bsf_link_set_in_status(ctx->outputs[0], AVERROR_EOF, pts);
+    return 0;
+}
+
+int av_bsf_source_get_status(AVBitStreamFilterContext *ctx)
+{
+    SourceContext *s = ctx->priv_data;
+
+    if (!s->eof && ff_bsf_outlink_get_status(ctx->outputs[0]))
+        s->eof = 1;
+
+    return s->eof ? AVERROR(EOF) : 0;
+}
+
+static av_cold int preinit(AVBitStreamFilterContext *ctx)
+{
+    SourceContext *c = ctx->priv_data;
+
+    c->par = avcodec_parameters_alloc();
+    if (!c->par)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static av_cold int init(AVBitStreamFilterContext *ctx)
+{
+    SourceContext *c = ctx->priv_data;
+
+    if (av_q2d(c->time_base) <= 0) {
+        av_log(ctx, AV_LOG_ERROR, "Invalid time base %d/%d\n", 
c->time_base.num, c->time_base.den);
+        return AVERROR(EINVAL);
+    }
+
+    c->warning_limit = 100;
+    return 0;
+}
+
+unsigned ff_bsf_source_get_nb_failed_requests(const AVBitStreamFilterContext 
*buffer_src)
+{
+    return ((SourceContext *)buffer_src->priv_data)->nb_failed_requests;
+}
+
+#define OFFSET(x) offsetof(SourceContext, x)
+#define FLAGS 
(AV_OPT_FLAG_BSF_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_VIDEO_PARAM)
+static const AVOption buffer_options[] = {
+    { "time_base", NULL, OFFSET(time_base), AV_OPT_TYPE_RATIONAL, { .dbl = 0 
}, 0, DBL_MAX, FLAGS },
+    { NULL },
+};
+
+BSF_DEFINE_CLASS(buffer);
+
+static av_cold void uninit(AVBitStreamFilterContext *ctx)
+{
+    SourceContext *s = ctx->priv_data;
+    avcodec_parameters_free(&s->par);
+}
+
+static int config_props(AVBitStreamFilterLink *link)
+{
+    SourceContext *c = link->src->priv_data;
+
+    link->time_base = c->time_base;
+    return avcodec_parameters_copy(link->par, c->par);
+}
+
+static int activate(AVBitStreamFilterContext *ctx)
+{
+    AVBitStreamFilterLink *outlink = ctx->outputs[0];
+    SourceContext *c = ctx->priv_data;
+
+    if (!c->eof && ff_bsf_outlink_get_status(outlink)) {
+        c->eof = 1;
+        return 0;
+    }
+
+    if (c->eof) {
+        ff_bsf_link_set_in_status(outlink, AVERROR_EOF, c->last_pts);
+        return 0;
+    }
+    c->nb_failed_requests++;
+    return FFERROR_SOURCE_EMPTY;
+}
+
+static const AVBitStreamFilterPad source_outputs[] = {
+    {
+        .name          = "default",
+        .config_props  = config_props,
+    },
+};
+
+const FFBitStreamFilter ff_source_bsf = {
+    .p.name        = "source",
+    .p.priv_class  = &buffer_class,
+    .priv_data_size = sizeof(SourceContext),
+    .activate  = activate,
+    .preinit   = preinit,
+    .init2     = init,
+    .uninit    = uninit,
+
+    BSFILTER_OUTPUTS(source_outputs),
+};
diff --git a/libavcodec/bsf_internal.h b/libavcodec/bsf_internal.h
index 922b03c01b..6bec1cbea7 100644
--- a/libavcodec/bsf_internal.h
+++ b/libavcodec/bsf_internal.h
@@ -19,10 +19,12 @@
 #ifndef AVCODEC_BSF_INTERNAL_H
 #define AVCODEC_BSF_INTERNAL_H
 
+#include "libavutil/container_fifo.h"
 #include "libavutil/log.h"
 
 #include "bsf.h"
 #include "packet.h"
+#include "bsf/filters.h"
 
 typedef struct FFBitStreamFilter {
     /**
@@ -35,8 +37,53 @@ typedef struct FFBitStreamFilter {
     int (*filter)(AVBSFContext *ctx, AVPacket *pkt);
     void (*close)(AVBSFContext *ctx);
     void (*flush)(AVBSFContext *ctx);
+
+    // Graph based API
+
+    /**
+     * List of static inputs.
+     */
+    const struct AVBitStreamFilterPad *inputs;
+
+    /**
+     * List of static outputs.
+     */
+    const struct AVBitStreamFilterPad *outputs;
+
+    /**
+     * The number of entries in the list of inputs.
+     */
+    uint8_t nb_inputs;
+
+    /**
+     * The number of entries in the list of outputs.
+     */
+    uint8_t nb_outputs;
+
+    int (*preinit)(AVBitStreamFilterContext *ctx);
+    int (*init2)(AVBitStreamFilterContext *ctx);
+    void (*uninit)(AVBitStreamFilterContext *ctx);
+
+    /**
+     * Filter activation function.
+     *
+     * Called when any processing is needed from the filter, instead of any
+     * filter_packet and request_packet on pads.
+     *
+     * The function must examine inlinks and outlinks and perform a single
+     * step of processing. If there is nothing to do, the function must do
+     * nothing and not return an error. If more steps are or may be
+     * possible, it must use ff_filter_set_ready() to schedule another
+     * activation.
+     */
+    int (*activate)(AVBitStreamFilterContext *ctx);
 } FFBitStreamFilter;
 
+static av_always_inline const FFBitStreamFilter *ff_bsf(const 
AVBitStreamFilter *bsf)
+{
+    return (const FFBitStreamFilter*)bsf;
+}
+
 /**
  * Called by the bitstream filters to get the next packet for filtering.
  * The filter is responsible for either freeing the packet or passing it to the
@@ -57,4 +104,149 @@ int ff_bsf_get_packet_ref(AVBSFContext *ctx, AVPacket 
*pkt);
 
 const AVClass *ff_bsf_child_class_iterate(void **opaque);
 
+
+// Graph based API
+
+typedef struct BitStreamFilterLinkInternal {
+    AVBitStreamFilterLink l;
+
+    /**
+     * Queue of packets waiting to be filtered.
+     */
+    AVContainerFifo *fifo;
+
+    /**
+     * If set, the source filter can not generate a packet as is.
+     * The goal is to avoid repeatedly calling the request_packet() method on
+     * the same link.
+     */
+    int packet_blocked_in;
+
+    /**
+     * Link input status.
+     * If not zero, all attempts of filter_packet will fail with the
+     * corresponding code.
+     */
+    int status_in;
+
+    /**
+     * Timestamp of the input status change.
+     */
+    int64_t status_in_pts;
+
+    /**
+     * Link output status.
+     * If not zero, all attempts of request_packet will fail with the
+     * corresponding code.
+     */
+    int status_out;
+
+    /**
+     * True if a packet is currently wanted on the output of this filter.
+     * Set when ff_request_packet() is called by the output,
+     * cleared when a packet is filtered.
+     */
+    int packet_wanted_out;
+
+    /**
+     * Index in the age array.
+     */
+    int age_index;
+
+    /** stage of the initialization of the link properties (dimensions, etc) */
+    enum {
+        AVLINK_UNINIT = 0,      ///< not started
+        AVLINK_STARTINIT,       ///< started, but incomplete
+        AVLINK_INIT             ///< complete
+    } init_state;
+} BitStreamFilterLinkInternal;
+
+static inline BitStreamFilterLinkInternal 
*ff_link_internal(AVBitStreamFilterLink *link)
+{
+    return (BitStreamFilterLinkInternal*)link;
+}
+
+int ff_bsf_config_links(AVBitStreamFilterContext *filter);
+
+/**
+ * Run one round of processing on a filter graph.
+ */
+int ff_bsf_graph_run_once(AVBitStreamFilterGraph *graph);
+
+int ff_bsf_activate(AVBitStreamFilterContext *ctx);
+
+void ff_bsf_graph_update_heap(AVBitStreamFilterGraph *graph, 
BitStreamFilterLinkInternal *li);
+
+typedef struct FFBitStreamFilterContext {
+    /**
+     * The public AVBitStreamFilterContext. See bsf.h for it.
+     */
+    AVBitStreamFilterContext p;
+
+    // AV_CLASS_STATE_FLAG_*
+    unsigned state_flags;
+
+    /**
+     * Ready status of the filter.
+     * A non-0 value means that the filter needs activating;
+     * a higher value suggests a more urgent activation.
+     */
+    unsigned ready;
+} FFBitStreamFilterContext;
+
+static inline FFBitStreamFilterContext *ffbsfctx(AVBitStreamFilterContext *ctx)
+{
+    return (FFBitStreamFilterContext*)ctx;
+}
+
+/**
+ * Free a filter context. This will also remove the filter from its
+ * filtergraph's list of filters.
+ *
+ * @param filter the filter to free
+ */
+void ff_bsf_free(AVBitStreamFilterContext *filter);
+
+typedef struct FFBitStreamFilterGraph {
+    /**
+     * The public AVBitStreamFilterGraph. See bsf.h for it.
+     */
+    AVBitStreamFilterGraph p;
+
+    struct BitStreamFilterLinkInternal **sink_links;
+    struct BitStreamFilterLinkInternal **source_links;
+    int sink_links_count;
+    int source_links_count;
+
+    size_t max_packet_queue;
+    size_t packets_queued;
+} FFBitStreamFilterGraph;
+
+static inline FFBitStreamFilterGraph *ffbsffiltergraph(AVBitStreamFilterGraph 
*graph)
+{
+    return (FFBitStreamFilterGraph*)graph;
+}
+
+static inline const FFBitStreamFilterGraph *cffbsffiltergraph(const 
AVBitStreamFilterGraph *graph)
+{
+    return (const FFBitStreamFilterGraph*)graph;
+}
+
+/**
+ * Allocate a new filter context and return it.
+ *
+ * @param[in]  filter what filter to create an instance of
+ * @param[in]  inst_name name to give to the new filter context
+ * @param[out] ctx a pointer into which the pointer to the newly-allocated 
context
+ *                 will be written
+ *
+ * @return 0 on success, an AVERROR code on failure
+ */
+int ff_bsf_alloc(const AVBitStreamFilter *filter, const char *inst_name, 
AVBitStreamFilterContext **ctx);
+
+/**
+ * Remove a filter from a graph;
+ */
+void ff_bsf_graph_remove_filter(AVBitStreamFilterGraph *graph, 
AVBitStreamFilterContext *filter);
+
 #endif /* AVCODEC_BSF_INTERNAL_H */
diff --git a/libavcodec/bsfgraph.c b/libavcodec/bsfgraph.c
new file mode 100644
index 0000000000..850b1a8456
--- /dev/null
+++ b/libavcodec/bsfgraph.c
@@ -0,0 +1,404 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/error.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+
+#include "bsf.h"
+#include "bsf_internal.h"
+
+#define OFFSET(x) offsetof(AVBitStreamFilterGraph, x)
+#define FLAGS 
(AV_OPT_FLAG_BSF_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_VIDEO_PARAM)
+static const AVOption filtergraph_options[] = {
+    {"max_buffered_packets"  , "maximum number of buffered packets allowed", 
OFFSET(max_buffered_packets),
+        AV_OPT_TYPE_UINT,   {.i64 = 0}, 0, UINT_MAX, FLAGS },
+    { NULL },
+};
+
+static const AVClass filtergraph_class = {
+    .class_name = "AVBitStreamFilterGraph",
+    .item_name  = av_default_item_name,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .option     = filtergraph_options,
+    .category   = AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+};
+
+AVBitStreamFilterGraph *av_bsf_graph_alloc(void)
+{
+    FFBitStreamFilterGraph *graph = av_mallocz(sizeof(*graph));
+    AVBitStreamFilterGraph *ret;
+
+    if (!graph)
+        return NULL;
+
+    ret = &graph->p;
+    ret->av_class = &filtergraph_class;
+    av_opt_set_defaults(ret);
+    graph->max_packet_queue = SIZE_MAX;
+
+    return ret;
+}
+
+void ff_bsf_graph_remove_filter(AVBitStreamFilterGraph *graph, 
AVBitStreamFilterContext *filter)
+{
+    int i, j;
+    for (i = 0; i < graph->nb_filters; i++) {
+        if (graph->filters[i] == filter) {
+            FFSWAP(AVBitStreamFilterContext*, graph->filters[i],
+                   graph->filters[graph->nb_filters - 1]);
+            graph->nb_filters--;
+            filter->graph = NULL;
+            for (j = 0; j<filter->nb_outputs; j++)
+                if (filter->outputs[j])
+                    filter->outputs[j]->graph = NULL;
+
+            return;
+        }
+    }
+}
+
+AVBitStreamFilterContext *av_bsf_graph_get_filter(AVBitStreamFilterGraph 
*graph, const char *name)
+{
+    int i;
+
+    for (i = 0; i < graph->nb_filters; i++)
+        if (graph->filters[i]->name && !strcmp(name, graph->filters[i]->name))
+            return graph->filters[i];
+
+    return NULL;
+}
+
+void av_bsf_graph_free(AVBitStreamFilterGraph **graphp)
+{
+    AVBitStreamFilterGraph *graph = *graphp;
+    FFBitStreamFilterGraph *graphi = ffbsffiltergraph(graph);
+
+    if (!graph)
+        return;
+
+    while (graph->nb_filters)
+        ff_bsf_free(graph->filters[0]);
+
+    av_freep(&graphi->sink_links);
+    av_freep(&graphi->source_links);
+
+    av_opt_free(graph);
+
+    av_freep(&graph->filters);
+    av_freep(graphp);
+}
+
+int av_bsf_graph_create_filter(AVBitStreamFilterContext **filt_ctx, const 
AVBitStreamFilter *filt,
+                               const char *name, AVDictionary **options, 
AVBitStreamFilterGraph *graph_ctx)
+{
+    AVBitStreamFilterContext *s;
+    int ret;
+
+    ret = av_bsf_graph_alloc_filter(&s, filt, name, graph_ctx);
+    if (ret < 0)
+        return ret;
+
+    ret = av_bsf_init_dict(s, options);
+    if (ret < 0)
+        goto fail;
+
+    if (filt_ctx)
+        *filt_ctx = s;
+
+    return 0;
+
+fail:
+    ff_bsf_free(s);
+    if (filt_ctx)
+        *filt_ctx = NULL;
+    return ret;
+}
+
+int av_bsf_graph_alloc_filter(AVBitStreamFilterContext **filt_ctx,
+                              const AVBitStreamFilter *filter,
+                              const char *name,
+                              AVBitStreamFilterGraph *graph)
+{
+    AVBitStreamFilterContext **filters, *s;
+    int ret;
+
+    if (!ff_bsf(filter)->activate && !ff_bsf(filter)->nb_inputs && 
!ff_bsf(filter)->nb_outputs)
+        return AVERROR(ENOTSUP);
+
+    filters = av_realloc_array(graph->filters, graph->nb_filters + 1, 
sizeof(*filters));
+    if (!filters)
+        return AVERROR(ENOMEM);
+    graph->filters = filters;
+
+    ret = ff_bsf_alloc(filter, name, &s);
+    if (ret < 0)
+        return ret;
+
+    graph->filters[graph->nb_filters++] = s;
+
+    s->graph = graph;
+
+    if (filt_ctx)
+        *filt_ctx = s;
+
+    return ret;
+}
+
+/**
+ * Check for the validity of graph.
+ *
+ * A graph is considered valid if all its input and output pads are
+ * connected.
+ *
+ * @return >= 0 in case of success, a negative value otherwise
+ */
+static int graph_check_validity(AVBitStreamFilterGraph *graph, void *log_ctx)
+{
+    AVBitStreamFilterContext *filt;
+    int i, j;
+
+    for (i = 0; i < graph->nb_filters; i++) {
+        const AVBitStreamFilterPad *pad;
+        filt = graph->filters[i];
+
+        for (j = 0; j < filt->nb_inputs; j++) {
+            if (!filt->inputs[j] || !filt->inputs[j]->src) {
+                pad = &filt->input_pads[j];
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Input pad \"%s\" of the filter instance \"%s\" of %s 
not connected to any source\n",
+                       pad->name, filt->name, filt->filter->name);
+                return AVERROR(EINVAL);
+            }
+        }
+
+        for (j = 0; j < filt->nb_outputs; j++) {
+            if (!filt->outputs[j] || !filt->outputs[j]->dst) {
+                pad = &filt->output_pads[j];
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Output pad \"%s\" of the filter instance \"%s\" of %s 
not connected to any destination\n",
+                       pad->name, filt->name, filt->filter->name);
+                return AVERROR(EINVAL);
+            }
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Configure all the links of graphctx.
+ *
+ * @return >= 0 in case of success, a negative value otherwise
+ */
+static int graph_config_links(AVBitStreamFilterGraph *graph, void *log_ctx)
+{
+    AVBitStreamFilterContext *filt;
+    int i, ret;
+
+    for (i = 0; i < graph->nb_filters; i++) {
+        filt = graph->filters[i];
+
+        if (!filt->nb_outputs) {
+            if ((ret = ff_bsf_config_links(filt)))
+                return ret;
+        }
+    }
+
+    return 0;
+}
+
+static int graph_config_pointers(AVBitStreamFilterGraph *graph, void *log_ctx)
+{
+    unsigned i, j;
+    int sink_links_count = 0, source_links_count = 0, n = 0;
+    AVBitStreamFilterContext *f;
+    BitStreamFilterLinkInternal **sinks, **sources;
+
+    for (i = 0; i < graph->nb_filters; i++) {
+        f = graph->filters[i];
+        for (j = 0; j < f->nb_inputs; j++) {
+            ff_link_internal(f->inputs[j])->age_index  = -1;
+        }
+        for (j = 0; j < f->nb_outputs; j++) {
+            ff_link_internal(f->outputs[j])->age_index = -1;
+        }
+        if (!f->nb_outputs) {
+            if (f->nb_inputs > INT_MAX - sink_links_count)
+                return AVERROR(EINVAL);
+            sink_links_count += f->nb_inputs;
+        }
+        if (!f->nb_inputs && !strcmp(f->filter->name, "source")) {
+            if (f->nb_outputs > INT_MAX - source_links_count)
+                return AVERROR(EINVAL);
+            source_links_count += f->nb_outputs;
+        }
+    }
+    sinks = av_calloc(sink_links_count, sizeof(*sinks));
+    if (!sinks)
+        return AVERROR(ENOMEM);
+    for (i = 0; i < graph->nb_filters; i++) {
+        f = graph->filters[i];
+        if (!f->nb_outputs) {
+            for (j = 0; j < f->nb_inputs; j++) {
+                sinks[n] = ff_link_internal(f->inputs[j]);
+                sinks[n]->age_index = n;
+                n++;
+            }
+        }
+    }
+    av_assert0(n == sink_links_count);
+    ffbsffiltergraph(graph)->sink_links       = sinks;
+    ffbsffiltergraph(graph)->sink_links_count = sink_links_count;
+
+    sources = av_calloc(source_links_count, sizeof(*sources));
+    if (!sources)
+        return AVERROR(ENOMEM);
+    for (i = 0, n = 0; i < graph->nb_filters; i++) {
+        f = graph->filters[i];
+        if (!f->nb_inputs && !strcmp(f->filter->name, "source")) {
+            for (j = 0; j < f->nb_outputs; j++) {
+                sources[n] = ff_link_internal(f->outputs[j]);
+                n++;
+            }
+        }
+    }
+    av_assert0(n == source_links_count);
+    ffbsffiltergraph(graph)->source_links       = sources;
+    ffbsffiltergraph(graph)->source_links_count = source_links_count;
+
+    return 0;
+}
+
+int av_bsf_graph_config(AVBitStreamFilterGraph *graphctx, void *log_ctx)
+{
+    int ret;
+
+    if (graphctx->max_buffered_packets)
+        ffbsffiltergraph(graphctx)->max_packet_queue = 
graphctx->max_buffered_packets;
+    if ((ret = graph_check_validity(graphctx, log_ctx)))
+        return ret;
+    if ((ret = graph_config_links(graphctx, log_ctx)))
+        return ret;
+    if ((ret = graph_config_pointers(graphctx, log_ctx)))
+        return ret;
+
+    return 0;
+}
+
+static void heap_bubble_up(FFBitStreamFilterGraph *graph,
+                           BitStreamFilterLinkInternal *li, int index)
+{
+    BitStreamFilterLinkInternal **links = graph->sink_links;
+
+    av_assert0(index >= 0);
+
+    while (index) {
+        int parent = (index - 1) >> 1;
+        if (links[parent]->l.current_pts_us >= li->l.current_pts_us)
+            break;
+        links[index] = links[parent];
+        links[index]->age_index = index;
+        index = parent;
+    }
+    links[index] = li;
+    li->age_index = index;
+}
+
+static void heap_bubble_down(FFBitStreamFilterGraph *graph,
+                             BitStreamFilterLinkInternal *li, int index)
+{
+    BitStreamFilterLinkInternal **links = graph->sink_links;
+
+    av_assert0(index >= 0);
+
+    while (1) {
+        int child = 2 * index + 1;
+        if (child >= graph->sink_links_count)
+            break;
+        if (child + 1 < graph->sink_links_count &&
+            links[child + 1]->l.current_pts_us < 
links[child]->l.current_pts_us)
+            child++;
+        if (li->l.current_pts_us < links[child]->l.current_pts_us)
+            break;
+        links[index] = links[child];
+        links[index]->age_index = index;
+        index = child;
+    }
+    links[index] = li;
+    li->age_index = index;
+}
+
+void ff_bsf_graph_update_heap(AVBitStreamFilterGraph *graph, 
BitStreamFilterLinkInternal *li)
+{
+    FFBitStreamFilterGraph  *graphi = ffbsffiltergraph(graph);
+
+    heap_bubble_up  (graphi, li, li->age_index);
+    heap_bubble_down(graphi, li, li->age_index);
+}
+
+int ff_bsf_graph_run_once(AVBitStreamFilterGraph *graph)
+{
+    FFBitStreamFilterContext *ctxi;
+    unsigned i;
+
+    av_assert0(graph->nb_filters);
+    ctxi = ffbsfctx(graph->filters[0]);
+    for (i = 1; i < graph->nb_filters; i++) {
+        FFBitStreamFilterContext *ctxi_other = ffbsfctx(graph->filters[i]);
+
+        if (ctxi_other->ready > ctxi->ready)
+            ctxi = ctxi_other;
+    }
+
+    if (!ctxi->ready)
+        return AVERROR(EAGAIN);
+
+    ctxi->ready = 0;
+
+    return ff_bsf_activate(&ctxi->p);
+}
+
+int av_bsf_graph_source_needs_input(const AVBitStreamFilterGraph *graph)
+{
+    const FFBitStreamFilterGraph *graphi = cffbsffiltergraph(graph);
+    int nb_requests, nb_requests_max = -1;
+    int best_input = AVERROR(EOF);
+
+    for (int i = 0; i < graphi->source_links_count; i++) {
+        const BitStreamFilterLinkInternal *sourcei = graphi->source_links[i];
+        const AVBitStreamFilterLink *source = &sourcei->l;
+
+        if (av_bsf_source_get_status(source->src) == AVERROR(EOF))
+            continue;
+
+        nb_requests = ff_bsf_source_get_nb_failed_requests(source->src);
+        if (nb_requests > nb_requests_max) {
+            nb_requests_max = nb_requests;
+            best_input = i;
+        }
+    }
+
+    return best_input;
+}
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 43794ea588..06631ffa8c 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
 
 #include "version_major.h"
 
-#define LIBAVCODEC_VERSION_MINOR   3
+#define LIBAVCODEC_VERSION_MINOR   4
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to