Browse code

avfilter: add streamselect and astreamselect filter

Signed-off-by: Clément Bœsch <u@pkh.me>
Signed-off-by: Paul B Mahol <onemda@gmail.com>

Paul B Mahol authored on 2016/01/18 19:22:32
Showing 6 changed files
... ...
@@ -64,6 +64,7 @@ version <next>:
64 64
 - new DCA decoder with full support for DTS-HD extensions
65 65
 - significant performance improvements in Windows Television (WTV) demuxer
66 66
 - nnedi deinterlacer
67
+- streamselect video and astreamselect audio filter
67 68
 
68 69
 
69 70
 version 2.8:
... ...
@@ -11321,6 +11321,45 @@ stereo3d=abl:sbsr
11321 11321
 @end example
11322 11322
 @end itemize
11323 11323
 
11324
+@section streamselect, astreamselect
11325
+Select video or audio streams.
11326
+
11327
+The filter accepts the following options:
11328
+
11329
+@table @option
11330
+@item inputs
11331
+Set number of inputs. Default is 2.
11332
+
11333
+@item map
11334
+Set input indexes to remap to outputs.
11335
+@end table
11336
+
11337
+@subsection Commands
11338
+
11339
+The @code{streamselect} and @code{astreamselect} filter supports the following
11340
+commands:
11341
+
11342
+@table @option
11343
+@item map
11344
+Set input indexes to remap to outputs.
11345
+@end table
11346
+
11347
+@subsection Examples
11348
+
11349
+@itemize
11350
+@item
11351
+Select first 5 seconds 1st stream and rest of time 2nd stream:
11352
+@example
11353
+sendcmd='5.0 streamselect map 1',streamselect=inputs=2:map=0
11354
+@end example
11355
+
11356
+@item
11357
+Same as above, but for audio:
11358
+@example
11359
+asendcmd='5.0 astreamselect map 1',astreamselect=inputs=2:map=0
11360
+@end example
11361
+@end itemize
11362
+
11324 11363
 @anchor{spp}
11325 11364
 @section spp
11326 11365
 
... ...
@@ -57,6 +57,7 @@ OBJS-$(CONFIG_ASETTB_FILTER)                 += settb.o
57 57
 OBJS-$(CONFIG_ASHOWINFO_FILTER)              += af_ashowinfo.o
58 58
 OBJS-$(CONFIG_ASPLIT_FILTER)                 += split.o
59 59
 OBJS-$(CONFIG_ASTATS_FILTER)                 += af_astats.o
60
+OBJS-$(CONFIG_ASTREAMSELECT_FILTER)          += f_streamselect.o
60 61
 OBJS-$(CONFIG_ASYNCTS_FILTER)                += af_asyncts.o
61 62
 OBJS-$(CONFIG_ATEMPO_FILTER)                 += af_atempo.o
62 63
 OBJS-$(CONFIG_ATRIM_FILTER)                  += trim.o
... ...
@@ -237,6 +238,7 @@ OBJS-$(CONFIG_SPLIT_FILTER)                  += split.o
237 237
 OBJS-$(CONFIG_SPP_FILTER)                    += vf_spp.o
238 238
 OBJS-$(CONFIG_SSIM_FILTER)                   += vf_ssim.o dualinput.o framesync.o
239 239
 OBJS-$(CONFIG_STEREO3D_FILTER)               += vf_stereo3d.o
240
+OBJS-$(CONFIG_STREAMSELECT_FILTER)           += f_streamselect.o
240 241
 OBJS-$(CONFIG_SUBTITLES_FILTER)              += vf_subtitles.o
241 242
 OBJS-$(CONFIG_SUPER2XSAI_FILTER)             += vf_super2xsai.o
242 243
 OBJS-$(CONFIG_SWAPUV_FILTER)                 += vf_swapuv.o
... ...
@@ -78,6 +78,7 @@ void avfilter_register_all(void)
78 78
     REGISTER_FILTER(ASHOWINFO,      ashowinfo,      af);
79 79
     REGISTER_FILTER(ASPLIT,         asplit,         af);
80 80
     REGISTER_FILTER(ASTATS,         astats,         af);
81
+    REGISTER_FILTER(ASTREAMSELECT,  astreamselect,  af);
81 82
     REGISTER_FILTER(ASYNCTS,        asyncts,        af);
82 83
     REGISTER_FILTER(ATEMPO,         atempo,         af);
83 84
     REGISTER_FILTER(ATRIM,          atrim,          af);
... ...
@@ -257,6 +258,7 @@ void avfilter_register_all(void)
257 257
     REGISTER_FILTER(SPP,            spp,            vf);
258 258
     REGISTER_FILTER(SSIM,           ssim,           vf);
259 259
     REGISTER_FILTER(STEREO3D,       stereo3d,       vf);
260
+    REGISTER_FILTER(STREAMSELECT,   streamselect,   vf);
260 261
     REGISTER_FILTER(SUBTITLES,      subtitles,      vf);
261 262
     REGISTER_FILTER(SUPER2XSAI,     super2xsai,     vf);
262 263
     REGISTER_FILTER(SWAPUV,         swapuv,         vf);
263 264
new file mode 100644
... ...
@@ -0,0 +1,353 @@
0
+/*
1
+ * This file is part of FFmpeg.
2
+ *
3
+ * FFmpeg is free software; you can redistribute it and/or
4
+ * modify it under the terms of the GNU Lesser General Public
5
+ * License as published by the Free Software Foundation; either
6
+ * version 2.1 of the License, or (at your option) any later version.
7
+ *
8
+ * FFmpeg is distributed in the hope that it will be useful,
9
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11
+ * Lesser General Public License for more details.
12
+ *
13
+ * You should have received a copy of the GNU Lesser General Public
14
+ * License along with FFmpeg; if not, write to the Free Software
15
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+ */
17
+
18
+#include "libavutil/avstring.h"
19
+#include "libavutil/internal.h"
20
+#include "libavutil/opt.h"
21
+#include "avfilter.h"
22
+#include "audio.h"
23
+#include "formats.h"
24
+#include "framesync.h"
25
+#include "internal.h"
26
+#include "video.h"
27
+
28
+typedef struct StreamSelectContext {
29
+    const AVClass *class;
30
+    int nb_inputs;
31
+    char *map_str;
32
+    int *map;
33
+    int nb_map;
34
+    int is_audio;
35
+    int64_t *last_pts;
36
+    AVFrame **frames;
37
+    FFFrameSync fs;
38
+} StreamSelectContext;
39
+
40
+#define OFFSET(x) offsetof(StreamSelectContext, x)
41
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
42
+static const AVOption streamselect_options[] = {
43
+    { "inputs",  "number of input streams",           OFFSET(nb_inputs),  AV_OPT_TYPE_INT,    {.i64=2},    2, INT_MAX,  .flags=FLAGS },
44
+    { "map",     "input indexes to remap to outputs", OFFSET(map_str),    AV_OPT_TYPE_STRING, {.str=NULL},              .flags=FLAGS },
45
+    { NULL }
46
+};
47
+
48
+AVFILTER_DEFINE_CLASS(streamselect);
49
+
50
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
51
+{
52
+    StreamSelectContext *s = inlink->dst->priv;
53
+    return ff_framesync_filter_frame(&s->fs, inlink, in);
54
+}
55
+
56
+static int process_frame(FFFrameSync *fs)
57
+{
58
+    AVFilterContext *ctx = fs->parent;
59
+    StreamSelectContext *s = fs->opaque;
60
+    AVFrame **in = s->frames;
61
+    int i, j, ret = 0;
62
+
63
+    for (i = 0; i < ctx->nb_inputs; i++) {
64
+        if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
65
+            return ret;
66
+    }
67
+
68
+    for (j = 0; j < ctx->nb_inputs; j++) {
69
+        for (i = 0; i < s->nb_map; i++) {
70
+            if (s->map[i] == j) {
71
+                AVFrame *out;
72
+
73
+                if (s->is_audio && s->last_pts[j] == in[j]->pts &&
74
+                    ctx->outputs[i]->frame_count > 0)
75
+                    continue;
76
+                out = av_frame_clone(in[j]);
77
+                if (!out)
78
+                    return AVERROR(ENOMEM);
79
+
80
+                out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, ctx->outputs[i]->time_base);
81
+                s->last_pts[j] = in[j]->pts;
82
+                ret = ff_filter_frame(ctx->outputs[i], out);
83
+                if (ret < 0)
84
+                    return ret;
85
+            }
86
+        }
87
+    }
88
+
89
+    return ret;
90
+}
91
+
92
+static int request_frame(AVFilterLink *outlink)
93
+{
94
+    StreamSelectContext *s = outlink->src->priv;
95
+    return ff_framesync_request_frame(&s->fs, outlink);
96
+}
97
+
98
+static int config_output(AVFilterLink *outlink)
99
+{
100
+    AVFilterContext *ctx = outlink->src;
101
+    StreamSelectContext *s = ctx->priv;
102
+    const int outlink_idx = FF_OUTLINK_IDX(outlink);
103
+    const int inlink_idx  = s->map[outlink_idx];
104
+    AVFilterLink *inlink = ctx->inputs[inlink_idx];
105
+    FFFrameSyncIn *in;
106
+    int i, ret;
107
+
108
+    av_log(ctx, AV_LOG_VERBOSE, "config output link %d "
109
+           "with settings from input link %d\n",
110
+           outlink_idx, inlink_idx);
111
+
112
+    switch (outlink->type) {
113
+    case AVMEDIA_TYPE_VIDEO:
114
+        outlink->w = inlink->w;
115
+        outlink->h = inlink->h;
116
+        outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
117
+        outlink->frame_rate = inlink->frame_rate;
118
+        break;
119
+    case AVMEDIA_TYPE_AUDIO:
120
+        outlink->sample_rate    = inlink->sample_rate;
121
+        outlink->channels       = inlink->channels;
122
+        outlink->channel_layout = inlink->channel_layout;
123
+        break;
124
+    }
125
+
126
+    outlink->time_base = inlink->time_base;
127
+    outlink->format = inlink->format;
128
+
129
+    if (s->fs.opaque == s)
130
+        return 0;
131
+
132
+    if ((ret = ff_framesync_init(&s->fs, ctx, ctx->nb_inputs)) < 0)
133
+        return ret;
134
+
135
+    in = s->fs.in;
136
+    s->fs.opaque = s;
137
+    s->fs.on_event = process_frame;
138
+
139
+    for (i = 0; i < ctx->nb_inputs; i++) {
140
+        in[i].time_base = ctx->inputs[i]->time_base;
141
+        in[i].sync      = 1;
142
+        in[i].before    = EXT_STOP;
143
+        in[i].after     = EXT_STOP;
144
+    }
145
+
146
+    s->frames = av_calloc(ctx->nb_inputs, sizeof(*s->frames));
147
+    if (!s->frames)
148
+        return AVERROR(ENOMEM);
149
+
150
+    return ff_framesync_configure(&s->fs);
151
+}
152
+
153
+static int parse_definition(AVFilterContext *ctx, int nb_pads, void *filter_frame, int is_audio)
154
+{
155
+    const int is_input = !!filter_frame;
156
+    const char *padtype = is_input ? "in" : "out";
157
+    int i = 0, ret = 0;
158
+
159
+    for (i = 0; i < nb_pads; i++) {
160
+        AVFilterPad pad = { 0 };
161
+
162
+        pad.type = is_audio ? AVMEDIA_TYPE_AUDIO : AVMEDIA_TYPE_VIDEO;
163
+
164
+        pad.name = av_asprintf("%sput%d", padtype, i);
165
+        if (!pad.name)
166
+            return AVERROR(ENOMEM);
167
+
168
+        av_log(ctx, AV_LOG_DEBUG, "Add %s pad %s\n", padtype, pad.name);
169
+
170
+        if (is_input) {
171
+            pad.filter_frame = filter_frame;
172
+            ret = ff_insert_inpad(ctx, i, &pad);
173
+        } else {
174
+            pad.config_props  = config_output;
175
+            pad.request_frame = request_frame;
176
+            ret = ff_insert_outpad(ctx, i, &pad);
177
+        }
178
+
179
+        if (ret < 0) {
180
+            av_freep(&pad.name);
181
+            return ret;
182
+        }
183
+    }
184
+
185
+    return 0;
186
+}
187
+
188
+static int parse_mapping(AVFilterContext *ctx, const char *map)
189
+{
190
+    StreamSelectContext *s = ctx->priv;
191
+    int *new_map;
192
+    int new_nb_map = 0;
193
+
194
+    if (!map) {
195
+        av_log(ctx, AV_LOG_ERROR, "mapping definition is not set\n");
196
+        return AVERROR(EINVAL);
197
+    }
198
+
199
+    new_map = av_calloc(s->nb_inputs, sizeof(*new_map));
200
+    if (!new_map)
201
+        return AVERROR(ENOMEM);
202
+
203
+    while (1) {
204
+        char *p;
205
+        const int n = strtol(map, &p, 0);
206
+
207
+        av_log(ctx, AV_LOG_DEBUG, "n=%d map=%p p=%p\n", n, map, p);
208
+
209
+        if (map == p)
210
+            break;
211
+        map = p;
212
+
213
+        if (new_nb_map >= s->nb_inputs) {
214
+            av_log(ctx, AV_LOG_ERROR, "Unable to map more than the %d "
215
+                   "input pads available\n", s->nb_inputs);
216
+            av_free(new_map);
217
+            return AVERROR(EINVAL);
218
+        }
219
+
220
+        if (n < 0 || n >= ctx->nb_inputs) {
221
+            av_log(ctx, AV_LOG_ERROR, "Input stream index %d doesn't exist "
222
+                   "(there is only %d input streams defined)\n",
223
+                   n, s->nb_inputs);
224
+            av_free(new_map);
225
+            return AVERROR(EINVAL);
226
+        }
227
+
228
+        av_log(ctx, AV_LOG_VERBOSE, "Map input stream %d to output stream %d\n", n, new_nb_map);
229
+        new_map[new_nb_map++] = n;
230
+    }
231
+
232
+    if (!new_nb_map) {
233
+        av_log(ctx, AV_LOG_ERROR, "invalid mapping\n");
234
+        av_free(new_map);
235
+        return AVERROR(EINVAL);
236
+    }
237
+
238
+    av_freep(&s->map);
239
+    s->map = new_map;
240
+    s->nb_map = new_nb_map;
241
+
242
+    av_log(ctx, AV_LOG_VERBOSE, "%d map set\n", s->nb_map);
243
+
244
+    return 0;
245
+}
246
+
247
+static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
248
+                           char *res, int res_len, int flags)
249
+{
250
+    if (!strcmp(cmd, "map")) {
251
+        int ret = parse_mapping(ctx, args);
252
+
253
+        if (ret < 0)
254
+            return ret;
255
+        return avfilter_config_links(ctx);
256
+    }
257
+    return AVERROR(ENOSYS);
258
+}
259
+
260
+static av_cold int init(AVFilterContext *ctx)
261
+{
262
+    StreamSelectContext *s = ctx->priv;
263
+    int ret, nb_outputs = 0;
264
+    char *map = s->map_str;
265
+
266
+    if (!strcmp(ctx->filter->name, "astreamselect"))
267
+        s->is_audio = 1;
268
+
269
+    for (;;) {
270
+        char *p;
271
+
272
+        strtol(map, &p, 0);
273
+        if (map == p)
274
+            break;
275
+        nb_outputs++;
276
+        map = p;
277
+    }
278
+
279
+    s->last_pts = av_calloc(s->nb_inputs, sizeof(*s->last_pts));
280
+    if (!s->last_pts)
281
+        return AVERROR(ENOMEM);
282
+
283
+    if ((ret = parse_definition(ctx, s->nb_inputs, filter_frame, s->is_audio)) < 0 ||
284
+        (ret = parse_definition(ctx, nb_outputs, NULL, s->is_audio)) < 0)
285
+        return ret;
286
+
287
+    av_log(ctx, AV_LOG_DEBUG, "Configured with %d inpad and %d outpad\n",
288
+           ctx->nb_inputs, ctx->nb_outputs);
289
+
290
+    return parse_mapping(ctx, s->map_str);
291
+}
292
+
293
+static av_cold void uninit(AVFilterContext *ctx)
294
+{
295
+    StreamSelectContext *s = ctx->priv;
296
+
297
+    av_freep(&s->last_pts);
298
+    av_freep(&s->map);
299
+    av_freep(&s->frames);
300
+    ff_framesync_uninit(&s->fs);
301
+}
302
+
303
+static int query_formats(AVFilterContext *ctx)
304
+{
305
+    AVFilterFormats *formats, *rates = NULL;
306
+    AVFilterChannelLayouts *layouts = NULL;
307
+    int ret, i;
308
+
309
+    for (i = 0; i < ctx->nb_inputs; i++) {
310
+        formats = ff_all_formats(ctx->inputs[i]->type);
311
+        if ((ret = ff_set_common_formats(ctx, formats)) < 0)
312
+            return ret;
313
+
314
+        if (ctx->inputs[i]->type == AVMEDIA_TYPE_AUDIO) {
315
+            rates = ff_all_samplerates();
316
+            if ((ret = ff_set_common_samplerates(ctx, rates)) < 0)
317
+                return ret;
318
+            layouts = ff_all_channel_counts();
319
+            if ((ret = ff_set_common_channel_layouts(ctx, layouts)) < 0)
320
+                return ret;
321
+        }
322
+    }
323
+
324
+    return 0;
325
+}
326
+
327
+AVFilter ff_vf_streamselect = {
328
+    .name            = "streamselect",
329
+    .description     = NULL_IF_CONFIG_SMALL("Select video streams"),
330
+    .init            = init,
331
+    .query_formats   = query_formats,
332
+    .process_command = process_command,
333
+    .uninit          = uninit,
334
+    .priv_size       = sizeof(StreamSelectContext),
335
+    .priv_class      = &streamselect_class,
336
+    .flags           = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS,
337
+};
338
+
339
+#define astreamselect_options streamselect_options
340
+AVFILTER_DEFINE_CLASS(astreamselect);
341
+
342
+AVFilter ff_af_astreamselect = {
343
+    .name            = "astreamselect",
344
+    .description     = NULL_IF_CONFIG_SMALL("Select audio streams"),
345
+    .init            = init,
346
+    .query_formats   = query_formats,
347
+    .process_command = process_command,
348
+    .uninit          = uninit,
349
+    .priv_size       = sizeof(StreamSelectContext),
350
+    .priv_class      = &astreamselect_class,
351
+    .flags           = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS,
352
+};
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR   6
33
-#define LIBAVFILTER_VERSION_MINOR  28
33
+#define LIBAVFILTER_VERSION_MINOR  29
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \