Browse code

avfilter: add hstack & vstack filter

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

Paul B Mahol authored on 2015/08/25 05:00:59
Showing 6 changed files
... ...
@@ -34,6 +34,7 @@ version <next>:
34 34
 - showfreqs filter
35 35
 - vectorscope filter
36 36
 - waveform filter
37
+- hstack and vstack filter
37 38
 
38 39
 
39 40
 version 2.7:
... ...
@@ -6589,6 +6589,21 @@ Set the scaling dimension: @code{2} for @code{hq2x}, @code{3} for
6589 6589
 Default is @code{3}.
6590 6590
 @end table
6591 6591
 
6592
+@section hstack
6593
+Stack input videos horizontally.
6594
+
6595
+All streams must be of same pixel format and of same height.
6596
+
6597
+Note that this filter is faster than using @ref{overlay} and @ref{pad} filter
6598
+to create same output.
6599
+
6600
+The filter accept the following option:
6601
+
6602
+@table @option
6603
+@item nb_inputs
6604
+Set number of input streams. Default is 2.
6605
+@end table
6606
+
6592 6607
 @section hue
6593 6608
 
6594 6609
 Modify the hue and/or the saturation of the input.
... ...
@@ -7736,6 +7751,7 @@ Set chroma strength.
7736 7736
 Must be a double value in the range 0-1000, default is @code{1.0}.
7737 7737
 @end table
7738 7738
 
7739
+@anchor{pad}
7739 7740
 @section pad
7740 7741
 
7741 7742
 Add paddings to the input image, and place the original input at the
... ...
@@ -10892,6 +10908,21 @@ vignette='PI/4+random(1)*PI/50':eval=frame
10892 10892
 
10893 10893
 @end itemize
10894 10894
 
10895
+@section vstack
10896
+Stack input videos vertically.
10897
+
10898
+All streams must be of same pixel format and of same width.
10899
+
10900
+Note that this filter is faster than using @ref{overlay} and @ref{pad} filter
10901
+to create same output.
10902
+
10903
+The filter accept the following option:
10904
+
10905
+@table @option
10906
+@item nb_inputs
10907
+Set number of input streams. Default is 2.
10908
+@end table
10909
+
10895 10910
 @section w3fdif
10896 10911
 
10897 10912
 Deinterlace the input video ("w3fdif" stands for "Weston 3 Field
... ...
@@ -153,6 +153,7 @@ OBJS-$(CONFIG_HISTEQ_FILTER)                 += vf_histeq.o
153 153
 OBJS-$(CONFIG_HISTOGRAM_FILTER)              += vf_histogram.o
154 154
 OBJS-$(CONFIG_HQDN3D_FILTER)                 += vf_hqdn3d.o
155 155
 OBJS-$(CONFIG_HQX_FILTER)                    += vf_hqx.o
156
+OBJS-$(CONFIG_HSTACK_FILTER)                 += vf_stack.o framesync.o
156 157
 OBJS-$(CONFIG_HUE_FILTER)                    += vf_hue.o
157 158
 OBJS-$(CONFIG_IDET_FILTER)                   += vf_idet.o
158 159
 OBJS-$(CONFIG_IL_FILTER)                     += vf_il.o
... ...
@@ -231,6 +232,7 @@ OBJS-$(CONFIG_VFLIP_FILTER)                  += vf_vflip.o
231 231
 OBJS-$(CONFIG_VIDSTABDETECT_FILTER)          += vidstabutils.o vf_vidstabdetect.o
232 232
 OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)       += vidstabutils.o vf_vidstabtransform.o
233 233
 OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o
234
+OBJS-$(CONFIG_VSTACK_FILTER)                 += vf_stack.o framesync.o
234 235
 OBJS-$(CONFIG_W3FDIF_FILTER)                 += vf_w3fdif.o
235 236
 OBJS-$(CONFIG_WAVEFORM_FILTER)               += vf_waveform.o
236 237
 OBJS-$(CONFIG_XBR_FILTER)                    += vf_xbr.o
... ...
@@ -169,6 +169,7 @@ void avfilter_register_all(void)
169 169
     REGISTER_FILTER(HISTOGRAM,      histogram,      vf);
170 170
     REGISTER_FILTER(HQDN3D,         hqdn3d,         vf);
171 171
     REGISTER_FILTER(HQX,            hqx,            vf);
172
+    REGISTER_FILTER(HSTACK,         hstack,         vf);
172 173
     REGISTER_FILTER(HUE,            hue,            vf);
173 174
     REGISTER_FILTER(IDET,           idet,           vf);
174 175
     REGISTER_FILTER(IL,             il,             vf);
... ...
@@ -246,6 +247,7 @@ void avfilter_register_all(void)
246 246
     REGISTER_FILTER(VIDSTABDETECT,  vidstabdetect,  vf);
247 247
     REGISTER_FILTER(VIDSTABTRANSFORM, vidstabtransform, vf);
248 248
     REGISTER_FILTER(VIGNETTE,       vignette,       vf);
249
+    REGISTER_FILTER(VSTACK,         vstack,         vf);
249 250
     REGISTER_FILTER(W3FDIF,         w3fdif,         vf);
250 251
     REGISTER_FILTER(WAVEFORM,       waveform,       vf);
251 252
     REGISTER_FILTER(XBR,            xbr,            vf);
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR  5
33
-#define LIBAVFILTER_VERSION_MINOR  37
33
+#define LIBAVFILTER_VERSION_MINOR  38
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
new file mode 100644
... ...
@@ -0,0 +1,271 @@
0
+/*
1
+ * Copyright (c) 2015 Paul B. Mahol
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+#include "libavutil/avstring.h"
21
+#include "libavutil/imgutils.h"
22
+#include "libavutil/opt.h"
23
+#include "libavutil/pixdesc.h"
24
+
25
+#include "avfilter.h"
26
+#include "formats.h"
27
+#include "internal.h"
28
+#include "framesync.h"
29
+#include "video.h"
30
+
31
+typedef struct StackContext {
32
+    const AVClass *class;
33
+    const AVPixFmtDescriptor *desc;
34
+    int nb_inputs;
35
+    int is_vertical;
36
+    int nb_planes;
37
+
38
+    AVFrame **frames;
39
+    FFFrameSync fs;
40
+} StackContext;
41
+
42
+static int query_formats(AVFilterContext *ctx)
43
+{
44
+    AVFilterFormats *pix_fmts = NULL;
45
+    int fmt;
46
+
47
+    for (fmt = 0; av_pix_fmt_desc_get(fmt); fmt++) {
48
+        const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
49
+        if (!(desc->flags & AV_PIX_FMT_FLAG_PAL ||
50
+              desc->flags & AV_PIX_FMT_FLAG_HWACCEL ||
51
+              desc->flags & AV_PIX_FMT_FLAG_BITSTREAM))
52
+            ff_add_format(&pix_fmts, fmt);
53
+    }
54
+
55
+    return ff_set_common_formats(ctx, pix_fmts);
56
+}
57
+
58
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
59
+{
60
+    StackContext *s = inlink->dst->priv;
61
+    return ff_framesync_filter_frame(&s->fs, inlink, in);
62
+}
63
+
64
+static av_cold int init(AVFilterContext *ctx)
65
+{
66
+    StackContext *s = ctx->priv;
67
+    int i, ret;
68
+
69
+    if (!strcmp(ctx->filter->name, "vstack"))
70
+        s->is_vertical = 1;
71
+
72
+    s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
73
+    if (!s->frames)
74
+        return AVERROR(ENOMEM);
75
+
76
+    for (i = 0; i < s->nb_inputs; i++) {
77
+        AVFilterPad pad = { 0 };
78
+
79
+        pad.type = AVMEDIA_TYPE_VIDEO;
80
+        pad.name = av_asprintf("input%d", i);
81
+        if (!pad.name)
82
+            return AVERROR(ENOMEM);
83
+        pad.filter_frame = filter_frame;
84
+
85
+        if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0) {
86
+            av_freep(&pad.name);
87
+            return ret;
88
+        }
89
+    }
90
+
91
+    return 0;
92
+}
93
+
94
+static int process_frame(FFFrameSync *fs)
95
+{
96
+    AVFilterContext *ctx = fs->parent;
97
+    AVFilterLink *outlink = ctx->outputs[0];
98
+    StackContext *s = fs->opaque;
99
+    AVFrame **in = s->frames;
100
+    AVFrame *out;
101
+    int i, p, ret, offset[4] = { 0 };
102
+
103
+    for (i = 0; i < s->nb_inputs; i++) {
104
+        if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
105
+            return ret;
106
+    }
107
+
108
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
109
+    if (!out)
110
+        return AVERROR(ENOMEM);
111
+    out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
112
+
113
+    for (i = 0; i < s->nb_inputs; i++) {
114
+        AVFilterLink *inlink = ctx->inputs[i];
115
+        int linesize[4];
116
+        int height[4];
117
+
118
+        if ((ret = av_image_fill_linesizes(linesize, inlink->format, inlink->w)) < 0)
119
+            return ret;
120
+
121
+        height[1] = height[2] = FF_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
122
+        height[0] = height[3] = inlink->h;
123
+
124
+        for (p = 0; p < s->nb_planes; p++) {
125
+            if (s->is_vertical) {
126
+                av_image_copy_plane(out->data[p] + offset[p] * out->linesize[p],
127
+                                    out->linesize[p],
128
+                                    in[i]->data[p],
129
+                                    in[i]->linesize[p],
130
+                                    linesize[p], height[p]);
131
+                offset[p] += height[p];
132
+            } else {
133
+                av_image_copy_plane(out->data[p] + offset[p],
134
+                                    out->linesize[p],
135
+                                    in[i]->data[p],
136
+                                    in[i]->linesize[p],
137
+                                    linesize[p], height[p]);
138
+                offset[p] += linesize[p];
139
+            }
140
+        }
141
+    }
142
+
143
+    return ff_filter_frame(outlink, out);
144
+}
145
+
146
+static int config_output(AVFilterLink *outlink)
147
+{
148
+    AVFilterContext *ctx = outlink->src;
149
+    StackContext *s = ctx->priv;
150
+    AVRational time_base = ctx->inputs[0]->time_base;
151
+    AVRational frame_rate = ctx->inputs[0]->frame_rate;
152
+    int height = ctx->inputs[0]->h;
153
+    int width = ctx->inputs[0]->w;
154
+    FFFrameSyncIn *in;
155
+    int i, ret;
156
+
157
+    if (s->is_vertical) {
158
+        for (i = 1; i < s->nb_inputs; i++) {
159
+            if (ctx->inputs[i]->w != width) {
160
+                av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
161
+                return AVERROR(EINVAL);
162
+            }
163
+            height += ctx->inputs[i]->h;
164
+        }
165
+    } else {
166
+        for (i = 1; i < s->nb_inputs; i++) {
167
+            if (ctx->inputs[i]->h != height) {
168
+                av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height);
169
+                return AVERROR(EINVAL);
170
+            }
171
+            width += ctx->inputs[i]->w;
172
+        }
173
+    }
174
+
175
+    s->desc = av_pix_fmt_desc_get(outlink->format);
176
+    if (!s->desc)
177
+        return AVERROR_BUG;
178
+    s->nb_planes = av_pix_fmt_count_planes(outlink->format);
179
+
180
+    outlink->w          = width;
181
+    outlink->h          = height;
182
+    outlink->time_base  = time_base;
183
+    outlink->frame_rate = frame_rate;
184
+
185
+    if ((ret = ff_framesync_init(&s->fs, ctx, s->nb_inputs)) < 0)
186
+        return ret;
187
+
188
+    in = s->fs.in;
189
+    s->fs.opaque = s;
190
+    s->fs.on_event = process_frame;
191
+
192
+    for (i = 0; i < s->nb_inputs; i++) {
193
+        AVFilterLink *inlink = ctx->inputs[i];
194
+
195
+        in[i].time_base = inlink->time_base;
196
+        in[i].sync   = 1;
197
+        in[i].before = EXT_STOP;
198
+        in[i].after  = EXT_INFINITY;
199
+    }
200
+
201
+    return ff_framesync_configure(&s->fs);
202
+}
203
+
204
+static int request_frame(AVFilterLink *outlink)
205
+{
206
+    StackContext *s = outlink->src->priv;
207
+    return ff_framesync_request_frame(&s->fs, outlink);
208
+}
209
+
210
+static av_cold void uninit(AVFilterContext *ctx)
211
+{
212
+    StackContext *s = ctx->priv;
213
+    ff_framesync_uninit(&s->fs);
214
+    av_freep(&s->frames);
215
+}
216
+
217
+#define OFFSET(x) offsetof(StackContext, x)
218
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
219
+static const AVOption stack_options[] = {
220
+    { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
221
+    { NULL },
222
+};
223
+
224
+static const AVFilterPad outputs[] = {
225
+    {
226
+        .name          = "default",
227
+        .type          = AVMEDIA_TYPE_VIDEO,
228
+        .config_props  = config_output,
229
+        .request_frame = request_frame,
230
+    },
231
+    { NULL }
232
+};
233
+
234
+#if CONFIG_HSTACK_FILTER
235
+
236
+#define hstack_options stack_options
237
+AVFILTER_DEFINE_CLASS(hstack);
238
+
239
+AVFilter ff_vf_hstack = {
240
+    .name          = "hstack",
241
+    .description   = NULL_IF_CONFIG_SMALL("Stack video inputs horizontally."),
242
+    .priv_size     = sizeof(StackContext),
243
+    .priv_class    = &hstack_class,
244
+    .query_formats = query_formats,
245
+    .outputs       = outputs,
246
+    .init          = init,
247
+    .uninit        = uninit,
248
+    .flags         = AVFILTER_FLAG_DYNAMIC_INPUTS,
249
+};
250
+
251
+#endif /* CONFIG_HSTACK_FILTER */
252
+
253
+#if CONFIG_VSTACK_FILTER
254
+
255
+#define vstack_options stack_options
256
+AVFILTER_DEFINE_CLASS(vstack);
257
+
258
+AVFilter ff_vf_vstack = {
259
+    .name          = "vstack",
260
+    .description   = NULL_IF_CONFIG_SMALL("Stack video inputs vertically."),
261
+    .priv_size     = sizeof(StackContext),
262
+    .priv_class    = &vstack_class,
263
+    .query_formats = query_formats,
264
+    .outputs       = outputs,
265
+    .init          = init,
266
+    .uninit        = uninit,
267
+    .flags         = AVFILTER_FLAG_DYNAMIC_INPUTS,
268
+};
269
+
270
+#endif /* CONFIG_VSTACK_FILTER */