diff mbox series

[FFmpeg-devel] avcodec: ViewQuest VQC decoder

Message ID 004802c9d634e3f66566978f04e276a7fedd1a5c.1665053079.git.pross@xvid.org
State New
Headers show
Series [FFmpeg-devel] avcodec: ViewQuest VQC decoder | expand

Checks

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

Commit Message

Peter Ross Oct. 6, 2022, 10:45 a.m. UTC
---
Fixes ticket #5601

 doc/general_contents.texi |   1 +
 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/codec_desc.c   |   7 +
 libavcodec/codec_id.h     |   1 +
 libavcodec/vqcdec.c       | 443 ++++++++++++++++++++++++++++++++++++++
 libavformat/riff.c        |   2 +
 7 files changed, 456 insertions(+)
 create mode 100644 libavcodec/vqcdec.c

Comments

Andreas Rheinhardt Oct. 6, 2022, 12:54 p.m. UTC | #1
Peter Ross:
> ---
> Fixes ticket #5601
> 
>  doc/general_contents.texi |   1 +
>  libavcodec/Makefile       |   1 +
>  libavcodec/allcodecs.c    |   1 +
>  libavcodec/codec_desc.c   |   7 +
>  libavcodec/codec_id.h     |   1 +
>  libavcodec/vqcdec.c       | 443 ++++++++++++++++++++++++++++++++++++++
>  libavformat/riff.c        |   2 +
>  7 files changed, 456 insertions(+)
>  create mode 100644 libavcodec/vqcdec.c
> 
> diff --git a/doc/general_contents.texi b/doc/general_contents.texi
> index b005cb6c3e..8399fcb6b7 100644
> --- a/doc/general_contents.texi
> +++ b/doc/general_contents.texi
> @@ -1339,6 +1339,7 @@ following image formats are supported:
>  @item TwinVQ (VQF flavor)    @tab     @tab  X
>  @item VIMA                   @tab     @tab  X
>      @tab Used in LucasArts SMUSH animations.
> +@item ViewQuest VQC          @tab     @tab  X
>  @item Vorbis                 @tab  E  @tab  X
>      @tab A native but very primitive encoder exists.
>  @item Voxware MetaSound      @tab     @tab  X
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index 592d9347f6..b4c28f166f 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -761,6 +761,7 @@ OBJS-$(CONFIG_VP9_QSV_ENCODER)         += qsvenc_vp9.o
>  OBJS-$(CONFIG_VPLAYER_DECODER)         += textdec.o ass.o
>  OBJS-$(CONFIG_VP9_V4L2M2M_DECODER)     += v4l2_m2m_dec.o
>  OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
> +OBJS-$(CONFIG_VQC_DECODER)             += vqcdec.o
>  OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o wavpackdata.o dsd.o
>  OBJS-$(CONFIG_WAVPACK_ENCODER)         += wavpackdata.o wavpackenc.o
>  OBJS-$(CONFIG_WBMP_DECODER)            += wbmpdec.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index cfeb01ac1c..46ad3b5a25 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -381,6 +381,7 @@ extern const FFCodec ff_vp9_decoder;
>  extern const FFCodec ff_vp9_rkmpp_decoder;
>  extern const FFCodec ff_vp9_v4l2m2m_decoder;
>  extern const FFCodec ff_vqa_decoder;
> +extern const FFCodec ff_vqc_decoder;
>  extern const FFCodec ff_wbmp_decoder;
>  extern const FFCodec ff_wbmp_encoder;
>  extern const FFCodec ff_webp_decoder;
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index 93b18f9072..24a0433dba 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -1916,6 +1916,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
>          .long_name = NULL_IF_CONFIG_SMALL("Media 100i"),
>          .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
>      },
> +    {
> +        .id        = AV_CODEC_ID_VQC,
> +        .type      = AVMEDIA_TYPE_VIDEO,
> +        .name      = "vqc",
> +        .long_name = NULL_IF_CONFIG_SMALL("ViewQuest VQC"),
> +        .props     = AV_CODEC_PROP_LOSSY,
> +    },
>  
>      /* various PCM "codecs" */
>      {
> diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
> index 82874daaa3..f436a2b624 100644
> --- a/libavcodec/codec_id.h
> +++ b/libavcodec/codec_id.h
> @@ -319,6 +319,7 @@ enum AVCodecID {
>      AV_CODEC_ID_RADIANCE_HDR,
>      AV_CODEC_ID_WBMP,
>      AV_CODEC_ID_MEDIA100,
> +    AV_CODEC_ID_VQC,
>  
>      /* various PCM "codecs" */
>      AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
> diff --git a/libavcodec/vqcdec.c b/libavcodec/vqcdec.c
> new file mode 100644
> index 0000000000..7297c1387d
> --- /dev/null
> +++ b/libavcodec/vqcdec.c
> @@ -0,0 +1,443 @@
> +/*
> + * ViewQuest VQC decoder
> + * Copyright (C) 2022 Peter Ross
> + *
> + * 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 "avcodec.h"
> +#include "get_bits.h"
> +#include "codec_internal.h"
> +#include "decode.h"
> +#include "libavutil/thread.h"
> +
> +#define VECTOR_VLC_BITS 6
> +
> +static const uint8_t vector_codes[] = {
> +       0,    4,    5,    6,    7,    2,  0xc,  0xd,
> +    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
> +};
> +
> +static const uint8_t vector_nbits[] = {
> +    2, 4, 4, 4, 4, 2, 4, 4,
> +    6, 6, 6, 6, 6, 6, 6, 6
> +};
> +
> +enum {
> +    SKIP_3 = 0x10,
> +    SKIP_4,
> +    SKIP_5,
> +    SKIP_6,
> +    STOP_RUN,
> +    SIGNED_8BIT,
> +    SIGNED_6BIT
> +};
> +
> +static const int8_t vector_symbols[] = {
> +    0, SKIP_3, SKIP_4, SKIP_5, SKIP_6, STOP_RUN, 1, -1,
> +    2, 3, 4, SIGNED_8BIT, -2, -3, -4, SIGNED_6BIT
> +};
> +
> +static VLC vector_vlc;
> +
> +static av_cold void vqc_init_static_data(void)
> +{
> +    static VLCElem code_table[64];
> +
> +    vector_vlc.table = code_table;
> +    vector_vlc.table_allocated = FF_ARRAY_ELEMS(code_table);
> +    ff_init_vlc_sparse(&vector_vlc, VECTOR_VLC_BITS, FF_ARRAY_ELEMS(vector_codes),
> +                       vector_nbits, 1, 1,
> +                       vector_codes, 1, 1,
> +                       vector_symbols, 1, 1,
> +                       INIT_VLC_USE_NEW_STATIC);
> +}

1. Some of your symbols are negative, yet ff_init_vlc_sparse will read
them as uint8_t, so that get_vlc2() actually returns e.g. 255 for the -1
symbol. This seems to work fine, as you are storing the result in an
uint8_t anyway. But it contains a surprise, so maybe it should be
documented?
2. Your code table is unnecessary, as the codes are already ordered from
left to right in the tree with no gaps, so that the codes can be easily
calculated from the lengths at runtime. In fact, the following should be
equivalent to your current code:
INIT_VLC_STATIC_FROM_LENGTHS(&vector_vlc, VECTOR_VLC_BITS,
FF_ARRAY_ELEMS(vector_nbits),
                             vector_nbits, 1,
                             vector_symbols, 1, 1,
                             0, 0, 1 << VECTOR_VLC_BITS);

> +
> +typedef struct VqcContext {
> +    AVFrame *frame;
> +    int16_t codebook[4][256];

Moving codebook to the end would likely reduce codesize (at least on x86
and x64, as the pointer + offset addressing mode only needs a one-byte
offset if offset is < 128).

> +    uint8_t * vectors;
> +    int16_t * coeff, *tmp1, *tmp2;
> +} VqcContext;
> +
> +static av_cold int vqc_decode_init(AVCodecContext * avctx)
> +{
> +    static AVOnce init_static_once = AV_ONCE_INIT;
> +    VqcContext *s = avctx->priv_data;
> +
> +    s->vectors = av_malloc((avctx->width * avctx->height * 3) / 2);
> +    if (!s->vectors)
> +        return AVERROR(ENOMEM);
> +
> +    s->coeff = av_malloc(2 * avctx->width * sizeof(int16_t));
> +    if (!s->coeff)
> +        return AVERROR(ENOMEM);
> +
> +    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> +    if (!s->tmp1)
> +        return AVERROR(ENOMEM);
> +
> +    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> +    if (!s->tmp2)
> +        return AVERROR(ENOMEM);
> +
> +    avctx->pix_fmt = AV_PIX_FMT_YUV420P;
> +    s->frame = av_frame_alloc();
> +    if (!s->frame)
> +        return AVERROR(ENOMEM);
> +
> +    ff_thread_once(&init_static_once, vqc_init_static_data);
> +
> +    return 0;
> +}
> +
> +static int seed_pow1(int x)
> +{
> +    return x >= 1 && x <= 5 ? 1 << x : 0;
> +}
> +
> +static int seed_pow2(int x)
> +{
> +    return x >= 1 && x <= 4 ? 1 << x : 1;
> +}
> +
> +static int bias(int x, int c)
> +{
> +    if (x < 0)
> +        return x - c;
> +    else if (x > 0)
> +        return x + c;
> +    else
> +        return 0;
> +}
> +
> +static void seed_codebooks(VqcContext * s, const int * seed)
> +{
> +    int book1 = -256 * seed[3];
> +    int book2 = -128 * seed[4];
> +    int book3 = -128 * seed[5];
> +    int book4 = -128 * seed[6];
> +
> +    for (int i = -128; i < 128; i++) {
> +        s->codebook[0][(uint8_t)i] = book1;
> +        s->codebook[1][(uint8_t)i] = bias(book2, seed[0]);
> +        s->codebook[2][(uint8_t)i] = bias(book3, seed[1]);
> +        s->codebook[3][(uint8_t)i] = bias(book4, seed[2]);
> +
> +        book1 += 2 * seed[3];
> +        book2 += seed[4];
> +        book3 += seed[5];
> +        book4 += seed[6];
> +    }
> +}
> +
> +static void decode_vectors(VqcContext * s, const uint8_t * buf, int size, int width, int height)
> +{
> +    GetBitContext gb;
> +    uint8_t * vectors = s->vectors;
> +    uint8_t * vectors_end = s->vectors + (width * height * 3) / 2;
> +
> +    memset(vectors, 0, 3 * width * height / 2);
> +
> +    init_get_bits8(&gb, buf, size);
> +
> +    for (int i = 0; i < 3 * width * height / 2 / 32; i++) {
> +        uint8_t * dst = vectors;
> +        int code;
> +
> +        *dst++ = get_bits(&gb, 8);
> +        *dst++ = get_bits(&gb, 8);
> +
> +        while (show_bits(&gb, 2) != 2) {
> +
> +            if (dst >= vectors_end - 1)
> +                return;
> +
> +            if (show_bits(&gb, 8) < 16) {
> +                *dst++ = 0;
> +                *dst++ = 0;
> +                skip_bits(&gb, 4);
> +                continue;
> +            }
> +
> +            code = get_vlc2(&gb, vector_vlc.table, VECTOR_VLC_BITS, 1);
> +            switch(code) {
> +            case SKIP_3: dst += 3; break;
> +            case SKIP_4: dst += 4; break;
> +            case SKIP_5: dst += 5; break;
> +            case SKIP_6: dst += 6; break;
> +            case SIGNED_8BIT: *dst++ = get_bits(&gb, 8); break;
> +            case SIGNED_6BIT: *dst++ = get_sbits(&gb, 6); break;
> +            default:
> +                *dst++ = code;
> +            }
> +        }
> +
> +        skip_bits(&gb, 2);
> +        vectors += 32;
> +    }
> +}
> +
> +static void load_coeffs(VqcContext * s, const uint8_t * v, int width, int coeff_width)
> +{
> +    int16_t * c0     = s->coeff;
> +    int16_t * c1     = s->coeff + coeff_width;
> +    int16_t * c0_125 = s->coeff + (coeff_width >> 3);
> +    int16_t * c1_125 = s->coeff + coeff_width + (coeff_width >> 3);
> +    int16_t * c0_25  = s->coeff + (coeff_width >> 2);
> +    int16_t * c1_25 =  s->coeff + coeff_width + (coeff_width >> 2);
> +    int16_t * c0_5  =  s->coeff + (coeff_width >> 1);
> +    int16_t * c1_5  =  s->coeff + coeff_width + (coeff_width >> 1);
> +
> +    for (int i = 0; i < width; i++) {
> +        c0[0] = s->codebook[0][v[0]];
> +        c0[1] = s->codebook[0][v[1]];
> +        c0 += 2;
> +
> +        c1[0] = s->codebook[0][v[2]];
> +        c1[1] = s->codebook[0][v[3]];
> +        c1 += 2;
> +
> +        c0_125[0] = s->codebook[1][v[4]];
> +        c0_125[1] = s->codebook[1][v[5]];
> +        c0_125 += 2;
> +
> +        c1_125[0] = s->codebook[1][v[6]];
> +        c1_125[1] = s->codebook[1][v[7]];
> +        c1_125 += 2;
> +
> +        c0_25[0] = s->codebook[2][v[8]];
> +        c0_25[1] = s->codebook[2][v[9]];
> +        c0_25[2] = s->codebook[2][v[10]];
> +        c0_25[3] = s->codebook[2][v[11]];
> +        c0_25 += 4;
> +
> +        c1_25[0] = s->codebook[2][v[12]];
> +        c1_25[1] = s->codebook[2][v[13]];
> +        c1_25[2] = s->codebook[2][v[14]];
> +        c1_25[3] = s->codebook[2][v[15]];
> +        c1_25 += 4;
> +
> +        if (v[16] | v[17] | v[18] | v[19]) {
> +            c0_5[0] = s->codebook[3][v[16]];
> +            c0_5[1] = s->codebook[3][v[17]];
> +            c0_5[2] = s->codebook[3][v[18]];
> +            c0_5[3] = s->codebook[3][v[19]];
> +        } else {
> +            c0_5[0] = c0_5[1] = c0_5[2] = c0_5[3] = 0;
> +        }
> +
> +        if (v[20] | v[21] | v[22] | v[23]) {
> +            c0_5[4] = s->codebook[3][v[20]];
> +            c0_5[5] = s->codebook[3][v[21]];
> +            c0_5[6] = s->codebook[3][v[22]];
> +            c0_5[7] = s->codebook[3][v[23]];
> +        } else {
> +            c0_5[4] = c0_5[5] = c0_5[6] = c0_5[7] = 0;
> +        }
> +        c0_5 += 8;
> +
> +        if (v[24] | v[25] | v[26] | v[27]) {
> +            c1_5[0] = s->codebook[3][v[24]];
> +            c1_5[1] = s->codebook[3][v[25]];
> +            c1_5[2] = s->codebook[3][v[26]];
> +            c1_5[3] = s->codebook[3][v[27]];
> +        } else {
> +            c1_5[0] = c1_5[1] = c1_5[2] = c1_5[3] = 0;
> +        }
> +
> +        if (v[28] | v[29] | v[30] | v[31]) {
> +            c1_5[4] = s->codebook[3][v[28]];
> +            c1_5[5] = s->codebook[3][v[29]];
> +            c1_5[6] = s->codebook[3][v[30]];
> +            c1_5[7] = s->codebook[3][v[31]];
> +        } else {
> +            c1_5[4] = c1_5[5] = c1_5[6] = c1_5[7] = 0;
> +        }
> +        c1_5 += 8;
> +
> +        v += 32;
> +    }
> +}
> +
> +static void transform1(const int16_t * a, const int16_t * b, int16_t * dst, int width)
> +{
> +    int s0 = a[0] + (b[0] >> 1);
> +
> +    for (int i = 0; i < width / 2 - 1; i++) {
> +        dst[i * 2] = s0;
> +        s0 = a[i + 1] + ((b[i] + b[i + 1]) >> 1);
> +        dst[i * 2 + 1] = ((dst[i * 2] + s0) >> 1) - 2 * b[i];
> +    }
> +
> +    dst[width - 2] = s0;
> +    dst[width - 1] = a[width / 2 - 1] + ((b[width / 2 - 2] - 2 * b[width / 2 - 1]) >> 2) - b[width / 2 - 1];
> +}
> +
> +static uint8_t sat1(int x)
> +{
> +    return x >= -128 ? x <= 127 ? x + 0x80 : 0xFF : 0x00;
> +}
> +
> +static uint8_t sat2(int x)
> +{
> +    return x >= -128 ? x <= 127 ? x + 0x80 : 0x00 : 0xFF;
> +}
> +
> +static void transform2(const int16_t * a, const int16_t * b, uint8_t * dst, int width)
> +{
> +    int s0 = a[0] + (b[0] >> 1);
> +    int tmp;
> +
> +    for (int i = 0; i < width / 2 - 1; i++) {
> +        dst[i * 2] = sat1(s0);
> +        tmp = a[i + 1] + ((b[i] + b[i + 1]) >> 1);
> +        dst[i * 2 + 1] = sat1(((tmp + s0) >> 1) - 2 * b[i]);
> +        s0 = tmp;
> +    }
> +
> +    dst[width - 2] = sat2(s0);
> +    dst[width - 1] = sat2(a[width / 2 - 1] + ((b[width / 2 - 2] - 2 * b[width / 2 - 1]) >> 2) - b[width / 2 - 1]);
> +}
> +
> +static void decode_strip(VqcContext * s, uint8_t * dst, int stride, int width)
> +{
> +    const int16_t * coeff;
> +
> +    for (int i = 0; i < width; i++) {
> +        int v0 = s->coeff[i];
> +        int v1 = s->coeff[width + i];
> +        s->coeff[i] = v0 - v1;
> +        s->coeff[width + i] = v0 + v1;
> +    }
> +
> +    coeff = s->coeff;
> +
> +    transform1(coeff, coeff + width / 8, s->tmp1, width / 4);
> +    transform1(s->tmp1, coeff + width / 4, s->tmp2, width / 2);
> +    transform2(s->tmp2, coeff + width / 2, dst, width);
> +
> +    coeff += width;
> +    dst += stride;
> +
> +    transform1(coeff, coeff + width / 8, s->tmp1, width / 4);
> +    transform1(s->tmp1, coeff + width / 4, s->tmp2, width / 2);
> +    transform2(s->tmp2, coeff + width / 2, dst, width);
> +}
> +
> +static void decode_frame(VqcContext * s, int width, int height)
> +{
> +    uint8_t * vectors = s->vectors;
> +    uint8_t * y = s->frame->data[0];
> +    uint8_t * u = s->frame->data[1];
> +    uint8_t * v = s->frame->data[2];
> +
> +    for (int j = 0; j < height / 4; j++) {
> +        load_coeffs(s, vectors, width / 16, width);
> +        decode_strip(s, y, s->frame->linesize[0], width);
> +        vectors += 2 * width;
> +        y += 2 * s->frame->linesize[0];
> +
> +        load_coeffs(s, vectors, width / 32, width / 2);
> +        decode_strip(s, u, s->frame->linesize[1], width / 2);
> +        vectors += width;
> +        u += 2 * s->frame->linesize[1];
> +
> +        load_coeffs(s, vectors, width / 16, width);
> +        decode_strip(s, y, s->frame->linesize[0], width);
> +        vectors += 2 * width;
> +        y += 2 * s->frame->linesize[0];
> +
> +        load_coeffs(s, vectors, width / 32, width / 2);
> +        decode_strip(s, v, s->frame->linesize[2], width / 2);
> +        vectors += width;
> +        v += 2 * s->frame->linesize[2];
> +    }
> +}
> +
> +static int vqc_decode_frame(AVCodecContext *avctx, AVFrame * rframe,
> +                            int * got_frame, AVPacket * avpkt)
> +{
> +    VqcContext *s = avctx->priv_data;
> +    int ret;
> +    uint8_t * buf = avpkt->data;

Missing const (the packet need not be writable, so you must not modify
it's data).

> +    int cache, seed[7], gamma, contrast;
> +
> +    if (avpkt->size < 7)
> +        return AVERROR_INVALIDDATA;
> +
> +    if ((ret = ff_reget_buffer(avctx, s->frame, 0)) < 0)
> +        return ret;
> +
> +    av_log(avctx, AV_LOG_DEBUG, "VQC%d format\n", (buf[2] & 1) + 1);
> +
> +    if (((buf[0] >> 1) & 7) != 5) {
> +        avpriv_request_sample(avctx, "subversion != 5\n");
> +        return AVERROR_PATCHWELCOME;
> +    }
> +
> +    cache = buf[4] | (AV_RL16(buf + 5) << 8);

Looks like AV_RL24(buf + 4) to me.

> +    seed[2] = seed_pow1((cache >> 1) & 7);
> +    seed[1] = seed_pow1((cache >> 4) & 7);
> +    seed[0] = seed_pow1((cache >> 7) & 7);
> +    seed[6] = seed_pow2((cache >> 10) & 7);
> +    seed[5] = seed_pow2((cache >> 13) & 7);
> +    seed[4] = seed_pow2((cache >> 16) & 7);
> +    seed[3] = seed_pow2((cache >> 19) & 7);
> +
> +    gamma = buf[0] >> 4;
> +    contrast = AV_RL16(buf + 2) >> 1;
> +    if (gamma || contrast)
> +        avpriv_request_sample(avctx, "gamma=0x%x, contrast=0x%x\n", gamma, contrast);
> +
> +    seed_codebooks(s, seed);
> +    decode_vectors(s, buf + 7, avpkt->size - 7, avctx->width, avctx->height);
> +    decode_frame(s, avctx->width, avctx->height);
> +
> +    if ((ret = av_frame_ref(rframe, s->frame)) < 0)
> +        return ret;
> +
> +    *got_frame = 1;
> +
> +    return avpkt->size;
> +}
> +
> +static av_cold int vqc_decode_end(AVCodecContext * avctx)
> +{
> +    VqcContext *s = avctx->priv_data;
> +
> +    av_freep(&s->vectors);
> +    av_freep(&s->coeff);
> +    av_freep(&s->tmp1);
> +    av_freep(&s->tmp2);
> +    av_frame_free(&s->frame);
> +
> +    return 0;
> +}
> +
> +const FFCodec ff_vqc_decoder = {
> +    .p.name         = "vqc",
> +    CODEC_LONG_NAME("ViewQuest VQC"),
> +    .p.type         = AVMEDIA_TYPE_VIDEO,
> +    .p.id           = AV_CODEC_ID_VQC,
> +    .priv_data_size = sizeof(VqcContext),
> +    .init           = vqc_decode_init,
> +    .close          = vqc_decode_end,
> +    FF_CODEC_DECODE_CB(vqc_decode_frame),
> +    .p.capabilities = AV_CODEC_CAP_DR1,
> +    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
> +};
Tomas Härdin Oct. 7, 2022, 9:38 a.m. UTC | #2
tor 2022-10-06 klockan 21:45 +1100 skrev Peter Ross:
> 
> +static av_cold int vqc_decode_init(AVCodecContext * avctx)
> +{
> +    static AVOnce init_static_once = AV_ONCE_INIT;
> +    VqcContext *s = avctx->priv_data;
> +
> +    s->vectors = av_malloc((avctx->width * avctx->height * 3) / 2);
> +    if (!s->vectors)
> +        return AVERROR(ENOMEM);
> +
> +    s->coeff = av_malloc(2 * avctx->width * sizeof(int16_t));
> +    if (!s->coeff)
> +        return AVERROR(ENOMEM);
> +
> +    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> +    if (!s->tmp1)
> +        return AVERROR(ENOMEM);
> +
> +    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);

av_malloc_array() perhaps? Not that these are likely to overflow since
max pixels is usually far away from INT_MAX

> +static uint8_t sat1(int x)
> +{
> +    return x >= -128 ? x <= 127 ? x + 0x80 : 0xFF : 0x00;
> +}

Use av_clip*()

> +static uint8_t sat2(int x)
> +{
> +    return x >= -128 ? x <= 127 ? x + 0x80 : 0x00 : 0xFF;
> +}

Doesn't look like av_clip() will work here. Or?

> +static int vqc_decode_frame(AVCodecContext *avctx, AVFrame * rframe,
> +                            int * got_frame, AVPacket * avpkt)
> +{
> +    VqcContext *s = avctx->priv_data;
> +    int ret;
> +    uint8_t * buf = avpkt->data;
> +    int cache, seed[7], gamma, contrast;
> +
> +    if (avpkt->size < 7)
> +        return AVERROR_INVALIDDATA;
> +
> +    if ((ret = ff_reget_buffer(avctx, s->frame, 0)) < 0)
> +        return ret;
> +
> +    av_log(avctx, AV_LOG_DEBUG, "VQC%d format\n", (buf[2] & 1) + 1);
> +
> +    if (((buf[0] >> 1) & 7) != 5) {
> +        avpriv_request_sample(avctx, "subversion != 5\n");
> +        return AVERROR_PATCHWELCOME;
> +    }
> +
> +    cache = buf[4] | (AV_RL16(buf + 5) << 8);

AV_RL24()

No tests? Looks like samp.avi would make a fine addition to FATE

/Tomas
Andreas Rheinhardt Oct. 7, 2022, 10:38 a.m. UTC | #3
Tomas Härdin:
> tor 2022-10-06 klockan 21:45 +1100 skrev Peter Ross:
>>
>> +static av_cold int vqc_decode_init(AVCodecContext * avctx)
>> +{
>> +    static AVOnce init_static_once = AV_ONCE_INIT;
>> +    VqcContext *s = avctx->priv_data;
>> +
>> +    s->vectors = av_malloc((avctx->width * avctx->height * 3) / 2);
>> +    if (!s->vectors)
>> +        return AVERROR(ENOMEM);
>> +
>> +    s->coeff = av_malloc(2 * avctx->width * sizeof(int16_t));
>> +    if (!s->coeff)
>> +        return AVERROR(ENOMEM);
>> +
>> +    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
>> +    if (!s->tmp1)
>> +        return AVERROR(ENOMEM);
>> +
>> +    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> 
> av_malloc_array() perhaps? Not that these are likely to overflow since
> max pixels is usually far away from INT_MAX
> 

Actually, sizeof(int16_t) is guaranteed to be two, so that
sizeof(int16_t) / 2 is always one.

- Andreas
Tomas Härdin Oct. 7, 2022, 12:29 p.m. UTC | #4
fre 2022-10-07 klockan 12:38 +0200 skrev Andreas Rheinhardt:
> Tomas Härdin:
> > tor 2022-10-06 klockan 21:45 +1100 skrev Peter Ross:
> > > 
> > > +static av_cold int vqc_decode_init(AVCodecContext * avctx)
> > > +{
> > > +    static AVOnce init_static_once = AV_ONCE_INIT;
> > > +    VqcContext *s = avctx->priv_data;
> > > +
> > > +    s->vectors = av_malloc((avctx->width * avctx->height * 3) /
> > > 2);
> > > +    if (!s->vectors)
> > > +        return AVERROR(ENOMEM);
> > > +
> > > +    s->coeff = av_malloc(2 * avctx->width * sizeof(int16_t));
> > > +    if (!s->coeff)
> > > +        return AVERROR(ENOMEM);
> > > +
> > > +    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> > > +    if (!s->tmp1)
> > > +        return AVERROR(ENOMEM);
> > > +
> > > +    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> > 
> > av_malloc_array() perhaps? Not that these are likely to overflow
> > since
> > max pixels is usually far away from INT_MAX
> > 
> 
> Actually, sizeof(int16_t) is guaranteed to be two, so that
> sizeof(int16_t) / 2 is always one.

Not on machines where CHAR_BIT >= 16. But we could enforce that during
configuration

/Tomas
Andreas Rheinhardt Oct. 7, 2022, 12:33 p.m. UTC | #5
Tomas Härdin:
> fre 2022-10-07 klockan 12:38 +0200 skrev Andreas Rheinhardt:
>> Tomas Härdin:
>>> tor 2022-10-06 klockan 21:45 +1100 skrev Peter Ross:
>>>>
>>>> +static av_cold int vqc_decode_init(AVCodecContext * avctx)
>>>> +{
>>>> +    static AVOnce init_static_once = AV_ONCE_INIT;
>>>> +    VqcContext *s = avctx->priv_data;
>>>> +
>>>> +    s->vectors = av_malloc((avctx->width * avctx->height * 3) /
>>>> 2);
>>>> +    if (!s->vectors)
>>>> +        return AVERROR(ENOMEM);
>>>> +
>>>> +    s->coeff = av_malloc(2 * avctx->width * sizeof(int16_t));
>>>> +    if (!s->coeff)
>>>> +        return AVERROR(ENOMEM);
>>>> +
>>>> +    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
>>>> +    if (!s->tmp1)
>>>> +        return AVERROR(ENOMEM);
>>>> +
>>>> +    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);
>>>
>>> av_malloc_array() perhaps? Not that these are likely to overflow
>>> since
>>> max pixels is usually far away from INT_MAX
>>>
>>
>> Actually, sizeof(int16_t) is guaranteed to be two, so that
>> sizeof(int16_t) / 2 is always one.
> 
> Not on machines where CHAR_BIT >= 16. But we could enforce that during
> configuration
> 

The fixed-wide integers are required by the spec to have no padding. We
rely on uint8_t/int8_t to be present and so CHAR_BIT divides 8, but it
has to be >= 8, too, so it is eight. (Apart from that, we probably rely
on CHAR_BIT to be eight in thousands of other places, too.)

- Andreas
Tomas Härdin Oct. 7, 2022, 12:48 p.m. UTC | #6
fre 2022-10-07 klockan 14:33 +0200 skrev Andreas Rheinhardt:
> Tomas Härdin:
> > fre 2022-10-07 klockan 12:38 +0200 skrev Andreas Rheinhardt:
> > > Tomas Härdin:
> > > > tor 2022-10-06 klockan 21:45 +1100 skrev Peter Ross:
> > > > > 
> > > > > +static av_cold int vqc_decode_init(AVCodecContext * avctx)
> > > > > +{
> > > > > +    static AVOnce init_static_once = AV_ONCE_INIT;
> > > > > +    VqcContext *s = avctx->priv_data;
> > > > > +
> > > > > +    s->vectors = av_malloc((avctx->width * avctx->height *
> > > > > 3) /
> > > > > 2);
> > > > > +    if (!s->vectors)
> > > > > +        return AVERROR(ENOMEM);
> > > > > +
> > > > > +    s->coeff = av_malloc(2 * avctx->width *
> > > > > sizeof(int16_t));
> > > > > +    if (!s->coeff)
> > > > > +        return AVERROR(ENOMEM);
> > > > > +
> > > > > +    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> > > > > +    if (!s->tmp1)
> > > > > +        return AVERROR(ENOMEM);
> > > > > +
> > > > > +    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);
> > > > 
> > > > av_malloc_array() perhaps? Not that these are likely to
> > > > overflow
> > > > since
> > > > max pixels is usually far away from INT_MAX
> > > > 
> > > 
> > > Actually, sizeof(int16_t) is guaranteed to be two, so that
> > > sizeof(int16_t) / 2 is always one.
> > 
> > Not on machines where CHAR_BIT >= 16. But we could enforce that
> > during
> > configuration
> > 
> 
> The fixed-wide integers are required by the spec to have no padding.
> We
> rely on uint8_t/int8_t to be present and so CHAR_BIT divides 8, but
> it
> has to be >= 8, too, so it is eight. (Apart from that, we probably
> rely
> on CHAR_BIT to be eight in thousands of other places, too.)

Right, the presence of (u)int8_t together with ISO/IEC 9899:1999 saying
CHAR_BIT >= 8 does imply CHAR_BIT == 8.

/Tomas
diff mbox series

Patch

diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index b005cb6c3e..8399fcb6b7 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1339,6 +1339,7 @@  following image formats are supported:
 @item TwinVQ (VQF flavor)    @tab     @tab  X
 @item VIMA                   @tab     @tab  X
     @tab Used in LucasArts SMUSH animations.
+@item ViewQuest VQC          @tab     @tab  X
 @item Vorbis                 @tab  E  @tab  X
     @tab A native but very primitive encoder exists.
 @item Voxware MetaSound      @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 592d9347f6..b4c28f166f 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -761,6 +761,7 @@  OBJS-$(CONFIG_VP9_QSV_ENCODER)         += qsvenc_vp9.o
 OBJS-$(CONFIG_VPLAYER_DECODER)         += textdec.o ass.o
 OBJS-$(CONFIG_VP9_V4L2M2M_DECODER)     += v4l2_m2m_dec.o
 OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
+OBJS-$(CONFIG_VQC_DECODER)             += vqcdec.o
 OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o wavpackdata.o dsd.o
 OBJS-$(CONFIG_WAVPACK_ENCODER)         += wavpackdata.o wavpackenc.o
 OBJS-$(CONFIG_WBMP_DECODER)            += wbmpdec.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index cfeb01ac1c..46ad3b5a25 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -381,6 +381,7 @@  extern const FFCodec ff_vp9_decoder;
 extern const FFCodec ff_vp9_rkmpp_decoder;
 extern const FFCodec ff_vp9_v4l2m2m_decoder;
 extern const FFCodec ff_vqa_decoder;
+extern const FFCodec ff_vqc_decoder;
 extern const FFCodec ff_wbmp_decoder;
 extern const FFCodec ff_wbmp_encoder;
 extern const FFCodec ff_webp_decoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 93b18f9072..24a0433dba 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1916,6 +1916,13 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("Media 100i"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_VQC,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "vqc",
+        .long_name = NULL_IF_CONFIG_SMALL("ViewQuest VQC"),
+        .props     = AV_CODEC_PROP_LOSSY,
+    },
 
     /* various PCM "codecs" */
     {
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 82874daaa3..f436a2b624 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -319,6 +319,7 @@  enum AVCodecID {
     AV_CODEC_ID_RADIANCE_HDR,
     AV_CODEC_ID_WBMP,
     AV_CODEC_ID_MEDIA100,
+    AV_CODEC_ID_VQC,
 
     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/vqcdec.c b/libavcodec/vqcdec.c
new file mode 100644
index 0000000000..7297c1387d
--- /dev/null
+++ b/libavcodec/vqcdec.c
@@ -0,0 +1,443 @@ 
+/*
+ * ViewQuest VQC decoder
+ * Copyright (C) 2022 Peter Ross
+ *
+ * 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 "avcodec.h"
+#include "get_bits.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "libavutil/thread.h"
+
+#define VECTOR_VLC_BITS 6
+
+static const uint8_t vector_codes[] = {
+       0,    4,    5,    6,    7,    2,  0xc,  0xd,
+    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
+};
+
+static const uint8_t vector_nbits[] = {
+    2, 4, 4, 4, 4, 2, 4, 4,
+    6, 6, 6, 6, 6, 6, 6, 6
+};
+
+enum {
+    SKIP_3 = 0x10,
+    SKIP_4,
+    SKIP_5,
+    SKIP_6,
+    STOP_RUN,
+    SIGNED_8BIT,
+    SIGNED_6BIT
+};
+
+static const int8_t vector_symbols[] = {
+    0, SKIP_3, SKIP_4, SKIP_5, SKIP_6, STOP_RUN, 1, -1,
+    2, 3, 4, SIGNED_8BIT, -2, -3, -4, SIGNED_6BIT
+};
+
+static VLC vector_vlc;
+
+static av_cold void vqc_init_static_data(void)
+{
+    static VLCElem code_table[64];
+
+    vector_vlc.table = code_table;
+    vector_vlc.table_allocated = FF_ARRAY_ELEMS(code_table);
+    ff_init_vlc_sparse(&vector_vlc, VECTOR_VLC_BITS, FF_ARRAY_ELEMS(vector_codes),
+                       vector_nbits, 1, 1,
+                       vector_codes, 1, 1,
+                       vector_symbols, 1, 1,
+                       INIT_VLC_USE_NEW_STATIC);
+}
+
+typedef struct VqcContext {
+    AVFrame *frame;
+    int16_t codebook[4][256];
+    uint8_t * vectors;
+    int16_t * coeff, *tmp1, *tmp2;
+} VqcContext;
+
+static av_cold int vqc_decode_init(AVCodecContext * avctx)
+{
+    static AVOnce init_static_once = AV_ONCE_INIT;
+    VqcContext *s = avctx->priv_data;
+
+    s->vectors = av_malloc((avctx->width * avctx->height * 3) / 2);
+    if (!s->vectors)
+        return AVERROR(ENOMEM);
+
+    s->coeff = av_malloc(2 * avctx->width * sizeof(int16_t));
+    if (!s->coeff)
+        return AVERROR(ENOMEM);
+
+    s->tmp1 = av_malloc(avctx->width * sizeof(int16_t) / 2);
+    if (!s->tmp1)
+        return AVERROR(ENOMEM);
+
+    s->tmp2 = av_malloc(avctx->width * sizeof(int16_t) / 2);
+    if (!s->tmp2)
+        return AVERROR(ENOMEM);
+
+    avctx->pix_fmt = AV_PIX_FMT_YUV420P;
+    s->frame = av_frame_alloc();
+    if (!s->frame)
+        return AVERROR(ENOMEM);
+
+    ff_thread_once(&init_static_once, vqc_init_static_data);
+
+    return 0;
+}
+
+static int seed_pow1(int x)
+{
+    return x >= 1 && x <= 5 ? 1 << x : 0;
+}
+
+static int seed_pow2(int x)
+{
+    return x >= 1 && x <= 4 ? 1 << x : 1;
+}
+
+static int bias(int x, int c)
+{
+    if (x < 0)
+        return x - c;
+    else if (x > 0)
+        return x + c;
+    else
+        return 0;
+}
+
+static void seed_codebooks(VqcContext * s, const int * seed)
+{
+    int book1 = -256 * seed[3];
+    int book2 = -128 * seed[4];
+    int book3 = -128 * seed[5];
+    int book4 = -128 * seed[6];
+
+    for (int i = -128; i < 128; i++) {
+        s->codebook[0][(uint8_t)i] = book1;
+        s->codebook[1][(uint8_t)i] = bias(book2, seed[0]);
+        s->codebook[2][(uint8_t)i] = bias(book3, seed[1]);
+        s->codebook[3][(uint8_t)i] = bias(book4, seed[2]);
+
+        book1 += 2 * seed[3];
+        book2 += seed[4];
+        book3 += seed[5];
+        book4 += seed[6];
+    }
+}
+
+static void decode_vectors(VqcContext * s, const uint8_t * buf, int size, int width, int height)
+{
+    GetBitContext gb;
+    uint8_t * vectors = s->vectors;
+    uint8_t * vectors_end = s->vectors + (width * height * 3) / 2;
+
+    memset(vectors, 0, 3 * width * height / 2);
+
+    init_get_bits8(&gb, buf, size);
+
+    for (int i = 0; i < 3 * width * height / 2 / 32; i++) {
+        uint8_t * dst = vectors;
+        int code;
+
+        *dst++ = get_bits(&gb, 8);
+        *dst++ = get_bits(&gb, 8);
+
+        while (show_bits(&gb, 2) != 2) {
+
+            if (dst >= vectors_end - 1)
+                return;
+
+            if (show_bits(&gb, 8) < 16) {
+                *dst++ = 0;
+                *dst++ = 0;
+                skip_bits(&gb, 4);
+                continue;
+            }
+
+            code = get_vlc2(&gb, vector_vlc.table, VECTOR_VLC_BITS, 1);
+            switch(code) {
+            case SKIP_3: dst += 3; break;
+            case SKIP_4: dst += 4; break;
+            case SKIP_5: dst += 5; break;
+            case SKIP_6: dst += 6; break;
+            case SIGNED_8BIT: *dst++ = get_bits(&gb, 8); break;
+            case SIGNED_6BIT: *dst++ = get_sbits(&gb, 6); break;
+            default:
+                *dst++ = code;
+            }
+        }
+
+        skip_bits(&gb, 2);
+        vectors += 32;
+    }
+}
+
+static void load_coeffs(VqcContext * s, const uint8_t * v, int width, int coeff_width)
+{
+    int16_t * c0     = s->coeff;
+    int16_t * c1     = s->coeff + coeff_width;
+    int16_t * c0_125 = s->coeff + (coeff_width >> 3);
+    int16_t * c1_125 = s->coeff + coeff_width + (coeff_width >> 3);
+    int16_t * c0_25  = s->coeff + (coeff_width >> 2);
+    int16_t * c1_25 =  s->coeff + coeff_width + (coeff_width >> 2);
+    int16_t * c0_5  =  s->coeff + (coeff_width >> 1);
+    int16_t * c1_5  =  s->coeff + coeff_width + (coeff_width >> 1);
+
+    for (int i = 0; i < width; i++) {
+        c0[0] = s->codebook[0][v[0]];
+        c0[1] = s->codebook[0][v[1]];
+        c0 += 2;
+
+        c1[0] = s->codebook[0][v[2]];
+        c1[1] = s->codebook[0][v[3]];
+        c1 += 2;
+
+        c0_125[0] = s->codebook[1][v[4]];
+        c0_125[1] = s->codebook[1][v[5]];
+        c0_125 += 2;
+
+        c1_125[0] = s->codebook[1][v[6]];
+        c1_125[1] = s->codebook[1][v[7]];
+        c1_125 += 2;
+
+        c0_25[0] = s->codebook[2][v[8]];
+        c0_25[1] = s->codebook[2][v[9]];
+        c0_25[2] = s->codebook[2][v[10]];
+        c0_25[3] = s->codebook[2][v[11]];
+        c0_25 += 4;
+
+        c1_25[0] = s->codebook[2][v[12]];
+        c1_25[1] = s->codebook[2][v[13]];
+        c1_25[2] = s->codebook[2][v[14]];
+        c1_25[3] = s->codebook[2][v[15]];
+        c1_25 += 4;
+
+        if (v[16] | v[17] | v[18] | v[19]) {
+            c0_5[0] = s->codebook[3][v[16]];
+            c0_5[1] = s->codebook[3][v[17]];
+            c0_5[2] = s->codebook[3][v[18]];
+            c0_5[3] = s->codebook[3][v[19]];
+        } else {
+            c0_5[0] = c0_5[1] = c0_5[2] = c0_5[3] = 0;
+        }
+
+        if (v[20] | v[21] | v[22] | v[23]) {
+            c0_5[4] = s->codebook[3][v[20]];
+            c0_5[5] = s->codebook[3][v[21]];
+            c0_5[6] = s->codebook[3][v[22]];
+            c0_5[7] = s->codebook[3][v[23]];
+        } else {
+            c0_5[4] = c0_5[5] = c0_5[6] = c0_5[7] = 0;
+        }
+        c0_5 += 8;
+
+        if (v[24] | v[25] | v[26] | v[27]) {
+            c1_5[0] = s->codebook[3][v[24]];
+            c1_5[1] = s->codebook[3][v[25]];
+            c1_5[2] = s->codebook[3][v[26]];
+            c1_5[3] = s->codebook[3][v[27]];
+        } else {
+            c1_5[0] = c1_5[1] = c1_5[2] = c1_5[3] = 0;
+        }
+
+        if (v[28] | v[29] | v[30] | v[31]) {
+            c1_5[4] = s->codebook[3][v[28]];
+            c1_5[5] = s->codebook[3][v[29]];
+            c1_5[6] = s->codebook[3][v[30]];
+            c1_5[7] = s->codebook[3][v[31]];
+        } else {
+            c1_5[4] = c1_5[5] = c1_5[6] = c1_5[7] = 0;
+        }
+        c1_5 += 8;
+
+        v += 32;
+    }
+}
+
+static void transform1(const int16_t * a, const int16_t * b, int16_t * dst, int width)
+{
+    int s0 = a[0] + (b[0] >> 1);
+
+    for (int i = 0; i < width / 2 - 1; i++) {
+        dst[i * 2] = s0;
+        s0 = a[i + 1] + ((b[i] + b[i + 1]) >> 1);
+        dst[i * 2 + 1] = ((dst[i * 2] + s0) >> 1) - 2 * b[i];
+    }
+
+    dst[width - 2] = s0;
+    dst[width - 1] = a[width / 2 - 1] + ((b[width / 2 - 2] - 2 * b[width / 2 - 1]) >> 2) - b[width / 2 - 1];
+}
+
+static uint8_t sat1(int x)
+{
+    return x >= -128 ? x <= 127 ? x + 0x80 : 0xFF : 0x00;
+}
+
+static uint8_t sat2(int x)
+{
+    return x >= -128 ? x <= 127 ? x + 0x80 : 0x00 : 0xFF;
+}
+
+static void transform2(const int16_t * a, const int16_t * b, uint8_t * dst, int width)
+{
+    int s0 = a[0] + (b[0] >> 1);
+    int tmp;
+
+    for (int i = 0; i < width / 2 - 1; i++) {
+        dst[i * 2] = sat1(s0);
+        tmp = a[i + 1] + ((b[i] + b[i + 1]) >> 1);
+        dst[i * 2 + 1] = sat1(((tmp + s0) >> 1) - 2 * b[i]);
+        s0 = tmp;
+    }
+
+    dst[width - 2] = sat2(s0);
+    dst[width - 1] = sat2(a[width / 2 - 1] + ((b[width / 2 - 2] - 2 * b[width / 2 - 1]) >> 2) - b[width / 2 - 1]);
+}
+
+static void decode_strip(VqcContext * s, uint8_t * dst, int stride, int width)
+{
+    const int16_t * coeff;
+
+    for (int i = 0; i < width; i++) {
+        int v0 = s->coeff[i];
+        int v1 = s->coeff[width + i];
+        s->coeff[i] = v0 - v1;
+        s->coeff[width + i] = v0 + v1;
+    }
+
+    coeff = s->coeff;
+
+    transform1(coeff, coeff + width / 8, s->tmp1, width / 4);
+    transform1(s->tmp1, coeff + width / 4, s->tmp2, width / 2);
+    transform2(s->tmp2, coeff + width / 2, dst, width);
+
+    coeff += width;
+    dst += stride;
+
+    transform1(coeff, coeff + width / 8, s->tmp1, width / 4);
+    transform1(s->tmp1, coeff + width / 4, s->tmp2, width / 2);
+    transform2(s->tmp2, coeff + width / 2, dst, width);
+}
+
+static void decode_frame(VqcContext * s, int width, int height)
+{
+    uint8_t * vectors = s->vectors;
+    uint8_t * y = s->frame->data[0];
+    uint8_t * u = s->frame->data[1];
+    uint8_t * v = s->frame->data[2];
+
+    for (int j = 0; j < height / 4; j++) {
+        load_coeffs(s, vectors, width / 16, width);
+        decode_strip(s, y, s->frame->linesize[0], width);
+        vectors += 2 * width;
+        y += 2 * s->frame->linesize[0];
+
+        load_coeffs(s, vectors, width / 32, width / 2);
+        decode_strip(s, u, s->frame->linesize[1], width / 2);
+        vectors += width;
+        u += 2 * s->frame->linesize[1];
+
+        load_coeffs(s, vectors, width / 16, width);
+        decode_strip(s, y, s->frame->linesize[0], width);
+        vectors += 2 * width;
+        y += 2 * s->frame->linesize[0];
+
+        load_coeffs(s, vectors, width / 32, width / 2);
+        decode_strip(s, v, s->frame->linesize[2], width / 2);
+        vectors += width;
+        v += 2 * s->frame->linesize[2];
+    }
+}
+
+static int vqc_decode_frame(AVCodecContext *avctx, AVFrame * rframe,
+                            int * got_frame, AVPacket * avpkt)
+{
+    VqcContext *s = avctx->priv_data;
+    int ret;
+    uint8_t * buf = avpkt->data;
+    int cache, seed[7], gamma, contrast;
+
+    if (avpkt->size < 7)
+        return AVERROR_INVALIDDATA;
+
+    if ((ret = ff_reget_buffer(avctx, s->frame, 0)) < 0)
+        return ret;
+
+    av_log(avctx, AV_LOG_DEBUG, "VQC%d format\n", (buf[2] & 1) + 1);
+
+    if (((buf[0] >> 1) & 7) != 5) {
+        avpriv_request_sample(avctx, "subversion != 5\n");
+        return AVERROR_PATCHWELCOME;
+    }
+
+    cache = buf[4] | (AV_RL16(buf + 5) << 8);
+    seed[2] = seed_pow1((cache >> 1) & 7);
+    seed[1] = seed_pow1((cache >> 4) & 7);
+    seed[0] = seed_pow1((cache >> 7) & 7);
+    seed[6] = seed_pow2((cache >> 10) & 7);
+    seed[5] = seed_pow2((cache >> 13) & 7);
+    seed[4] = seed_pow2((cache >> 16) & 7);
+    seed[3] = seed_pow2((cache >> 19) & 7);
+
+    gamma = buf[0] >> 4;
+    contrast = AV_RL16(buf + 2) >> 1;
+    if (gamma || contrast)
+        avpriv_request_sample(avctx, "gamma=0x%x, contrast=0x%x\n", gamma, contrast);
+
+    seed_codebooks(s, seed);
+    decode_vectors(s, buf + 7, avpkt->size - 7, avctx->width, avctx->height);
+    decode_frame(s, avctx->width, avctx->height);
+
+    if ((ret = av_frame_ref(rframe, s->frame)) < 0)
+        return ret;
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+static av_cold int vqc_decode_end(AVCodecContext * avctx)
+{
+    VqcContext *s = avctx->priv_data;
+
+    av_freep(&s->vectors);
+    av_freep(&s->coeff);
+    av_freep(&s->tmp1);
+    av_freep(&s->tmp2);
+    av_frame_free(&s->frame);
+
+    return 0;
+}
+
+const FFCodec ff_vqc_decoder = {
+    .p.name         = "vqc",
+    CODEC_LONG_NAME("ViewQuest VQC"),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_VQC,
+    .priv_data_size = sizeof(VqcContext),
+    .init           = vqc_decode_init,
+    .close          = vqc_decode_end,
+    FF_CODEC_DECODE_CB(vqc_decode_frame),
+    .p.capabilities = AV_CODEC_CAP_DR1,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
+};
diff --git a/libavformat/riff.c b/libavformat/riff.c
index 0d3f322545..7319406b39 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -499,6 +499,8 @@  const AVCodecTag ff_codec_bmp_tags[] = {
     { AV_CODEC_ID_MVHA,         MKTAG('M', 'V', 'H', 'A') },
     { AV_CODEC_ID_MV30,         MKTAG('M', 'V', '3', '0') },
     { AV_CODEC_ID_NOTCHLC,      MKTAG('n', 'l', 'c', '1') },
+    { AV_CODEC_ID_VQC,          MKTAG('V', 'Q', 'C', '1') },
+    { AV_CODEC_ID_VQC,          MKTAG('V', 'Q', 'C', '2') },
     { AV_CODEC_ID_NONE,         0 }
 };