[FFmpeg-devel] [PATCH v4 1/2] avcodec: add decoder for argonaut games' adpcm codec

Zane van Iperen zane at zanevaniperen.com
Wed Jan 22 02:57:25 EET 2020


Adds support for the ADPCM variant used by some Argonaut Games' games,
such as 'Croc! Legend of the Gobbos', and 'Croc 2'.

Signed-off-by: Zane van Iperen <zane at zanevaniperen.com>
---
 Changelog               |   1 +
 doc/general.texi        |   1 +
 libavcodec/Makefile     |   1 +
 libavcodec/adpcm.c      | 152 ++++++++++++++++++++++++++++++++++++++++
 libavcodec/allcodecs.c  |   1 +
 libavcodec/avcodec.h    |   1 +
 libavcodec/codec_desc.c |   7 ++
 libavcodec/version.h    |   2 +-
 8 files changed, 165 insertions(+), 1 deletion(-)

diff --git a/Changelog b/Changelog
index 2ccd2645fc..c029d73c72 100644
--- a/Changelog
+++ b/Changelog
@@ -30,6 +30,7 @@ version <next>:
 - MPEG-H 3D Audio support in mp4
 - thistogram filter
 - freezeframes filter
+- Argonaut Games ADPCM decoder
 
 
 version 4.2:
diff --git a/doc/general.texi b/doc/general.texi
index 4bd4b4f6b9..85db50462c 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -1079,6 +1079,7 @@ following image formats are supported:
 @item ACELP.KELVIN           @tab     @tab  X
 @item ADPCM 4X Movie         @tab     @tab  X
 @item APDCM Yamaha AICA      @tab     @tab  X
+ at item ADPCM Argonaut Games   @tab     @tab  X
 @item ADPCM CDROM XA         @tab     @tab  X
 @item ADPCM Creative Technology @tab     @tab  X
     @tab 16 -> 4, 8 -> 4, 8 -> 3, 8 -> 2
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index c1f35b40d8..a2fbb910a0 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -817,6 +817,7 @@ OBJS-$(CONFIG_ADPCM_ADX_ENCODER)          += adxenc.o adx.o
 OBJS-$(CONFIG_ADPCM_AFC_DECODER)          += adpcm.o adpcm_data.o
 OBJS-$(CONFIG_ADPCM_AGM_DECODER)          += adpcm.o adpcm_data.o
 OBJS-$(CONFIG_ADPCM_AICA_DECODER)         += adpcm.o adpcm_data.o
+OBJS-$(CONFIG_ADPCM_ARGO_DECODER)         += adpcm.o adpcm_data.o
 OBJS-$(CONFIG_ADPCM_CT_DECODER)           += adpcm.o adpcm_data.o
 OBJS-$(CONFIG_ADPCM_DTK_DECODER)          += adpcm.o adpcm_data.o
 OBJS-$(CONFIG_ADPCM_EA_DECODER)           += adpcm.o adpcm_data.o
diff --git a/libavcodec/adpcm.c b/libavcodec/adpcm.c
index 7b5b3d9698..af9830d9ff 100644
--- a/libavcodec/adpcm.c
+++ b/libavcodec/adpcm.c
@@ -12,6 +12,7 @@
  * EA ADPCM XAS decoder by Peter Ross (pross at xvid.org)
  * MAXIS EA ADPCM decoder by Robert Marston (rmarston at gmail.com)
  * THP ADPCM decoder by Marco Gerards (mgerards at xs4all.nl)
+ * Argonaut Games ADPCM decoder by Zane van Iperen (zane at zanevaniperen.com)
  *
  * This file is part of FFmpeg.
  *
@@ -148,6 +149,10 @@ static av_cold int adpcm_decode_init(AVCodecContext * avctx)
         if (avctx->extradata && avctx->extradata_size >= 2)
             c->vqa_version = AV_RL16(avctx->extradata);
         break;
+    case AV_CODEC_ID_ADPCM_ARGO:
+        if (avctx->bits_per_coded_sample != 4)
+            return AVERROR_INVALIDDATA;
+        break;
     default:
         break;
     }
@@ -546,6 +551,118 @@ static void adpcm_swf_decode(AVCodecContext *avctx, const uint8_t *buf, int buf_
     }
 }
 
+/*
+ * Argonaut Decoder 1: (prev0 + (s << (c + 2)))
+ */
+static int16_t *adpcm_argo_decoder_1(uint8_t c, int16_t *dst, const uint8_t *src,
+                                     const int16_t *prev_, int nsamples, int stride)
+{
+    int16_t prev;
+    int8_t s;
+
+    av_assert0(stride == 0 || stride == 1);
+    ++stride;
+
+    prev = prev_[stride];
+
+    c += 2;
+    for (int i = 0; i < nsamples / 2; i++, src++) {
+        s = (int8_t)((*src & 0xF0u) << 0u);
+        *dst = prev = ((prev * (1u << 6u)) + (s * (1u << c))) >> 6;
+        dst += stride;
+
+        s = (int8_t)((*src & 0x0Fu) << 4u);
+        *dst = prev = ((prev * (1u << 6u)) + (s * (1u << c))) >> 6;
+        dst += stride;
+    }
+
+    return dst;
+}
+
+/*
+ * Argonaut Decoder 2: (2 * prev0) - (1 * prev1) + (s << (c + 2))
+ */
+static int16_t *adpcm_argo_decoder_2(uint8_t c, int16_t *dst, const uint8_t *src,
+                                     const int16_t *prev_, int nsamples, int stride)
+{
+    int16_t cprev[2];
+    int8_t s;
+
+    av_assert0(stride == 0 || stride == 1);
+    ++stride;
+
+    /* [t-1, t-2] */
+    cprev[0] = prev_[stride];
+    cprev[1] = prev_[0];
+
+    c += 2;
+    for (int i = 0; i < nsamples / 2; i++, src++) {
+        s = (int8_t)((*src & 0xF0u) << 0u);
+        *dst = ((2 * cprev[0] * (1u << 6u)) - (cprev[1] * (1u << 6u)) + (s * (1u << c))) >> 6;
+        cprev[1] = cprev[0];
+        cprev[0] = *dst;
+        dst += stride;
+
+        s = (int8_t)((*src & 0x0Fu) << 4u);
+        *dst = ((2 * cprev[0] * (1u << 6u)) - (cprev[1] * (1u << 6u)) + (s * (1u << c))) >> 6;
+        cprev[1] = cprev[0];
+        cprev[0] = *dst;
+        dst += stride;
+    }
+
+    return dst;
+}
+
+/**
+ * Decode a block of Argonaut ADPCM samples.
+ *
+ * The format of each block:
+ *   uint8_t left_control;
+ *   uint4_t left_samples[];
+ *   ---- and if stereo ----
+ *   uint8_t right_control;
+ *   uint4_t right_samples[];
+ *
+ * Format of the control byte:
+ * MSB [SSSSDRRR] LSB
+ *   S = (Shift Amount - 2)
+ *   D = Decoder flag. If set, use decoder 2, otherwise use decoder 1
+ *   R = Reserved
+ *
+ * @param dst       A pointer to the buffer to write the samples.
+ *                  This must be at least `nsamples * nchannels`
+ * @param src       A pointer to the current block.
+ * @param prev      A pointer to the previous two decoded samples, one per channel.
+ *                    For mono this is:   [Lt-2, Lt-1]
+ *                    For stereo this is: [Lt-2, Rt-2, Lt-1, Rt-1]
+ * @param nsamples  The number of samples per channel in the block.
+ *                  Must be divisible by and greater than 2.
+ * @param nchannels The number of channels. Must be 1 or 2.
+ *
+ */
+static int16_t *adpcm_argo_decode_block(int16_t *dst, const uint8_t *src,
+                                        const int16_t *prev, int nsamples,
+                                        int nchannels)
+{
+    uint8_t c;
+
+    av_assert0(nsamples > 2 && (nsamples & 0x1) == 0);
+    av_assert0(nchannels == 1 || nchannels == 2);
+
+    /* NB: nsamples/2 because samples are 4 bits, not 8. */
+    for (int i = 0; i < nchannels; i++, src += nsamples / 2) {
+        /* Get the control byte and run the samples through the decoder. */
+        c = *src++;
+
+        if (c & 0x04)
+            adpcm_argo_decoder_2(c >> 4, dst + i, src, prev + i, nsamples, nchannels - 1);
+        else
+            adpcm_argo_decoder_1(c >> 4, dst + i, src, prev + i, nsamples, nchannels - 1);
+    }
+
+    return dst + (nsamples * nchannels);
+}
+
 /**
  * Get the number of samples that will be decoded from the packet.
  * In one case, this is actually the maximum number of samples possible to
@@ -584,6 +701,11 @@ static int get_nb_samples(AVCodecContext *avctx, GetByteContext *gb,
             return 0;
         nb_samples = 64;
         break;
+    case AV_CODEC_ID_ADPCM_ARGO:
+        if (buf_size < 17 * ch)
+            return 0;
+        nb_samples = 32;
+        break;
     /* simple 4-bit adpcm */
     case AV_CODEC_ID_ADPCM_CT:
     case AV_CODEC_ID_ADPCM_IMA_APC:
@@ -1770,6 +1892,35 @@ static int adpcm_decode_frame(AVCodecContext *avctx, void *data,
             }
         }
         break;
+    case AV_CODEC_ID_ADPCM_ARGO:
+    {
+        int16_t prev[4];
+
+        if (avctx->channels == 1) {
+            prev[0] = c->status[0].sample1;
+            prev[1] = c->status[0].sample2;
+        } else {
+            prev[0] = c->status[0].sample1;
+            prev[1] = c->status[1].sample1;
+            prev[2] = c->status[0].sample2;
+            prev[3] = c->status[1].sample2;
+        }
+
+        samples = adpcm_argo_decode_block(samples, buf, prev, nb_samples, avctx->channels);
+
+        if (avctx->channels == 1) {
+            c->status[0].sample1 = *(samples - 2);
+            c->status[0].sample2 = *(samples - 1);
+        } else {
+            c->status[0].sample1 = *(samples - 4);
+            c->status[1].sample1 = *(samples - 3);
+            c->status[0].sample2 = *(samples - 2);
+            c->status[1].sample2 = *(samples - 1);
+        }
+
+        bytestream2_seek(&gb, 0, SEEK_END);
+        break;
+    }
 
     default:
         av_assert0(0); // unsupported codec_id should not happen
@@ -1824,6 +1975,7 @@ ADPCM_DECODER(AV_CODEC_ID_ADPCM_4XM,         sample_fmts_s16p, adpcm_4xm,
 ADPCM_DECODER(AV_CODEC_ID_ADPCM_AFC,         sample_fmts_s16p, adpcm_afc,         "ADPCM Nintendo Gamecube AFC");
 ADPCM_DECODER(AV_CODEC_ID_ADPCM_AGM,         sample_fmts_s16,  adpcm_agm,         "ADPCM AmuseGraphics Movie");
 ADPCM_DECODER(AV_CODEC_ID_ADPCM_AICA,        sample_fmts_s16p, adpcm_aica,        "ADPCM Yamaha AICA");
+ADPCM_DECODER(AV_CODEC_ID_ADPCM_ARGO,        sample_fmts_s16,  adpcm_argo,        "ADPCM Argonaut Games");
 ADPCM_DECODER(AV_CODEC_ID_ADPCM_CT,          sample_fmts_s16,  adpcm_ct,          "ADPCM Creative Technology");
 ADPCM_DECODER(AV_CODEC_ID_ADPCM_DTK,         sample_fmts_s16p, adpcm_dtk,         "ADPCM Nintendo Gamecube DTK");
 ADPCM_DECODER(AV_CODEC_ID_ADPCM_EA,          sample_fmts_s16,  adpcm_ea,          "ADPCM Electronic Arts");
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index ec7366144f..01a083d06b 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -582,6 +582,7 @@ extern AVCodec ff_adpcm_adx_decoder;
 extern AVCodec ff_adpcm_afc_decoder;
 extern AVCodec ff_adpcm_agm_decoder;
 extern AVCodec ff_adpcm_aica_decoder;
+extern AVCodec ff_adpcm_argo_decoder;
 extern AVCodec ff_adpcm_ct_decoder;
 extern AVCodec ff_adpcm_dtk_decoder;
 extern AVCodec ff_adpcm_ea_decoder;
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 4b0e7c0853..ce126353b3 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -545,6 +545,7 @@ enum AVCodecID {
     AV_CODEC_ID_ADPCM_IMA_DAT4,
     AV_CODEC_ID_ADPCM_MTAF,
     AV_CODEC_ID_ADPCM_AGM,
+    AV_CODEC_ID_ADPCM_ARGO,
 
     /* AMR */
     AV_CODEC_ID_AMR_NB = 0x12000,
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 529b838e5b..32f573d58c 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2297,6 +2297,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("ADPCM AmuseGraphics Movie AGM"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_ADPCM_ARGO,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "adpcm_argo",
+        .long_name = NULL_IF_CONFIG_SMALL("ADPCM Argonaut Games"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
+    },
 
     /* AMR */
     {
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 6cf333eeb6..2fba26e8d0 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -28,7 +28,7 @@
 #include "libavutil/version.h"
 
 #define LIBAVCODEC_VERSION_MAJOR  58
-#define LIBAVCODEC_VERSION_MINOR  66
+#define LIBAVCODEC_VERSION_MINOR  67
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
-- 
2.17.1




More information about the ffmpeg-devel mailing list