diff mbox series

[FFmpeg-devel] avcodec: add QOI decoder and demuxer and parser

Message ID 20220531113236.58153-1-onemda@gmail.com
State New
Headers show
Series [FFmpeg-devel] avcodec: add QOI decoder and demuxer and parser | expand

Checks

Context Check Description
andriy/configure_x86 warning Failed to apply patch
andriy/configure_armv7_RPi4 warning Failed to apply patch

Commit Message

Paul B Mahol May 31, 2022, 11:32 a.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 libavcodec/Makefile      |   2 +
 libavcodec/allcodecs.c   |   1 +
 libavcodec/codec_desc.c  |   7 ++
 libavcodec/codec_id.h    |   1 +
 libavcodec/parsers.c     |   1 +
 libavcodec/qoi_parser.c  |  77 ++++++++++++++++++++++
 libavcodec/qoidec.c      | 139 +++++++++++++++++++++++++++++++++++++++
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/img2.c       |   1 +
 libavformat/img2dec.c    |  12 ++++
 11 files changed, 243 insertions(+)
 create mode 100644 libavcodec/qoi_parser.c
 create mode 100644 libavcodec/qoidec.c

Comments

Tomas Härdin May 31, 2022, 5:07 p.m. UTC | #1
tis 2022-05-31 klockan 13:32 +0200 skrev Paul B Mahol:
> 
> diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
> index 03234b7543..a716dc87c3 100644
> --- a/libavcodec/codec_id.h
> +++ b/libavcodec/codec_id.h
> @@ -311,6 +311,7 @@ enum AVCodecID {
>      AV_CODEC_ID_VBN,
>      AV_CODEC_ID_JPEGXL,
>      AV_CODEC_ID_BINKVIDEO2,
> +    AV_CODEC_ID_QOI,

Missing minor version bump?

> +++ b/libavcodec/qoi_parser.c
> 
> +typedef struct QOIParseContext {
> +    ParseContext pc;
> +} QOIParseContext;

Could just be ParseContext

> +
> +static int qoi_parse(AVCodecParserContext *s, AVCodecContext *avctx,
> +                     const uint8_t **poutbuf, int *poutbuf_size,
> +                     const uint8_t *buf, int buf_size)

Looks OK

> +static int qoi_decode_frame(AVCodecContext *avctx, AVFrame *p,
> +                            int *got_frame, AVPacket *avpkt)
> +{
> +    [...]
> +    avctx->pix_fmt = AV_PIX_FMT_RGBA;

Would be nice if RGB pictures were actually decoded as RGB, but it
might not be strictly necessary as far as I can tell from the spec.

The reference implementation will use whichever pixel format is
specific in the file unless the user explicitly requests either of the
two pixel formats. So users coming from that context would expect our
decoder to automagically pick the format specified in the file I think.

> +
> +    if ((ret = ff_get_buffer(avctx, p, 0)) < 0)
> +        return ret;
> +
> +    dst = p->data[0];
> +    len = width * height * 4LL;
> +    for (int n = 0, off_x = 0, off_y = 0; n < len; n += 4, off_x++)
> {
> +        if (off_x >= width) {
> +            off_x = 0;
> +            off_y++;
> +            dst += p->linesize[0];
> +        }
> +        if (run > 0) {
> +            run--;
> +        } else if (bytestream2_get_bytes_left(&gb) > 0) {
> +            int chunk = bytestream2_get_byteu(&gb);
> +
> +            if (chunk == QOI_OP_RGB) {
> +                px[0] = bytestream2_get_byte(&gb);
> +                px[1] = bytestream2_get_byte(&gb);
> +                px[2] = bytestream2_get_byte(&gb);
> +            } else if (chunk == QOI_OP_RGBA) {
> +                px[0] = bytestream2_get_byte(&gb);
> +                px[1] = bytestream2_get_byte(&gb);
> +                px[2] = bytestream2_get_byte(&gb);
> +                px[3] = bytestream2_get_byte(&gb);

This will silently accept RGBA chunks in RGB pictures which *might* be
incorrect. The spec is not clear on this. The reference implementation
will accept this but discard the alpha channel *on the output*, if the
file is marked as RGB and the user doesn't explicitly say that they
want RGBA. It updates px.a regardless, which changes the hash, so we
can't just ignore this chunk in RGB mode. Unlike the official decoder
this one will output garbage alpha for RGB files.

> --- a/libavformat/img2dec.c
> +++ b/libavformat/img2dec.c
> @@ -1131,6 +1131,17 @@ static int photocd_probe(const AVProbeData *p)
>      return AVPROBE_SCORE_MAX - 1;
>  }
>  
> +static int qoi_probe(const AVProbeData *p)
> +{
> +    if (memcmp(p->buf, "qoif", 4))
> +        return 0;
> +
> +    if (AV_RB32(p->buf + 4) == 0 || AV_RB32(p->buf + 8) == 0)
> +        return 0;

Should also check channels and colorspace

/Tomas
Anton Khirnov June 1, 2022, 9:51 a.m. UTC | #2
Quoting Paul B Mahol (2022-05-31 13:32:36)
> +const FFCodec ff_qoi_decoder = {
> +    .p.name         = "qoi",
> +    .p.long_name    = NULL_IF_CONFIG_SMALL("QOI (Quite OK Image format) image"),
> +    .p.type         = AVMEDIA_TYPE_VIDEO,
> +    .p.id           = AV_CODEC_ID_QOI,
> +    .p.capabilities = AV_CODEC_CAP_DR1,
> +    FF_CODEC_DECODE_CB(qoi_decode_frame),

Add trivially-true init-threadsafe and init-cleanup?
Paul B Mahol June 1, 2022, 10:28 a.m. UTC | #3
On Wed, Jun 1, 2022 at 11:51 AM Anton Khirnov <anton@khirnov.net> wrote:

> Quoting Paul B Mahol (2022-05-31 13:32:36)
> > +const FFCodec ff_qoi_decoder = {
> > +    .p.name         = "qoi",
> > +    .p.long_name    = NULL_IF_CONFIG_SMALL("QOI (Quite OK Image format)
> image"),
> > +    .p.type         = AVMEDIA_TYPE_VIDEO,
> > +    .p.id           = AV_CODEC_ID_QOI,
> > +    .p.capabilities = AV_CODEC_CAP_DR1,
> > +    FF_CODEC_DECODE_CB(qoi_decode_frame),
>
> Add trivially-true init-threadsafe and init-cleanup?
>
>
No other trivial image codec, for example BMP, does this.


> --
> Anton Khirnov
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
diff mbox series

Patch

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index e6eb8c0854..f74a7abedc 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -594,6 +594,7 @@  OBJS-$(CONFIG_QCELP_DECODER)           += qcelpdec.o                     \
 OBJS-$(CONFIG_QDM2_DECODER)            += qdm2.o
 OBJS-$(CONFIG_QDMC_DECODER)            += qdmc.o
 OBJS-$(CONFIG_QDRAW_DECODER)           += qdrw.o
+OBJS-$(CONFIG_QOI_DECODER)             += qoidec.o
 OBJS-$(CONFIG_QPEG_DECODER)            += qpeg.o
 OBJS-$(CONFIG_QTRLE_DECODER)           += qtrle.o
 OBJS-$(CONFIG_QTRLE_ENCODER)           += qtrleenc.o
@@ -1152,6 +1153,7 @@  OBJS-$(CONFIG_OPUS_PARSER)             += opus_parser.o opus.o opustab.o \
                                           opus_rc.o vorbis_data.o
 OBJS-$(CONFIG_PNG_PARSER)              += png_parser.o
 OBJS-$(CONFIG_PNM_PARSER)              += pnm_parser.o pnm.o
+OBJS-$(CONFIG_QOI_PARSER)              += qoi_parser.o
 OBJS-$(CONFIG_RV30_PARSER)             += rv34_parser.o
 OBJS-$(CONFIG_RV40_PARSER)             += rv34_parser.o
 OBJS-$(CONFIG_SBC_PARSER)              += sbc_parser.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 3ae41827a2..4c0a85184c 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -270,6 +270,7 @@  extern const FFCodec ff_prosumer_decoder;
 extern const FFCodec ff_psd_decoder;
 extern const FFCodec ff_ptx_decoder;
 extern const FFCodec ff_qdraw_decoder;
+extern const FFCodec ff_qoi_decoder;
 extern const FFCodec ff_qpeg_decoder;
 extern const FFCodec ff_qtrle_encoder;
 extern const FFCodec ff_qtrle_decoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 3bcf22e6e7..dd877ca0bd 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1886,6 +1886,13 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("Bink video 2"),
         .props     = AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_QOI,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "qoi",
+        .long_name = NULL_IF_CONFIG_SMALL("QOI (Quite OK Image)"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* various PCM "codecs" */
     {
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 03234b7543..a716dc87c3 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -311,6 +311,7 @@  enum AVCodecID {
     AV_CODEC_ID_VBN,
     AV_CODEC_ID_JPEGXL,
     AV_CODEC_ID_BINKVIDEO2,
+    AV_CODEC_ID_QOI,
 
     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c
index 6b40c18d80..b59388835d 100644
--- a/libavcodec/parsers.c
+++ b/libavcodec/parsers.c
@@ -60,6 +60,7 @@  extern const AVCodecParser ff_mpegvideo_parser;
 extern const AVCodecParser ff_opus_parser;
 extern const AVCodecParser ff_png_parser;
 extern const AVCodecParser ff_pnm_parser;
+extern const AVCodecParser ff_qoi_parser;
 extern const AVCodecParser ff_rv30_parser;
 extern const AVCodecParser ff_rv40_parser;
 extern const AVCodecParser ff_sbc_parser;
diff --git a/libavcodec/qoi_parser.c b/libavcodec/qoi_parser.c
new file mode 100644
index 0000000000..c2badde56a
--- /dev/null
+++ b/libavcodec/qoi_parser.c
@@ -0,0 +1,77 @@ 
+/*
+ * QOI parser
+ * Copyright (c) 2022 Paul B Mahol
+ *
+ * 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
+ * QOI parser
+ */
+
+#include "parser.h"
+
+typedef struct QOIParseContext {
+    ParseContext pc;
+} QOIParseContext;
+
+static int qoi_parse(AVCodecParserContext *s, AVCodecContext *avctx,
+                     const uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size)
+{
+    QOIParseContext *ipc = s->priv_data;
+    uint64_t state = ipc->pc.state;
+    int next = END_NOT_FOUND, i = 0;
+
+    s->pict_type = AV_PICTURE_TYPE_NONE;
+    s->duration  = 1;
+
+    *poutbuf_size = 0;
+    *poutbuf = NULL;
+
+    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
+        next = buf_size;
+    } else {
+        for (; i < buf_size; i++) {
+            state = (state << 8) | buf[i];
+            if (state == 0x01LL) {
+                next = i + 1;
+                break;
+            }
+        }
+
+        ipc->pc.state = state;
+        if (ff_combine_frame(&ipc->pc, next, &buf, &buf_size) < 0) {
+            *poutbuf = NULL;
+            *poutbuf_size = 0;
+            return buf_size;
+        }
+    }
+
+    *poutbuf      = buf;
+    *poutbuf_size = buf_size;
+
+    return next;
+}
+
+const AVCodecParser ff_qoi_parser = {
+    .codec_ids      = { AV_CODEC_ID_QOI },
+    .priv_data_size = sizeof(QOIParseContext),
+    .parser_parse   = qoi_parse,
+    .parser_close   = ff_parse_close,
+};
diff --git a/libavcodec/qoidec.c b/libavcodec/qoidec.c
new file mode 100644
index 0000000000..fb245a35ce
--- /dev/null
+++ b/libavcodec/qoidec.c
@@ -0,0 +1,139 @@ 
+/*
+ * QOI image format
+ *
+ * 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
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/avstring.h"
+#include "avcodec.h"
+#include "internal.h"
+#include "bytestream.h"
+#include "codec_internal.h"
+
+#define QOI_OP_INDEX  0x00 /* 00xxxxxx */
+#define QOI_OP_DIFF   0x40 /* 01xxxxxx */
+#define QOI_OP_LUMA   0x80 /* 10xxxxxx */
+#define QOI_OP_RUN    0xc0 /* 11xxxxxx */
+#define QOI_OP_RGB    0xfe /* 11111110 */
+#define QOI_OP_RGBA   0xff /* 11111111 */
+
+#define QOI_MASK_2    0xc0 /* 11000000 */
+
+#define QOI_COLOR_HASH(px) (px[0]*3 + px[1]*5 + px[2]*7 + px[3]*11)
+
+static int qoi_decode_frame(AVCodecContext *avctx, AVFrame *p,
+                            int *got_frame, AVPacket *avpkt)
+{
+    const uint8_t *buf = avpkt->data;
+    int ret, buf_size = avpkt->size;
+    int width, height, run = 0;
+    uint8_t index[64][4] = { 0 };
+    uint8_t px[4] = { 0, 0, 0, 255 };
+    GetByteContext gb;
+    uint8_t *dst;
+    uint64_t len;
+
+    if (buf_size < 20)
+        return AVERROR_INVALIDDATA;
+
+    bytestream2_init(&gb, buf, buf_size);
+    bytestream2_skip(&gb, 4);
+    width  = bytestream2_get_be32(&gb);
+    height = bytestream2_get_be32(&gb);
+    bytestream2_skip(&gb, 2);
+
+    if ((ret = ff_set_dimensions(avctx, width, height)) < 0)
+        return ret;
+
+    if ((ret = av_image_check_size(avctx->width, avctx->height, 0, NULL)) < 0)
+        return ret;
+
+    avctx->pix_fmt = AV_PIX_FMT_RGBA;
+
+    if ((ret = ff_get_buffer(avctx, p, 0)) < 0)
+        return ret;
+
+    dst = p->data[0];
+    len = width * height * 4LL;
+    for (int n = 0, off_x = 0, off_y = 0; n < len; n += 4, off_x++) {
+        if (off_x >= width) {
+            off_x = 0;
+            off_y++;
+            dst += p->linesize[0];
+        }
+        if (run > 0) {
+            run--;
+        } else if (bytestream2_get_bytes_left(&gb) > 0) {
+            int chunk = bytestream2_get_byteu(&gb);
+
+            if (chunk == QOI_OP_RGB) {
+                px[0] = bytestream2_get_byte(&gb);
+                px[1] = bytestream2_get_byte(&gb);
+                px[2] = bytestream2_get_byte(&gb);
+            } else if (chunk == QOI_OP_RGBA) {
+                px[0] = bytestream2_get_byte(&gb);
+                px[1] = bytestream2_get_byte(&gb);
+                px[2] = bytestream2_get_byte(&gb);
+                px[3] = bytestream2_get_byte(&gb);
+            } else if ((chunk & QOI_MASK_2) == QOI_OP_INDEX) {
+                px[0] = index[chunk][0];
+                px[1] = index[chunk][1];
+                px[2] = index[chunk][2];
+                px[3] = index[chunk][3];
+            } else if ((chunk & QOI_MASK_2) == QOI_OP_DIFF) {
+                px[0] += ((chunk >> 4) & 0x03) - 2;
+                px[1] += ((chunk >> 2) & 0x03) - 2;
+                px[2] += ( chunk       & 0x03) - 2;
+            } else if ((chunk & QOI_MASK_2) == QOI_OP_LUMA) {
+                int b2 = bytestream2_get_byte(&gb);
+                int vg = (chunk & 0x3f) - 32;
+                px[0] += vg - 8 + ((b2 >> 4) & 0x0f);
+                px[1] += vg;
+                px[2] += vg - 8 +  (b2       & 0x0f);
+            } else if ((chunk & QOI_MASK_2) == QOI_OP_RUN) {
+                run = chunk & 0x3f;
+            }
+
+            memcpy(index[QOI_COLOR_HASH(px) % 64], px, 4);
+        } else {
+            break;
+        }
+
+        memcpy(&dst[off_x * 4], px, 4);
+    }
+
+    p->key_frame = 1;
+    p->pict_type = AV_PICTURE_TYPE_I;
+
+    *got_frame   = 1;
+
+    return buf_size;
+}
+
+const FFCodec ff_qoi_decoder = {
+    .p.name         = "qoi",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("QOI (Quite OK Image format) image"),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_QOI,
+    .p.capabilities = AV_CODEC_CAP_DR1,
+    FF_CODEC_DECODE_CB(qoi_decode_frame),
+};
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 8e612b6cc7..1416bf31bd 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -289,6 +289,7 @@  OBJS-$(CONFIG_IMAGE_PNG_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_PPM_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_PSD_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_QDRAW_PIPE_DEMUXER)   += img2dec.o img2.o
+OBJS-$(CONFIG_IMAGE_QOI_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_SGI_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_SVG_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_SUNRAST_PIPE_DEMUXER) += img2dec.o img2.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 1802536633..8b84b52c64 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -524,6 +524,7 @@  extern const AVInputFormat  ff_image_png_pipe_demuxer;
 extern const AVInputFormat  ff_image_ppm_pipe_demuxer;
 extern const AVInputFormat  ff_image_psd_pipe_demuxer;
 extern const AVInputFormat  ff_image_qdraw_pipe_demuxer;
+extern const AVInputFormat  ff_image_qoi_pipe_demuxer;
 extern const AVInputFormat  ff_image_sgi_pipe_demuxer;
 extern const AVInputFormat  ff_image_svg_pipe_demuxer;
 extern const AVInputFormat  ff_image_sunrast_pipe_demuxer;
diff --git a/libavformat/img2.c b/libavformat/img2.c
index 566ef873ca..68cb7de2c1 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -89,6 +89,7 @@  const IdStrMap ff_img_tags[] = {
     { AV_CODEC_ID_GEM,        "timg"     },
     { AV_CODEC_ID_VBN,        "vbn"      },
     { AV_CODEC_ID_JPEGXL,     "jxl"      },
+    { AV_CODEC_ID_QOI,        "qoi"      },
     { AV_CODEC_ID_NONE,       NULL       }
 };
 
diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
index 5f9d1f094f..2ffbef6a44 100644
--- a/libavformat/img2dec.c
+++ b/libavformat/img2dec.c
@@ -1131,6 +1131,17 @@  static int photocd_probe(const AVProbeData *p)
     return AVPROBE_SCORE_MAX - 1;
 }
 
+static int qoi_probe(const AVProbeData *p)
+{
+    if (memcmp(p->buf, "qoif", 4))
+        return 0;
+
+    if (AV_RB32(p->buf + 4) == 0 || AV_RB32(p->buf + 8) == 0)
+        return 0;
+
+    return AVPROBE_SCORE_MAX - 1;
+}
+
 static int gem_probe(const AVProbeData *p)
 {
     const uint8_t *b = p->buf;
@@ -1208,6 +1219,7 @@  IMAGEAUTO_DEMUXER(png,       PNG)
 IMAGEAUTO_DEMUXER(ppm,       PPM)
 IMAGEAUTO_DEMUXER(psd,       PSD)
 IMAGEAUTO_DEMUXER(qdraw,     QDRAW)
+IMAGEAUTO_DEMUXER(qoi,       QOI)
 IMAGEAUTO_DEMUXER(sgi,       SGI)
 IMAGEAUTO_DEMUXER(sunrast,   SUNRAST)
 IMAGEAUTO_DEMUXER(svg,       SVG)