00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00026 #include "libavutil/opt.h"
00027 #include "libavutil/pixdesc.h"
00028 #include "avfilter.h"
00029 #include "drawutils.h"
00030 #include "formats.h"
00031 #include "video.h"
00032 #include "internal.h"
00033
00034 typedef struct {
00035 const AVClass *class;
00036 unsigned w, h;
00037 unsigned margin;
00038 unsigned padding;
00039 unsigned current;
00040 unsigned nb_frames;
00041 FFDrawContext draw;
00042 FFDrawColor blank;
00043 } TileContext;
00044
00045 #define REASONABLE_SIZE 1024
00046
00047 #define OFFSET(x) offsetof(TileContext, x)
00048 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
00049
00050 static const AVOption tile_options[] = {
00051 { "layout", "set grid size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE,
00052 {.str = "6x5"}, 0, 0, FLAGS },
00053 { "margin", "set outer border margin in pixels", OFFSET(margin),
00054 AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1024, FLAGS },
00055 { "padding", "set inner border thickness in pixels", OFFSET(padding),
00056 AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1024, FLAGS },
00057 { "nb_frames", "set maximum number of frame to render", OFFSET(nb_frames),
00058 AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, FLAGS },
00059 {NULL},
00060 };
00061
00062 AVFILTER_DEFINE_CLASS(tile);
00063
00064 static av_cold int init(AVFilterContext *ctx, const char *args)
00065 {
00066 TileContext *tile = ctx->priv;
00067 static const char *shorthand[] = { "layout", "nb_frames", "margin", "padding", NULL };
00068 int ret;
00069
00070 tile->class = &tile_class;
00071 av_opt_set_defaults(tile);
00072
00073 if ((ret = av_opt_set_from_string(tile, args, shorthand, "=", ":")) < 0)
00074 return ret;
00075
00076 if (tile->w > REASONABLE_SIZE || tile->h > REASONABLE_SIZE) {
00077 av_log(ctx, AV_LOG_ERROR, "Tile size %ux%u is insane.\n",
00078 tile->w, tile->h);
00079 return AVERROR(EINVAL);
00080 }
00081
00082 if (tile->nb_frames == 0) {
00083 tile->nb_frames = tile->w * tile->h;
00084 } else if (tile->nb_frames > tile->w * tile->h) {
00085 av_log(ctx, AV_LOG_ERROR, "nb_frames must be less than or equal to %dx%d=%d\n",
00086 tile->w, tile->h, tile->w * tile->h);
00087 return AVERROR(EINVAL);
00088 }
00089
00090 return 0;
00091 }
00092
00093 static int query_formats(AVFilterContext *ctx)
00094 {
00095 ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
00096 return 0;
00097 }
00098
00099 static int config_props(AVFilterLink *outlink)
00100 {
00101 AVFilterContext *ctx = outlink->src;
00102 TileContext *tile = ctx->priv;
00103 AVFilterLink *inlink = ctx->inputs[0];
00104 const unsigned total_margin_w = (tile->w - 1) * tile->padding + 2*tile->margin;
00105 const unsigned total_margin_h = (tile->h - 1) * tile->padding + 2*tile->margin;
00106
00107 if (inlink->w > (INT_MAX - total_margin_w) / tile->w) {
00108 av_log(ctx, AV_LOG_ERROR, "Total width %ux%u is too much.\n",
00109 tile->w, inlink->w);
00110 return AVERROR(EINVAL);
00111 }
00112 if (inlink->h > (INT_MAX - total_margin_h) / tile->h) {
00113 av_log(ctx, AV_LOG_ERROR, "Total height %ux%u is too much.\n",
00114 tile->h, inlink->h);
00115 return AVERROR(EINVAL);
00116 }
00117 outlink->w = tile->w * inlink->w + total_margin_w;
00118 outlink->h = tile->h * inlink->h + total_margin_h;
00119 outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
00120 outlink->frame_rate = av_mul_q(inlink->frame_rate,
00121 (AVRational){ 1, tile->nb_frames });
00122 ff_draw_init(&tile->draw, inlink->format, 0);
00123
00124 ff_draw_color(&tile->draw, &tile->blank, (uint8_t[]){ 0, 0, 0, -1 });
00125
00126 return 0;
00127 }
00128
00129 static void get_current_tile_pos(AVFilterContext *ctx, unsigned *x, unsigned *y)
00130 {
00131 TileContext *tile = ctx->priv;
00132 AVFilterLink *inlink = ctx->inputs[0];
00133 const unsigned tx = tile->current % tile->w;
00134 const unsigned ty = tile->current / tile->w;
00135
00136 *x = tile->margin + (inlink->w + tile->padding) * tx;
00137 *y = tile->margin + (inlink->h + tile->padding) * ty;
00138 }
00139
00140 static void draw_blank_frame(AVFilterContext *ctx, AVFilterBufferRef *out_buf)
00141 {
00142 TileContext *tile = ctx->priv;
00143 AVFilterLink *inlink = ctx->inputs[0];
00144 unsigned x0, y0;
00145
00146 get_current_tile_pos(ctx, &x0, &y0);
00147 ff_fill_rectangle(&tile->draw, &tile->blank,
00148 out_buf->data, out_buf->linesize,
00149 x0, y0, inlink->w, inlink->h);
00150 tile->current++;
00151 }
00152 static int end_last_frame(AVFilterContext *ctx)
00153 {
00154 TileContext *tile = ctx->priv;
00155 AVFilterLink *outlink = ctx->outputs[0];
00156 AVFilterBufferRef *out_buf = outlink->out_buf;
00157 int ret;
00158
00159 outlink->out_buf = NULL;
00160 while (tile->current < tile->nb_frames)
00161 draw_blank_frame(ctx, out_buf);
00162 ret = ff_filter_frame(outlink, out_buf);
00163 tile->current = 0;
00164 return ret;
00165 }
00166
00167
00168
00169
00170
00171 static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *picref)
00172 {
00173 AVFilterContext *ctx = inlink->dst;
00174 TileContext *tile = ctx->priv;
00175 AVFilterLink *outlink = ctx->outputs[0];
00176 unsigned x0, y0;
00177
00178 if (!tile->current) {
00179 outlink->out_buf = ff_get_video_buffer(outlink, AV_PERM_WRITE,
00180 outlink->w, outlink->h);
00181 if (!outlink->out_buf)
00182 return AVERROR(ENOMEM);
00183 avfilter_copy_buffer_ref_props(outlink->out_buf, picref);
00184 outlink->out_buf->video->w = outlink->w;
00185 outlink->out_buf->video->h = outlink->h;
00186
00187
00188 if (tile->margin || tile->padding)
00189 ff_fill_rectangle(&tile->draw, &tile->blank,
00190 outlink->out_buf->data,
00191 outlink->out_buf->linesize,
00192 0, 0, outlink->w, outlink->h);
00193 }
00194
00195 get_current_tile_pos(ctx, &x0, &y0);
00196 ff_copy_rectangle2(&tile->draw,
00197 outlink->out_buf->data, outlink->out_buf->linesize,
00198 inlink ->cur_buf->data, inlink ->cur_buf->linesize,
00199 x0, y0, 0, 0, inlink->w, inlink->h);
00200
00201 avfilter_unref_bufferp(&inlink->cur_buf);
00202 if (++tile->current == tile->nb_frames)
00203 return end_last_frame(ctx);
00204
00205 return 0;
00206 }
00207
00208 static int request_frame(AVFilterLink *outlink)
00209 {
00210 AVFilterContext *ctx = outlink->src;
00211 TileContext *tile = ctx->priv;
00212 AVFilterLink *inlink = ctx->inputs[0];
00213 int r;
00214
00215 while (1) {
00216 r = ff_request_frame(inlink);
00217 if (r < 0) {
00218 if (r == AVERROR_EOF && tile->current)
00219 r = end_last_frame(ctx);
00220 break;
00221 }
00222 if (!tile->current)
00223 break;
00224 }
00225 return r;
00226 }
00227
00228 static const AVFilterPad tile_inputs[] = {
00229 {
00230 .name = "default",
00231 .type = AVMEDIA_TYPE_VIDEO,
00232 .filter_frame = filter_frame,
00233 .min_perms = AV_PERM_READ,
00234 },
00235 { NULL }
00236 };
00237
00238 static const AVFilterPad tile_outputs[] = {
00239 {
00240 .name = "default",
00241 .type = AVMEDIA_TYPE_VIDEO,
00242 .config_props = config_props,
00243 .request_frame = request_frame,
00244 },
00245 { NULL }
00246 };
00247
00248 AVFilter avfilter_vf_tile = {
00249 .name = "tile",
00250 .description = NULL_IF_CONFIG_SMALL("Tile several successive frames together."),
00251 .init = init,
00252 .query_formats = query_formats,
00253 .priv_size = sizeof(TileContext),
00254 .inputs = tile_inputs,
00255 .outputs = tile_outputs,
00256 .priv_class = &tile_class,
00257 };