lavfi/tile: allow named arguments.
[ffmpeg.git] / libavfilter / vf_tile.c
1 /*
2  * Copyright (c) 2012 Nicolas George
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 /**
22  * @file
23  * tile video filter
24  */
25
26 #include "libavutil/opt.h"
27 #include "libavutil/pixdesc.h"
28 #include "avfilter.h"
29 #include "drawutils.h"
30 #include "formats.h"
31 #include "video.h"
32 #include "internal.h"
33
34 typedef struct {
35     const AVClass *class;
36     unsigned w, h;
37     unsigned current;
38     FFDrawContext draw;
39     FFDrawColor blank;
40 } TileContext;
41
42 #define REASONABLE_SIZE 1024
43
44 #define OFFSET(x) offsetof(TileContext, x)
45 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
46
47 static const AVOption tile_options[] = {
48     { "layout", "set grid size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE,
49         {.str = "6x5"}, 0, 0, FLAGS },
50     {NULL},
51 };
52
53 AVFILTER_DEFINE_CLASS(tile);
54
55 static av_cold int init(AVFilterContext *ctx, const char *args)
56 {
57     TileContext *tile = ctx->priv;
58     static const char *shorthand[] = { "layout", NULL };
59     int ret;
60
61     tile->class = &tile_class;
62     av_opt_set_defaults(tile);
63
64     if ((ret = av_opt_set_from_string(tile, args, shorthand, "=", ":")) < 0)
65         return ret;
66
67     if (tile->w > REASONABLE_SIZE || tile->h > REASONABLE_SIZE) {
68         av_log(ctx, AV_LOG_ERROR, "Tile size %ux%u is insane.\n",
69                tile->w, tile->h);
70         return AVERROR(EINVAL);
71     }
72     return 0;
73 }
74
75 static int query_formats(AVFilterContext *ctx)
76 {
77     ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
78     return 0;
79 }
80
81 static int config_props(AVFilterLink *outlink)
82 {
83     AVFilterContext *ctx = outlink->src;
84     TileContext *tile   = ctx->priv;
85     AVFilterLink *inlink = ctx->inputs[0];
86
87     if (inlink->w > INT_MAX / tile->w) {
88         av_log(ctx, AV_LOG_ERROR, "Total width %ux%u is too much.\n",
89                tile->w, inlink->w);
90         return AVERROR(EINVAL);
91     }
92     if (inlink->h > INT_MAX / tile->h) {
93         av_log(ctx, AV_LOG_ERROR, "Total height %ux%u is too much.\n",
94                tile->h, inlink->h);
95         return AVERROR(EINVAL);
96     }
97     outlink->w = tile->w * inlink->w;
98     outlink->h = tile->h * inlink->h;
99     outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
100     outlink->frame_rate = av_mul_q(inlink->frame_rate,
101                                    (AVRational){ 1, tile->w * tile->h });
102     ff_draw_init(&tile->draw, inlink->format, 0);
103     /* TODO make the color an option, or find an unified way of choosing it */
104     ff_draw_color(&tile->draw, &tile->blank, (uint8_t[]){ 0, 0, 0, -1 });
105
106     return 0;
107 }
108
109 /* Note: direct rendering is not possible since there is no guarantee that
110  * buffers are fed to start_frame in the order they were obtained from
111  * get_buffer (think B-frames). */
112
113 static int start_frame(AVFilterLink *inlink, AVFilterBufferRef *picref)
114 {
115     AVFilterContext *ctx  = inlink->dst;
116     TileContext *tile    = ctx->priv;
117     AVFilterLink *outlink = ctx->outputs[0];
118
119     if (tile->current)
120         return 0;
121     outlink->out_buf = ff_get_video_buffer(outlink, AV_PERM_WRITE,
122                                                  outlink->w, outlink->h);
123     avfilter_copy_buffer_ref_props(outlink->out_buf, picref);
124     outlink->out_buf->video->w = outlink->w;
125     outlink->out_buf->video->h = outlink->h;
126     return 0;
127 }
128
129 static int draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
130 {
131     AVFilterContext *ctx  = inlink->dst;
132     TileContext *tile    = ctx->priv;
133     AVFilterLink *outlink = ctx->outputs[0];
134     unsigned x0 = inlink->w * (tile->current % tile->w);
135     unsigned y0 = inlink->h * (tile->current / tile->w);
136
137     ff_copy_rectangle2(&tile->draw,
138                        outlink->out_buf->data, outlink->out_buf->linesize,
139                        inlink ->cur_buf->data, inlink ->cur_buf->linesize,
140                        x0, y0 + y, 0, y, inlink->cur_buf->video->w, h);
141     /* TODO if tile->w == 1 && slice_dir is always 1, we could draw_slice
142      * immediately. */
143     return 0;
144 }
145
146 static void draw_blank_frame(AVFilterContext *ctx, AVFilterBufferRef *out_buf)
147 {
148     TileContext *tile    = ctx->priv;
149     AVFilterLink *inlink  = ctx->inputs[0];
150     unsigned x0 = inlink->w * (tile->current % tile->w);
151     unsigned y0 = inlink->h * (tile->current / tile->w);
152
153     ff_fill_rectangle(&tile->draw, &tile->blank,
154                       out_buf->data, out_buf->linesize,
155                       x0, y0, inlink->w, inlink->h);
156     tile->current++;
157 }
158 static void end_last_frame(AVFilterContext *ctx)
159 {
160     TileContext *tile    = ctx->priv;
161     AVFilterLink *outlink = ctx->outputs[0];
162     AVFilterBufferRef *out_buf = outlink->out_buf;
163
164     outlink->out_buf = NULL;
165     ff_start_frame(outlink, out_buf);
166     while (tile->current < tile->w * tile->h)
167         draw_blank_frame(ctx, out_buf);
168     ff_draw_slice(outlink, 0, out_buf->video->h, 1);
169     ff_end_frame(outlink);
170     tile->current = 0;
171 }
172
173 static int end_frame(AVFilterLink *inlink)
174 {
175     AVFilterContext *ctx  = inlink->dst;
176     TileContext *tile    = ctx->priv;
177
178     avfilter_unref_bufferp(&inlink->cur_buf);
179     if (++tile->current == tile->w * tile->h)
180         end_last_frame(ctx);
181     return 0;
182 }
183
184 static int request_frame(AVFilterLink *outlink)
185 {
186     AVFilterContext *ctx = outlink->src;
187     TileContext *tile   = ctx->priv;
188     AVFilterLink *inlink = ctx->inputs[0];
189     int r;
190
191     while (1) {
192         r = ff_request_frame(inlink);
193         if (r < 0) {
194             if (r == AVERROR_EOF && tile->current)
195                 end_last_frame(ctx);
196             else
197                 return r;
198             break;
199         }
200         if (!tile->current) /* done */
201             break;
202     }
203     return 0;
204 }
205
206
207 AVFilter avfilter_vf_tile = {
208     .name          = "tile",
209     .description   = NULL_IF_CONFIG_SMALL("Tile several successive frames together."),
210     .init          = init,
211     .query_formats = query_formats,
212     .priv_size     = sizeof(TileContext),
213     .inputs = (const AVFilterPad[]) {
214         { .name        = "default",
215           .type        = AVMEDIA_TYPE_VIDEO,
216           .start_frame = start_frame,
217           .draw_slice  = draw_slice,
218           .end_frame   = end_frame,
219           .min_perms   = AV_PERM_READ, },
220         { .name = NULL }
221     },
222     .outputs = (const AVFilterPad[]) {
223         { .name          = "default",
224           .type          = AVMEDIA_TYPE_VIDEO,
225           .config_props  = config_props,
226           .request_frame = request_frame },
227         { .name = NULL }
228     },
229     .priv_class = &tile_class,
230 };