avfilter: add hstack & vstack filter
[ffmpeg.git] / libavfilter / vf_stack.c
1 /*
2  * Copyright (c) 2015 Paul B. Mahol
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "libavutil/avstring.h"
22 #include "libavutil/imgutils.h"
23 #include "libavutil/opt.h"
24 #include "libavutil/pixdesc.h"
25
26 #include "avfilter.h"
27 #include "formats.h"
28 #include "internal.h"
29 #include "framesync.h"
30 #include "video.h"
31
32 typedef struct StackContext {
33     const AVClass *class;
34     const AVPixFmtDescriptor *desc;
35     int nb_inputs;
36     int is_vertical;
37     int nb_planes;
38
39     AVFrame **frames;
40     FFFrameSync fs;
41 } StackContext;
42
43 static int query_formats(AVFilterContext *ctx)
44 {
45     AVFilterFormats *pix_fmts = NULL;
46     int fmt;
47
48     for (fmt = 0; av_pix_fmt_desc_get(fmt); fmt++) {
49         const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
50         if (!(desc->flags & AV_PIX_FMT_FLAG_PAL ||
51               desc->flags & AV_PIX_FMT_FLAG_HWACCEL ||
52               desc->flags & AV_PIX_FMT_FLAG_BITSTREAM))
53             ff_add_format(&pix_fmts, fmt);
54     }
55
56     return ff_set_common_formats(ctx, pix_fmts);
57 }
58
59 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
60 {
61     StackContext *s = inlink->dst->priv;
62     return ff_framesync_filter_frame(&s->fs, inlink, in);
63 }
64
65 static av_cold int init(AVFilterContext *ctx)
66 {
67     StackContext *s = ctx->priv;
68     int i, ret;
69
70     if (!strcmp(ctx->filter->name, "vstack"))
71         s->is_vertical = 1;
72
73     s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
74     if (!s->frames)
75         return AVERROR(ENOMEM);
76
77     for (i = 0; i < s->nb_inputs; i++) {
78         AVFilterPad pad = { 0 };
79
80         pad.type = AVMEDIA_TYPE_VIDEO;
81         pad.name = av_asprintf("input%d", i);
82         if (!pad.name)
83             return AVERROR(ENOMEM);
84         pad.filter_frame = filter_frame;
85
86         if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0) {
87             av_freep(&pad.name);
88             return ret;
89         }
90     }
91
92     return 0;
93 }
94
95 static int process_frame(FFFrameSync *fs)
96 {
97     AVFilterContext *ctx = fs->parent;
98     AVFilterLink *outlink = ctx->outputs[0];
99     StackContext *s = fs->opaque;
100     AVFrame **in = s->frames;
101     AVFrame *out;
102     int i, p, ret, offset[4] = { 0 };
103
104     for (i = 0; i < s->nb_inputs; i++) {
105         if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
106             return ret;
107     }
108
109     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
110     if (!out)
111         return AVERROR(ENOMEM);
112     out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
113
114     for (i = 0; i < s->nb_inputs; i++) {
115         AVFilterLink *inlink = ctx->inputs[i];
116         int linesize[4];
117         int height[4];
118
119         if ((ret = av_image_fill_linesizes(linesize, inlink->format, inlink->w)) < 0)
120             return ret;
121
122         height[1] = height[2] = FF_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
123         height[0] = height[3] = inlink->h;
124
125         for (p = 0; p < s->nb_planes; p++) {
126             if (s->is_vertical) {
127                 av_image_copy_plane(out->data[p] + offset[p] * out->linesize[p],
128                                     out->linesize[p],
129                                     in[i]->data[p],
130                                     in[i]->linesize[p],
131                                     linesize[p], height[p]);
132                 offset[p] += height[p];
133             } else {
134                 av_image_copy_plane(out->data[p] + offset[p],
135                                     out->linesize[p],
136                                     in[i]->data[p],
137                                     in[i]->linesize[p],
138                                     linesize[p], height[p]);
139                 offset[p] += linesize[p];
140             }
141         }
142     }
143
144     return ff_filter_frame(outlink, out);
145 }
146
147 static int config_output(AVFilterLink *outlink)
148 {
149     AVFilterContext *ctx = outlink->src;
150     StackContext *s = ctx->priv;
151     AVRational time_base = ctx->inputs[0]->time_base;
152     AVRational frame_rate = ctx->inputs[0]->frame_rate;
153     int height = ctx->inputs[0]->h;
154     int width = ctx->inputs[0]->w;
155     FFFrameSyncIn *in;
156     int i, ret;
157
158     if (s->is_vertical) {
159         for (i = 1; i < s->nb_inputs; i++) {
160             if (ctx->inputs[i]->w != width) {
161                 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);
162                 return AVERROR(EINVAL);
163             }
164             height += ctx->inputs[i]->h;
165         }
166     } else {
167         for (i = 1; i < s->nb_inputs; i++) {
168             if (ctx->inputs[i]->h != height) {
169                 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);
170                 return AVERROR(EINVAL);
171             }
172             width += ctx->inputs[i]->w;
173         }
174     }
175
176     s->desc = av_pix_fmt_desc_get(outlink->format);
177     if (!s->desc)
178         return AVERROR_BUG;
179     s->nb_planes = av_pix_fmt_count_planes(outlink->format);
180
181     outlink->w          = width;
182     outlink->h          = height;
183     outlink->time_base  = time_base;
184     outlink->frame_rate = frame_rate;
185
186     if ((ret = ff_framesync_init(&s->fs, ctx, s->nb_inputs)) < 0)
187         return ret;
188
189     in = s->fs.in;
190     s->fs.opaque = s;
191     s->fs.on_event = process_frame;
192
193     for (i = 0; i < s->nb_inputs; i++) {
194         AVFilterLink *inlink = ctx->inputs[i];
195
196         in[i].time_base = inlink->time_base;
197         in[i].sync   = 1;
198         in[i].before = EXT_STOP;
199         in[i].after  = EXT_INFINITY;
200     }
201
202     return ff_framesync_configure(&s->fs);
203 }
204
205 static int request_frame(AVFilterLink *outlink)
206 {
207     StackContext *s = outlink->src->priv;
208     return ff_framesync_request_frame(&s->fs, outlink);
209 }
210
211 static av_cold void uninit(AVFilterContext *ctx)
212 {
213     StackContext *s = ctx->priv;
214     ff_framesync_uninit(&s->fs);
215     av_freep(&s->frames);
216 }
217
218 #define OFFSET(x) offsetof(StackContext, x)
219 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
220 static const AVOption stack_options[] = {
221     { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
222     { NULL },
223 };
224
225 static const AVFilterPad outputs[] = {
226     {
227         .name          = "default",
228         .type          = AVMEDIA_TYPE_VIDEO,
229         .config_props  = config_output,
230         .request_frame = request_frame,
231     },
232     { NULL }
233 };
234
235 #if CONFIG_HSTACK_FILTER
236
237 #define hstack_options stack_options
238 AVFILTER_DEFINE_CLASS(hstack);
239
240 AVFilter ff_vf_hstack = {
241     .name          = "hstack",
242     .description   = NULL_IF_CONFIG_SMALL("Stack video inputs horizontally."),
243     .priv_size     = sizeof(StackContext),
244     .priv_class    = &hstack_class,
245     .query_formats = query_formats,
246     .outputs       = outputs,
247     .init          = init,
248     .uninit        = uninit,
249     .flags         = AVFILTER_FLAG_DYNAMIC_INPUTS,
250 };
251
252 #endif /* CONFIG_HSTACK_FILTER */
253
254 #if CONFIG_VSTACK_FILTER
255
256 #define vstack_options stack_options
257 AVFILTER_DEFINE_CLASS(vstack);
258
259 AVFilter ff_vf_vstack = {
260     .name          = "vstack",
261     .description   = NULL_IF_CONFIG_SMALL("Stack video inputs vertically."),
262     .priv_size     = sizeof(StackContext),
263     .priv_class    = &vstack_class,
264     .query_formats = query_formats,
265     .outputs       = outputs,
266     .init          = init,
267     .uninit        = uninit,
268     .flags         = AVFILTER_FLAG_DYNAMIC_INPUTS,
269 };
270
271 #endif /* CONFIG_VSTACK_FILTER */