vf_drawbox_vaapi.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /*
  2. * This file is part of FFmpeg.
  3. *
  4. * FFmpeg is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Lesser General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2.1 of the License, or (at your option) any later version.
  8. *
  9. * FFmpeg is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public
  15. * License along with FFmpeg; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. */
  18. #include "libavutil/colorspace.h"
  19. #include "libavutil/eval.h"
  20. #include "libavutil/opt.h"
  21. #include "avfilter.h"
  22. #include "internal.h"
  23. #include "vaapi_vpp.h"
  24. #include "video.h"
  25. static const char *const var_names[] = {
  26. "in_h", "ih",
  27. "in_w", "iw",
  28. "x",
  29. "y",
  30. "h",
  31. "w",
  32. "t",
  33. "fill",
  34. NULL
  35. };
  36. enum var_name {
  37. VAR_IN_H, VAR_IH,
  38. VAR_IN_W, VAR_IW,
  39. VAR_X,
  40. VAR_Y,
  41. VAR_H,
  42. VAR_W,
  43. VAR_T,
  44. VAR_MAX,
  45. VARS_NB
  46. };
  47. static const int NUM_EXPR_EVALS = 5;
  48. typedef struct DrawboxVAAPIContext {
  49. VAAPIVPPContext vpp_ctx; // must be the first field
  50. VARectangle outer_rect, inner_rect;
  51. /* The hardware frame context containing the frames for outer_rect. */
  52. AVBufferRef *outer_frames_ref;
  53. AVHWFramesContext *outer_frames;
  54. AVFrame *outer_frame;
  55. char *x_expr;
  56. char *y_expr;
  57. char *w_expr;
  58. char *h_expr;
  59. char *t_expr;
  60. int w, h;
  61. int x, y;
  62. int replace;
  63. uint32_t thickness;
  64. uint8_t drawbox_rgba[4];
  65. int fill;
  66. } DrawboxVAAPIContext;
  67. static int drawbox_vaapi_config_output(AVFilterLink *outlink)
  68. {
  69. AVFilterContext *avctx = outlink->src;
  70. AVFilterLink *inlink = avctx->inputs[0];
  71. DrawboxVAAPIContext *ctx = avctx->priv;
  72. VAAPIVPPContext *vpp_ctx = avctx->priv;
  73. double var_values[VARS_NB], res;
  74. int ret, i;
  75. char *expr;
  76. var_values[VAR_IN_W] = var_values[VAR_IW] = inlink->w;
  77. var_values[VAR_IN_H] = var_values[VAR_IH] = inlink->h;
  78. var_values[VAR_X] = NAN;
  79. var_values[VAR_Y] = NAN;
  80. var_values[VAR_H] = NAN;
  81. var_values[VAR_W] = NAN;
  82. var_values[VAR_T] = NAN;
  83. for (i = 0; i <= NUM_EXPR_EVALS; i++) {
  84. /* evaluate expressions, fail on last iteration */
  85. var_values[VAR_MAX] = inlink->w;
  86. if ((ret = av_expr_parse_and_eval(&res, (expr = ctx->x_expr),
  87. var_names, var_values,
  88. NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
  89. goto fail;
  90. ctx->x = var_values[VAR_X] = res;
  91. var_values[VAR_MAX] = inlink->h;
  92. if ((ret = av_expr_parse_and_eval(&res, (expr = ctx->y_expr),
  93. var_names, var_values,
  94. NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
  95. goto fail;
  96. ctx->y = var_values[VAR_Y] = res;
  97. var_values[VAR_MAX] = inlink->w - ctx->x;
  98. if ((ret = av_expr_parse_and_eval(&res, (expr = ctx->w_expr),
  99. var_names, var_values,
  100. NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
  101. goto fail;
  102. ctx->w = var_values[VAR_W] = res;
  103. var_values[VAR_MAX] = inlink->h - ctx->y;
  104. if ((ret = av_expr_parse_and_eval(&res, (expr = ctx->h_expr),
  105. var_names, var_values,
  106. NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
  107. goto fail;
  108. ctx->h = var_values[VAR_H] = res;
  109. var_values[VAR_MAX] = INT_MAX;
  110. if ((ret = av_expr_parse_and_eval(&res, (expr = ctx->t_expr),
  111. var_names, var_values,
  112. NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
  113. goto fail;
  114. ctx->thickness = var_values[VAR_T] = res;
  115. }
  116. /* Sanity check */
  117. ctx->w = (ctx->w > 0) ? ctx->w : inlink->w;
  118. ctx->h = (ctx->h > 0) ? ctx->h : inlink->h;
  119. if (ctx->x + ctx->w > inlink->w)
  120. ctx->w = inlink->w - ctx->x;
  121. if (ctx->y + ctx->h > inlink->h)
  122. ctx->h = inlink->h - ctx->y;
  123. ctx->outer_rect.x = ctx->x;
  124. ctx->outer_rect.y = ctx->y;
  125. ctx->outer_rect.width = ctx->w;
  126. ctx->outer_rect.height = ctx->h;
  127. if (ctx->outer_rect.width <= ctx->thickness * 2 ||
  128. ctx->outer_rect.height <= ctx->thickness * 2) {
  129. ctx->fill = 1;
  130. } else {
  131. ctx->fill = 0;
  132. ctx->inner_rect.x = ctx->outer_rect.x + ctx->thickness;
  133. ctx->inner_rect.y = ctx->outer_rect.y + ctx->thickness;
  134. ctx->inner_rect.width = ctx->outer_rect.width - ctx->thickness * 2;
  135. ctx->inner_rect.height = ctx->outer_rect.height - ctx->thickness * 2;
  136. }
  137. vpp_ctx->output_width = inlink->w;
  138. vpp_ctx->output_height = inlink->h;
  139. ret = ff_vaapi_vpp_config_output(outlink);
  140. if (ret < 0)
  141. return ret;
  142. ctx->outer_frames_ref = av_hwframe_ctx_alloc(vpp_ctx->device_ref);
  143. if (!ctx->outer_frames_ref) {
  144. return AVERROR(ENOMEM);
  145. }
  146. ctx->outer_frames = (AVHWFramesContext*)ctx->outer_frames_ref->data;
  147. ctx->outer_frames->format = AV_PIX_FMT_VAAPI;
  148. ctx->outer_frames->sw_format = vpp_ctx->input_frames->sw_format;
  149. ctx->outer_frames->width = ctx->outer_rect.width;
  150. ctx->outer_frames->height = ctx->outer_rect.height;
  151. return av_hwframe_ctx_init(ctx->outer_frames_ref);
  152. fail:
  153. av_log(avctx, AV_LOG_ERROR,
  154. "Error when evaluating the expression '%s'.\n",
  155. expr);
  156. return ret;
  157. }
  158. static int drawbox_vaapi_filter_frame(AVFilterLink *link, AVFrame *input_frame)
  159. {
  160. AVFilterContext *avctx = link->dst;
  161. AVFilterLink *outlink = avctx->outputs[0];
  162. VAAPIVPPContext *vpp_ctx = avctx->priv;
  163. DrawboxVAAPIContext *drawbox_ctx = avctx->priv;
  164. AVFrame *output_frame = NULL;
  165. VAProcPipelineParameterBuffer box_params;
  166. VAProcPipelineParameterBuffer params[3];
  167. VABlendState blend_state = {
  168. .flags = VA_BLEND_GLOBAL_ALPHA,
  169. };
  170. VARectangle box[4];
  171. int err, nb_params = 0;
  172. if (!input_frame->hw_frames_ctx ||
  173. vpp_ctx->va_context == VA_INVALID_ID) {
  174. err = AVERROR(EINVAL);
  175. goto fail;
  176. }
  177. if (!drawbox_ctx->outer_frame) {
  178. drawbox_ctx->outer_frame = av_frame_alloc();
  179. if (!drawbox_ctx->outer_frame) {
  180. err = AVERROR(ENOMEM);
  181. goto fail;
  182. }
  183. err = av_hwframe_get_buffer(drawbox_ctx->outer_frames_ref, drawbox_ctx->outer_frame, 0);
  184. if (err < 0) {
  185. err = AVERROR(ENOMEM);
  186. goto fail;
  187. }
  188. /* Create image for the outer rect */
  189. err = ff_vaapi_vpp_init_params(avctx, &box_params,
  190. input_frame, drawbox_ctx->outer_frame);
  191. if (err < 0)
  192. goto fail;
  193. blend_state.global_alpha = 0.0f;
  194. box_params.surface_region = &drawbox_ctx->outer_rect;
  195. box_params.blend_state = &blend_state;
  196. box_params.output_background_color = (drawbox_ctx->drawbox_rgba[3] << 24 |
  197. drawbox_ctx->drawbox_rgba[0] << 16 |
  198. drawbox_ctx->drawbox_rgba[1] << 8 |
  199. drawbox_ctx->drawbox_rgba[2]);
  200. err = ff_vaapi_vpp_render_picture(avctx, &box_params, drawbox_ctx->outer_frame);
  201. if (err < 0)
  202. goto fail;
  203. }
  204. /* Draw outer & inner rects on the input video, then we can get a box*/
  205. output_frame = ff_get_video_buffer(outlink, outlink->w, outlink->h);
  206. if (!output_frame) {
  207. err = AVERROR(ENOMEM);
  208. goto fail;
  209. }
  210. err = av_frame_copy_props(output_frame, input_frame);
  211. if (err < 0)
  212. goto fail;
  213. err = ff_vaapi_vpp_init_params(avctx, &params[nb_params],
  214. input_frame, output_frame);
  215. if (err < 0)
  216. goto fail;
  217. box[0].x = 0;
  218. box[0].y = 0;
  219. box[0].width = link->w;
  220. box[0].height = link->h;
  221. params[nb_params].surface_region = &box[0];
  222. params[nb_params].output_background_color = 0;
  223. nb_params++;
  224. err = ff_vaapi_vpp_init_params(avctx, &params[nb_params],
  225. drawbox_ctx->outer_frame, output_frame);
  226. if (err < 0)
  227. goto fail;
  228. box[1] = drawbox_ctx->outer_rect;
  229. if (drawbox_ctx->drawbox_rgba[3] != 255 && !drawbox_ctx->replace) {
  230. blend_state.global_alpha = (float)drawbox_ctx->drawbox_rgba[3] / 255;
  231. params[nb_params].blend_state = &blend_state;
  232. }
  233. params[nb_params].output_region = &box[1];
  234. params[nb_params].output_background_color = 0;
  235. nb_params++;
  236. if (!drawbox_ctx->fill) {
  237. box[3] = box[2] = drawbox_ctx->inner_rect;
  238. params[nb_params] = params[0];
  239. params[nb_params].surface_region = &box[2];
  240. params[nb_params].output_region = &box[3];
  241. params[nb_params].output_background_color = 0;
  242. nb_params++;
  243. }
  244. err = ff_vaapi_vpp_render_pictures(avctx, params, nb_params, output_frame);
  245. if (err < 0)
  246. goto fail;
  247. av_frame_free(&input_frame);
  248. return ff_filter_frame(outlink, output_frame);
  249. fail:
  250. av_frame_free(&input_frame);
  251. av_frame_free(&output_frame);
  252. return err;
  253. }
  254. static av_cold int drawbox_vaapi_init(AVFilterContext *avctx)
  255. {
  256. VAAPIVPPContext *vpp_ctx = avctx->priv;
  257. ff_vaapi_vpp_ctx_init(avctx);
  258. vpp_ctx->pipeline_uninit = ff_vaapi_vpp_pipeline_uninit;
  259. vpp_ctx->output_format = AV_PIX_FMT_NONE;
  260. return 0;
  261. }
  262. static av_cold void drawbox_vaapi_uninit(AVFilterContext *avctx)
  263. {
  264. DrawboxVAAPIContext *ctx = avctx->priv;
  265. av_frame_free(&ctx->outer_frame);
  266. av_buffer_unref(&ctx->outer_frames_ref);
  267. ff_vaapi_vpp_ctx_uninit(avctx);
  268. }
  269. #define OFFSET(x) offsetof(DrawboxVAAPIContext, x)
  270. #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
  271. static const AVOption drawbox_vaapi_options[] = {
  272. { "x", "set horizontal position of the left box edge", OFFSET(x_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
  273. { "y", "set vertical position of the top box edge", OFFSET(y_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
  274. { "width", "set width of the box", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
  275. { "w", "set width of the box", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
  276. { "height", "set height of the box", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
  277. { "h", "set height of the box", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
  278. { "color", "set color of the box", OFFSET(drawbox_rgba), AV_OPT_TYPE_COLOR, { .str = "black" }, 0, 0, FLAGS },
  279. { "c", "set color of the box", OFFSET(drawbox_rgba), AV_OPT_TYPE_COLOR, { .str = "black" }, 0, 0, FLAGS },
  280. { "thickness", "set the box thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, { .str="3" }, 0, 0, FLAGS },
  281. { "t", "set the box thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, { .str="3" }, 0, 0, FLAGS },
  282. { "replace", "replace color", OFFSET(replace), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
  283. { NULL }
  284. };
  285. AVFILTER_DEFINE_CLASS(drawbox_vaapi);
  286. static const AVFilterPad drawbox_vaapi_inputs[] = {
  287. {
  288. .name = "default",
  289. .type = AVMEDIA_TYPE_VIDEO,
  290. .filter_frame = drawbox_vaapi_filter_frame,
  291. .config_props = &ff_vaapi_vpp_config_input,
  292. },
  293. };
  294. static const AVFilterPad drawbox_vaapi_outputs[] = {
  295. {
  296. .name = "default",
  297. .type = AVMEDIA_TYPE_VIDEO,
  298. .config_props = &drawbox_vaapi_config_output,
  299. },
  300. };
  301. const AVFilter ff_vf_drawbox_vaapi = {
  302. .name = "drawbox_vaapi",
  303. .description = NULL_IF_CONFIG_SMALL("Draw a colored box on the input video."),
  304. .priv_size = sizeof(DrawboxVAAPIContext),
  305. .priv_class = &drawbox_vaapi_class,
  306. .init = &drawbox_vaapi_init,
  307. .uninit = &drawbox_vaapi_uninit,
  308. FILTER_INPUTS(drawbox_vaapi_inputs),
  309. FILTER_OUTPUTS(drawbox_vaapi_outputs),
  310. FILTER_QUERY_FUNC(&ff_vaapi_vpp_query_formats),
  311. .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
  312. };