diff mbox series

[FFmpeg-devel,2/2] lavfi: add qrencodesrc source

Message ID 20231130004914.329717-3-stefasab@gmail.com
State New
Headers show
Series [FFmpeg-devel,1/2] lavfi: introduce textutils | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Stefano Sabatini Nov. 30, 2023, 12:49 a.m. UTC
---
 configure                   |   4 +
 libavfilter/Makefile        |   1 +
 libavfilter/allfilters.c    |   1 +
 libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
 4 files changed, 441 insertions(+)
 create mode 100644 libavfilter/vsrc_qrencode.c

Comments

Stefano Sabatini Dec. 9, 2023, 6:13 p.m. UTC | #1
On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> ---
>  configure                   |   4 +
>  libavfilter/Makefile        |   1 +
>  libavfilter/allfilters.c    |   1 +
>  libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 441 insertions(+)
>  create mode 100644 libavfilter/vsrc_qrencode.c

Rev2 with padding and doc.
Kyle Swanson Dec. 11, 2023, 8:59 p.m. UTC | #2
Hi,

On Sat, Dec 9, 2023 at 10:14 AM Stefano Sabatini <stefasab@gmail.com> wrote:
>
> On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> > ---
> >  configure                   |   4 +
> >  libavfilter/Makefile        |   1 +
> >  libavfilter/allfilters.c    |   1 +
> >  libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
> >  4 files changed, 441 insertions(+)
> >  create mode 100644 libavfilter/vsrc_qrencode.c
>
> Rev2 with padding and doc.

libavfilter/vsrc_qrencode.c:42:10: fatal error: 'textutils.h' file not found
#include "textutils.h"
                ^~~~~~~~~~~~~
1 error generated.

Thanks,
Kyle
Stefano Sabatini Dec. 11, 2023, 11:34 p.m. UTC | #3
On date Monday 2023-12-11 12:59:39 -0800, Kyle Swanson wrote:
> Hi,
> 
> On Sat, Dec 9, 2023 at 10:14 AM Stefano Sabatini <stefasab@gmail.com> wrote:
> >
> > On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> > > ---
> > >  configure                   |   4 +
> > >  libavfilter/Makefile        |   1 +
> > >  libavfilter/allfilters.c    |   1 +
> > >  libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 441 insertions(+)
> > >  create mode 100644 libavfilter/vsrc_qrencode.c
> >
> > Rev2 with padding and doc.
> 
> libavfilter/vsrc_qrencode.c:42:10: fatal error: 'textutils.h' file not found
> #include "textutils.h"
>                 ^~~~~~~~~~~~~
> 1 error generated.

This depends on:
[PATCH 1/2] lavfi: introduce textutils
Stefano Sabatini Dec. 17, 2023, 6 p.m. UTC | #4
On date Saturday 2023-12-09 19:13:46 +0100, Stefano Sabatini wrote:
> On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> > ---
> >  configure                   |   4 +
> >  libavfilter/Makefile        |   1 +
> >  libavfilter/allfilters.c    |   1 +
> >  libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
> >  4 files changed, 441 insertions(+)
> >  create mode 100644 libavfilter/vsrc_qrencode.c
> 
> Rev2 with padding and doc.

Rev3 including a filter and fancy expressions.
Stefano Sabatini Dec. 24, 2023, 5:43 p.m. UTC | #5
On date Sunday 2023-12-17 19:00:01 +0100, Stefano Sabatini wrote:
> On date Saturday 2023-12-09 19:13:46 +0100, Stefano Sabatini wrote:
> > On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> > > ---
> > >  configure                   |   4 +
> > >  libavfilter/Makefile        |   1 +
> > >  libavfilter/allfilters.c    |   1 +
> > >  libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 441 insertions(+)
> > >  create mode 100644 libavfilter/vsrc_qrencode.c
> > 
> > Rev2 with padding and doc.
> 
> Rev3 including a filter and fancy expressions.

> From 2e0e177f4e6e7a14d69f1e4d927b115b92b334d2 Mon Sep 17 00:00:00 2001
> From: Stefano Sabatini <stefasab@gmail.com>
> Date: Tue, 28 Nov 2023 23:58:15 +0100
> Subject: [PATCH] lavfi: add qrencode source and filter
> 
> ---
>  Changelog                |   1 +
>  configure                |   7 +
>  doc/filters.texi         | 410 ++++++++++++++++++++
>  libavfilter/Makefile     |   2 +
>  libavfilter/allfilters.c |   2 +
>  libavfilter/qrencode.c   | 803 +++++++++++++++++++++++++++++++++++++++
>  6 files changed, 1225 insertions(+)
>  create mode 100644 libavfilter/qrencode.c

Will apply in a week or so if I see no comments.
Stefano Sabatini Jan. 2, 2024, 9:09 p.m. UTC | #6
On date Sunday 2023-12-24 18:43:04 +0100, Stefano Sabatini wrote:
> On date Sunday 2023-12-17 19:00:01 +0100, Stefano Sabatini wrote:
> > On date Saturday 2023-12-09 19:13:46 +0100, Stefano Sabatini wrote:
> > > On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> > > > ---
> > > >  configure                   |   4 +
> > > >  libavfilter/Makefile        |   1 +
> > > >  libavfilter/allfilters.c    |   1 +
> > > >  libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
> > > >  4 files changed, 441 insertions(+)
> > > >  create mode 100644 libavfilter/vsrc_qrencode.c
> > > 
> > > Rev2 with padding and doc.
> > 
> > Rev3 including a filter and fancy expressions.
> 
> > From 2e0e177f4e6e7a14d69f1e4d927b115b92b334d2 Mon Sep 17 00:00:00 2001
> > From: Stefano Sabatini <stefasab@gmail.com>
> > Date: Tue, 28 Nov 2023 23:58:15 +0100
> > Subject: [PATCH] lavfi: add qrencode source and filter
> > 
> > ---
> >  Changelog                |   1 +
> >  configure                |   7 +
> >  doc/filters.texi         | 410 ++++++++++++++++++++
> >  libavfilter/Makefile     |   2 +
> >  libavfilter/allfilters.c |   2 +
> >  libavfilter/qrencode.c   | 803 +++++++++++++++++++++++++++++++++++++++
> >  6 files changed, 1225 insertions(+)
> >  create mode 100644 libavfilter/qrencode.c
> 
> Will apply in a week or so if I see no comments.

Applied.
diff mbox series

Patch

diff --git a/configure b/configure
index d6e4a1e7df..f197f499dd 100755
--- a/configure
+++ b/configure
@@ -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]
@@ -1877,6 +1878,7 @@  EXTERNAL_LIBRARY_LIST="
     libopus
     libplacebo
     libpulse
+    libqrencode
     librabbitmq
     librav1e
     librist
@@ -3763,6 +3765,7 @@  nnedi_filter_deps="gpl"
 ocr_filter_deps="libtesseract"
 ocv_filter_deps="libopencv"
 openclsrc_filter_deps="opencl"
+qrencodesrc_filter_deps="libqrencode"
 overlay_opencl_filter_deps="opencl"
 overlay_qsv_filter_deps="libmfx"
 overlay_qsv_filter_select="qsvvpp"
@@ -6803,6 +6806,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
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e49be354bb..3eee1ba085 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -597,6 +597,7 @@  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_QRENCODESRC_FILTER)            += vsrc_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
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index aa49703c6e..3d8c454ab0 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -559,6 +559,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;
diff --git a/libavfilter/vsrc_qrencode.c b/libavfilter/vsrc_qrencode.c
new file mode 100644
index 0000000000..76ebbda999
--- /dev/null
+++ b/libavfilter/vsrc_qrencode.c
@@ -0,0 +1,435 @@ 
+/*
+ * 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.
+ *
+ * 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 "libavutil/internal.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/lfg.h"
+#include "libavutil/random_seed.h"
+
+#include "avfilter.h"
+#include "internal.h"
+#include "formats.h"
+#include "textutils.h"
+#include "video.h"
+#include "libswscale/swscale.h"
+
+#include <qrencode.h>
+
+enum var_name {
+    VAR_N,
+    VAR_QW,
+    VAR_T,
+    VAR_W,
+    VAR_VARS_NB
+};
+
+static const char *const var_names[] = {
+    "n",             ///< number of frame
+    "qw",          ///< width of the rendered QR code
+    "t",             ///< timestamp expressed in seconds
+    "w",             ///< width of the frame
+    NULL
+};
+
+enum Expansion {
+    EXPANSION_NONE,
+    EXPANSION_NORMAL
+};
+
+typedef struct QREncodeContext {
+    const AVClass *class;
+    int width;
+    unsigned char *text;
+    AVBPrint expanded_text;         ///< used to contain the expanded text
+    char *textfile;
+    uint64_t pts;
+
+    int level;
+    char case_sensitive;
+
+    uint8_t foreground_color[4];
+    uint8_t background_color[4];
+
+    uint8_t *qrcode_data[4];
+    int qrcode_linesize[4];
+    uint8_t qrcode_width;
+    AVRational frame_rate;
+
+    int expansion;                  ///< expansion mode to use for the text
+    TextExpander text_expander;     ///< text expander in case exp_mode == NORMAL
+    double var_values[VAR_VARS_NB];
+    AVLFG  prng;                    ///< random generator
+} QREncodeContext;
+
+#define OFFSET(x) offsetof(QREncodeContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption qrencode_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 },
+    { "width",    "set video width", OFFSET(width),     AV_OPT_TYPE_INT, {.i64 = 64}, 16, INT_MAX, FLAGS },
+    { "w",        "set video width", OFFSET(width),     AV_OPT_TYPE_INT, {.i64 = 64}, 16, 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",     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},
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(qrencode);
+
+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 pts = 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, pts, 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)qr->var_values[VAR_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_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->prng);
+}
+
+static int func_eval_expr_int_format(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_eval_expr_int_format(ctx, bp, argv[0],
+                                         fun2_names, fun2,
+                                         var_names, qr->var_values,
+                                         &qr->prng,
+                                         argv[1][0], positions);
+}
+
+static TextExpanderFunction text_expander_functions[] = {
+    { "e",               1, 1, func_eval_expr },
+    { "eif",             2, 3, func_eval_expr_int_format },
+    { "expr",            1, 1, func_eval_expr },
+    { "expr_int_format", 2, 3, func_eval_expr_int_format },
+    { "frame_num",       0, 0, func_frame_num },
+    { "gmtime",          0, 1, func_strftime },
+    { "localtime",       0, 1, func_strftime },
+    { "n",               0, 0, func_frame_num },
+    { "pts",             0, 3, func_pts }
+};
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    QREncodeContext *qr = ctx->priv;
+    int err;
+
+    qr->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 ((err = ff_load_textfile(ctx, (const char *)qr->textfile, &(qr->text), NULL)) < 0)
+            return err;
+    }
+
+    av_log(ctx, AV_LOG_VERBOSE,
+           "w:%"PRId64" case_sensitive:%d level:%d\n",
+           qr->width, qr->case_sensitive, qr->level);
+
+    av_lfg_init(&qr->prng, av_get_random_seed());
+
+    qr->text_expander = (TextExpander) {
+        .ctx = ctx,
+        .functions = text_expander_functions,
+        .functions_nb = FF_ARRAY_ELEMS(text_expander_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_bprint_finalize(&qr->expanded_text, NULL);
+    av_freep(&qr->qrcode_data[0]);
+}
+
+static int config_props(AVFilterLink *outlink)
+{
+    QREncodeContext *qr = outlink->src->priv;
+
+    qr->var_values[VAR_W] = outlink->w = qr->width;
+    outlink->h = qr->width;
+    outlink->time_base = av_inv_q(qr->frame_rate);
+    outlink->frame_rate = qr->frame_rate;
+
+    return 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 request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = (AVFilterContext *)outlink->src;
+    QREncodeContext *qr = ctx->priv;
+    AVFrame *picref = ff_get_video_buffer(outlink, qr->width, qr->width);
+    struct SwsContext *sws = NULL;
+    QRcode *qrcode = NULL;
+    int i, j;
+    int ret;
+    uint8_t *srcp;
+    uint8_t *dstp0, *dstp;
+
+    if (!picref)
+        return AVERROR(ENOMEM);
+    picref->sample_aspect_ratio = (AVRational) {1, 1};
+    qr->var_values[VAR_N] = picref->pts = qr->pts++;
+    qr->var_values[VAR_T] = qr->pts * av_q2d(outlink->time_base);
+
+    av_inv_q(qr->frame_rate);
+
+    switch (qr->expansion) {
+    case EXPANSION_NONE:
+        av_bprintf(&qr->expanded_text, "%s", qr->text);
+        break;
+    case EXPANSION_NORMAL:
+        if ((ret = ff_expand_text(&qr->text_expander, qr->text, &qr->expanded_text)) < 0)
+            return ret;
+        break;
+    }
+
+    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 error;
+    }
+
+    qr->var_values[VAR_QW] = qrcode->width;
+    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
+
+    if (qrcode->width != qr->qrcode_width) {
+        qr->qrcode_width = qrcode->width;
+        ret = av_image_alloc(qr->qrcode_data, qr->qrcode_linesize,
+                             qrcode->width, qrcode->width,
+                             AV_PIX_FMT_RGBA, 16);
+        if (ret < 0) {
+            av_log(ctx, AV_LOG_ERROR, "Failed to allocate image for QR code with width %d\n", qrcode->width);
+            goto error;
+        }
+    }
+
+    dstp0 = qr->qrcode_data[0];
+    srcp = qrcode->data;
+
+    for (i = 0; i < qrcode->width; i++) {
+        dstp = dstp0;
+
+        for (j = 0; j < qrcode->width; j++) {
+            if ((*srcp++)&1) {
+                *dstp++ = qr->foreground_color[0];
+                *dstp++ = qr->foreground_color[1];
+                *dstp++ = qr->foreground_color[2];
+                *dstp++ = qr->foreground_color[3];
+            } else {
+                *dstp++ = qr->background_color[0];
+                *dstp++ = qr->background_color[1];
+                *dstp++ = qr->background_color[2];
+                *dstp++ = qr->background_color[3];
+            }
+        }
+        dstp0 += qr->qrcode_linesize[0];
+    }
+
+    sws = sws_alloc_context();
+    if (!sws) {
+        ret = AVERROR(ENOMEM);
+        goto error;
+    }
+
+    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_RGBA, 0);
+    av_opt_set_int(sws, "dstw", qr->width, 0);
+    av_opt_set_int(sws, "dsth", qr->width, 0);
+    av_opt_set_int(sws, "dst_format", outlink->format, 0);
+    av_opt_set_int(sws, "sws_flags", SWS_POINT, 0);
+
+    if ((ret = sws_init_context(sws, NULL, NULL)) < 0)
+        goto error;
+
+    sws_scale(sws,
+              (const uint8_t *const *)&qr->qrcode_data, qr->qrcode_linesize,
+              0, qrcode->width,
+              picref->data, picref->linesize);
+    ret = ff_filter_frame(outlink, picref);
+
+error:
+    sws_freeContext(sws);
+    QRcode_free(qrcode);
+
+    return ret;
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE };
+
+    return ff_set_common_formats_from_list(ctx, pix_fmts);
+}
+
+static const AVFilterPad qrencode_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .request_frame = request_frame,
+        .config_props  = config_props,
+    }
+};
+
+const AVFilter ff_vsrc_qrencodesrc = {
+    .name          = "qrencode",
+    .description   = NULL_IF_CONFIG_SMALL("Generate a QR code."),
+    .priv_size     = sizeof(QREncodeContext),
+    .priv_class    = &qrencode_class,
+    .init          = init,
+    .uninit        = uninit,
+    .inputs        = NULL,
+    FILTER_OUTPUTS(qrencode_outputs),
+    FILTER_QUERY_FUNC(query_formats),
+};