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]
