2 * Copyright (c) 2012 Nicolas George
4 * This file is part of FFmpeg.
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.
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.
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
26 #include "libavutil/opt.h"
27 #include "libavutil/pixdesc.h"
29 #include "drawutils.h"
44 uint8_t rgba_color
[4];
47 #define REASONABLE_SIZE 1024
49 #define OFFSET(x) offsetof(TileContext, x)
50 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
52 static const AVOption tile_options
[] = {
53 { "layout", "set grid size", OFFSET(w
), AV_OPT_TYPE_IMAGE_SIZE
,
54 {.str
= "6x5"}, 0, 0, FLAGS
},
55 { "nb_frames", "set maximum number of frame to render", OFFSET(nb_frames
),
56 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, INT_MAX
, FLAGS
},
57 { "margin", "set outer border margin in pixels", OFFSET(margin
),
58 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1024, FLAGS
},
59 { "padding", "set inner border thickness in pixels", OFFSET(padding
),
60 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1024, FLAGS
},
61 { "color", "set the color of the unused area", OFFSET(rgba_color
), AV_OPT_TYPE_COLOR
, {.str
= "black"}, .flags
= FLAGS
},
65 AVFILTER_DEFINE_CLASS(tile
);
67 static av_cold
int init(AVFilterContext
*ctx
)
69 TileContext
*tile
= ctx
->priv
;
71 if (tile
->w
> REASONABLE_SIZE
|| tile
->h
> REASONABLE_SIZE
) {
72 av_log(ctx
, AV_LOG_ERROR
, "Tile size %ux%u is insane.\n",
74 return AVERROR(EINVAL
);
77 if (tile
->nb_frames
== 0) {
78 tile
->nb_frames
= tile
->w
* tile
->h
;
79 } else if (tile
->nb_frames
> tile
->w
* tile
->h
) {
80 av_log(ctx
, AV_LOG_ERROR
, "nb_frames must be less than or equal to %dx%d=%d\n",
81 tile
->w
, tile
->h
, tile
->w
* tile
->h
);
82 return AVERROR(EINVAL
);
88 static int query_formats(AVFilterContext
*ctx
)
90 ff_set_common_formats(ctx
, ff_draw_supported_pixel_formats(0));
94 static int config_props(AVFilterLink
*outlink
)
96 AVFilterContext
*ctx
= outlink
->src
;
97 TileContext
*tile
= ctx
->priv
;
98 AVFilterLink
*inlink
= ctx
->inputs
[0];
99 const unsigned total_margin_w
= (tile
->w
- 1) * tile
->padding
+ 2*tile
->margin
;
100 const unsigned total_margin_h
= (tile
->h
- 1) * tile
->padding
+ 2*tile
->margin
;
102 if (inlink
->w
> (INT_MAX
- total_margin_w
) / tile
->w
) {
103 av_log(ctx
, AV_LOG_ERROR
, "Total width %ux%u is too much.\n",
105 return AVERROR(EINVAL
);
107 if (inlink
->h
> (INT_MAX
- total_margin_h
) / tile
->h
) {
108 av_log(ctx
, AV_LOG_ERROR
, "Total height %ux%u is too much.\n",
110 return AVERROR(EINVAL
);
112 outlink
->w
= tile
->w
* inlink
->w
+ total_margin_w
;
113 outlink
->h
= tile
->h
* inlink
->h
+ total_margin_h
;
114 outlink
->sample_aspect_ratio
= inlink
->sample_aspect_ratio
;
115 outlink
->frame_rate
= av_mul_q(inlink
->frame_rate
,
116 av_make_q(1, tile
->nb_frames
));
117 ff_draw_init(&tile
->draw
, inlink
->format
, 0);
118 ff_draw_color(&tile
->draw
, &tile
->blank
, tile
->rgba_color
);
120 outlink
->flags
|= FF_LINK_FLAG_REQUEST_LOOP
;
125 static void get_current_tile_pos(AVFilterContext
*ctx
, unsigned *x
, unsigned *y
)
127 TileContext
*tile
= ctx
->priv
;
128 AVFilterLink
*inlink
= ctx
->inputs
[0];
129 const unsigned tx
= tile
->current
% tile
->w
;
130 const unsigned ty
= tile
->current
/ tile
->w
;
132 *x
= tile
->margin
+ (inlink
->w
+ tile
->padding
) * tx
;
133 *y
= tile
->margin
+ (inlink
->h
+ tile
->padding
) * ty
;
136 static void draw_blank_frame(AVFilterContext
*ctx
, AVFrame
*out_buf
)
138 TileContext
*tile
= ctx
->priv
;
139 AVFilterLink
*inlink
= ctx
->inputs
[0];
142 get_current_tile_pos(ctx
, &x0
, &y0
);
143 ff_fill_rectangle(&tile
->draw
, &tile
->blank
,
144 out_buf
->data
, out_buf
->linesize
,
145 x0
, y0
, inlink
->w
, inlink
->h
);
148 static int end_last_frame(AVFilterContext
*ctx
)
150 TileContext
*tile
= ctx
->priv
;
151 AVFilterLink
*outlink
= ctx
->outputs
[0];
152 AVFrame
*out_buf
= tile
->out_ref
;
155 while (tile
->current
< tile
->nb_frames
)
156 draw_blank_frame(ctx
, out_buf
);
157 ret
= ff_filter_frame(outlink
, out_buf
);
162 /* Note: direct rendering is not possible since there is no guarantee that
163 * buffers are fed to filter_frame in the order they were obtained from
164 * get_buffer (think B-frames). */
166 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*picref
)
168 AVFilterContext
*ctx
= inlink
->dst
;
169 TileContext
*tile
= ctx
->priv
;
170 AVFilterLink
*outlink
= ctx
->outputs
[0];
173 if (!tile
->current
) {
174 tile
->out_ref
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
175 if (!tile
->out_ref
) {
176 av_frame_free(&picref
);
177 return AVERROR(ENOMEM
);
179 av_frame_copy_props(tile
->out_ref
, picref
);
180 tile
->out_ref
->width
= outlink
->w
;
181 tile
->out_ref
->height
= outlink
->h
;
183 /* fill surface once for margin/padding */
184 if (tile
->margin
|| tile
->padding
)
185 ff_fill_rectangle(&tile
->draw
, &tile
->blank
,
187 tile
->out_ref
->linesize
,
188 0, 0, outlink
->w
, outlink
->h
);
191 get_current_tile_pos(ctx
, &x0
, &y0
);
192 ff_copy_rectangle2(&tile
->draw
,
193 tile
->out_ref
->data
, tile
->out_ref
->linesize
,
194 picref
->data
, picref
->linesize
,
195 x0
, y0
, 0, 0, inlink
->w
, inlink
->h
);
197 av_frame_free(&picref
);
198 if (++tile
->current
== tile
->nb_frames
)
199 return end_last_frame(ctx
);
204 static int request_frame(AVFilterLink
*outlink
)
206 AVFilterContext
*ctx
= outlink
->src
;
207 TileContext
*tile
= ctx
->priv
;
208 AVFilterLink
*inlink
= ctx
->inputs
[0];
211 r
= ff_request_frame(inlink
);
212 if (r
== AVERROR_EOF
&& tile
->current
)
213 r
= end_last_frame(ctx
);
217 static const AVFilterPad tile_inputs
[] = {
220 .type
= AVMEDIA_TYPE_VIDEO
,
221 .filter_frame
= filter_frame
,
226 static const AVFilterPad tile_outputs
[] = {
229 .type
= AVMEDIA_TYPE_VIDEO
,
230 .config_props
= config_props
,
231 .request_frame
= request_frame
,
236 AVFilter ff_vf_tile
= {
238 .description
= NULL_IF_CONFIG_SMALL("Tile several successive frames together."),
240 .query_formats
= query_formats
,
241 .priv_size
= sizeof(TileContext
),
242 .inputs
= tile_inputs
,
243 .outputs
= tile_outputs
,
244 .priv_class
= &tile_class
,