From patchwork Thu Nov 30 00:49:14 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefano Sabatini X-Patchwork-Id: 44847 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:a301:b0:181:818d:5e7f with SMTP id x1csp31300pzk; Wed, 29 Nov 2023 16:50:18 -0800 (PST) X-Google-Smtp-Source: AGHT+IGp5NHV9qg6LiQwUdAktix0t/0Hz88XfjdiB7S8Y6lfXPh5Bb4PQk+Y9RKgTOJ3/gLJOGtp X-Received: by 2002:a2e:940e:0:b0:2c5:1f70:a266 with SMTP id i14-20020a2e940e000000b002c51f70a266mr11278369ljh.50.1701305418522; Wed, 29 Nov 2023 16:50:18 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1701305418; cv=none; d=google.com; s=arc-20160816; b=Sf5ENBjAcg52hKZCfAPr8Ww3TDhDLVn749dNs2aSL3mBkDz7cNtGMn5B63ITMFViNZ xroksv7y+8zYja7hFuBxJ3JVggub32c+ZFkwjpRBobMJpXuk6XpNExwob9z84pJ+gQid vEfG96NDOJDnvYSgB0jFzXx/aTQkBomjRSIxpaxi13WOJyFzqbXqVWufLVTmsi2kIO1U TIHB8LOvGUr3wvzgKkTLPNldAHL71dj2+D9lCJLpHsO3n8hO4eHUe2oLzEmHJuaKm4Oc pL7xjrPLLAGYe43MA5uZdsN8h2Vy+BDd/TSC4XBcWcnjk9/uEcuAIClax6KG9OYOViQr QxeA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=Uwp6we+BQ99mXUXL1wn263jPo5Ky5VhMmDIkIp1LeLw=; fh=QdWxt2OToL83TTnLQn0lGhLakV7i1QyAJdC8te7qN0E=; b=yq1ciuhY1fa7gJw5NbAaeiG6uDGK+XCxOK5Q69guMMGQRk4d7bs+GD4cEecCoifYJV oan5O7JFNiHeZY9XnjvmJi2Q/jmShTseNYgjGric5x7pnVXZyiTQh2voab1nEdWsMxjW tru1R9rHuAE5TN3inZF+lLROV939UEnWYr3oP1riHjo2FC3elL3nO5l/6vr5IHCCUG/Z LYJI5FxKA04HyXIkIf01/8n5u6VpM0duAsXrzhhswcnMl5tbPgMr20CQ4+H1G9ig+w24 VysTzh7PZjsryQlk8AOWKzcKdE/YG3XGorKO5dQ3zEFNJ4g41DdRO8DZn1xxJR0DWkkM Ukxg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=cfLdrL4W; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id d24-20020a170906371800b00a176ea1b877si60526ejc.80.2023.11.29.16.50.02; Wed, 29 Nov 2023 16:50:18 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=cfLdrL4W; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 82B1268D062; Thu, 30 Nov 2023 02:49:42 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ej1-f41.google.com (mail-ej1-f41.google.com [209.85.218.41]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 0F14F68D046 for ; Thu, 30 Nov 2023 02:49:34 +0200 (EET) Received: by mail-ej1-f41.google.com with SMTP id a640c23a62f3a-a04196fc957so46915966b.2 for ; Wed, 29 Nov 2023 16:49:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1701305372; x=1701910172; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=AFc5An4aprPUxjgindXKdtdneNgqtx+271M6MSdLdPw=; b=cfLdrL4WTNSqqxKW0wCzZEVPnnXIlUngVqbY+OP6tFaNn7cKWsFHcKDkHdyd85ViC9 ghv2hBwHBXgZHgxt6ZHRnN35au5+WkF1mDuZBcOAo6qgQztq1O//gCEFe4vRRM7lF1uz z8Anf7qnLXEP3EHBkb1/3E0kVmCUoh1XkHkiELj1sZQL+ECk6nuM6mZ4RzZV+fahJj3g +zyNbsyozm2IhuO5nnqQcMojRtPcSElYEj4ah0kecmG5n0DtEsbs5Uvujyf6kcOVPsx9 JaknXXg9WC38pIhs5gOTDu5qkD77QfJ5m6HqM2PE7UxUYDSWEDtSiygQc/Sr3axlUiTd 5WNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1701305372; x=1701910172; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=AFc5An4aprPUxjgindXKdtdneNgqtx+271M6MSdLdPw=; b=eI9vNAfNAmAuEVwPWABZQmT9by8G7s5TRvq1zg6X3gDLY5+IHb0c6gXbJnMvNNQmQ0 DzKDgQaoVaDpFCc2Y2L7UqD2t/YaftmCKWjm8db9MdNsKVAqmjEqkc5Az91BSk8gMuqN 1N7deFddV0EjgSQ6OklA9cMYZFZLbgcsVIx1Tm99fHXagC9+uxFGYLOb1trjAalkt3g7 ogcdnE4Jeq1aKoEPSrzAqugtrxKZAIXdJmrxRkmVv37VGPiAQBimczS9/HGQLM0KfmM/ 0SThQw4Diu6jPaYjd4iueahkxbCo+xKkXZkwcl+7r76aOt5nypR1TU06PBn0d/GufvGF vZLg== X-Gm-Message-State: AOJu0YwbXM/1PjnQmHFCQJZTYs8KsLZo1/4i9UQsFdWYcXYF3vKzrlEs LPvF6R83YQQpbDBIGkrI7OdgwG+ALQWyhhjm X-Received: by 2002:a17:906:212:b0:a0a:725:aedf with SMTP id 18-20020a170906021200b00a0a0725aedfmr12363039ejd.15.1701305371743; Wed, 29 Nov 2023 16:49:31 -0800 (PST) Received: from mariano (dynamic-adsl-84-220-189-10.clienti.tiscali.it. [84.220.189.10]) by smtp.gmail.com with ESMTPSA id o21-20020a17090611d500b00a0a2553ec99sm60788eja.65.2023.11.29.16.49.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 29 Nov 2023 16:49:30 -0800 (PST) Received: by mariano (Postfix, from userid 1000) id 10685BFCDE; Thu, 30 Nov 2023 01:49:29 +0100 (CET) From: Stefano Sabatini To: FFmpeg development discussions and patches Date: Thu, 30 Nov 2023 01:49:14 +0100 Message-Id: <20231130004914.329717-3-stefasab@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231130004914.329717-1-stefasab@gmail.com> References: <20231130004914.329717-1-stefasab@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 2/2] lavfi: add qrencodesrc source X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Stefano Sabatini Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: ioje1QdwhHEF --- 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 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 + +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), +};