Browse code

avfilter: add mergeplanes filter

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

Paul B Mahol authored on 2013/09/28 03:37:51
Showing 7 changed files
... ...
@@ -44,6 +44,7 @@ version <next>
44 44
 - Enhanced Low Delay AAC (ER AAC ELD) decoding (no LD SBR support)
45 45
 - Linux framebuffer output device
46 46
 - HEVC decoder, raw HEVC demuxer, HEVC demuxing in TS, Matroska and MP4
47
+- mergeplanes filter
47 48
 
48 49
 
49 50
 version 2.0:
... ...
@@ -334,6 +334,7 @@ Filters:
334 334
   vf_extractplanes.c                    Paul B Mahol
335 335
   vf_histogram.c                        Paul B Mahol
336 336
   vf_il.c                               Paul B Mahol
337
+  vf_mergeplanes.c                      Paul B Mahol
337 338
   vf_psnr.c                             Paul B Mahol
338 339
   vf_scale.c                            Michael Niedermayer
339 340
   vf_separatefields.c                   Paul B Mahol
... ...
@@ -5260,6 +5260,65 @@ lutyuv=y='bitand(val, 128+64+32)'
5260 5260
 @end example
5261 5261
 @end itemize
5262 5262
 
5263
+@section mergeplanes
5264
+
5265
+Merge color channel components from several video streams.
5266
+
5267
+The filter accepts up to 4 input streams, and merge selected input
5268
+planes to the output video.
5269
+
5270
+This filter accepts the following options:
5271
+@table @option
5272
+@item mapping
5273
+Set input to output plane mapping. Default is @code{0}.
5274
+
5275
+The mappings is specified as a bitmap. It should be specified as a
5276
+hexadecimal number in the form 0xAa[Bb[Cc[Dd]]]. 'Aa' describes the
5277
+mapping for the first plane of the output stream. 'A' sets the number of
5278
+the input stream to use (from 0 to 3), and 'a' the plane number of the
5279
+corresponding input to use (from 0 to 3). The rest of the mappings is
5280
+similar, 'Bb' describes the mapping for the output stream second
5281
+plane, 'Cc' describes the mapping for the output stream third plane and
5282
+'Dd' describes the mapping for the output stream fourth plane.
5283
+
5284
+@item format
5285
+Set output pixel format. Default is @code{yuva444p}.
5286
+@end table
5287
+
5288
+@subsection Examples
5289
+
5290
+@itemize
5291
+@item
5292
+Merge three gray video streams of same width and height into single video stream:
5293
+@example
5294
+[a0][a1][a2]mergeplanes=0x001020:yuv444p
5295
+@end example
5296
+
5297
+@item
5298
+Merge 1st yuv444p stream and 2nd gray video stream into yuva444p video stream:
5299
+@example
5300
+[a0][a1]mergeplanes=0x00010210:yuva444p
5301
+@end example
5302
+
5303
+@item
5304
+Swap Y and A plane in yuva444p stream:
5305
+@example
5306
+format=yuva444p,mergeplanes=0x03010200:yuva444p
5307
+@end example
5308
+
5309
+@item
5310
+Swap U and V plane in yuv420p stream:
5311
+@example
5312
+format=yuv420p,mergeplanes=0x000201:yuv420p
5313
+@end example
5314
+
5315
+@item
5316
+Cast a rgb24 clip to yuv444p:
5317
+@example
5318
+format=rgb24,mergeplanes=0x000102:yuv444p
5319
+@end example
5320
+@end itemize
5321
+
5263 5322
 @section mcdeint
5264 5323
 
5265 5324
 Apply motion-compensation deinterlacing.
... ...
@@ -158,6 +158,7 @@ OBJS-$(CONFIG_LUT_FILTER)                    += vf_lut.o
158 158
 OBJS-$(CONFIG_LUTRGB_FILTER)                 += vf_lut.o
159 159
 OBJS-$(CONFIG_LUTYUV_FILTER)                 += vf_lut.o
160 160
 OBJS-$(CONFIG_MCDEINT_FILTER)                += vf_mcdeint.o
161
+OBJS-$(CONFIG_MERGEPLANES_FILTER)            += vf_mergeplanes.o framesync.o
161 162
 OBJS-$(CONFIG_MP_FILTER)                     += vf_mp.o
162 163
 OBJS-$(CONFIG_MPDECIMATE_FILTER)             += vf_mpdecimate.o
163 164
 OBJS-$(CONFIG_NEGATE_FILTER)                 += vf_lut.o
... ...
@@ -154,6 +154,7 @@ void avfilter_register_all(void)
154 154
     REGISTER_FILTER(LUTRGB,         lutrgb,         vf);
155 155
     REGISTER_FILTER(LUTYUV,         lutyuv,         vf);
156 156
     REGISTER_FILTER(MCDEINT,        mcdeint,        vf);
157
+    REGISTER_FILTER(MERGEPLANES,    mergeplanes,    vf);
157 158
     REGISTER_FILTER(MP,             mp,             vf);
158 159
     REGISTER_FILTER(MPDECIMATE,     mpdecimate,     vf);
159 160
     REGISTER_FILTER(NEGATE,         negate,         vf);
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR  3
33
-#define LIBAVFILTER_VERSION_MINOR  89
33
+#define LIBAVFILTER_VERSION_MINOR  90
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,313 @@
0
+/*
1
+ * Copyright (c) 2013 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/avassert.h"
21
+#include "libavutil/avstring.h"
22
+#include "libavutil/imgutils.h"
23
+#include "libavutil/opt.h"
24
+#include "libavutil/pixdesc.h"
25
+#include "avfilter.h"
26
+#include "internal.h"
27
+#include "framesync.h"
28
+
29
+typedef struct InputParam {
30
+    int depth[4];
31
+    int nb_planes;
32
+    int planewidth[4];
33
+    int planeheight[4];
34
+} InputParam;
35
+
36
+typedef struct MergePlanesContext {
37
+    const AVClass *class;
38
+    int64_t mapping;
39
+    const enum AVPixelFormat out_fmt;
40
+    int nb_inputs;
41
+    int nb_planes;
42
+    int planewidth[4];
43
+    int planeheight[4];
44
+    int map[4][2];
45
+    const AVPixFmtDescriptor *outdesc;
46
+
47
+    FFFrameSync fs;
48
+    FFFrameSyncIn fsin[3]; /* must be immediately after fs */
49
+} MergePlanesContext;
50
+
51
+#define OFFSET(x) offsetof(MergePlanesContext, x)
52
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
53
+static const AVOption mergeplanes_options[] = {
54
+    { "mapping", "set input to output plane mapping", OFFSET(mapping), AV_OPT_TYPE_INT, {.i64=0}, 0, 0x33333333, FLAGS },
55
+    { "format", "set output pixel format", OFFSET(out_fmt), AV_OPT_TYPE_PIXEL_FMT, {.i64=AV_PIX_FMT_YUVA444P}, .flags=FLAGS },
56
+    { NULL }
57
+};
58
+
59
+AVFILTER_DEFINE_CLASS(mergeplanes);
60
+
61
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
62
+{
63
+    MergePlanesContext *s = inlink->dst->priv;
64
+    return ff_framesync_filter_frame(&s->fs, inlink, in);
65
+}
66
+
67
+static av_cold int init(AVFilterContext *ctx)
68
+{
69
+    MergePlanesContext *s = ctx->priv;
70
+    int64_t m = s->mapping;
71
+    int i, ret;
72
+
73
+    s->outdesc = av_pix_fmt_desc_get(s->out_fmt);
74
+    if (!(s->outdesc->flags & AV_PIX_FMT_FLAG_PLANAR) ||
75
+        s->outdesc->nb_components < 2) {
76
+        av_log(ctx, AV_LOG_ERROR, "Only planar formats with more than one component are supported.\n");
77
+        return AVERROR(EINVAL);
78
+    }
79
+    s->nb_planes = av_pix_fmt_count_planes(s->out_fmt);
80
+
81
+    for (i = s->nb_planes - 1; i >= 0; i--) {
82
+        s->map[i][0] = m & 0xf;
83
+        m >>= 4;
84
+        s->map[i][1] = m & 0xf;
85
+        m >>= 4;
86
+
87
+        if (s->map[i][0] > 3 || s->map[i][1] > 3) {
88
+            av_log(ctx, AV_LOG_ERROR, "Mapping with out of range input and/or plane number.\n");
89
+            return AVERROR(EINVAL);
90
+        }
91
+
92
+        s->nb_inputs = FFMAX(s->nb_inputs, s->map[i][1] + 1);
93
+    }
94
+
95
+    av_assert0(s->nb_inputs && s->nb_inputs <= 4);
96
+
97
+    for (i = 0; i < s->nb_inputs; i++) {
98
+        AVFilterPad pad = { 0 };
99
+
100
+        pad.type = AVMEDIA_TYPE_VIDEO;
101
+        pad.name = av_asprintf("in%d", i);
102
+        if (!pad.name)
103
+            return AVERROR(ENOMEM);
104
+        pad.filter_frame = filter_frame;
105
+
106
+        if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0){
107
+            av_freep(&pad.name);
108
+            return ret;
109
+        }
110
+    }
111
+
112
+    return 0;
113
+}
114
+
115
+static int query_formats(AVFilterContext *ctx)
116
+{
117
+    MergePlanesContext *s = ctx->priv;
118
+    AVFilterFormats *formats = NULL;
119
+    int i;
120
+
121
+    s->outdesc = av_pix_fmt_desc_get(s->out_fmt);
122
+    for (i = 0; i < AV_PIX_FMT_NB; i++) {
123
+        const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(i);
124
+        if (desc->comp[0].depth_minus1 == s->outdesc->comp[0].depth_minus1 &&
125
+            av_pix_fmt_count_planes(i) == desc->nb_components)
126
+            ff_add_format(&formats, i);
127
+    }
128
+
129
+    for (i = 0; i < s->nb_inputs; i++)
130
+        ff_formats_ref(formats, &ctx->inputs[i]->out_formats);
131
+
132
+    formats = NULL;
133
+    ff_add_format(&formats, s->out_fmt);
134
+    ff_formats_ref(formats, &ctx->outputs[0]->in_formats);
135
+
136
+    return 0;
137
+}
138
+
139
+static int process_frame(FFFrameSync *fs)
140
+{
141
+    AVFilterContext *ctx = fs->parent;
142
+    AVFilterLink *outlink = ctx->outputs[0];
143
+    MergePlanesContext *s = fs->opaque;
144
+    AVFrame *in[4] = { NULL };
145
+    AVFrame *out;
146
+    int i, ret;
147
+
148
+    for (i = 0; i < s->nb_inputs; i++) {
149
+        if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
150
+            return ret;
151
+    }
152
+
153
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
154
+    if (!out)
155
+        return AVERROR(ENOMEM);
156
+    out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
157
+
158
+    for (i = 0; i < s->nb_planes; i++) {
159
+        const int input = s->map[i][1];
160
+        const int plane = s->map[i][0];
161
+
162
+        av_image_copy_plane(out->data[i], out->linesize[i],
163
+                            in[input]->data[plane], in[input]->linesize[plane],
164
+                            s->planewidth[i], s->planeheight[i]);
165
+    }
166
+
167
+    return ff_filter_frame(outlink, out);
168
+}
169
+
170
+static int config_output(AVFilterLink *outlink)
171
+{
172
+    AVFilterContext *ctx = outlink->src;
173
+    MergePlanesContext *s = ctx->priv;
174
+    InputParam inputsp[4];
175
+    FFFrameSyncIn *in;
176
+    int i;
177
+
178
+    ff_framesync_init(&s->fs, ctx, s->nb_inputs);
179
+    in = s->fs.in;
180
+    s->fs.opaque = s;
181
+    s->fs.on_event = process_frame;
182
+
183
+    outlink->w = ctx->inputs[0]->w;
184
+    outlink->h = ctx->inputs[0]->h;
185
+    outlink->time_base = ctx->inputs[0]->time_base;
186
+    outlink->frame_rate = ctx->inputs[0]->frame_rate;
187
+    outlink->sample_aspect_ratio = ctx->inputs[0]->sample_aspect_ratio;
188
+
189
+    s->planewidth[1]  =
190
+    s->planewidth[2]  = FF_CEIL_RSHIFT(outlink->w, s->outdesc->log2_chroma_w);
191
+    s->planewidth[0]  =
192
+    s->planewidth[3]  = outlink->w;
193
+    s->planeheight[1] =
194
+    s->planeheight[2] = FF_CEIL_RSHIFT(outlink->h, s->outdesc->log2_chroma_h);
195
+    s->planeheight[0] =
196
+    s->planeheight[3] = outlink->h;
197
+
198
+    for (i = 0; i < s->nb_inputs; i++) {
199
+        InputParam *inputp = &inputsp[i];
200
+        AVFilterLink *inlink = ctx->inputs[i];
201
+        const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(inlink->format);
202
+        int j;
203
+
204
+        if (outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num ||
205
+            outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) {
206
+            av_log(ctx, AV_LOG_ERROR, "input #%d link %s SAR %d:%d "
207
+                                      "does not match output link %s SAR %d:%d\n",
208
+                                      i, ctx->input_pads[i].name,
209
+                                      inlink->sample_aspect_ratio.num,
210
+                                      inlink->sample_aspect_ratio.den,
211
+                                      ctx->output_pads[0].name,
212
+                                      outlink->sample_aspect_ratio.num,
213
+                                      outlink->sample_aspect_ratio.den);
214
+            return AVERROR(EINVAL);
215
+        }
216
+
217
+        inputp->planewidth[1]  =
218
+        inputp->planewidth[2]  = FF_CEIL_RSHIFT(inlink->w, indesc->log2_chroma_w);
219
+        inputp->planewidth[0]  =
220
+        inputp->planewidth[3]  = inlink->w;
221
+        inputp->planeheight[1] =
222
+        inputp->planeheight[2] = FF_CEIL_RSHIFT(inlink->h, indesc->log2_chroma_h);
223
+        inputp->planeheight[0] =
224
+        inputp->planeheight[3] = inlink->h;
225
+        inputp->nb_planes = av_pix_fmt_count_planes(inlink->format);
226
+
227
+        for (j = 0; j < inputp->nb_planes; j++)
228
+            inputp->depth[j] = indesc->comp[j].depth_minus1 + 1;
229
+
230
+        in[i].time_base = inlink->time_base;
231
+        in[i].sync   = 1;
232
+        in[i].before = EXT_STOP;
233
+        in[i].after  = EXT_STOP;
234
+    }
235
+
236
+    for (i = 0; i < s->nb_planes; i++) {
237
+        const int input = s->map[i][1];
238
+        const int plane = s->map[i][0];
239
+        InputParam *inputp = &inputsp[input];
240
+
241
+        if (plane + 1 > inputp->nb_planes) {
242
+            av_log(ctx, AV_LOG_ERROR, "input %d does not have %d plane\n",
243
+                                      input, plane);
244
+            goto fail;
245
+        }
246
+        if (s->outdesc->comp[i].depth_minus1 + 1 != inputp->depth[plane]) {
247
+            av_log(ctx, AV_LOG_ERROR, "output plane %d depth %d does not "
248
+                                      "match input %d plane %d depth %d\n",
249
+                                      i, s->outdesc->comp[i].depth_minus1 + 1,
250
+                                      input, plane, inputp->depth[plane]);
251
+            goto fail;
252
+        }
253
+        if (s->planewidth[i] != inputp->planewidth[plane]) {
254
+            av_log(ctx, AV_LOG_ERROR, "output plane %d width %d does not "
255
+                                      "match input %d plane %d width %d\n",
256
+                                      i, s->planewidth[i],
257
+                                      input, plane, inputp->planewidth[plane]);
258
+            goto fail;
259
+        }
260
+        if (s->planeheight[i] != inputp->planeheight[plane]) {
261
+            av_log(ctx, AV_LOG_ERROR, "output plane %d height %d does not "
262
+                                      "match input %d plane %d height %d\n",
263
+                                      i, s->planeheight[i],
264
+                                      input, plane, inputp->planeheight[plane]);
265
+            goto fail;
266
+        }
267
+    }
268
+
269
+    return ff_framesync_configure(&s->fs);
270
+fail:
271
+    return AVERROR(EINVAL);
272
+}
273
+
274
+static int request_frame(AVFilterLink *outlink)
275
+{
276
+    MergePlanesContext *s = outlink->src->priv;
277
+    return ff_framesync_request_frame(&s->fs, outlink);
278
+}
279
+
280
+static av_cold void uninit(AVFilterContext *ctx)
281
+{
282
+    MergePlanesContext *s = ctx->priv;
283
+    int i;
284
+
285
+    ff_framesync_uninit(&s->fs);
286
+
287
+    for (i = 0; i < ctx->nb_inputs; i++)
288
+        av_freep(&ctx->input_pads[i].name);
289
+}
290
+
291
+static const AVFilterPad mergeplanes_outputs[] = {
292
+    {
293
+        .name          = "default",
294
+        .type          = AVMEDIA_TYPE_VIDEO,
295
+        .config_props  = config_output,
296
+        .request_frame = request_frame,
297
+    },
298
+    { NULL }
299
+};
300
+
301
+AVFilter avfilter_vf_mergeplanes = {
302
+    .name          = "mergeplanes",
303
+    .description   = NULL_IF_CONFIG_SMALL("Merge planes."),
304
+    .priv_size     = sizeof(MergePlanesContext),
305
+    .priv_class    = &mergeplanes_class,
306
+    .init          = init,
307
+    .uninit        = uninit,
308
+    .query_formats = query_formats,
309
+    .inputs        = NULL,
310
+    .outputs       = mergeplanes_outputs,
311
+    .flags         = AVFILTER_FLAG_DYNAMIC_INPUTS,
312
+};