You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

473 lines
15KB

  1. /*
  2. * Copyright (c) 2015 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. #include "libavutil/avstring.h"
  21. #include "libavutil/imgutils.h"
  22. #include "libavutil/opt.h"
  23. #include "libavutil/parseutils.h"
  24. #include "libavutil/pixdesc.h"
  25. #include "avfilter.h"
  26. #include "drawutils.h"
  27. #include "formats.h"
  28. #include "internal.h"
  29. #include "framesync.h"
  30. #include "video.h"
  31. typedef struct StackItem {
  32. int x[4], y[4];
  33. int linesize[4];
  34. int height[4];
  35. } StackItem;
  36. typedef struct StackContext {
  37. const AVClass *class;
  38. const AVPixFmtDescriptor *desc;
  39. int nb_inputs;
  40. char *layout;
  41. int shortest;
  42. int is_vertical;
  43. int is_horizontal;
  44. int nb_planes;
  45. uint8_t fillcolor[4];
  46. char *fillcolor_str;
  47. int fillcolor_enable;
  48. FFDrawContext draw;
  49. FFDrawColor color;
  50. StackItem *items;
  51. AVFrame **frames;
  52. FFFrameSync fs;
  53. } StackContext;
  54. static int query_formats(AVFilterContext *ctx)
  55. {
  56. AVFilterFormats *formats = NULL;
  57. StackContext *s = ctx->priv;
  58. int ret;
  59. if (s->fillcolor_enable) {
  60. return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
  61. }
  62. ret = ff_formats_pixdesc_filter(&formats, 0,
  63. AV_PIX_FMT_FLAG_HWACCEL |
  64. AV_PIX_FMT_FLAG_BITSTREAM |
  65. AV_PIX_FMT_FLAG_PAL);
  66. if (ret < 0)
  67. return ret;
  68. return ff_set_common_formats(ctx, formats);
  69. }
  70. static av_cold int init(AVFilterContext *ctx)
  71. {
  72. StackContext *s = ctx->priv;
  73. int i, ret;
  74. if (!strcmp(ctx->filter->name, "vstack"))
  75. s->is_vertical = 1;
  76. if (!strcmp(ctx->filter->name, "hstack"))
  77. s->is_horizontal = 1;
  78. s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
  79. if (!s->frames)
  80. return AVERROR(ENOMEM);
  81. s->items = av_calloc(s->nb_inputs, sizeof(*s->items));
  82. if (!s->items)
  83. return AVERROR(ENOMEM);
  84. if (!strcmp(ctx->filter->name, "xstack")) {
  85. if (strcmp(s->fillcolor_str, "none") &&
  86. av_parse_color(s->fillcolor, s->fillcolor_str, -1, ctx) >= 0) {
  87. s->fillcolor_enable = 1;
  88. } else {
  89. s->fillcolor_enable = 0;
  90. }
  91. if (!s->layout) {
  92. if (s->nb_inputs == 2) {
  93. s->layout = av_strdup("0_0|w0_0");
  94. if (!s->layout)
  95. return AVERROR(ENOMEM);
  96. } else {
  97. av_log(ctx, AV_LOG_ERROR, "No layout specified.\n");
  98. return AVERROR(EINVAL);
  99. }
  100. }
  101. }
  102. for (i = 0; i < s->nb_inputs; i++) {
  103. AVFilterPad pad = { 0 };
  104. pad.type = AVMEDIA_TYPE_VIDEO;
  105. pad.name = av_asprintf("input%d", i);
  106. if (!pad.name)
  107. return AVERROR(ENOMEM);
  108. if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0) {
  109. av_freep(&pad.name);
  110. return ret;
  111. }
  112. }
  113. return 0;
  114. }
  115. static int process_slice(AVFilterContext *ctx, void *arg, int job, int nb_jobs)
  116. {
  117. StackContext *s = ctx->priv;
  118. AVFrame *out = arg;
  119. AVFrame **in = s->frames;
  120. const int start = (s->nb_inputs * job ) / nb_jobs;
  121. const int end = (s->nb_inputs * (job+1)) / nb_jobs;
  122. for (int i = start; i < end; i++) {
  123. StackItem *item = &s->items[i];
  124. for (int p = 0; p < s->nb_planes; p++) {
  125. av_image_copy_plane(out->data[p] + out->linesize[p] * item->y[p] + item->x[p],
  126. out->linesize[p],
  127. in[i]->data[p],
  128. in[i]->linesize[p],
  129. item->linesize[p], item->height[p]);
  130. }
  131. }
  132. return 0;
  133. }
  134. static int process_frame(FFFrameSync *fs)
  135. {
  136. AVFilterContext *ctx = fs->parent;
  137. AVFilterLink *outlink = ctx->outputs[0];
  138. StackContext *s = fs->opaque;
  139. AVFrame **in = s->frames;
  140. AVFrame *out;
  141. int i, ret;
  142. for (i = 0; i < s->nb_inputs; i++) {
  143. if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
  144. return ret;
  145. }
  146. out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
  147. if (!out)
  148. return AVERROR(ENOMEM);
  149. out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
  150. out->sample_aspect_ratio = outlink->sample_aspect_ratio;
  151. if (s->fillcolor_enable)
  152. ff_fill_rectangle(&s->draw, &s->color, out->data, out->linesize,
  153. 0, 0, outlink->w, outlink->h);
  154. ctx->internal->execute(ctx, process_slice, out, NULL, FFMIN(s->nb_inputs, ff_filter_get_nb_threads(ctx)));
  155. return ff_filter_frame(outlink, out);
  156. }
  157. static int config_output(AVFilterLink *outlink)
  158. {
  159. AVFilterContext *ctx = outlink->src;
  160. StackContext *s = ctx->priv;
  161. AVRational frame_rate = ctx->inputs[0]->frame_rate;
  162. AVRational sar = ctx->inputs[0]->sample_aspect_ratio;
  163. int height = ctx->inputs[0]->h;
  164. int width = ctx->inputs[0]->w;
  165. FFFrameSyncIn *in;
  166. int i, ret;
  167. s->desc = av_pix_fmt_desc_get(outlink->format);
  168. if (!s->desc)
  169. return AVERROR_BUG;
  170. if (s->is_vertical) {
  171. for (i = 0; i < s->nb_inputs; i++) {
  172. AVFilterLink *inlink = ctx->inputs[i];
  173. StackItem *item = &s->items[i];
  174. if (ctx->inputs[i]->w != width) {
  175. av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
  176. return AVERROR(EINVAL);
  177. }
  178. if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
  179. return ret;
  180. }
  181. item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
  182. item->height[0] = item->height[3] = inlink->h;
  183. if (i) {
  184. item->y[1] = item->y[2] = AV_CEIL_RSHIFT(height, s->desc->log2_chroma_h);
  185. item->y[0] = item->y[3] = height;
  186. height += ctx->inputs[i]->h;
  187. }
  188. }
  189. } else if (s->is_horizontal) {
  190. for (i = 0; i < s->nb_inputs; i++) {
  191. AVFilterLink *inlink = ctx->inputs[i];
  192. StackItem *item = &s->items[i];
  193. if (ctx->inputs[i]->h != height) {
  194. av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height);
  195. return AVERROR(EINVAL);
  196. }
  197. if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
  198. return ret;
  199. }
  200. item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
  201. item->height[0] = item->height[3] = inlink->h;
  202. if (i) {
  203. if ((ret = av_image_fill_linesizes(item->x, inlink->format, width)) < 0) {
  204. return ret;
  205. }
  206. width += ctx->inputs[i]->w;
  207. }
  208. }
  209. } else {
  210. char *arg, *p = s->layout, *saveptr = NULL;
  211. char *arg2, *p2, *saveptr2 = NULL;
  212. char *arg3, *p3, *saveptr3 = NULL;
  213. int inw, inh, size;
  214. if (s->fillcolor_enable) {
  215. ff_draw_init(&s->draw, ctx->inputs[0]->format, 0);
  216. ff_draw_color(&s->draw, &s->color, s->fillcolor);
  217. }
  218. for (i = 0; i < s->nb_inputs; i++) {
  219. AVFilterLink *inlink = ctx->inputs[i];
  220. StackItem *item = &s->items[i];
  221. if (!(arg = av_strtok(p, "|", &saveptr)))
  222. return AVERROR(EINVAL);
  223. p = NULL;
  224. if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
  225. return ret;
  226. }
  227. item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
  228. item->height[0] = item->height[3] = inlink->h;
  229. p2 = arg;
  230. inw = inh = 0;
  231. for (int j = 0; j < 2; j++) {
  232. if (!(arg2 = av_strtok(p2, "_", &saveptr2)))
  233. return AVERROR(EINVAL);
  234. p2 = NULL;
  235. p3 = arg2;
  236. while ((arg3 = av_strtok(p3, "+", &saveptr3))) {
  237. p3 = NULL;
  238. if (sscanf(arg3, "w%d", &size) == 1) {
  239. if (size == i || size < 0 || size >= s->nb_inputs)
  240. return AVERROR(EINVAL);
  241. if (!j)
  242. inw += ctx->inputs[size]->w;
  243. else
  244. inh += ctx->inputs[size]->w;
  245. } else if (sscanf(arg3, "h%d", &size) == 1) {
  246. if (size == i || size < 0 || size >= s->nb_inputs)
  247. return AVERROR(EINVAL);
  248. if (!j)
  249. inw += ctx->inputs[size]->h;
  250. else
  251. inh += ctx->inputs[size]->h;
  252. } else if (sscanf(arg3, "%d", &size) == 1) {
  253. if (size < 0)
  254. return AVERROR(EINVAL);
  255. if (!j)
  256. inw += size;
  257. else
  258. inh += size;
  259. } else {
  260. return AVERROR(EINVAL);
  261. }
  262. }
  263. }
  264. if ((ret = av_image_fill_linesizes(item->x, inlink->format, inw)) < 0) {
  265. return ret;
  266. }
  267. item->y[1] = item->y[2] = AV_CEIL_RSHIFT(inh, s->desc->log2_chroma_h);
  268. item->y[0] = item->y[3] = inh;
  269. width = FFMAX(width, inlink->w + inw);
  270. height = FFMAX(height, inlink->h + inh);
  271. }
  272. }
  273. s->nb_planes = av_pix_fmt_count_planes(outlink->format);
  274. outlink->w = width;
  275. outlink->h = height;
  276. outlink->frame_rate = frame_rate;
  277. outlink->sample_aspect_ratio = sar;
  278. for (i = 1; i < s->nb_inputs; i++) {
  279. AVFilterLink *inlink = ctx->inputs[i];
  280. if (outlink->frame_rate.num != inlink->frame_rate.num ||
  281. outlink->frame_rate.den != inlink->frame_rate.den) {
  282. av_log(ctx, AV_LOG_VERBOSE,
  283. "Video inputs have different frame rates, output will be VFR\n");
  284. outlink->frame_rate = av_make_q(1, 0);
  285. break;
  286. }
  287. }
  288. if ((ret = ff_framesync_init(&s->fs, ctx, s->nb_inputs)) < 0)
  289. return ret;
  290. in = s->fs.in;
  291. s->fs.opaque = s;
  292. s->fs.on_event = process_frame;
  293. for (i = 0; i < s->nb_inputs; i++) {
  294. AVFilterLink *inlink = ctx->inputs[i];
  295. in[i].time_base = inlink->time_base;
  296. in[i].sync = 1;
  297. in[i].before = EXT_STOP;
  298. in[i].after = s->shortest ? EXT_STOP : EXT_INFINITY;
  299. }
  300. ret = ff_framesync_configure(&s->fs);
  301. outlink->time_base = s->fs.time_base;
  302. return ret;
  303. }
  304. static av_cold void uninit(AVFilterContext *ctx)
  305. {
  306. StackContext *s = ctx->priv;
  307. int i;
  308. ff_framesync_uninit(&s->fs);
  309. av_freep(&s->frames);
  310. av_freep(&s->items);
  311. for (i = 0; i < ctx->nb_inputs; i++)
  312. av_freep(&ctx->input_pads[i].name);
  313. }
  314. static int activate(AVFilterContext *ctx)
  315. {
  316. StackContext *s = ctx->priv;
  317. return ff_framesync_activate(&s->fs);
  318. }
  319. #define OFFSET(x) offsetof(StackContext, x)
  320. #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
  321. static const AVOption stack_options[] = {
  322. { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
  323. { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
  324. { NULL },
  325. };
  326. static const AVFilterPad outputs[] = {
  327. {
  328. .name = "default",
  329. .type = AVMEDIA_TYPE_VIDEO,
  330. .config_props = config_output,
  331. },
  332. { NULL }
  333. };
  334. #if CONFIG_HSTACK_FILTER
  335. #define hstack_options stack_options
  336. AVFILTER_DEFINE_CLASS(hstack);
  337. AVFilter ff_vf_hstack = {
  338. .name = "hstack",
  339. .description = NULL_IF_CONFIG_SMALL("Stack video inputs horizontally."),
  340. .priv_size = sizeof(StackContext),
  341. .priv_class = &hstack_class,
  342. .query_formats = query_formats,
  343. .outputs = outputs,
  344. .init = init,
  345. .uninit = uninit,
  346. .activate = activate,
  347. .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
  348. };
  349. #endif /* CONFIG_HSTACK_FILTER */
  350. #if CONFIG_VSTACK_FILTER
  351. #define vstack_options stack_options
  352. AVFILTER_DEFINE_CLASS(vstack);
  353. AVFilter ff_vf_vstack = {
  354. .name = "vstack",
  355. .description = NULL_IF_CONFIG_SMALL("Stack video inputs vertically."),
  356. .priv_size = sizeof(StackContext),
  357. .priv_class = &vstack_class,
  358. .query_formats = query_formats,
  359. .outputs = outputs,
  360. .init = init,
  361. .uninit = uninit,
  362. .activate = activate,
  363. .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
  364. };
  365. #endif /* CONFIG_VSTACK_FILTER */
  366. #if CONFIG_XSTACK_FILTER
  367. static const AVOption xstack_options[] = {
  368. { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
  369. { "layout", "set custom layout", OFFSET(layout), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, .flags = FLAGS },
  370. { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
  371. { "fill", "set the color for unused pixels", OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str = "none"}, .flags = FLAGS },
  372. { NULL },
  373. };
  374. AVFILTER_DEFINE_CLASS(xstack);
  375. AVFilter ff_vf_xstack = {
  376. .name = "xstack",
  377. .description = NULL_IF_CONFIG_SMALL("Stack video inputs into custom layout."),
  378. .priv_size = sizeof(StackContext),
  379. .priv_class = &xstack_class,
  380. .query_formats = query_formats,
  381. .outputs = outputs,
  382. .init = init,
  383. .uninit = uninit,
  384. .activate = activate,
  385. .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
  386. };
  387. #endif /* CONFIG_XSTACK_FILTER */