mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-25 08:42:39 +00:00
790f793844
There are lots of files that don't need it: The number of object files that actually need it went down from 2011 to 884 here. Keep it for external users in order to not cause breakages. Also improve the other headers a bit while just at it. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
824 lines
29 KiB
C
824 lines
29 KiB
C
/*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* @file QR encoder source and filter.
|
|
*
|
|
* A QR code (quick-response code) is a type of two-dimensional matrix
|
|
* barcode, invented in 1994, by Japanese company Denso Wave for
|
|
* labelling automobile parts.
|
|
*
|
|
* This source uses the libqrencode library to generate QR code:
|
|
* https://fukuchi.org/works/qrencode/
|
|
*/
|
|
|
|
//#define DEBUG
|
|
|
|
#include "config_components.h"
|
|
|
|
#include "libavutil/internal.h"
|
|
#include "libavutil/imgutils.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/lfg.h"
|
|
#include "libavutil/random_seed.h"
|
|
|
|
#include "avfilter.h"
|
|
#include "drawutils.h"
|
|
#include "internal.h"
|
|
#include "formats.h"
|
|
#include "textutils.h"
|
|
#include "video.h"
|
|
#include "libswscale/swscale.h"
|
|
|
|
#include <qrencode.h>
|
|
|
|
enum var_name {
|
|
VAR_dar,
|
|
VAR_duration,
|
|
VAR_hsub, VAR_vsub,
|
|
VAR_main_h, VAR_H,
|
|
VAR_main_w, VAR_W,
|
|
VAR_n,
|
|
VAR_pict_type,
|
|
VAR_qr_w, VAR_w,
|
|
VAR_rendered_padded_qr_w, VAR_Q,
|
|
VAR_rendered_qr_w, VAR_q,
|
|
VAR_sar,
|
|
VAR_t,
|
|
VAR_x,
|
|
VAR_y,
|
|
VAR_VARS_NB
|
|
};
|
|
|
|
static const char *const var_names[] = {
|
|
"dar",
|
|
"duration",
|
|
"hsub", "vsub",
|
|
"main_h", "H", ///< height of the input video
|
|
"main_w", "W", ///< width of the input video
|
|
"n", ///< number of frame
|
|
"pict_type",
|
|
"qr_w", "w", ///< width of the QR code
|
|
"rendered_padded_qr_w", "Q", ///< width of the rendered QR code
|
|
"rendered_qr_w", "q", ///< width of the rendered QR code
|
|
"sar",
|
|
"t", ///< timestamp expressed in seconds
|
|
"x",
|
|
"y",
|
|
NULL
|
|
};
|
|
|
|
#define V(name_) qr->var_values[VAR_##name_]
|
|
|
|
enum Expansion {
|
|
EXPANSION_NONE,
|
|
EXPANSION_NORMAL
|
|
};
|
|
|
|
typedef struct QREncodeContext {
|
|
const AVClass *class;
|
|
|
|
char is_source;
|
|
char *x_expr;
|
|
char *y_expr;
|
|
AVExpr *x_pexpr, *y_pexpr;
|
|
|
|
char *rendered_qrcode_width_expr;
|
|
char *rendered_padded_qrcode_width_expr;
|
|
AVExpr *rendered_qrcode_width_pexpr, *rendered_padded_qrcode_width_pexpr;
|
|
|
|
int rendered_qrcode_width;
|
|
int rendered_padded_qrcode_width;
|
|
|
|
unsigned char *text;
|
|
char *textfile;
|
|
uint64_t pts;
|
|
|
|
int level;
|
|
char case_sensitive;
|
|
|
|
uint8_t foreground_color[4];
|
|
uint8_t background_color[4];
|
|
|
|
FFDrawContext draw;
|
|
FFDrawColor draw_foreground_color; ///< foreground color
|
|
FFDrawColor draw_background_color; ///< background color
|
|
|
|
/* these are only used when nothing must be encoded */
|
|
FFDrawContext draw0;
|
|
FFDrawColor draw0_background_color; ///< background color
|
|
|
|
uint8_t *qrcode_data[4];
|
|
int qrcode_linesize[4];
|
|
uint8_t *qrcode_mask_data[4];
|
|
int qrcode_mask_linesize[4];
|
|
|
|
/* only used for filter to contain scaled image to blend on top of input */
|
|
uint8_t *rendered_qrcode_data[4];
|
|
int rendered_qrcode_linesize[4];
|
|
|
|
int qrcode_width;
|
|
int padded_qrcode_width;
|
|
|
|
AVRational frame_rate;
|
|
|
|
int expansion; ///< expansion mode to use for the text
|
|
FFExpandTextContext expand_text; ///< expand text in case expansion is enabled
|
|
AVBPrint expanded_text; ///< used to contain the expanded text
|
|
|
|
double var_values[VAR_VARS_NB];
|
|
AVLFG lfg; ///< random generator
|
|
AVDictionary *metadata;
|
|
} QREncodeContext;
|
|
|
|
#define OFFSET(x) offsetof(QREncodeContext, x)
|
|
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
|
#define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
|
|
|
|
#define COMMON_OPTIONS \
|
|
{ "qrcode_width", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS }, \
|
|
{ "q", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS }, \
|
|
{ "padded_qrcode_width", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "q"}, 0, INT_MAX, FLAGS }, \
|
|
{ "Q", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "q"}, 0, INT_MAX, FLAGS }, \
|
|
{ "case_sensitive", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, \
|
|
{ "cs", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, \
|
|
\
|
|
{ "level", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, .unit = "level"}, \
|
|
{ "l", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, .unit = "level"}, \
|
|
{ "L", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_L }, 0, 0, FLAGS, .unit = "level" }, \
|
|
{ "M", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_M }, 0, 0, FLAGS, .unit = "level" }, \
|
|
{ "Q", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_Q }, 0, 0, FLAGS, .unit = "level" }, \
|
|
{ "H", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_H }, 0, 0, FLAGS, .unit = "level" }, \
|
|
\
|
|
{"expansion", "set the expansion mode", OFFSET(expansion), AV_OPT_TYPE_INT, {.i64=EXPANSION_NORMAL}, 0, 2, FLAGS, .unit = "expansion"}, \
|
|
{"none", "set no expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NONE}, 0, 0, FLAGS, .unit = "expansion"}, \
|
|
{"normal", "set normal expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NORMAL}, 0, 0, FLAGS, .unit = "expansion"}, \
|
|
\
|
|
{ "foreground_color", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS }, \
|
|
{ "fc", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS }, \
|
|
{ "background_color", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS }, \
|
|
{ "bc", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS }, \
|
|
\
|
|
{"text", "set text to encode", OFFSET(text), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, \
|
|
{"textfile", "set text file to encode", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, \
|
|
|
|
static const char *const fun2_names[] = {
|
|
"rand"
|
|
};
|
|
|
|
static double drand(void *opaque, double min, double max)
|
|
{
|
|
return min + (max-min) / UINT_MAX * av_lfg_get(opaque);
|
|
}
|
|
|
|
static const ff_eval_func2 fun2[] = {
|
|
drand,
|
|
NULL
|
|
};
|
|
|
|
static int func_pts(void *ctx, AVBPrint *bp, const char *function_name,
|
|
unsigned argc, char **argv)
|
|
{
|
|
QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
|
|
const char *fmt;
|
|
const char *strftime_fmt = NULL;
|
|
const char *delta = NULL;
|
|
double t = qr->var_values[VAR_t];
|
|
|
|
// argv: pts, FMT, [DELTA, strftime_fmt]
|
|
|
|
fmt = argc >= 1 ? argv[0] : "flt";
|
|
if (argc >= 2) {
|
|
delta = argv[1];
|
|
}
|
|
if (argc >= 3) {
|
|
strftime_fmt = argv[2];
|
|
}
|
|
|
|
return ff_print_pts(ctx, bp, t, delta, fmt, strftime_fmt);
|
|
}
|
|
|
|
static int func_frame_num(void *ctx, AVBPrint *bp, const char *function_name,
|
|
unsigned argc, char **argv)
|
|
{
|
|
QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
|
|
|
|
av_bprintf(bp, "%d", (int)V(n));
|
|
return 0;
|
|
}
|
|
|
|
static int func_strftime(void *ctx, AVBPrint *bp, const char *function_name,
|
|
unsigned argc, char **argv)
|
|
{
|
|
const char *strftime_fmt = argc ? argv[0] : NULL;
|
|
|
|
return ff_print_time(ctx, bp, strftime_fmt, !strcmp(function_name, "localtime"));
|
|
}
|
|
|
|
static int func_frame_metadata(void *ctx, AVBPrint *bp, const char *function_name,
|
|
unsigned argc, char **argv)
|
|
{
|
|
QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
|
|
AVDictionaryEntry *e = av_dict_get(qr->metadata, argv[0], NULL, 0);
|
|
|
|
if (e && e->value)
|
|
av_bprintf(bp, "%s", e->value);
|
|
else if (argc >= 2)
|
|
av_bprintf(bp, "%s", argv[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int func_eval_expr(void *ctx, AVBPrint *bp, const char *function_name,
|
|
unsigned argc, char **argv)
|
|
{
|
|
QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
|
|
|
|
return ff_print_eval_expr(ctx, bp, argv[0],
|
|
fun2_names, fun2,
|
|
var_names, qr->var_values, &qr->lfg);
|
|
}
|
|
|
|
static int func_eval_expr_formatted(void *ctx, AVBPrint *bp, const char *function_name,
|
|
unsigned argc, char **argv)
|
|
{
|
|
QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
|
|
int ret;
|
|
int positions = -1;
|
|
|
|
/*
|
|
* argv[0] expression to be converted to `int`
|
|
* argv[1] format: 'x', 'X', 'd' or 'u'
|
|
* argv[2] positions printed (optional)
|
|
*/
|
|
|
|
if (argc == 3) {
|
|
ret = sscanf(argv[2], "%u", &positions);
|
|
if (ret != 1) {
|
|
av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions"
|
|
" to print: '%s'\n", argv[2]);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
}
|
|
|
|
return ff_print_formatted_eval_expr(ctx, bp, argv[0],
|
|
fun2_names, fun2,
|
|
var_names, qr->var_values,
|
|
&qr->lfg,
|
|
argv[1][0], positions);
|
|
}
|
|
|
|
static FFExpandTextFunction expand_text_functions[] = {
|
|
{ "expr", 1, 1, func_eval_expr },
|
|
{ "e", 1, 1, func_eval_expr },
|
|
{ "expr_formatted", 2, 3, func_eval_expr_formatted },
|
|
{ "ef", 2, 3, func_eval_expr_formatted },
|
|
{ "metadata", 1, 2, func_frame_metadata },
|
|
{ "frame_num", 0, 0, func_frame_num },
|
|
{ "n", 0, 0, func_frame_num },
|
|
{ "gmtime", 0, 1, func_strftime },
|
|
{ "localtime", 0, 1, func_strftime },
|
|
{ "pts", 0, 3, func_pts }
|
|
};
|
|
|
|
static av_cold int init(AVFilterContext *ctx)
|
|
{
|
|
QREncodeContext *qr = ctx->priv;
|
|
int ret;
|
|
|
|
av_lfg_init(&qr->lfg, av_get_random_seed());
|
|
|
|
qr->qrcode_width = -1;
|
|
qr->rendered_padded_qrcode_width = -1;
|
|
|
|
if (qr->textfile) {
|
|
if (qr->text) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Both text and text file provided. Please provide only one\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
if ((ret = ff_load_textfile(ctx, (const char *)qr->textfile, &(qr->text), NULL)) < 0)
|
|
return ret;
|
|
}
|
|
|
|
qr->expand_text = (FFExpandTextContext) {
|
|
.log_ctx = ctx,
|
|
.functions = expand_text_functions,
|
|
.functions_nb = FF_ARRAY_ELEMS(expand_text_functions)
|
|
};
|
|
|
|
av_bprint_init(&qr->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static av_cold void uninit(AVFilterContext *ctx)
|
|
{
|
|
QREncodeContext *qr = ctx->priv;
|
|
|
|
av_expr_free(qr->x_pexpr);
|
|
av_expr_free(qr->y_pexpr);
|
|
|
|
av_bprint_finalize(&qr->expanded_text, NULL);
|
|
|
|
av_freep(&qr->qrcode_data[0]);
|
|
av_freep(&qr->rendered_qrcode_data[0]);
|
|
av_freep(&qr->qrcode_mask_data[0]);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void show_qrcode(AVFilterContext *ctx, const QRcode *qrcode)
|
|
{
|
|
int i, j;
|
|
char *line = av_malloc(qrcode->width + 1);
|
|
const char *p = qrcode->data;
|
|
|
|
if (!line)
|
|
return;
|
|
for (i = 0; i < qrcode->width; i++) {
|
|
for (j = 0; j < qrcode->width; j++)
|
|
line[j] = (*p++)&1 ? '@' : ' ';
|
|
line[j] = 0;
|
|
av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line);
|
|
}
|
|
av_free(line);
|
|
}
|
|
#endif
|
|
|
|
static int draw_qrcode(AVFilterContext *ctx, AVFrame *frame)
|
|
{
|
|
QREncodeContext *qr = ctx->priv;
|
|
struct SwsContext *sws = NULL;
|
|
QRcode *qrcode = NULL;
|
|
int i, j;
|
|
char qrcode_width_changed;
|
|
int ret;
|
|
int offset;
|
|
uint8_t *srcp;
|
|
uint8_t *dstp0, *dstp;
|
|
|
|
av_bprint_clear(&qr->expanded_text);
|
|
|
|
switch (qr->expansion) {
|
|
case EXPANSION_NONE:
|
|
av_bprintf(&qr->expanded_text, "%s", qr->text);
|
|
break;
|
|
case EXPANSION_NORMAL:
|
|
if ((ret = ff_expand_text(&qr->expand_text, qr->text, &qr->expanded_text)) < 0)
|
|
return ret;
|
|
break;
|
|
}
|
|
|
|
if (!qr->expanded_text.str || qr->expanded_text.str[0] == 0) {
|
|
if (qr->is_source) {
|
|
ff_fill_rectangle(&qr->draw0, &qr->draw0_background_color,
|
|
frame->data, frame->linesize,
|
|
0, 0, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
av_log(ctx, AV_LOG_DEBUG, "Encoding string '%s'\n", qr->expanded_text.str);
|
|
qrcode = QRcode_encodeString(qr->expanded_text.str, 1, qr->level, QR_MODE_8,
|
|
qr->case_sensitive);
|
|
if (!qrcode) {
|
|
ret = AVERROR(errno);
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Failed to encode string with error \'%s\'\n", av_err2str(ret));
|
|
goto end;
|
|
}
|
|
|
|
av_log(ctx, AV_LOG_DEBUG,
|
|
"Encoded QR with width:%d version:%d\n", qrcode->width, qrcode->version);
|
|
#ifdef DEBUG
|
|
show_qrcode(ctx, (const QRcode *)qrcode);
|
|
#endif
|
|
|
|
qrcode_width_changed = qr->qrcode_width != qrcode->width;
|
|
qr->qrcode_width = qrcode->width;
|
|
|
|
// realloc mask if needed
|
|
if (qrcode_width_changed) {
|
|
av_freep(&qr->qrcode_mask_data[0]);
|
|
ret = av_image_alloc(qr->qrcode_mask_data, qr->qrcode_mask_linesize,
|
|
qrcode->width, qrcode->width,
|
|
AV_PIX_FMT_GRAY8, 16);
|
|
if (ret < 0) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Failed to allocate image for QR code with width %d\n", qrcode->width);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* fill mask */
|
|
dstp0 = qr->qrcode_mask_data[0];
|
|
srcp = qrcode->data;
|
|
|
|
for (i = 0; i < qrcode->width; i++) {
|
|
dstp = dstp0;
|
|
for (j = 0; j < qrcode->width; j++)
|
|
*dstp++ = (*srcp++ & 1) ? 255 : 0;
|
|
dstp0 += qr->qrcode_mask_linesize[0];
|
|
}
|
|
|
|
if (qr->is_source) {
|
|
if (qrcode_width_changed) {
|
|
/* realloc padded image */
|
|
|
|
// compute virtual non-rendered padded size
|
|
// Q/q = W/w
|
|
qr->padded_qrcode_width =
|
|
((double)qr->rendered_padded_qrcode_width / qr->rendered_qrcode_width) * qrcode->width;
|
|
|
|
av_freep(&qr->qrcode_data[0]);
|
|
ret = av_image_alloc(qr->qrcode_data, qr->qrcode_linesize,
|
|
qr->padded_qrcode_width, qr->padded_qrcode_width,
|
|
AV_PIX_FMT_ARGB, 16);
|
|
if (ret < 0) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Failed to allocate image for QR code with width %d\n",
|
|
qr->padded_qrcode_width);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* fill padding */
|
|
ff_fill_rectangle(&qr->draw, &qr->draw_background_color,
|
|
qr->qrcode_data, qr->qrcode_linesize,
|
|
0, 0, qr->padded_qrcode_width, qr->padded_qrcode_width);
|
|
|
|
/* blend mask */
|
|
offset = (qr->padded_qrcode_width - qr->qrcode_width) / 2;
|
|
ff_blend_mask(&qr->draw, &qr->draw_foreground_color,
|
|
qr->qrcode_data, qr->qrcode_linesize,
|
|
qr->padded_qrcode_width, qr->padded_qrcode_width,
|
|
qr->qrcode_mask_data[0], qr->qrcode_mask_linesize[0], qrcode->width, qrcode->width,
|
|
3, 0, offset, offset);
|
|
|
|
/* scale padded QR over the frame */
|
|
sws = sws_alloc_context();
|
|
if (!sws) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto end;
|
|
}
|
|
|
|
av_opt_set_int(sws, "srcw", qr->padded_qrcode_width, 0);
|
|
av_opt_set_int(sws, "srch", qr->padded_qrcode_width, 0);
|
|
av_opt_set_int(sws, "src_format", AV_PIX_FMT_ARGB, 0);
|
|
av_opt_set_int(sws, "dstw", qr->rendered_padded_qrcode_width, 0);
|
|
av_opt_set_int(sws, "dsth", qr->rendered_padded_qrcode_width, 0);
|
|
av_opt_set_int(sws, "dst_format", frame->format, 0);
|
|
av_opt_set_int(sws, "sws_flags", SWS_POINT, 0);
|
|
|
|
if ((ret = sws_init_context(sws, NULL, NULL)) < 0)
|
|
goto end;
|
|
|
|
sws_scale(sws,
|
|
(const uint8_t *const *)&qr->qrcode_data, qr->qrcode_linesize,
|
|
0, qr->padded_qrcode_width,
|
|
frame->data, frame->linesize);
|
|
} else {
|
|
#define EVAL_EXPR(name_) \
|
|
av_expr_eval(qr->name_##_pexpr, qr->var_values, &qr->lfg);
|
|
|
|
V(qr_w) = V(w) = qrcode->width;
|
|
|
|
V(rendered_qr_w) = V(q) = EVAL_EXPR(rendered_qrcode_width);
|
|
V(rendered_padded_qr_w) = V(Q) = EVAL_EXPR(rendered_padded_qrcode_width);
|
|
/* It is necessary if q is expressed from Q */
|
|
V(rendered_qr_w) = V(q) = EVAL_EXPR(rendered_qrcode_width);
|
|
|
|
V(x) = EVAL_EXPR(x);
|
|
V(y) = EVAL_EXPR(y);
|
|
/* It is necessary if x is expressed from y */
|
|
V(x) = EVAL_EXPR(x);
|
|
|
|
av_log(ctx, AV_LOG_DEBUG,
|
|
"Rendering QR code with values n:%d w:%d q:%d Q:%d x:%d y:%d t:%f\n",
|
|
(int)V(n), (int)V(w), (int)V(q), (int)V(Q), (int)V(x), (int)V(y), V(t));
|
|
|
|
/* blend rectangle over the target */
|
|
ff_blend_rectangle(&qr->draw, &qr->draw_background_color,
|
|
frame->data, frame->linesize, frame->width, frame->height,
|
|
V(x), V(y), V(Q), V(Q));
|
|
|
|
if (V(q) != qr->rendered_qrcode_width) {
|
|
av_freep(&qr->rendered_qrcode_data[0]);
|
|
qr->rendered_qrcode_width = V(q);
|
|
|
|
ret = av_image_alloc(qr->rendered_qrcode_data, qr->rendered_qrcode_linesize,
|
|
qr->rendered_qrcode_width, qr->rendered_qrcode_width,
|
|
AV_PIX_FMT_GRAY8, 16);
|
|
if (ret < 0) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Failed to allocate image for rendered QR code with width %d\n",
|
|
qr->rendered_qrcode_width);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* scale mask */
|
|
sws = sws_alloc_context();
|
|
if (!sws) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto end;
|
|
}
|
|
|
|
av_opt_set_int(sws, "srcw", qr->qrcode_width, 0);
|
|
av_opt_set_int(sws, "srch", qr->qrcode_width, 0);
|
|
av_opt_set_int(sws, "src_format", AV_PIX_FMT_GRAY8, 0);
|
|
av_opt_set_int(sws, "dstw", qr->rendered_qrcode_width, 0);
|
|
av_opt_set_int(sws, "dsth", qr->rendered_qrcode_width, 0);
|
|
av_opt_set_int(sws, "dst_format", AV_PIX_FMT_GRAY8, 0);
|
|
av_opt_set_int(sws, "sws_flags", SWS_POINT, 0);
|
|
|
|
if ((ret = sws_init_context(sws, NULL, NULL)) < 0)
|
|
goto end;
|
|
|
|
sws_scale(sws,
|
|
(const uint8_t *const *)&qr->qrcode_mask_data, qr->qrcode_mask_linesize,
|
|
0, qr->qrcode_width,
|
|
qr->rendered_qrcode_data, qr->rendered_qrcode_linesize);
|
|
|
|
/* blend mask over the input frame */
|
|
offset = (V(Q) - V(q)) / 2;
|
|
ff_blend_mask(&qr->draw, &qr->draw_foreground_color,
|
|
frame->data, frame->linesize, frame->width, frame->height,
|
|
qr->rendered_qrcode_data[0], qr->rendered_qrcode_linesize[0],
|
|
qr->rendered_qrcode_width, qr->rendered_qrcode_width,
|
|
3, 0, V(x) + offset, V(y) + offset);
|
|
}
|
|
|
|
end:
|
|
sws_freeContext(sws);
|
|
QRcode_free(qrcode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if CONFIG_QRENCODESRC_FILTER
|
|
|
|
static const AVOption qrencodesrc_options[] = {
|
|
COMMON_OPTIONS
|
|
{ "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
|
|
{ "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
|
|
{ NULL }
|
|
};
|
|
|
|
AVFILTER_DEFINE_CLASS(qrencodesrc);
|
|
|
|
static int qrencodesrc_config_props(AVFilterLink *outlink)
|
|
{
|
|
AVFilterContext *ctx = outlink->src;
|
|
QREncodeContext *qr = ctx->priv;
|
|
int ret;
|
|
|
|
qr->is_source = 1;
|
|
V(x) = V(y) = 0;
|
|
|
|
#define PARSE_AND_EVAL_EXPR(var_name_, expr_name_) \
|
|
ret = av_expr_parse_and_eval(&qr->var_values[VAR_##var_name_], \
|
|
qr->expr_name_##_expr, \
|
|
var_names, qr->var_values, \
|
|
NULL, NULL, \
|
|
fun2_names, fun2, \
|
|
&qr->lfg, 0, ctx); \
|
|
if (ret < 0) { \
|
|
av_log(ctx, AV_LOG_ERROR, \
|
|
"Could not evaluate expression '%s'\n", \
|
|
qr->expr_name_##_expr); \
|
|
return ret; \
|
|
}
|
|
|
|
/* undefined for the source */
|
|
V(main_w) = V(W) = NAN;
|
|
V(main_h) = V(H) = NAN;
|
|
V(x) = V(y) = V(t) = V(n) = NAN;
|
|
V(dar) = V(sar) = 1.0;
|
|
|
|
PARSE_AND_EVAL_EXPR(rendered_qr_w, rendered_qrcode_width);
|
|
V(q) = V(rendered_qr_w);
|
|
PARSE_AND_EVAL_EXPR(rendered_padded_qr_w, rendered_padded_qrcode_width);
|
|
V(Q) = V(rendered_padded_qr_w);
|
|
PARSE_AND_EVAL_EXPR(rendered_qr_w, rendered_qrcode_width);
|
|
V(q) = V(rendered_qr_w);
|
|
|
|
qr->rendered_qrcode_width = V(rendered_qr_w);
|
|
qr->rendered_padded_qrcode_width = V(rendered_padded_qr_w);
|
|
|
|
av_log(ctx, AV_LOG_VERBOSE,
|
|
"q:%d Q:%d case_sensitive:%d level:%d\n",
|
|
(int)qr->rendered_qrcode_width, (int)qr->rendered_padded_qrcode_width,
|
|
qr->case_sensitive, qr->level);
|
|
|
|
if (qr->rendered_padded_qrcode_width < qr->rendered_qrcode_width) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Resulting padded QR code width (%d) is lesser than the QR code width (%d)\n",
|
|
qr->rendered_padded_qrcode_width, qr->rendered_qrcode_width);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
ff_draw_init(&qr->draw, AV_PIX_FMT_ARGB, FF_DRAW_PROCESS_ALPHA);
|
|
ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color);
|
|
ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color);
|
|
|
|
ff_draw_init2(&qr->draw0, outlink->format, outlink->colorspace, outlink->color_range, FF_DRAW_PROCESS_ALPHA);
|
|
ff_draw_color(&qr->draw0, &qr->draw0_background_color, (const uint8_t *)&qr->background_color);
|
|
|
|
outlink->w = qr->rendered_padded_qrcode_width;
|
|
outlink->h = qr->rendered_padded_qrcode_width;
|
|
outlink->time_base = av_inv_q(qr->frame_rate);
|
|
outlink->frame_rate = qr->frame_rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int request_frame(AVFilterLink *outlink)
|
|
{
|
|
AVFilterContext *ctx = (AVFilterContext *)outlink->src;
|
|
QREncodeContext *qr = ctx->priv;
|
|
AVFrame *frame =
|
|
ff_get_video_buffer(outlink, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width);
|
|
int ret;
|
|
|
|
if (!frame)
|
|
return AVERROR(ENOMEM);
|
|
frame->sample_aspect_ratio = (AVRational) {1, 1};
|
|
V(n) = frame->pts = qr->pts++;
|
|
V(t) = qr->pts * av_q2d(outlink->time_base);
|
|
|
|
if ((ret = draw_qrcode(ctx, frame)) < 0)
|
|
return ret;
|
|
|
|
return ff_filter_frame(outlink, frame);
|
|
}
|
|
|
|
static int qrencodesrc_query_formats(AVFilterContext *ctx)
|
|
{
|
|
enum AVPixelFormat pix_fmt;
|
|
FFDrawContext draw;
|
|
AVFilterFormats *fmts = NULL;
|
|
int ret;
|
|
|
|
// this is needed to support both the no-draw and draw cases
|
|
// for the no-draw case we use FFDrawContext to write on the input picture ref
|
|
for (pix_fmt = 0; av_pix_fmt_desc_get(pix_fmt); pix_fmt++)
|
|
if (ff_draw_init(&draw, pix_fmt, 0) >= 0 &&
|
|
sws_isSupportedOutput(pix_fmt) &&
|
|
(ret = ff_add_format(&fmts, pix_fmt)) < 0)
|
|
return ret;
|
|
|
|
return ff_set_common_formats(ctx, fmts);
|
|
}
|
|
|
|
static const AVFilterPad qrencodesrc_outputs[] = {
|
|
{
|
|
.name = "default",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.request_frame = request_frame,
|
|
.config_props = qrencodesrc_config_props,
|
|
}
|
|
};
|
|
|
|
const AVFilter ff_vsrc_qrencodesrc = {
|
|
.name = "qrencodesrc",
|
|
.description = NULL_IF_CONFIG_SMALL("Generate a QR code."),
|
|
.priv_size = sizeof(QREncodeContext),
|
|
.priv_class = &qrencodesrc_class,
|
|
.init = init,
|
|
.uninit = uninit,
|
|
.inputs = NULL,
|
|
FILTER_OUTPUTS(qrencodesrc_outputs),
|
|
FILTER_QUERY_FUNC(qrencodesrc_query_formats),
|
|
};
|
|
|
|
#endif // CONFIG_QRENCODESRC_FILTER
|
|
|
|
#if CONFIG_QRENCODE_FILTER
|
|
|
|
static const AVOption qrencode_options[] = {
|
|
COMMON_OPTIONS
|
|
{"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS},
|
|
{"y", "set y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS},
|
|
{ NULL }
|
|
};
|
|
|
|
AVFILTER_DEFINE_CLASS(qrencode);
|
|
|
|
static int qrencode_config_input(AVFilterLink *inlink)
|
|
{
|
|
AVFilterContext *ctx = inlink->dst;
|
|
QREncodeContext *qr = ctx->priv;
|
|
char *expr;
|
|
int ret;
|
|
|
|
qr->is_source = 0;
|
|
|
|
ff_draw_init2(&qr->draw, inlink->format, inlink->colorspace, inlink->color_range,
|
|
FF_DRAW_PROCESS_ALPHA);
|
|
|
|
V(W) = V(main_w) = inlink->w;
|
|
V(H) = V(main_h) = inlink->h;
|
|
V(sar) = inlink->sample_aspect_ratio.num ? av_q2d(inlink->sample_aspect_ratio) : 1;
|
|
V(dar) = (double)inlink->w / inlink->h * V(sar);
|
|
V(hsub) = 1 << qr->draw.hsub_max;
|
|
V(vsub) = 1 << qr->draw.vsub_max;
|
|
V(t) = NAN;
|
|
V(x) = V(y) = NAN;
|
|
|
|
qr->x_pexpr = qr->y_pexpr = NULL;
|
|
qr->x_pexpr = qr->y_pexpr = NULL;
|
|
|
|
#define PARSE_EXPR(name_) \
|
|
ret = av_expr_parse(&qr->name_##_pexpr, expr = qr->name_##_expr, var_names, \
|
|
NULL, NULL, fun2_names, fun2, 0, ctx); \
|
|
if (ret < 0) { \
|
|
av_log(ctx, AV_LOG_ERROR, \
|
|
"Could not to parse expression '%s' for '%s'\n", \
|
|
expr, #name_); \
|
|
return AVERROR(EINVAL); \
|
|
}
|
|
|
|
PARSE_EXPR(x);
|
|
PARSE_EXPR(y);
|
|
PARSE_EXPR(rendered_qrcode_width);
|
|
PARSE_EXPR(rendered_padded_qrcode_width);
|
|
|
|
ff_draw_init2(&qr->draw, inlink->format, inlink->colorspace, inlink->color_range,
|
|
FF_DRAW_PROCESS_ALPHA);
|
|
ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color);
|
|
ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color);
|
|
|
|
qr->rendered_qrcode_width = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qrencode_query_formats(AVFilterContext *ctx)
|
|
{
|
|
return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
|
|
}
|
|
|
|
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
|
{
|
|
AVFilterContext *ctx = inlink->dst;
|
|
AVFilterLink *outlink = ctx->outputs[0];
|
|
QREncodeContext *qr = ctx->priv;
|
|
int ret;
|
|
|
|
V(n) = inlink->frame_count_out;
|
|
V(t) = frame->pts == AV_NOPTS_VALUE ?
|
|
NAN : frame->pts * av_q2d(inlink->time_base);
|
|
V(pict_type) = frame->pict_type;
|
|
V(duration) = frame->duration * av_q2d(inlink->time_base);
|
|
|
|
qr->metadata = frame->metadata;
|
|
|
|
if ((ret = draw_qrcode(ctx, frame)) < 0)
|
|
return ret;
|
|
|
|
return ff_filter_frame(outlink, frame);
|
|
}
|
|
|
|
static const AVFilterPad avfilter_vf_qrencode_inputs[] = {
|
|
{
|
|
.name = "default",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
|
|
.filter_frame = filter_frame,
|
|
.config_props = qrencode_config_input,
|
|
},
|
|
};
|
|
|
|
const AVFilter ff_vf_qrencode = {
|
|
.name = "qrencode",
|
|
.description = NULL_IF_CONFIG_SMALL("Draw a QR code on top of video frames."),
|
|
.priv_size = sizeof(QREncodeContext),
|
|
.priv_class = &qrencode_class,
|
|
.init = init,
|
|
.uninit = uninit,
|
|
FILTER_INPUTS(avfilter_vf_qrencode_inputs),
|
|
FILTER_OUTPUTS(ff_video_default_filterpad),
|
|
FILTER_QUERY_FUNC(qrencode_query_formats),
|
|
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
|
|
};
|
|
|
|
#endif // CONFIG_QRENCODE_FILTER
|