[FFmpeg-devel] [PATCH] avfilter: add streamselect filter

Paul B Mahol onemda at gmail.com
Mon Jan 18 15:26:52 CET 2016


Signed-off-by: Paul B Mahol <onemda at gmail.com>
---

Partially working, it can switch video only and audio only streams.
I'm thinking about doing only one single output of single media type,
so there would be astreamselect and streamselect filter.
I don't think current behaviour is possible, or even useful if not too
complicated.

Comments welcome!

---
 libavfilter/Makefile           |   1 +
 libavfilter/allfilters.c       |   1 +
 libavfilter/avf_streamselect.c | 328 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 330 insertions(+)
 create mode 100644 libavfilter/avf_streamselect.c

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e3e3561..5bc4c35 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -292,6 +292,7 @@ OBJS-$(CONFIG_SHOWVOLUME_FILTER)             += avf_showvolume.o
 OBJS-$(CONFIG_SHOWWAVES_FILTER)              += avf_showwaves.o
 OBJS-$(CONFIG_SHOWWAVESPIC_FILTER)           += avf_showwaves.o
 OBJS-$(CONFIG_SPECTRUMSYNTH_FILTER)          += vaf_spectrumsynth.o window_func.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 1faf393..ac01b06 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -312,6 +312,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(SHOWWAVES,      showwaves,      avf);
     REGISTER_FILTER(SHOWWAVESPIC,   showwavespic,   avf);
     REGISTER_FILTER(SPECTRUMSYNTH,  spectrumsynth,  vaf);
+    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..4adf78e
--- /dev/null
+++ b/libavfilter/avf_streamselect.c
@@ -0,0 +1,328 @@
+/*
+ * 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 "framesync.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;
+    AVFrame **frames;
+    FFFrameSync fs;
+} 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 *in)
+{
+    StreamSelectContext *s = inlink->dst->priv;
+    return ff_framesync_filter_frame(&s->fs, inlink, in);
+}
+
+static int process_frame(FFFrameSync *fs)
+{
+    AVFilterContext *ctx = fs->parent;
+    StreamSelectContext *s = fs->opaque;
+    AVFrame **in = s->frames;
+    int i, j, ret = 0;
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
+            return ret;
+    }
+
+    for (j = 0; j < ctx->nb_inputs; j++) {
+        for (i = 0; i < s->nb_map; i++) {
+            if (s->map[i] == j) {
+                AVFrame *out = av_frame_clone(in[j]);
+
+                if (!out)
+                    return AVERROR(ENOMEM);
+
+                out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, ctx->outputs[i]->time_base);
+                ret = ff_filter_frame(ctx->outputs[i], out);
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    StreamSelectContext *s = outlink->src->priv;
+    return ff_framesync_request_frame(&s->fs, outlink);
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    StreamSelectContext *s = ctx->priv;
+
+    inlink->partial_buf_size =
+    inlink->min_samples =
+    inlink->max_samples = inlink->sample_rate;
+
+    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];
+    FFFrameSyncIn *in;
+    int i, ret;
+
+    av_log(ctx, AV_LOG_DEBUG, "config output link %d "
+           "with settings from input link %d\n",
+           outlink_idx, inlink_idx);
+
+    switch (outlink->type) {
+    case AVMEDIA_TYPE_VIDEO:
+        outlink->w = inlink->w;
+        outlink->h = inlink->h;
+        outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
+        outlink->frame_rate = inlink->frame_rate;
+        break;
+    case AVMEDIA_TYPE_AUDIO:
+        outlink->sample_rate    = inlink->sample_rate;
+        outlink->channels       = inlink->channels;
+        outlink->channel_layout = inlink->channel_layout;
+        break;
+    }
+
+    outlink->time_base = inlink->time_base;
+
+    if (outlink_idx)
+        return 0;
+
+    if ((ret = ff_framesync_init(&s->fs, ctx, ctx->nb_inputs)) < 0)
+        return ret;
+
+    in = s->fs.in;
+    s->fs.opaque = s;
+    s->fs.on_event = process_frame;
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        AVFilterLink *inlink = ctx->inputs[i];
+
+        in[i].time_base = inlink->time_base;
+        in[i].sync   = 1;
+        in[i].before = EXT_STOP;
+        in[i].after  = EXT_STOP;
+    }
+
+    s->frames = av_calloc(ctx->nb_inputs, sizeof(*s->frames));
+    if (!s->frames)
+        return AVERROR(ENOMEM);
+
+    return ff_framesync_configure(&s->fs);
+}
+
+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 = 0, 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.config_props = config_input;
+            pad.filter_frame = filter_frame;
+            ret = ff_insert_inpad(ctx, i, &pad);
+        } else {
+            pad.config_props  = config_output;
+            pad.request_frame = request_frame;
+            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;
+
+    if (!map) {
+        av_log(ctx, AV_LOG_ERROR, "mapping definition is not set\n");
+        return AVERROR(EINVAL);
+    }
+
+    av_freep(&s->map);
+    s->nb_map = 0;
+    s->map = av_calloc(ctx->nb_outputs, sizeof(*s->map));
+    if (!s->map)
+        return AVERROR(ENOMEM);
+
+    for (;;) {
+        char *p;
+        const int n = strtol(map, &p, 0);
+
+        av_log(ctx, 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 init(AVFilterContext *ctx)
+{
+    StreamSelectContext *s = ctx->priv;
+    int ret;
+
+    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);
+    av_freep(&s->frames);
+    ff_framesync_uninit(&s->fs);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    AVFilterFormats *formats, *rates = NULL;
+    AVFilterChannelLayouts *layouts = NULL;
+    int ret, i;
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        formats = ff_all_formats(ctx->inputs[i]->type);
+        if ((ret = ff_set_common_formats(ctx, formats)) < 0)
+            return ret;
+
+        if (ctx->inputs[i]->type == AVMEDIA_TYPE_AUDIO) {
+            rates = ff_all_samplerates();
+            if ((ret = ff_set_common_samplerates(ctx, rates)) < 0)
+                return ret;
+            layouts = ff_all_channel_counts();
+            if ((ret = ff_set_common_channel_layouts(ctx, layouts)) < 0)
+                return ret;
+        }
+    }
+
+    return 0;
+}
+
+AVFilter ff_avf_streamselect = {
+    .name            = "streamselect",
+    .description     = NULL_IF_CONFIG_SMALL("Select streams"),
+    .init            = init,
+    .query_formats   = query_formats,
+    .process_command = process_command,
+    .uninit          = uninit,
+    .priv_size       = sizeof(StreamSelectContext),
+    .priv_class      = &streamselect_class,
+    .flags           = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS,
+};
-- 
1.9.1



More information about the ffmpeg-devel mailing list