1
0
Fork 0

lavfi: add qrencode source and filter

This commit is contained in:
Stefano Sabatini 2023-11-28 23:58:15 +01:00
parent 732fb122e6
commit 899302bb5f
6 changed files with 1259 additions and 0 deletions

View File

@ -13,6 +13,7 @@ version <next>:
- IAMF raw demuxer and muxer
- D3D12VA hardware accelerated H264, HEVC, VP9, AV1, MPEG-2 and VC1 decoding
- tiltandshift filter
- qrencode filter and qrencodesrc source
version 6.1:
- libaribcaption decoder

7
configure vendored
View File

@ -256,6 +256,7 @@ External library support:
--enable-libopus enable Opus de/encoding via libopus [no]
--enable-libplacebo enable libplacebo library [no]
--enable-libpulse enable Pulseaudio input via libpulse [no]
--enable-libqrencode enable QR encode generation via libqrencode [no]
--enable-librabbitmq enable RabbitMQ library [no]
--enable-librav1e enable AV1 encoding via rav1e [no]
--enable-librist enable RIST via librist [no]
@ -1881,6 +1882,7 @@ EXTERNAL_LIBRARY_LIST="
libopus
libplacebo
libpulse
libqrencode
librabbitmq
librav1e
librist
@ -3789,6 +3791,8 @@ nnedi_filter_deps="gpl"
ocr_filter_deps="libtesseract"
ocv_filter_deps="libopencv"
openclsrc_filter_deps="opencl"
qrencode_filter_deps="libqrencode"
qrencodesrc_filter_deps="libqrencode"
overlay_opencl_filter_deps="opencl"
overlay_qsv_filter_deps="libmfx"
overlay_qsv_filter_select="qsvvpp"
@ -6840,6 +6844,7 @@ enabled libopus && {
}
enabled libplacebo && require_pkg_config libplacebo "libplacebo >= 4.192.0" libplacebo/vulkan.h pl_vulkan_create
enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new
enabled libqrencode && require_pkg_config libqrencode libqrencode qrencode.h QRcode_encodeString
enabled librabbitmq && require_pkg_config librabbitmq "librabbitmq >= 0.7.1" amqp.h amqp_new_connection
enabled librav1e && require_pkg_config librav1e "rav1e >= 0.5.0" rav1e.h rav1e_context_new
enabled librist && require_pkg_config librist "librist >= 0.2.7" librist/librist.h rist_receiver_create
@ -7668,6 +7673,8 @@ enabled mcdeint_filter && prepend avfilter_deps "avcodec"
enabled movie_filter && prepend avfilter_deps "avformat avcodec"
enabled pan_filter && prepend avfilter_deps "swresample"
enabled pp_filter && prepend avfilter_deps "postproc"
enabled qrencode_filter && prepend_avfilter_deps "swscale"
enabled qrencodesrc_filter && prepend_avfilter_deps "swscale"
enabled removelogo_filter && prepend avfilter_deps "avformat avcodec swscale"
enabled sab_filter && prepend avfilter_deps "swscale"
enabled scale_filter && prepend avfilter_deps "swscale"

View File

@ -20096,6 +20096,302 @@ qp=2+2*sin(PI*qp)
@end example
@end itemize
@section qrencode
Generate a QR code using the libqrencode library (see
@url{https://fukuchi.org/works/qrencode/}), and overlay it on top of the current
frame.
To enable the compilation of this filter, you need to configure FFmpeg with with
@code{--enable-libqrencode}.
The QR code is generated from the provided text or text pattern. The
corresponding QR code is scaled and overlayed into the video output according to
the specified options.
In case no text is specified, no QR code is overlaied.
This filter accepts the following options:
@table @option
@item qrcode_width, q
@item padded_qrcode_width, Q
Specify an expression for the width of the rendered QR code, with and without
padding. The @var{qrcode_width} expression can reference the value set by the
@var{padded_qrcode_width} expression, and vice versa.
By default @var{padded_qrcode_width} is set to @var{qrcode_width}, meaning that
there is no padding.
These expressions are evaluated for each new frame.
See the @ref{qrencode_expressions,,qrencode Expressions} section for details.
@item x
@item y
Specify an expression for positioning the padded QR code top-left corner. The
@var{x} expression can reference the value set by the @var{y} expression, and
vice.
By default @var{x} and @var{y} are set set to @var{0}, meaning that the QR code
is placed in the top left corner of the input.
These expressions are evaluated for each new frame.
See the @ref{qrencode_expressions,,qrencode Expressions} section for details.
@item case_sensitive, cs
Instruct libqrencode to use case sensitive encoding. This is enabled by
default. This can be disabled to reduce the QR encoding size.
@item level, l
Specify the QR encoding error correction level. With an higher correction level,
the encoding size will increase but the code will be more robust to corruption.
Lower level is @var{L}.
It accepts the following values:
@table @samp
@item L
@item M
@item Q
@item H
@end table
@item expansion
Select how the input text is expanded. Can be either @code{none}, or
@code{normal} (default). See the @ref{qrencode_text_expansion,,qrencode Text expansion}
section below for details.
@item text
@item textfile
Define the text to be rendered. In case neither is specified, no QR is encoded
(just an empty colored frame).
In case expansion is enabled, the text is treated as a text template, using the
qrencode expansion mechanism. See the @ref{qrencode_text_expansion,,qrencode
Text expansion} section below for details.
@item background_color, bc
@item foreground_color, fc
Set the QR code and background color. The default value of
@var{foreground_color} is "black", the default value of @var{background_color}
is "white".
For the syntax of the color options, check the @ref{color syntax,,"Color"
section in the ffmpeg-utils manual,ffmpeg-utils}.
@end table
@anchor{qrencode_expressions}
@subsection qrencode Expressions
The expressions set by the options contain the following constants and functions.
@table @option
@item dar
input display aspect ratio, it is the same as (@var{w} / @var{h}) * @var{sar}
@item duration
the current frame's duration, in seconds
@item hsub
@item vsub
horizontal and vertical chroma subsample values. For example for the
pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1.
@item main_h, H
the input height
@item main_w, W
the input width
@item n
the number of input frame, starting from 0
@item pict_type
a number representing the picture type
@item qr_w, w
the width of the encoded QR code
@item rendered_qr_w, q
@item rendered_padded_qr_w, Q
the width of the rendered QR code, without and without padding.
These parameters allow the @var{q} and @var{Q} expressions to refer to each
other, so you can for example specify @code{q=3/4*Q}.
@item rand(min, max)
return a random number included between @var{min} and @var{max}
@item sar
the input sample aspect ratio
@item t
timestamp expressed in seconds, NAN if the input timestamp is unknown
@item x
@item y
the x and y offset coordinates where the text is drawn.
These parameters allow the @var{x} and @var{y} expressions to refer to each
other, so you can for example specify @code{y=x/dar}.
@end table
@anchor{qrencode_text_expansion}
@subsection qrencode Text expansion
If @option{expansion} is set to @code{none}, the text is printed verbatim.
If @option{expansion} is set to @code{normal} (which is the default),
the following expansion mechanism is used.
The backslash character @samp{\}, followed by any character, always expands to
the second character.
Sequences of the form @code{%@{...@}} are expanded. The text between the
braces is a function name, possibly followed by arguments separated by ':'.
If the arguments contain special characters or delimiters (':' or '@}'),
they should be escaped.
Note that they probably must also be escaped as the value for the @option{text}
option in the filter argument string and as the filter argument in the
filtergraph description, and possibly also for the shell, that makes up to four
levels of escaping; using a text file with the @option{textfile} option avoids
these problems.
The following functions are available:
@table @command
@item n, frame_num
return the frame number
@item pts
Return the presentation timestamp of the current frame.
It can take up to two arguments.
The first argument is the format of the timestamp; it defaults to @code{flt} for
seconds as a decimal number with microsecond accuracy; @code{hms} stands for a
formatted @var{[-]HH:MM:SS.mmm} timestamp with millisecond accuracy.
@code{gmtime} stands for the timestamp of the frame formatted as UTC time;
@code{localtime} stands for the timestamp of the frame formatted as local time
zone time. If the format is set to @code{hms24hh}, the time is formatted in 24h
format (00-23).
The second argument is an offset added to the timestamp.
If the format is set to @code{localtime} or @code{gmtime}, a third argument may
be supplied: a @code{strftime} C function format string. By default,
@var{YYYY-MM-DD HH:MM:SS} format will be used.
@item expr, e
Evaluate the expression's value and output as a double.
It must take one argument specifying the expression to be evaluated, accepting
the constants and functions defined in @ref{qrencode_expressions}.
@item expr_formatted, ef
Evaluate the expression's value and output as a formatted string.
The first argument is the expression to be evaluated, just as for the @var{expr} function.
The second argument specifies the output format. Allowed values are @samp{x},
@samp{X}, @samp{d} and @samp{u}. They are treated exactly as in the
@code{printf} function.
The third parameter is optional and sets the number of positions taken by the output.
It can be used to add padding with zeros from the left.
@item gmtime
The time at which the filter is running, expressed in UTC.
It can accept an argument: a @code{strftime} C function format string.
The format string is extended to support the variable @var{%[1-6]N}
which prints fractions of the second with optionally specified number of digits.
@item localtime
The time at which the filter is running, expressed in the local time zone.
It can accept an argument: a @code{strftime} C function format string.
The format string is extended to support the variable @var{%[1-6]N}
which prints fractions of the second with optionally specified number of digits.
@item metadata
Frame metadata. Takes one or two arguments.
The first argument is mandatory and specifies the metadata key.
The second argument is optional and specifies a default value, used when the
metadata key is not found or empty.
Available metadata can be identified by inspecting entries starting with TAG
included within each frame section printed by running @code{ffprobe
-show_frames}.
String metadata generated in filters leading to the qrencode filter are also
available.
@item rand(min, max)
return a random number included between @var{min} and @var{max}
@end table
@subsection Examples
@itemize
@item
Generate a QR code encoding the specified text with the default size, overalaid
in the top left corner of the input video, with the default size:
@example
qrencode=text=www.ffmpeg.org
@end example
@item
Same as below, but select blue on pink colors:
@example
qrencode=text=www.ffmpeg.org:bc=pink@@0.5:fc=blue
@end example
@item
Place the QR code in the bottom right corner of the input video:
@example
qrencode=text=www.ffmpeg.org:x=W-Q:y=H-Q
@end example
@item
Generate a QR code with width of 200 pixels and padding, making the padded width
4/3 of the QR code width:
@example
qrencode=text=www.ffmpeg.org:q=200:Q=4/3*q
@end example
@item
Generate a QR code with padded width of 200 pixels and padding, making the QR
code width 3/4 of the padded width:
@example
qrencode=text=www.ffmpeg.org:Q=200:q=3/4*Q
@end example
@item
Make the QR code a fraction of the input video width:
@example
qrencode=text=www.ffmpeg.org:q=W/5
@end example
@item
Generate a QR code encoding the frame number:
@example
qrencode=text=%@{n@}
@end example
@item
Generate a QR code encoding the GMT timestamp:
@example
qrencode=text=%@{gmtime@}
@end example
@item
Generate a QR code encoding the timestamp expressed as a float:
@example
qrencode=text=%@{pts@}
@end example
@end itemize
@section random
Flush video frames from internal cache of frames into a random order.
@ -28749,6 +29045,137 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
@end example
@end itemize
@section qrencodesrc
Generate a QR code using the libqrencode library (see
@url{https://fukuchi.org/works/qrencode/}).
To enable the compilation of this source, you need to configure FFmpeg with with
@code{--enable-libqrencode}.
The QR code is generated from the provided text or text pattern. The
corresponding QR code is scaled and put in the video output according to the
specified output size options.
In case no text is specified, the QR code is not generated, but an empty colored
output is returned instead.
This source accepts the following options:
@table @option
@item qrcode_width, q
@item padded_qrcode_width, Q
Specify an expression for the width of the rendered QR code, with and without
padding. The @var{qrcode_width} expression can reference the value set by the
@var{padded_qrcode_width} expression, and vice versa.
By default @var{padded_qrcode_width} is set to @var{qrcode_width}, meaning that
there is no padding.
These expressions are evaluated only once, when initializing the source.
See the @ref{qrencode_expressions,,qrencode Expressions} section for details.
Note that some of the constants are missing for the source (for example the
@var{x} or @var{t} or ¸@var{n}), since they only makes sense when evaluating the
expression for each frame rather than at initialization time.
@item rate, r
Specify the frame rate of the sourced video, as the number of frames
generated per second. It has to be a string in the format
@var{frame_rate_num}/@var{frame_rate_den}, an integer number, a floating point
number or a valid video frame rate abbreviation. The default value is
"25".
@item case_sensitive, cs
Instruct libqrencode to use case sensitive encoding. This is enabled by
default. This can be disabled to reduce the QR encoding size.
@item level, l
Specify the QR encoding error correction level. With an higher correction level,
the encoding size will increase but the code will be more robust to corruption.
Lower level is @var{L}.
It accepts the following values:
@table @samp
@item L
@item M
@item Q
@item H
@end table
@item expansion
Select how the input text is expanded. Can be either @code{none}, or
@code{normal} (default). See the @ref{qrencode_text_expansion,,qrencode Text expansion}
section for details.
@item text
@item textfile
Define the text to be rendered. In case neither is specified, no QR is encoded
(just an empty colored frame).
In case expansion is enabled, the text is treated as a text template, using the
qrencode expansion mechanism. See the @ref{qrencode_text_expansion,,qrencode
Text expansion} section for details.
@item background_color, bc
@item foreground_color, fc
Set the QR code and background color. The default value of
@var{foreground_color} is "black", the default value of @var{background_color}
is "white".
For the syntax of the color options, check the @ref{color syntax,,"Color"
section in the ffmpeg-utils manual,ffmpeg-utils}.
@end table
@subsection Examples
@itemize
@item
Generate a QR code encoding the specified text with the default size:
@example
qrencodesrc=text=www.ffmpeg.org
@end example
@item
Same as below, but select blue on pink colors:
@example
qrencodesrc=text=www.ffmpeg.org:bc=pink:fc=blue
@end example
@item
Generate a QR code with width of 200 pixels and padding, making the padded width
4/3 of the QR code width:
@example
qrencodesrc=text=www.ffmpeg.org:q=200:Q=4/3*q
@end example
@item
Generate a QR code with padded width of 200 pixels and padding, making the QR
code width 3/4 of the padded width:
@example
qrencodesrc=text=www.ffmpeg.org:Q=200:q=3/4*Q
@end example
@item
Generate a QR code encoding the frame number:
@example
qrencodesrc=text=%@{n@}
@end example
@item
Generate a QR code encoding the GMT timestamp:
@example
qrencodesrc=text=%@{gmtime@}
@end example
@item
Generate a QR code encoding the timestamp expressed as a float:
@example
qrencodesrc=text=%@{pts@}
@end example
@end itemize
@anchor{allrgb}
@anchor{allyuv}
@anchor{color}

View File

@ -599,6 +599,8 @@ OBJS-$(CONFIG_NULLSRC_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_OPENCLSRC_FILTER) += vf_program_opencl.o opencl.o
OBJS-$(CONFIG_PAL75BARS_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_PAL100BARS_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_QRENCODE_FILTER) += qrencode.o textutils.o
OBJS-$(CONFIG_QRENCODESRC_FILTER) += qrencode.o textutils.o
OBJS-$(CONFIG_RGBTESTSRC_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_SIERPINSKI_FILTER) += vsrc_sierpinski.o
OBJS-$(CONFIG_SMPTEBARS_FILTER) += vsrc_testsrc.o

View File

@ -411,6 +411,7 @@ extern const AVFilter ff_vf_pseudocolor;
extern const AVFilter ff_vf_psnr;
extern const AVFilter ff_vf_pullup;
extern const AVFilter ff_vf_qp;
extern const AVFilter ff_vf_qrencode;
extern const AVFilter ff_vf_random;
extern const AVFilter ff_vf_readeia608;
extern const AVFilter ff_vf_readvitc;
@ -561,6 +562,7 @@ extern const AVFilter ff_vsrc_mandelbrot;
extern const AVFilter ff_vsrc_mptestsrc;
extern const AVFilter ff_vsrc_nullsrc;
extern const AVFilter ff_vsrc_openclsrc;
extern const AVFilter ff_vsrc_qrencodesrc;
extern const AVFilter ff_vsrc_pal75bars;
extern const AVFilter ff_vsrc_pal100bars;
extern const AVFilter ff_vsrc_rgbtestsrc;

820
libavfilter/qrencode.c Normal file
View File

@ -0,0 +1,820 @@
/*
* 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/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, "level"}, \
{ "l", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"}, \
{ "L", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_L }, 0, 0, FLAGS, "level" }, \
{ "M", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_M }, 0, 0, FLAGS, "level" }, \
{ "Q", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_Q }, 0, 0, FLAGS, "level" }, \
{ "H", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_H }, 0, 0, FLAGS, "level" }, \
\
{"expansion", "set the expansion mode", OFFSET(expansion), AV_OPT_TYPE_INT, {.i64=EXPANSION_NORMAL}, 0, 2, FLAGS, "expansion"}, \
{"none", "set no expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NONE}, 0, 0, FLAGS, "expansion"}, \
{"normal", "set normal expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NORMAL}, 0, 0, FLAGS, "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_init(&qr->draw0, outlink->format, 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_init(&qr->draw, inlink->format, 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_init(&qr->draw, inlink->format, 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