diff mbox series

[FFmpeg-devel] lavfi: add quirc filter

Message ID 20231226162033.782945-1-stefasab@gmail.com
State New
Headers show
Series [FFmpeg-devel] lavfi: add quirc filter | expand

Checks

Context Check Description
andriy/configure_x86 warning Failed to apply patch
yinshiyou/configure_loongarch64 warning Failed to apply patch

Commit Message

Stefano Sabatini Dec. 26, 2023, 4:20 p.m. UTC
---
 Changelog                |   1 +
 configure                |   4 +
 doc/filters.texi         |  35 ++++++++
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 225 insertions(+)
 create mode 100644 libavfilter/vf_quirc.c

Comments

Andreas Rheinhardt Dec. 26, 2023, 7:08 p.m. UTC | #1
Stefano Sabatini:
> ---
>  Changelog                |   1 +
>  configure                |   4 +
>  doc/filters.texi         |  35 ++++++++
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
>  6 files changed, 225 insertions(+)
>  create mode 100644 libavfilter/vf_quirc.c
> 
> diff --git a/Changelog b/Changelog
> index b483bb9c69..424bfc11af 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -14,6 +14,7 @@ version <next>:
>  - D3D12VA hardware accelerated H264, HEVC, VP9, AV1, MPEG-2 and VC1 decoding
>  - tiltandshift filter
>  - qrencode filter and qrencodesrc source
> +- quirc filter
>  
>  version 6.1:
>  - libaribcaption decoder
> diff --git a/configure b/configure
> index 0b2f4e9792..419ff76bc1 100755
> --- a/configure
> +++ b/configure
> @@ -257,6 +257,7 @@ External library support:
>    --enable-libplacebo      enable libplacebo library [no]
>    --enable-libpulse        enable Pulseaudio input via libpulse [no]
>    --enable-libqrencode     enable QR encode generation via libqrencode [no]
> +  --enable-libquirc        enable QR decoding via libquirc [no]
>    --enable-librabbitmq     enable RabbitMQ library [no]
>    --enable-librav1e        enable AV1 encoding via rav1e [no]
>    --enable-librist         enable RIST via librist [no]
> @@ -1883,6 +1884,7 @@ EXTERNAL_LIBRARY_LIST="
>      libplacebo
>      libpulse
>      libqrencode
> +    libquirc
>      librabbitmq
>      librav1e
>      librist
> @@ -3793,6 +3795,7 @@ ocv_filter_deps="libopencv"
>  openclsrc_filter_deps="opencl"
>  qrencode_filter_deps="libqrencode"
>  qrencodesrc_filter_deps="libqrencode"
> +quirc_filter_deps="libquirc"
>  overlay_opencl_filter_deps="opencl"
>  overlay_qsv_filter_deps="libmfx"
>  overlay_qsv_filter_select="qsvvpp"
> @@ -6845,6 +6848,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 libquirc          && require libquirc quirc.h quirc_decode -lquirc
>  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/doc/filters.texi b/doc/filters.texi
> index 04898ede31..5fbe381d74 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -20368,6 +20368,41 @@ qrencode=text=%@{pts@}
>  
>  @end itemize
>  
> +@section quirc
> +
> +Identify and decode a QR code using the libquirc library (see
> +@url{https://github.com/dlbeer/quirc/}), and print the identified QR codes
> +positions and payload as metadata.
> +
> +To enable the compilation of this filter, you need to configure FFmpeg with with
> +@code{--enable-libquirc}.
> +
> +For each found QR code in the input video, some metadata entries are added with
> +the prefix @var{lavfi.quirc.N}, where @var{N} is the index, starting from 0,
> +associated to the QR code.
> +
> +A description of each metadata value follows:
> +
> +@table @option
> +@item lavfi.quirc.count
> +the number of found QR codes, it is not set in case none was found
> +
> +@item lavfi.quirc.N.x
> +@item lavfi.quirc.N.y
> +the top-left position of the @var{N} QR code
> +
> +@item lavfi.quirc.N.X
> +@item lavfi.quirc.N.Y
> +the bottom-right position of the @var{N} QR code
> +
> +@item lavfi.quirc.N.w
> +@item lavfi.quirc.N.h
> +the size of the @var{N} QR code
> +
> +@item lavfi.quirc.N.payload
> +the payload of the QR code
> +@end table
> +
>  @section random
>  
>  Flush video frames from internal cache of frames into a random order.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 31371ceb1a..f65fb9a5a7 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -437,6 +437,7 @@ OBJS-$(CONFIG_PSEUDOCOLOR_FILTER)            += vf_pseudocolor.o
>  OBJS-$(CONFIG_PSNR_FILTER)                   += vf_psnr.o framesync.o
>  OBJS-$(CONFIG_PULLUP_FILTER)                 += vf_pullup.o
>  OBJS-$(CONFIG_QP_FILTER)                     += vf_qp.o
> +OBJS-$(CONFIG_QUIRC_FILTER)                  += vf_quirc.o
>  OBJS-$(CONFIG_RANDOM_FILTER)                 += vf_random.o
>  OBJS-$(CONFIG_READEIA608_FILTER)             += vf_readeia608.o
>  OBJS-$(CONFIG_READVITC_FILTER)               += vf_readvitc.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 20feb37967..b8570dbab2 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -412,6 +412,7 @@ 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_quirc;
>  extern const AVFilter ff_vf_random;
>  extern const AVFilter ff_vf_readeia608;
>  extern const AVFilter ff_vf_readvitc;
> diff --git a/libavfilter/vf_quirc.c b/libavfilter/vf_quirc.c
> new file mode 100644
> index 0000000000..6945a6aa31
> --- /dev/null
> +++ b/libavfilter/vf_quirc.c
> @@ -0,0 +1,183 @@
> +/*
> + * 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 decoder video filter
> + *
> + * Use libquirc library to decode the content of QR codes, and put the decoded
> + * content to metadata. See:
> + * https://github.com/dlbeer/quirc
> + */
> +
> +#include "config_components.h"

Unneeded.

> +//#include "libavutil/internal.h"
> +#include "libavutil/opt.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "video.h"
> +#include <quirc.h>
> +
> +typedef struct QuircContext {
> +    const AVClass *class;
> +
> +    struct quirc *quirc;
> +} QuircContext;
> +
> +static const AVOption quirc_options[] = {
> +    { NULL }
> +};

There is no need for empty options (and also no need for an AVClass for
a filter without options).

> +
> +static av_cold int init(AVFilterContext *ctx)
> +{
> +    QuircContext *quirc = ctx->priv;
> +
> +    quirc->quirc = quirc_new();
> +    if (!quirc->quirc) {
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    QuircContext *quirc = ctx->priv;
> +
> +    quirc_destroy(quirc->quirc);
> +}
> +
> +static int config_input(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    QuircContext *quirc = ctx->priv;
> +    int err;
> +
> +    err = quirc_resize(quirc->quirc, inlink->w, inlink->h);
> +    if (err == -1) {
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    return 0;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE};
> +    return ff_set_common_formats_from_list(ctx, pix_fmts);
> +}

FILTER_QUERY_FUNC is inappropriate for this.

> +
> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    QuircContext *quirc = ctx->priv;
> +    int codes_count;
> +    int i, w, h;
> +    uint8_t *image, *imagep, *srcp;
> +
> +    image = quirc_begin(quirc->quirc, &w, &h);
> +
> +    /* copy input image to quirc buffer */
> +    imagep = image;
> +    srcp = frame->data[0];
> +    for (i = 0; i < inlink->h; i++) {
> +        memcpy(imagep, srcp, w);
> +        imagep += w;
> +        srcp += frame->linesize[0];
> +    }
> +
> +    quirc_end(quirc->quirc);
> +
> +    codes_count = quirc_count(quirc->quirc);
> +    av_log(ctx, AV_LOG_VERBOSE,
> +           "Found count %d codes in image #%ld\n", codes_count, inlink->frame_count_out);
> +
> +    if (codes_count) {
> +        AVDictionary **metadata = &frame->metadata;
> +
> +        av_dict_set_int(metadata, "lavfi.quirc.count", codes_count, 0);
> +
> +        for (i = 0; i < codes_count; i++) {
> +            struct quirc_code code;
> +            struct quirc_data data;
> +            struct quirc_point top_left, bottom_right;
> +            quirc_decode_error_t err;
> +            int w, h;
> +            char metadata_key[64];
> +
> +            quirc_extract(quirc->quirc, i, &code);
> +
> +            err = quirc_decode(&code, &data);
> +            if (err) {
> +                av_log(ctx, AV_LOG_WARNING,
> +                       "Failed to decode image: %s\n", quirc_strerror(err));
> +                continue;
> +            }
> +
> +            top_left = code.corners[0];
> +            bottom_right = code.corners[2];
> +            w = bottom_right.x - top_left.x;
> +            h = bottom_right.y - top_left.y;
> +
> +#define SET_METADATA_I(key_, value_)                                    \
> +            snprintf(metadata_key, sizeof(metadata_key)-1, "lavfi.quirc.%d." #key_, i); \
> +            av_dict_set_int(metadata, metadata_key, value_, AV_DICT_MATCH_CASE)
> +
> +            SET_METADATA_I(x, top_left.x);
> +            SET_METADATA_I(y, top_left.y);
> +            SET_METADATA_I(X, bottom_right.x);
> +            SET_METADATA_I(Y, bottom_right.y);
> +            SET_METADATA_I(w, w);
> +            SET_METADATA_I(h, h);
> +            snprintf(metadata_key, sizeof(metadata_key)-1, "lavfi.quirc.%d.payload", i); \
> +            av_dict_set(metadata, metadata_key, data.payload, 0);
> +
> +            av_log(ctx, AV_LOG_INFO,
> +                   "Found QR code at position %d+%d:%dx%d with payload: %s\n",
> +                   top_left.x, top_left.y, w, h, data.payload);
> +        }
> +    }
> +
> +    return ff_filter_frame(outlink, frame);
> +}
> +
> +AVFILTER_DEFINE_CLASS(quirc);
> +
> +static const AVFilterPad inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .filter_frame = filter_frame,
> +        .config_props = config_input
> +    },
> +};
> +
> +const AVFilter ff_vf_quirc = {
> +    .name        = "quirc",
> +    .description = NULL_IF_CONFIG_SMALL("Decode and show QR codes content."),
> +    .priv_size   = sizeof(QuircContext),
> +    .priv_class  = &quirc_class,
> +    .init        = init,
> +    .uninit      = uninit,
> +    FILTER_INPUTS(inputs),
> +    FILTER_OUTPUTS(ff_video_default_filterpad),
> +    FILTER_QUERY_FUNC(query_formats),
> +    .flags       = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
> +                   AVFILTER_FLAG_METADATA_ONLY,
> +};
> +
James Almer Dec. 26, 2023, 7:21 p.m. UTC | #2
On 12/26/2023 1:20 PM, Stefano Sabatini wrote:
> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    QuircContext *quirc = ctx->priv;
> +    int codes_count;
> +    int i, w, h;
> +    uint8_t *image, *imagep, *srcp;
> +
> +    image = quirc_begin(quirc->quirc, &w, &h);

Can't this fail? Or does the allocation take place in the quirc_resize() 
call?

> +
> +    /* copy input image to quirc buffer */
> +    imagep = image;
> +    srcp = frame->data[0];
> +    for (i = 0; i < inlink->h; i++) {
> +        memcpy(imagep, srcp, w);

Can w differ from inlink->w?

> +        imagep += w;
> +        srcp += frame->linesize[0];
> +    }

Maybe instead do

av_image_copy_plane(image, w, frame->data[0], frame->linesize[0],
                     w, inlink->h);
Stefano Sabatini Dec. 27, 2023, 1:19 a.m. UTC | #3
On date Tuesday 2023-12-26 16:21:55 -0300, James Almer wrote:
> On 12/26/2023 1:20 PM, Stefano Sabatini wrote:
> > +static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
> > +{
> > +    AVFilterContext *ctx = inlink->dst;
> > +    AVFilterLink *outlink = ctx->outputs[0];
> > +    QuircContext *quirc = ctx->priv;
> > +    int codes_count;
> > +    int i, w, h;
> > +    uint8_t *image, *imagep, *srcp;
> > +
> > +    image = quirc_begin(quirc->quirc, &w, &h);
> 

> Can't this fail?

> Or does the allocation take place in the quirc_resize()
> call?

correct

> > +
> > +    /* copy input image to quirc buffer */
> > +    imagep = image;
> > +    srcp = frame->data[0];
> > +    for (i = 0; i < inlink->h; i++) {
> > +        memcpy(imagep, srcp, w);
> 

> Can w differ from inlink->w?

No, given that the resize is done in config_inputs.


> > +        imagep += w;
> > +        srcp += frame->linesize[0];
> > +    }
> 
> Maybe instead do
> 

> av_image_copy_plane(image, w, frame->data[0], frame->linesize[0],
>                     w, inlink->h);

good catch, thanks for the feedback
Stefano Sabatini Dec. 27, 2023, 1:20 a.m. UTC | #4
On date Tuesday 2023-12-26 20:08:10 +0100, Andreas Rheinhardt wrote:
> Stefano Sabatini:
[...]
> > +static int query_formats(AVFilterContext *ctx)
> > +{
> > +    static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE};
> > +    return ff_set_common_formats_from_list(ctx, pix_fmts);
> > +}
> 
> FILTER_QUERY_FUNC is inappropriate for this.

What should I use instead?
Stefano Sabatini Dec. 27, 2023, 1:25 a.m. UTC | #5
On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini wrote:
> ---
>  Changelog                |   1 +
>  configure                |   4 +
>  doc/filters.texi         |  35 ++++++++
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
>  6 files changed, 225 insertions(+)
>  create mode 100644 libavfilter/vf_quirc.c

V2 with a few fixes and all corners put in the metadata (e.g. in case
the QR code is rotated).
James Almer Dec. 27, 2023, 1:33 a.m. UTC | #6
On 12/26/2023 10:25 PM, Stefano Sabatini wrote:
> On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini wrote:
>> ---
>>   Changelog                |   1 +
>>   configure                |   4 +
>>   doc/filters.texi         |  35 ++++++++
>>   libavfilter/Makefile     |   1 +
>>   libavfilter/allfilters.c |   1 +
>>   libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
>>   6 files changed, 225 insertions(+)
>>   create mode 100644 libavfilter/vf_quirc.c
> 
> V2 with a few fixes and all corners put in the metadata (e.g. in case
> the QR code is rotated).

Looking at the library, the license is very permissive and the code 
hasn't been touched in many years. It is also pretty small, so why not 
just add it as a native filter instead of requiring an external 
dependency for what seems to be a relatively simple process?
Stefano Sabatini Dec. 27, 2023, 11:37 a.m. UTC | #7
On date Tuesday 2023-12-26 22:33:36 -0300, James Almer wrote:
> On 12/26/2023 10:25 PM, Stefano Sabatini wrote:
> > On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini wrote:
> > > ---
> > >   Changelog                |   1 +
> > >   configure                |   4 +
> > >   doc/filters.texi         |  35 ++++++++
> > >   libavfilter/Makefile     |   1 +
> > >   libavfilter/allfilters.c |   1 +
> > >   libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
> > >   6 files changed, 225 insertions(+)
> > >   create mode 100644 libavfilter/vf_quirc.c
> > 
> > V2 with a few fixes and all corners put in the metadata (e.g. in case
> > the QR code is rotated).
> 

> Looking at the library, the license is very permissive and the code hasn't
> been touched in many years. It is also pretty small, so why not just add it
> as a native filter instead of requiring an external dependency for what
> seems to be a relatively simple process?

I see pros and cons, in total that would be about 3K lines of pretty
clean code and data, and this would simplify integration for end-users
(since they would not need to build the library, which seems not
packaged by many distributions), and having the code would help to
solve similar problems and probably could be generalized and optimized
(e.g. to support other pixel formats).

OTOH it would add to the maintenance burden since we would be owners
of the code, which also means we would not benefit from fixes to the
upstream project, in case they happen (last commit is from March
2023, so not very old):
https://github.com/dlbeer/quirc/commit/542848dd6b9b0eaa9587bbf25b9bc67bd8a71fca
Tomas Härdin Dec. 27, 2023, 2:49 p.m. UTC | #8
ons 2023-12-27 klockan 12:37 +0100 skrev Stefano Sabatini:
> On date Tuesday 2023-12-26 22:33:36 -0300, James Almer wrote:
> > On 12/26/2023 10:25 PM, Stefano Sabatini wrote:
> > > On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini
> > > wrote:
> > > > ---
> > > >   Changelog                |   1 +
> > > >   configure                |   4 +
> > > >   doc/filters.texi         |  35 ++++++++
> > > >   libavfilter/Makefile     |   1 +
> > > >   libavfilter/allfilters.c |   1 +
> > > >   libavfilter/vf_quirc.c   | 183
> > > > +++++++++++++++++++++++++++++++++++++++
> > > >   6 files changed, 225 insertions(+)
> > > >   create mode 100644 libavfilter/vf_quirc.c
> > > 
> > > V2 with a few fixes and all corners put in the metadata (e.g. in
> > > case
> > > the QR code is rotated).
> > 
> 
> > Looking at the library, the license is very permissive and the code
> > hasn't
> > been touched in many years. It is also pretty small, so why not
> > just add it
> > as a native filter instead of requiring an external dependency for
> > what
> > seems to be a relatively simple process?
> 
> I see pros and cons, in total that would be about 3K lines of pretty
> clean code and data, and this would simplify integration for end-
> users
> (since they would not need to build the library, which seems not
> packaged by many distributions), and having the code would help to
> solve similar problems and probably could be generalized and
> optimized
> (e.g. to support other pixel formats).
> 
> OTOH it would add to the maintenance burden since we would be owners
> of the code, which also means we would not benefit from fixes to the
> upstream project, in case they happen (last commit is from March
> 2023, so not very old):
> https://github.com/dlbeer/quirc/commit/542848dd6b9b0eaa9587bbf25b9bc67bd8a71fca

We could use git-subtree to keep in sync with upstream and to
contribute patches back. This is something I've done with other
projects. It has the following benefits:

1) Not having to deal with git-submodule
2) Having a copy of the code in case upstream goes down
3) Possibility of applying FFmpeg-only changes, say to the build
system, without having to have scripts that apply patches on top of the
submodule
4) Development can be concentrated on one implementation rather than
many. This is a point I've made with MXF but it applies here too

However, a bit of work is necessary to make packagers happy with such
an arrangeent, since I expect they would want libquirc to be a separate
package.

On the other hand we can just force users to build and install
libquirc. This is less work for us.

/Tomas
James Almer Dec. 27, 2023, 5:16 p.m. UTC | #9
On 12/27/2023 8:37 AM, Stefano Sabatini wrote:
> On date Tuesday 2023-12-26 22:33:36 -0300, James Almer wrote:
>> On 12/26/2023 10:25 PM, Stefano Sabatini wrote:
>>> On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini wrote:
>>>> ---
>>>>    Changelog                |   1 +
>>>>    configure                |   4 +
>>>>    doc/filters.texi         |  35 ++++++++
>>>>    libavfilter/Makefile     |   1 +
>>>>    libavfilter/allfilters.c |   1 +
>>>>    libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
>>>>    6 files changed, 225 insertions(+)
>>>>    create mode 100644 libavfilter/vf_quirc.c
>>>
>>> V2 with a few fixes and all corners put in the metadata (e.g. in case
>>> the QR code is rotated).
>>
> 
>> Looking at the library, the license is very permissive and the code hasn't
>> been touched in many years. It is also pretty small, so why not just add it
>> as a native filter instead of requiring an external dependency for what
>> seems to be a relatively simple process?
> 
> I see pros and cons, in total that would be about 3K lines of pretty
> clean code and data, and this would simplify integration for end-users
> (since they would not need to build the library, which seems not
> packaged by many distributions), and having the code would help to
> solve similar problems and probably could be generalized and optimized
> (e.g. to support other pixel formats).
> 
> OTOH it would add to the maintenance burden since we would be owners
> of the code, which also means we would not benefit from fixes to the
> upstream project, in case they happen (last commit is from March
> 2023, so not very old):
> https://github.com/dlbeer/quirc/commit/542848dd6b9b0eaa9587bbf25b9bc67bd8a71fca

That's a build system change, so not really relevant. Last real change 
was 
https://github.com/dlbeer/quirc/commit/cc673124335785d220dbb9057b21c51e4a87e0b2, 
also from March 2023, but the one before it is from August 2021.

I really think this should be ported as a native filter. It's not big 
and complex like a scaler (sws, zscale) which should not live within 
libavfilter. Any change can be contributed back to libquirc (A library 
that's not going to be abandoned like it happened with libdcadec after 
it was merged into lavc, because its license is more permissive than our 
LGPL, which has its merits).

Lets try to keep external dependencies as limited as possible.
Stefano Sabatini Dec. 27, 2023, 11:20 p.m. UTC | #10
On date Wednesday 2023-12-27 14:16:24 -0300, James Almer wrote:
> On 12/27/2023 8:37 AM, Stefano Sabatini wrote:
> > On date Tuesday 2023-12-26 22:33:36 -0300, James Almer wrote:
> > > On 12/26/2023 10:25 PM, Stefano Sabatini wrote:
> > > > On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini wrote:
> > > > > ---
> > > > >    Changelog                |   1 +
> > > > >    configure                |   4 +
> > > > >    doc/filters.texi         |  35 ++++++++
> > > > >    libavfilter/Makefile     |   1 +
> > > > >    libavfilter/allfilters.c |   1 +
> > > > >    libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
> > > > >    6 files changed, 225 insertions(+)
> > > > >    create mode 100644 libavfilter/vf_quirc.c
> > > > 
> > > > V2 with a few fixes and all corners put in the metadata (e.g. in case
> > > > the QR code is rotated).
> > > 
> > 
> > > Looking at the library, the license is very permissive and the code hasn't
> > > been touched in many years. It is also pretty small, so why not just add it
> > > as a native filter instead of requiring an external dependency for what
> > > seems to be a relatively simple process?
> > 
> > I see pros and cons, in total that would be about 3K lines of pretty
> > clean code and data, and this would simplify integration for end-users
> > (since they would not need to build the library, which seems not
> > packaged by many distributions), and having the code would help to
> > solve similar problems and probably could be generalized and optimized
> > (e.g. to support other pixel formats).
> > 
> > OTOH it would add to the maintenance burden since we would be owners
> > of the code, which also means we would not benefit from fixes to the
> > upstream project, in case they happen (last commit is from March
> > 2023, so not very old):
> > https://github.com/dlbeer/quirc/commit/542848dd6b9b0eaa9587bbf25b9bc67bd8a71fca
> 
> That's a build system change, so not really relevant. Last real change was https://github.com/dlbeer/quirc/commit/cc673124335785d220dbb9057b21c51e4a87e0b2,
> also from March 2023, but the one before it is from August 2021.
> 

> I really think this should be ported as a native filter. It's not big and
> complex like a scaler (sws, zscale) which should not live within
> libavfilter.

> Any change can be contributed back to libquirc

This is not realistic, given that a reimplementation would be possibly
completely refactored to fit into FFmpeg.

> (A library
> that's not going to be abandoned like it happened with libdcadec after it
> was merged into lavc,

OTOH, this library is quite outside the scope of the FFmpeg, so it
makes sense to keep it as external dependency. This is a quite
different use case than a decoder, a QR-decoder library can make sense
outside of a multimedia library, for a decoder you would need a
complete multimedia library anyway.

I was checking the code, and porting would be a serious effort and
comprise several thousands lines of code (against the moderate effort
of wrapping it - which is already done), also some of the logic would
not really fit into FFmpeg because it is quite specific to this
application domain (QR code decoding), so it makes sense for it to
live within an external library. Not to mention that this would be
a duplication of effort.

*Unless* someone is willing to port/reimplement the code, but this
should not be a blocker for the wrapper and can be done as a separate
step.
James Almer Dec. 28, 2023, 12:57 a.m. UTC | #11
On 12/27/2023 8:20 PM, Stefano Sabatini wrote:
> On date Wednesday 2023-12-27 14:16:24 -0300, James Almer wrote:
>> On 12/27/2023 8:37 AM, Stefano Sabatini wrote:
>>> On date Tuesday 2023-12-26 22:33:36 -0300, James Almer wrote:
>>>> On 12/26/2023 10:25 PM, Stefano Sabatini wrote:
>>>>> On date Tuesday 2023-12-26 17:20:33 +0100, Stefano Sabatini wrote:
>>>>>> ---
>>>>>>     Changelog                |   1 +
>>>>>>     configure                |   4 +
>>>>>>     doc/filters.texi         |  35 ++++++++
>>>>>>     libavfilter/Makefile     |   1 +
>>>>>>     libavfilter/allfilters.c |   1 +
>>>>>>     libavfilter/vf_quirc.c   | 183 +++++++++++++++++++++++++++++++++++++++
>>>>>>     6 files changed, 225 insertions(+)
>>>>>>     create mode 100644 libavfilter/vf_quirc.c
>>>>>
>>>>> V2 with a few fixes and all corners put in the metadata (e.g. in case
>>>>> the QR code is rotated).
>>>>
>>>
>>>> Looking at the library, the license is very permissive and the code hasn't
>>>> been touched in many years. It is also pretty small, so why not just add it
>>>> as a native filter instead of requiring an external dependency for what
>>>> seems to be a relatively simple process?
>>>
>>> I see pros and cons, in total that would be about 3K lines of pretty
>>> clean code and data, and this would simplify integration for end-users
>>> (since they would not need to build the library, which seems not
>>> packaged by many distributions), and having the code would help to
>>> solve similar problems and probably could be generalized and optimized
>>> (e.g. to support other pixel formats).
>>>
>>> OTOH it would add to the maintenance burden since we would be owners
>>> of the code, which also means we would not benefit from fixes to the
>>> upstream project, in case they happen (last commit is from March
>>> 2023, so not very old):
>>> https://github.com/dlbeer/quirc/commit/542848dd6b9b0eaa9587bbf25b9bc67bd8a71fca
>>
>> That's a build system change, so not really relevant. Last real change was https://github.com/dlbeer/quirc/commit/cc673124335785d220dbb9057b21c51e4a87e0b2,
>> also from March 2023, but the one before it is from August 2021.
>>
> 
>> I really think this should be ported as a native filter. It's not big and
>> complex like a scaler (sws, zscale) which should not live within
>> libavfilter.
> 
>> Any change can be contributed back to libquirc
> 
> This is not realistic, given that a reimplementation would be possibly
> completely refactored to fit into FFmpeg.
> 
>> (A library
>> that's not going to be abandoned like it happened with libdcadec after it
>> was merged into lavc,
> 
> OTOH, this library is quite outside the scope of the FFmpeg, so it
> makes sense to keep it as external dependency. This is a quite
> different use case than a decoder, a QR-decoder library can make sense
> outside of a multimedia library, for a decoder you would need a
> complete multimedia library anyway.
> 
> I was checking the code, and porting would be a serious effort and
> comprise several thousands lines of code (against the moderate effort
> of wrapping it - which is already done), also some of the logic would
> not really fit into FFmpeg because it is quite specific to this
> application domain (QR code decoding), so it makes sense for it to
> live within an external library. Not to mention that this would be
> a duplication of effort.

Image analysis is within FFmpeg's scope, which QR code identification 
and decoding would be about.

> 
> *Unless* someone is willing to port/reimplement the code, but this
> should not be a blocker for the wrapper and can be done as a separate
> step.

No, i'm not blocking anything. Just stating that ideally this would be a 
native module.
Tomas Härdin Dec. 28, 2023, 10:24 a.m. UTC | #12
tor 2023-12-28 klockan 00:20 +0100 skrev Stefano Sabatini:
> I was checking the code, and porting would be a serious effort and
> comprise several thousands lines of code (against the moderate effort
> of wrapping it - which is already done), also some of the logic would
> not really fit into FFmpeg because it is quite specific to this
> application domain (QR code decoding), so it makes sense for it to
> live within an external library. Not to mention that this would be
> a duplication of effort.

This is the same point I've been making lately. With an appropriate
build system a lot of effort can be saved. It's only really when
existing implementations are too slow or mising features that it might
be necessary to reimplement them.

/Tomas
Stefano Sabatini Dec. 29, 2023, 11:57 a.m. UTC | #13
On date Wednesday 2023-12-27 21:57:19 -0300, James Almer wrote:
> On 12/27/2023 8:20 PM, Stefano Sabatini wrote:
[...]
> > > I really think this should be ported as a native filter. It's not big and
> > > complex like a scaler (sws, zscale) which should not live within
> > > libavfilter.
> > 
> > > Any change can be contributed back to libquirc
> > 
> > This is not realistic, given that a reimplementation would be possibly
> > completely refactored to fit into FFmpeg.
> > 
> > > (A library
> > > that's not going to be abandoned like it happened with libdcadec after it
> > > was merged into lavc,
> > 
> > OTOH, this library is quite outside the scope of the FFmpeg, so it
> > makes sense to keep it as external dependency. This is a quite
> > different use case than a decoder, a QR-decoder library can make sense
> > outside of a multimedia library, for a decoder you would need a
> > complete multimedia library anyway.
> > 
> > I was checking the code, and porting would be a serious effort and
> > comprise several thousands lines of code (against the moderate effort
> > of wrapping it - which is already done), also some of the logic would
> > not really fit into FFmpeg because it is quite specific to this
> > application domain (QR code decoding), so it makes sense for it to
> > live within an external library. Not to mention that this would be
> > a duplication of effort.
> 
> Image analysis is within FFmpeg's scope, which QR code identification and
> decoding would be about.
> 
> > 
> > *Unless* someone is willing to port/reimplement the code, but this
> > should not be a blocker for the wrapper and can be done as a separate
> > step.
> 
> No, i'm not blocking anything. Just stating that ideally this would be a
> native module.

I'm not against this of course, but as I already stated this would
require more effort. It is probably worth it (polynomials and Galois
fields based error correction) as they would be probably reused in
other parts of the code. Ditto for the QR encoder.

But I plan to have this functionality in with the wrappers - this
should also help with an eventual native port of the features.

I plan to push this patch in a few days, unless I see comments.

Thanks for the feedback.
Stefano Sabatini Jan. 2, 2024, 9:13 p.m. UTC | #14
On date Friday 2023-12-29 12:57:15 +0100, Stefano Sabatini wrote:
> On date Wednesday 2023-12-27 21:57:19 -0300, James Almer wrote:
> > On 12/27/2023 8:20 PM, Stefano Sabatini wrote:
> [...]
> > > > I really think this should be ported as a native filter. It's not big and
> > > > complex like a scaler (sws, zscale) which should not live within
> > > > libavfilter.
> > > 
> > > > Any change can be contributed back to libquirc
> > > 
> > > This is not realistic, given that a reimplementation would be possibly
> > > completely refactored to fit into FFmpeg.
> > > 
> > > > (A library
> > > > that's not going to be abandoned like it happened with libdcadec after it
> > > > was merged into lavc,
> > > 
> > > OTOH, this library is quite outside the scope of the FFmpeg, so it
> > > makes sense to keep it as external dependency. This is a quite
> > > different use case than a decoder, a QR-decoder library can make sense
> > > outside of a multimedia library, for a decoder you would need a
> > > complete multimedia library anyway.
> > > 
> > > I was checking the code, and porting would be a serious effort and
> > > comprise several thousands lines of code (against the moderate effort
> > > of wrapping it - which is already done), also some of the logic would
> > > not really fit into FFmpeg because it is quite specific to this
> > > application domain (QR code decoding), so it makes sense for it to
> > > live within an external library. Not to mention that this would be
> > > a duplication of effort.
> > 
> > Image analysis is within FFmpeg's scope, which QR code identification and
> > decoding would be about.
> > 
> > > 
> > > *Unless* someone is willing to port/reimplement the code, but this
> > > should not be a blocker for the wrapper and can be done as a separate
> > > step.
> > 
> > No, i'm not blocking anything. Just stating that ideally this would be a
> > native module.
> 
> I'm not against this of course, but as I already stated this would
> require more effort. It is probably worth it (polynomials and Galois
> fields based error correction) as they would be probably reused in
> other parts of the code. Ditto for the QR encoder.
> 
> But I plan to have this functionality in with the wrappers - this
> should also help with an eventual native port of the features.
> 
> I plan to push this patch in a few days, unless I see comments.
> 
> Thanks for the feedback.

Applied.
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index b483bb9c69..424bfc11af 100644
--- a/Changelog
+++ b/Changelog
@@ -14,6 +14,7 @@  version <next>:
 - D3D12VA hardware accelerated H264, HEVC, VP9, AV1, MPEG-2 and VC1 decoding
 - tiltandshift filter
 - qrencode filter and qrencodesrc source
+- quirc filter
 
 version 6.1:
 - libaribcaption decoder
diff --git a/configure b/configure
index 0b2f4e9792..419ff76bc1 100755
--- a/configure
+++ b/configure
@@ -257,6 +257,7 @@  External library support:
   --enable-libplacebo      enable libplacebo library [no]
   --enable-libpulse        enable Pulseaudio input via libpulse [no]
   --enable-libqrencode     enable QR encode generation via libqrencode [no]
+  --enable-libquirc        enable QR decoding via libquirc [no]
   --enable-librabbitmq     enable RabbitMQ library [no]
   --enable-librav1e        enable AV1 encoding via rav1e [no]
   --enable-librist         enable RIST via librist [no]
@@ -1883,6 +1884,7 @@  EXTERNAL_LIBRARY_LIST="
     libplacebo
     libpulse
     libqrencode
+    libquirc
     librabbitmq
     librav1e
     librist
@@ -3793,6 +3795,7 @@  ocv_filter_deps="libopencv"
 openclsrc_filter_deps="opencl"
 qrencode_filter_deps="libqrencode"
 qrencodesrc_filter_deps="libqrencode"
+quirc_filter_deps="libquirc"
 overlay_opencl_filter_deps="opencl"
 overlay_qsv_filter_deps="libmfx"
 overlay_qsv_filter_select="qsvvpp"
@@ -6845,6 +6848,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 libquirc          && require libquirc quirc.h quirc_decode -lquirc
 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/doc/filters.texi b/doc/filters.texi
index 04898ede31..5fbe381d74 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -20368,6 +20368,41 @@  qrencode=text=%@{pts@}
 
 @end itemize
 
+@section quirc
+
+Identify and decode a QR code using the libquirc library (see
+@url{https://github.com/dlbeer/quirc/}), and print the identified QR codes
+positions and payload as metadata.
+
+To enable the compilation of this filter, you need to configure FFmpeg with with
+@code{--enable-libquirc}.
+
+For each found QR code in the input video, some metadata entries are added with
+the prefix @var{lavfi.quirc.N}, where @var{N} is the index, starting from 0,
+associated to the QR code.
+
+A description of each metadata value follows:
+
+@table @option
+@item lavfi.quirc.count
+the number of found QR codes, it is not set in case none was found
+
+@item lavfi.quirc.N.x
+@item lavfi.quirc.N.y
+the top-left position of the @var{N} QR code
+
+@item lavfi.quirc.N.X
+@item lavfi.quirc.N.Y
+the bottom-right position of the @var{N} QR code
+
+@item lavfi.quirc.N.w
+@item lavfi.quirc.N.h
+the size of the @var{N} QR code
+
+@item lavfi.quirc.N.payload
+the payload of the QR code
+@end table
+
 @section random
 
 Flush video frames from internal cache of frames into a random order.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 31371ceb1a..f65fb9a5a7 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -437,6 +437,7 @@  OBJS-$(CONFIG_PSEUDOCOLOR_FILTER)            += vf_pseudocolor.o
 OBJS-$(CONFIG_PSNR_FILTER)                   += vf_psnr.o framesync.o
 OBJS-$(CONFIG_PULLUP_FILTER)                 += vf_pullup.o
 OBJS-$(CONFIG_QP_FILTER)                     += vf_qp.o
+OBJS-$(CONFIG_QUIRC_FILTER)                  += vf_quirc.o
 OBJS-$(CONFIG_RANDOM_FILTER)                 += vf_random.o
 OBJS-$(CONFIG_READEIA608_FILTER)             += vf_readeia608.o
 OBJS-$(CONFIG_READVITC_FILTER)               += vf_readvitc.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 20feb37967..b8570dbab2 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -412,6 +412,7 @@  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_quirc;
 extern const AVFilter ff_vf_random;
 extern const AVFilter ff_vf_readeia608;
 extern const AVFilter ff_vf_readvitc;
diff --git a/libavfilter/vf_quirc.c b/libavfilter/vf_quirc.c
new file mode 100644
index 0000000000..6945a6aa31
--- /dev/null
+++ b/libavfilter/vf_quirc.c
@@ -0,0 +1,183 @@ 
+/*
+ * 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 decoder video filter
+ *
+ * Use libquirc library to decode the content of QR codes, and put the decoded
+ * content to metadata. See:
+ * https://github.com/dlbeer/quirc
+ */
+
+#include "config_components.h"
+//#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "video.h"
+#include <quirc.h>
+
+typedef struct QuircContext {
+    const AVClass *class;
+
+    struct quirc *quirc;
+} QuircContext;
+
+static const AVOption quirc_options[] = {
+    { NULL }
+};
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    QuircContext *quirc = ctx->priv;
+
+    quirc->quirc = quirc_new();
+    if (!quirc->quirc) {
+        return AVERROR(ENOMEM);
+    }
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    QuircContext *quirc = ctx->priv;
+
+    quirc_destroy(quirc->quirc);
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    QuircContext *quirc = ctx->priv;
+    int err;
+
+    err = quirc_resize(quirc->quirc, inlink->w, inlink->h);
+    if (err == -1) {
+        return AVERROR(ENOMEM);
+    }
+
+    return 0;
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE};
+    return ff_set_common_formats_from_list(ctx, pix_fmts);
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    QuircContext *quirc = ctx->priv;
+    int codes_count;
+    int i, w, h;
+    uint8_t *image, *imagep, *srcp;
+
+    image = quirc_begin(quirc->quirc, &w, &h);
+
+    /* copy input image to quirc buffer */
+    imagep = image;
+    srcp = frame->data[0];
+    for (i = 0; i < inlink->h; i++) {
+        memcpy(imagep, srcp, w);
+        imagep += w;
+        srcp += frame->linesize[0];
+    }
+
+    quirc_end(quirc->quirc);
+
+    codes_count = quirc_count(quirc->quirc);
+    av_log(ctx, AV_LOG_VERBOSE,
+           "Found count %d codes in image #%ld\n", codes_count, inlink->frame_count_out);
+
+    if (codes_count) {
+        AVDictionary **metadata = &frame->metadata;
+
+        av_dict_set_int(metadata, "lavfi.quirc.count", codes_count, 0);
+
+        for (i = 0; i < codes_count; i++) {
+            struct quirc_code code;
+            struct quirc_data data;
+            struct quirc_point top_left, bottom_right;
+            quirc_decode_error_t err;
+            int w, h;
+            char metadata_key[64];
+
+            quirc_extract(quirc->quirc, i, &code);
+
+            err = quirc_decode(&code, &data);
+            if (err) {
+                av_log(ctx, AV_LOG_WARNING,
+                       "Failed to decode image: %s\n", quirc_strerror(err));
+                continue;
+            }
+
+            top_left = code.corners[0];
+            bottom_right = code.corners[2];
+            w = bottom_right.x - top_left.x;
+            h = bottom_right.y - top_left.y;
+
+#define SET_METADATA_I(key_, value_)                                    \
+            snprintf(metadata_key, sizeof(metadata_key)-1, "lavfi.quirc.%d." #key_, i); \
+            av_dict_set_int(metadata, metadata_key, value_, AV_DICT_MATCH_CASE)
+
+            SET_METADATA_I(x, top_left.x);
+            SET_METADATA_I(y, top_left.y);
+            SET_METADATA_I(X, bottom_right.x);
+            SET_METADATA_I(Y, bottom_right.y);
+            SET_METADATA_I(w, w);
+            SET_METADATA_I(h, h);
+            snprintf(metadata_key, sizeof(metadata_key)-1, "lavfi.quirc.%d.payload", i); \
+            av_dict_set(metadata, metadata_key, data.payload, 0);
+
+            av_log(ctx, AV_LOG_INFO,
+                   "Found QR code at position %d+%d:%dx%d with payload: %s\n",
+                   top_left.x, top_left.y, w, h, data.payload);
+        }
+    }
+
+    return ff_filter_frame(outlink, frame);
+}
+
+AVFILTER_DEFINE_CLASS(quirc);
+
+static const AVFilterPad inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+        .config_props = config_input
+    },
+};
+
+const AVFilter ff_vf_quirc = {
+    .name        = "quirc",
+    .description = NULL_IF_CONFIG_SMALL("Decode and show QR codes content."),
+    .priv_size   = sizeof(QuircContext),
+    .priv_class  = &quirc_class,
+    .init        = init,
+    .uninit      = uninit,
+    FILTER_INPUTS(inputs),
+    FILTER_OUTPUTS(ff_video_default_filterpad),
+    FILTER_QUERY_FUNC(query_formats),
+    .flags       = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
+                   AVFILTER_FLAG_METADATA_ONLY,
+};
+