Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2013 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/avassert.h" | |
22 | #include "libavutil/avstring.h" | |
23 | #include "libavutil/imgutils.h" | |
24 | #include "libavutil/opt.h" | |
25 | #include "libavutil/pixdesc.h" | |
26 | #include "avfilter.h" | |
27 | #include "internal.h" | |
28 | #include "framesync.h" | |
29 | ||
30 | typedef struct InputParam { | |
31 | int depth[4]; | |
32 | int nb_planes; | |
33 | int planewidth[4]; | |
34 | int planeheight[4]; | |
35 | } InputParam; | |
36 | ||
37 | typedef struct MergePlanesContext { | |
38 | const AVClass *class; | |
39 | int64_t mapping; | |
40 | const enum AVPixelFormat out_fmt; | |
41 | int nb_inputs; | |
42 | int nb_planes; | |
43 | int planewidth[4]; | |
44 | int planeheight[4]; | |
45 | int map[4][2]; | |
46 | const AVPixFmtDescriptor *outdesc; | |
47 | ||
48 | FFFrameSync fs; | |
49 | FFFrameSyncIn fsin[3]; /* must be immediately after fs */ | |
50 | } MergePlanesContext; | |
51 | ||
52 | #define OFFSET(x) offsetof(MergePlanesContext, x) | |
53 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | |
54 | static const AVOption mergeplanes_options[] = { | |
55 | { "mapping", "set input to output plane mapping", OFFSET(mapping), AV_OPT_TYPE_INT, {.i64=0}, 0, 0x33333333, FLAGS }, | |
56 | { "format", "set output pixel format", OFFSET(out_fmt), AV_OPT_TYPE_PIXEL_FMT, {.i64=AV_PIX_FMT_YUVA444P}, 0, INT_MAX, .flags=FLAGS }, | |
57 | { NULL } | |
58 | }; | |
59 | ||
60 | AVFILTER_DEFINE_CLASS(mergeplanes); | |
61 | ||
62 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
63 | { | |
64 | MergePlanesContext *s = inlink->dst->priv; | |
65 | return ff_framesync_filter_frame(&s->fs, inlink, in); | |
66 | } | |
67 | ||
68 | static av_cold int init(AVFilterContext *ctx) | |
69 | { | |
70 | MergePlanesContext *s = ctx->priv; | |
71 | int64_t m = s->mapping; | |
72 | int i, ret; | |
73 | ||
74 | s->outdesc = av_pix_fmt_desc_get(s->out_fmt); | |
75 | if (!(s->outdesc->flags & AV_PIX_FMT_FLAG_PLANAR) || | |
76 | s->outdesc->nb_components < 2) { | |
77 | av_log(ctx, AV_LOG_ERROR, "Only planar formats with more than one component are supported.\n"); | |
78 | return AVERROR(EINVAL); | |
79 | } | |
80 | s->nb_planes = av_pix_fmt_count_planes(s->out_fmt); | |
81 | ||
82 | for (i = s->nb_planes - 1; i >= 0; i--) { | |
83 | s->map[i][0] = m & 0xf; | |
84 | m >>= 4; | |
85 | s->map[i][1] = m & 0xf; | |
86 | m >>= 4; | |
87 | ||
88 | if (s->map[i][0] > 3 || s->map[i][1] > 3) { | |
89 | av_log(ctx, AV_LOG_ERROR, "Mapping with out of range input and/or plane number.\n"); | |
90 | return AVERROR(EINVAL); | |
91 | } | |
92 | ||
93 | s->nb_inputs = FFMAX(s->nb_inputs, s->map[i][1] + 1); | |
94 | } | |
95 | ||
96 | av_assert0(s->nb_inputs && s->nb_inputs <= 4); | |
97 | ||
98 | for (i = 0; i < s->nb_inputs; i++) { | |
99 | AVFilterPad pad = { 0 }; | |
100 | ||
101 | pad.type = AVMEDIA_TYPE_VIDEO; | |
102 | pad.name = av_asprintf("in%d", i); | |
103 | if (!pad.name) | |
104 | return AVERROR(ENOMEM); | |
105 | pad.filter_frame = filter_frame; | |
106 | ||
107 | if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0){ | |
108 | av_freep(&pad.name); | |
109 | return ret; | |
110 | } | |
111 | } | |
112 | ||
113 | return 0; | |
114 | } | |
115 | ||
116 | static int query_formats(AVFilterContext *ctx) | |
117 | { | |
118 | MergePlanesContext *s = ctx->priv; | |
119 | AVFilterFormats *formats = NULL; | |
120 | int i; | |
121 | ||
122 | s->outdesc = av_pix_fmt_desc_get(s->out_fmt); | |
123 | for (i = 0; av_pix_fmt_desc_get(i); i++) { | |
124 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(i); | |
125 | if (desc->comp[0].depth_minus1 == s->outdesc->comp[0].depth_minus1 && | |
126 | av_pix_fmt_count_planes(i) == desc->nb_components) | |
127 | ff_add_format(&formats, i); | |
128 | } | |
129 | ||
130 | for (i = 0; i < s->nb_inputs; i++) | |
131 | ff_formats_ref(formats, &ctx->inputs[i]->out_formats); | |
132 | ||
133 | formats = NULL; | |
134 | ff_add_format(&formats, s->out_fmt); | |
135 | ff_formats_ref(formats, &ctx->outputs[0]->in_formats); | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
140 | static int process_frame(FFFrameSync *fs) | |
141 | { | |
142 | AVFilterContext *ctx = fs->parent; | |
143 | AVFilterLink *outlink = ctx->outputs[0]; | |
144 | MergePlanesContext *s = fs->opaque; | |
145 | AVFrame *in[4] = { NULL }; | |
146 | AVFrame *out; | |
147 | int i, ret; | |
148 | ||
149 | for (i = 0; i < s->nb_inputs; i++) { | |
150 | if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0) | |
151 | return ret; | |
152 | } | |
153 | ||
154 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
155 | if (!out) | |
156 | return AVERROR(ENOMEM); | |
157 | out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); | |
158 | ||
159 | for (i = 0; i < s->nb_planes; i++) { | |
160 | const int input = s->map[i][1]; | |
161 | const int plane = s->map[i][0]; | |
162 | ||
163 | av_image_copy_plane(out->data[i], out->linesize[i], | |
164 | in[input]->data[plane], in[input]->linesize[plane], | |
165 | s->planewidth[i], s->planeheight[i]); | |
166 | } | |
167 | ||
168 | return ff_filter_frame(outlink, out); | |
169 | } | |
170 | ||
171 | static int config_output(AVFilterLink *outlink) | |
172 | { | |
173 | AVFilterContext *ctx = outlink->src; | |
174 | MergePlanesContext *s = ctx->priv; | |
175 | InputParam inputsp[4]; | |
176 | FFFrameSyncIn *in; | |
177 | int i; | |
178 | ||
179 | ff_framesync_init(&s->fs, ctx, s->nb_inputs); | |
180 | in = s->fs.in; | |
181 | s->fs.opaque = s; | |
182 | s->fs.on_event = process_frame; | |
183 | ||
184 | outlink->w = ctx->inputs[0]->w; | |
185 | outlink->h = ctx->inputs[0]->h; | |
186 | outlink->time_base = ctx->inputs[0]->time_base; | |
187 | outlink->frame_rate = ctx->inputs[0]->frame_rate; | |
188 | outlink->sample_aspect_ratio = ctx->inputs[0]->sample_aspect_ratio; | |
189 | ||
190 | s->planewidth[1] = | |
191 | s->planewidth[2] = FF_CEIL_RSHIFT(outlink->w, s->outdesc->log2_chroma_w); | |
192 | s->planewidth[0] = | |
193 | s->planewidth[3] = outlink->w; | |
194 | s->planeheight[1] = | |
195 | s->planeheight[2] = FF_CEIL_RSHIFT(outlink->h, s->outdesc->log2_chroma_h); | |
196 | s->planeheight[0] = | |
197 | s->planeheight[3] = outlink->h; | |
198 | ||
199 | for (i = 0; i < s->nb_inputs; i++) { | |
200 | InputParam *inputp = &inputsp[i]; | |
201 | AVFilterLink *inlink = ctx->inputs[i]; | |
202 | const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(inlink->format); | |
203 | int j; | |
204 | ||
205 | if (outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num || | |
206 | outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) { | |
207 | av_log(ctx, AV_LOG_ERROR, "input #%d link %s SAR %d:%d " | |
208 | "does not match output link %s SAR %d:%d\n", | |
209 | i, ctx->input_pads[i].name, | |
210 | inlink->sample_aspect_ratio.num, | |
211 | inlink->sample_aspect_ratio.den, | |
212 | ctx->output_pads[0].name, | |
213 | outlink->sample_aspect_ratio.num, | |
214 | outlink->sample_aspect_ratio.den); | |
215 | return AVERROR(EINVAL); | |
216 | } | |
217 | ||
218 | inputp->planewidth[1] = | |
219 | inputp->planewidth[2] = FF_CEIL_RSHIFT(inlink->w, indesc->log2_chroma_w); | |
220 | inputp->planewidth[0] = | |
221 | inputp->planewidth[3] = inlink->w; | |
222 | inputp->planeheight[1] = | |
223 | inputp->planeheight[2] = FF_CEIL_RSHIFT(inlink->h, indesc->log2_chroma_h); | |
224 | inputp->planeheight[0] = | |
225 | inputp->planeheight[3] = inlink->h; | |
226 | inputp->nb_planes = av_pix_fmt_count_planes(inlink->format); | |
227 | ||
228 | for (j = 0; j < inputp->nb_planes; j++) | |
229 | inputp->depth[j] = indesc->comp[j].depth_minus1 + 1; | |
230 | ||
231 | in[i].time_base = inlink->time_base; | |
232 | in[i].sync = 1; | |
233 | in[i].before = EXT_STOP; | |
234 | in[i].after = EXT_STOP; | |
235 | } | |
236 | ||
237 | for (i = 0; i < s->nb_planes; i++) { | |
238 | const int input = s->map[i][1]; | |
239 | const int plane = s->map[i][0]; | |
240 | InputParam *inputp = &inputsp[input]; | |
241 | ||
242 | if (plane + 1 > inputp->nb_planes) { | |
243 | av_log(ctx, AV_LOG_ERROR, "input %d does not have %d plane\n", | |
244 | input, plane); | |
245 | goto fail; | |
246 | } | |
247 | if (s->outdesc->comp[i].depth_minus1 + 1 != inputp->depth[plane]) { | |
248 | av_log(ctx, AV_LOG_ERROR, "output plane %d depth %d does not " | |
249 | "match input %d plane %d depth %d\n", | |
250 | i, s->outdesc->comp[i].depth_minus1 + 1, | |
251 | input, plane, inputp->depth[plane]); | |
252 | goto fail; | |
253 | } | |
254 | if (s->planewidth[i] != inputp->planewidth[plane]) { | |
255 | av_log(ctx, AV_LOG_ERROR, "output plane %d width %d does not " | |
256 | "match input %d plane %d width %d\n", | |
257 | i, s->planewidth[i], | |
258 | input, plane, inputp->planewidth[plane]); | |
259 | goto fail; | |
260 | } | |
261 | if (s->planeheight[i] != inputp->planeheight[plane]) { | |
262 | av_log(ctx, AV_LOG_ERROR, "output plane %d height %d does not " | |
263 | "match input %d plane %d height %d\n", | |
264 | i, s->planeheight[i], | |
265 | input, plane, inputp->planeheight[plane]); | |
266 | goto fail; | |
267 | } | |
268 | } | |
269 | ||
270 | return ff_framesync_configure(&s->fs); | |
271 | fail: | |
272 | return AVERROR(EINVAL); | |
273 | } | |
274 | ||
275 | static int request_frame(AVFilterLink *outlink) | |
276 | { | |
277 | MergePlanesContext *s = outlink->src->priv; | |
278 | return ff_framesync_request_frame(&s->fs, outlink); | |
279 | } | |
280 | ||
281 | static av_cold void uninit(AVFilterContext *ctx) | |
282 | { | |
283 | MergePlanesContext *s = ctx->priv; | |
284 | int i; | |
285 | ||
286 | ff_framesync_uninit(&s->fs); | |
287 | ||
288 | for (i = 0; i < ctx->nb_inputs; i++) | |
289 | av_freep(&ctx->input_pads[i].name); | |
290 | } | |
291 | ||
292 | static const AVFilterPad mergeplanes_outputs[] = { | |
293 | { | |
294 | .name = "default", | |
295 | .type = AVMEDIA_TYPE_VIDEO, | |
296 | .config_props = config_output, | |
297 | .request_frame = request_frame, | |
298 | }, | |
299 | { NULL } | |
300 | }; | |
301 | ||
302 | AVFilter ff_vf_mergeplanes = { | |
303 | .name = "mergeplanes", | |
304 | .description = NULL_IF_CONFIG_SMALL("Merge planes."), | |
305 | .priv_size = sizeof(MergePlanesContext), | |
306 | .priv_class = &mergeplanes_class, | |
307 | .init = init, | |
308 | .uninit = uninit, | |
309 | .query_formats = query_formats, | |
310 | .inputs = NULL, | |
311 | .outputs = mergeplanes_outputs, | |
312 | .flags = AVFILTER_FLAG_DYNAMIC_INPUTS, | |
313 | }; |