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 34typedef struct { 35 const AVClass *class; 36 unsigned w, h; 37 unsigned margin; 38 unsigned padding; 39 unsigned current; 40 unsigned nb_frames; 41 FFDrawContext draw; 42 FFDrawColor blank; 43 AVFrame *out_ref; 44 uint8_t rgba_color[4]; 45} TileContext; 46 47#define REASONABLE_SIZE 1024 48 49#define OFFSET(x) offsetof(TileContext, x) 50#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM 51 52static 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 }, 62 { NULL } 63}; 64 65AVFILTER_DEFINE_CLASS(tile); 66 67static av_cold int init(AVFilterContext *ctx) 68{ 69 TileContext *tile = ctx->priv; 70 71 if (tile->w > REASONABLE_SIZE || tile->h > REASONABLE_SIZE) { 72 av_log(ctx, AV_LOG_ERROR, "Tile size %ux%u is insane.\n", 73 tile->w, tile->h); 74 return AVERROR(EINVAL); 75 } 76 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); 83 } 84 85 return 0; 86} 87 88static int query_formats(AVFilterContext *ctx) 89{ 90 ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0)); 91 return 0; 92} 93 94static int config_props(AVFilterLink *outlink) 95{ 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; 101 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", 104 tile->w, inlink->w); 105 return AVERROR(EINVAL); 106 } 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", 109 tile->h, inlink->h); 110 return AVERROR(EINVAL); 111 } 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); 119 120 outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP; 121 122 return 0; 123} 124 125static void get_current_tile_pos(AVFilterContext *ctx, unsigned *x, unsigned *y) 126{ 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; 131 132 *x = tile->margin + (inlink->w + tile->padding) * tx; 133 *y = tile->margin + (inlink->h + tile->padding) * ty; 134} 135 136static void draw_blank_frame(AVFilterContext *ctx, AVFrame *out_buf) 137{ 138 TileContext *tile = ctx->priv; 139 AVFilterLink *inlink = ctx->inputs[0]; 140 unsigned x0, y0; 141 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); 146 tile->current++; 147} 148static int end_last_frame(AVFilterContext *ctx) 149{ 150 TileContext *tile = ctx->priv; 151 AVFilterLink *outlink = ctx->outputs[0]; 152 AVFrame *out_buf = tile->out_ref; 153 int ret; 154 155 while (tile->current < tile->nb_frames) 156 draw_blank_frame(ctx, out_buf); 157 ret = ff_filter_frame(outlink, out_buf); 158 tile->current = 0; 159 return ret; 160} 161 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). */ 165 166static int filter_frame(AVFilterLink *inlink, AVFrame *picref) 167{ 168 AVFilterContext *ctx = inlink->dst; 169 TileContext *tile = ctx->priv; 170 AVFilterLink *outlink = ctx->outputs[0]; 171 unsigned x0, y0; 172 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); 178 } 179 av_frame_copy_props(tile->out_ref, picref); 180 tile->out_ref->width = outlink->w; 181 tile->out_ref->height = outlink->h; 182 183 /* fill surface once for margin/padding */ 184 if (tile->margin || tile->padding) 185 ff_fill_rectangle(&tile->draw, &tile->blank, 186 tile->out_ref->data, 187 tile->out_ref->linesize, 188 0, 0, outlink->w, outlink->h); 189 } 190 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); 196 197 av_frame_free(&picref); 198 if (++tile->current == tile->nb_frames) 199 return end_last_frame(ctx); 200 201 return 0; 202} 203 204static int request_frame(AVFilterLink *outlink) 205{ 206 AVFilterContext *ctx = outlink->src; 207 TileContext *tile = ctx->priv; 208 AVFilterLink *inlink = ctx->inputs[0]; 209 int r; 210 211 r = ff_request_frame(inlink); 212 if (r == AVERROR_EOF && tile->current) 213 r = end_last_frame(ctx); 214 return r; 215} 216 217static const AVFilterPad tile_inputs[] = { 218 { 219 .name = "default", 220 .type = AVMEDIA_TYPE_VIDEO, 221 .filter_frame = filter_frame, 222 }, 223 { NULL } 224}; 225 226static const AVFilterPad tile_outputs[] = { 227 { 228 .name = "default", 229 .type = AVMEDIA_TYPE_VIDEO, 230 .config_props = config_props, 231 .request_frame = request_frame, 232 }, 233 { NULL } 234}; 235 236AVFilter ff_vf_tile = { 237 .name = "tile", 238 .description = NULL_IF_CONFIG_SMALL("Tile several successive frames together."), 239 .init = init, 240 .query_formats = query_formats, 241 .priv_size = sizeof(TileContext), 242 .inputs = tile_inputs, 243 .outputs = tile_outputs, 244 .priv_class = &tile_class, 245}; 246