From: Clément Bœsch <clem...@stupeflix.com> --- No documentation so the concept is simply to map N streams to M other streams, with the possibility to live configure the filter (process_command).
For example, let's say you have 2 a/v inputs and you want to live switch between the two, you will do something like: ffmpeg -i ... -i ... -lavfi "[0:0][0:1][1:0][1:1]streamselect=in=vava:out=va:map=0 1" then send commands such as "map 0 1" or "map 2 3" to select one or another source. This is a WIP because I have a few issues right now (and also because mostly untested): - I always receive frames from the first input link... I suppose I need to write a "smart" request_frame callback, but things changed recently so maybe there are new guidelines for this? Nicolas, can I call for your help again? - the command input in ffmpeg cli doesn't work anymore here (reproduce with: `ffmpeg -f lavfi -i testsrc -f null -` and press 'c': can't type anything then). I hacked something around to be able to send commands. - sendcmd/zmq filters seem to need to be in the filtergraph itself to allow forwarding commands, and it doesn't seem possible to fit well with "multimedia" filters (we have sendcmd/asendcmd zmq/azmq) --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/avf_streamselect.c | 215 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 libavfilter/avf_streamselect.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 1f4abeb..f2e6b0d 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -281,6 +281,7 @@ OBJS-$(CONFIG_SHOWSPECTRUM_FILTER) += avf_showspectrum.o OBJS-$(CONFIG_SHOWVOLUME_FILTER) += avf_showvolume.o OBJS-$(CONFIG_SHOWWAVES_FILTER) += avf_showwaves.o OBJS-$(CONFIG_SHOWWAVESPIC_FILTER) += avf_showwaves.o +OBJS-$(CONFIG_STREAMSELECT_FILTER) += avf_streamselect.o # multimedia sources OBJS-$(CONFIG_AMOVIE_FILTER) += src_movie.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 63b8fdb..cf805df 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -302,6 +302,7 @@ void avfilter_register_all(void) REGISTER_FILTER(SHOWVOLUME, showvolume, avf); REGISTER_FILTER(SHOWWAVES, showwaves, avf); REGISTER_FILTER(SHOWWAVESPIC, showwavespic, avf); + REGISTER_FILTER(STREAMSELECT, streamselect, avf); /* multimedia sources */ REGISTER_FILTER(AMOVIE, amovie, avsrc); diff --git a/libavfilter/avf_streamselect.c b/libavfilter/avf_streamselect.c new file mode 100644 index 0000000..4a424ed --- /dev/null +++ b/libavfilter/avf_streamselect.c @@ -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 + */ + +#include "libavutil/avstring.h" +#include "libavutil/internal.h" +#include "libavutil/opt.h" +#include "avfilter.h" +#include "audio.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef struct StreamSelectContext { + const AVClass *class; + char *in_str; + char *out_str; + char *map_str; + int *map; + int nb_map; +} StreamSelectContext; + +#define OFFSET(x) offsetof(StreamSelectContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM +static const AVOption streamselect_options[] = { + { "in", "input streams definition", OFFSET(in_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags=FLAGS }, + { "out", "output streams definition", OFFSET(out_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags=FLAGS }, + { "map", "input indexes to remap to outputs", OFFSET(map_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags=FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(streamselect); + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + int i; + AVFilterContext *ctx = inlink->dst; + StreamSelectContext *s = ctx->priv; + const int inlink_idx = FF_INLINK_IDX(inlink); + + //av_log(0,0,"inlink %d %p\n", inlink_idx, inlink); + + for (i = 0; i < s->nb_map; i++) + if (s->map[i] == inlink_idx) + return ff_filter_frame(ctx->outputs[i], frame); + av_frame_free(&frame); + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + StreamSelectContext *s = ctx->priv; + const int outlink_idx = FF_OUTLINK_IDX(outlink); + const int inlink_idx = s->map[outlink_idx]; + AVFilterLink *inlink = ctx->inputs[inlink_idx]; + + av_log(ctx, AV_LOG_DEBUG, "config output link %d " + "with settings from input link %d\n", + outlink_idx, inlink_idx); + + outlink->w = inlink->w; + outlink->h = inlink->h; + outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; + return 0; +} + +static int parse_definition(AVFilterContext *ctx, const char *def, void *filter_frame) +{ + const int is_input = !!filter_frame; + const char *padtype = is_input ? "in" : "out"; + int i, ret = 0; + + if (!def || !def[i]) { + av_log(ctx, AV_LOG_ERROR, "%sput streams definition is not set\n", padtype); + return AVERROR(EINVAL); + } + + for (i = 0; def[i]; i++) { + AVFilterPad pad = { 0 }; + + switch (def[i]) { + case 'v': + pad.type = AVMEDIA_TYPE_VIDEO; + break; + case 'a': + pad.type = AVMEDIA_TYPE_AUDIO; + break; + default: + av_log(ctx, AV_LOG_ERROR, "Unknown/unsupported '%c' stream type\n", def[i]); + return AVERROR(EINVAL); + } + + pad.name = av_asprintf("%sput%d", padtype, i); + if (!pad.name) + return AVERROR(ENOMEM); + + av_log(ctx, AV_LOG_DEBUG, "Add %s pad %s\n", padtype, pad.name); + + if (is_input) { + pad.filter_frame = filter_frame; + ret = ff_insert_inpad(ctx, i, &pad); + } else { + pad.config_props = config_output; + ret = ff_insert_outpad(ctx, i, &pad); + } + + if (ret < 0) { + av_freep(&pad.name); + return ret; + } + } + + return 0; +} + +static int parse_mapping(AVFilterContext *ctx, const char *map) +{ + StreamSelectContext *s = ctx->priv; + + av_freep(&s->map); + s->nb_map = 0; + s->map = av_malloc_array(ctx->nb_outputs, sizeof(*s->map)); + if (!s->map) + return AVERROR(ENOMEM); + + for (;;) { + char *p; + const int n = strtol(map, &p, 0); + + av_log(0,0,"n=%d map=%p p=%p\n", n, map, p); + + if (map == p) + break; + map = p; + + if (s->nb_map >= ctx->nb_outputs) { + av_log(ctx, AV_LOG_ERROR, "Unable to map more than the %d " + "output pads available\n", ctx->nb_outputs); + return AVERROR(EINVAL); + } + + if (n < 0 || n >= ctx->nb_inputs) { + av_log(ctx, AV_LOG_ERROR, "Input stream index %d doesn't exist " + "(there is only %d input streams defined)\n", + n, ctx->nb_outputs); + return AVERROR(EINVAL); + } + + av_log(ctx, AV_LOG_VERBOSE, "Map input stream %d to output stream %d\n", n, s->nb_map); + s->map[s->nb_map++] = n; + } + + av_log(ctx, AV_LOG_VERBOSE, "%d map set\n", s->nb_map); + + return 0; +} + +static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, + char *res, int res_len, int flags) +{ + if (!strcmp(cmd, "map")) { + int ret = parse_mapping(ctx, args); + if (ret < 0) + return ret; + return avfilter_config_links(ctx); + } + return AVERROR(ENOSYS); +} + +static av_cold int streamselect_init(AVFilterContext *ctx) +{ + int ret; + StreamSelectContext *s = ctx->priv; + + if ((ret = parse_definition(ctx, s->in_str, filter_frame)) < 0 || + (ret = parse_definition(ctx, s->out_str, NULL)) < 0) + return ret; + + av_log(ctx, AV_LOG_DEBUG, "Configured with %d inpad and %d outpad\n", + ctx->nb_inputs, ctx->nb_outputs); + + return parse_mapping(ctx, s->map_str); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + StreamSelectContext *s = ctx->priv; + av_freep(&s->map); +} + +AVFilter ff_avf_streamselect = { + .name = "streamselect", + .description = NULL_IF_CONFIG_SMALL("Select streams"), + .init = streamselect_init, + .process_command = process_command, + .uninit = uninit, + .priv_size = sizeof(StreamSelectContext), + .priv_class = &streamselect_class, + .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS, +}; -- 2.6.2 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel