diff mbox series

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

Message ID 20200119031111.26647-2-zane@zanevaniperen.com
State Superseded
Headers show
Series Argonaut Games ASF and ADPCM decoding support
Related show

Checks

Context Check Description
andriy/ffmpeg-patchwork pending
andriy/ffmpeg-patchwork success Applied patch
andriy/ffmpeg-patchwork success Configure finished
andriy/ffmpeg-patchwork success Make finished
andriy/ffmpeg-patchwork success Make fate finished

Commit Message

Zane van Iperen Jan. 19, 2020, 3:11 a.m. UTC
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@zanevaniperen.com>
---
 Changelog               |   2 +-
 doc/general.texi        |   1 +
 libavcodec/Makefile     |   1 +
 libavcodec/adpcm_argo.c | 264 ++++++++++++++++++++++++++++++++++++++++
 libavcodec/allcodecs.c  |   1 +
 libavcodec/avcodec.h    |   1 +
 libavcodec/codec_desc.c |   7 ++
 libavcodec/version.h    |   2 +-
 8 files changed, 277 insertions(+), 2 deletions(-)
 create mode 100644 libavcodec/adpcm_argo.c
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index 2ccd2645fc..e26320c0ce 100644
--- a/Changelog
+++ b/Changelog
@@ -30,7 +30,7 @@  version <next>:
 - MPEG-H 3D Audio support in mp4
 - thistogram filter
 - freezeframes filter
-
+- Argonaut Games ADPCM decoder
 
 version 4.2:
 - tpad filter
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
+@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..526b3ce96b 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_argo.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_argo.c b/libavcodec/adpcm_argo.c
new file mode 100644
index 0000000000..d5b32e62ba
--- /dev/null
+++ b/libavcodec/adpcm_argo.c
@@ -0,0 +1,264 @@ 
+/*
+ * Argonaut Games ADPCM decoder
+ *
+ * Copyright (C) 2020 Zane van Iperen (zane@zanevaniperen.com)
+ *
+ * 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 "internal.h"
+#include "libavutil/avassert.h"
+
+/*
+ * Sanity Checks:
+ *
+ * ./ffmpeg -loglevel error -i OUTRO.ASF -map 0:a -f md5 -
+ * MD5=e0428a6c55382f48502f04b3a7f7b94b
+ *
+ * ./ffmpeg -loglevel error -i CBK2.asf -map 0:a -f md5 -
+ * MD5=589f3f3c518e5d58aa404b7576db5b70
+*/
+
+/**
+ * Decode a block of 4-bit ADPCM samples for a channel.
+ *
+ * @param c         The shift amount - 2.
+ * @param dst       A pointer to the buffer to write the decoded samples,
+ *                  @p stride elements apart.
+ * @param src       A pointer to the first encoded sample.
+ *                  This should be `nsamples / 2` bytes.
+ * @param prev      The two previous samples, with @p stride elements
+ *                  between each channel.
+ * @param nsamples  The number of samples in the channel.
+ * @param stride    The difference in int16_t's between output samples.
+ *                  This is 0 for mono, 1 for stereo.
+ *
+ * @remark Each iteration does 2 samples at a time to avoid nasty bit-twiddling.
+ */
+typedef int16_t *(*ADPCMDecoder) (
+    uint8_t c,
+    int16_t *dst,
+    const uint8_t *src,
+    const int16_t *prev,
+    int nsamples,
+    int stride
+);
+
+/*
+ * Decoder 1: (prev0 + (s << (c + 2)))
+ */
+static int16_t *adpcm_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 << 6) + (s << c)) >> 6;
+        dst += stride;
+
+        s = (int8_t) ((*src & 0x0Fu) << 4u);
+        *dst = prev = ((prev << 6) + (s << c)) >> 6;
+        dst += stride;
+    }
+
+    return dst;
+}
+
+/*
+ * Decoder 2: (2 * prev0) - (1 * prev1) + (s << (c + 2))
+ */
+static int16_t *adpcm_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) {
+
+        /* NB: (x << 7) == 2*(x << 6) */
+
+        s = (int8_t) ((*src & 0xF0u) << 0u);
+        *dst = ((cprev[0] << 7) - (cprev[1] << 6) + (s << c)) >> 6;
+        cprev[1] = cprev[0];
+        cprev[0] = *dst;
+        dst += stride;
+
+        s = (int8_t) ((*src & 0x0Fu) << 4u);
+        *dst = ((cprev[0] << 7) - (cprev[1] << 6) + (s << c)) >> 6;
+        cprev[1] = cprev[0];
+        cprev[0] = *dst;
+        dst += stride;
+    }
+
+    return dst;
+}
+
+static ADPCMDecoder adpcm_decoders[2] = { adpcm_decoder_1, adpcm_decoder_2 };
+
+/**
+ * Decode a block of 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_decode_block(int16_t *dst, const uint8_t *src,
+                                 const int16_t *prev,
+                                 int nsamples, int nchannels)
+{
+    unsigned char 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++;
+        adpcm_decoders[!!(c & 0x04)] (c >> 4, dst + i, src, prev + i, nsamples, nchannels - 1);
+    }
+
+    return dst + (nsamples * nchannels);
+}
+
+
+#define MAX_CHANNELS (2)
+#define PREVIOUS_SAMPLE_COUNT (2)
+
+typedef struct ADPCMArgoDecoderContext {
+    int16_t prev[MAX_CHANNELS * PREVIOUS_SAMPLE_COUNT];
+} ADPCMArgoDecoderContext;
+
+static av_cold int adpcm_decode_init(AVCodecContext *avctx)
+{
+    ADPCMArgoDecoderContext *ctx = avctx->priv_data;
+
+    if (avctx->channels > MAX_CHANNELS) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid channel count %d\n", avctx->channels);
+        return AVERROR(EINVAL);
+    }
+
+    if (avctx->bits_per_coded_sample != 4) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid number of bits %d\n", avctx->bits_per_coded_sample);
+        return AVERROR(EINVAL);
+    }
+
+    avctx->sample_fmt = AV_SAMPLE_FMT_S16;
+
+    for (int i = 0; i < MAX_CHANNELS * PREVIOUS_SAMPLE_COUNT; ++i)
+        ctx->prev[i] = 0;
+
+    return 0;
+}
+
+static int adpcm_decode_frame(AVCodecContext * avctx, void *data,
+                                 int *got_frame_ptr, AVPacket * avpkt)
+{
+    int r;
+    AVFrame *frame = data;
+    ADPCMArgoDecoderContext *argo = avctx->priv_data;
+    int16_t *dst;
+
+    if (avctx->channels == 1 && avpkt->size != 17) {
+        av_log(avctx, AV_LOG_WARNING,
+               "unexpected mono packet size, expected 17, got %d\n",
+               avpkt->size);
+    } else if(avctx->channels == 2 && avpkt->size != 34) {
+        av_log(avctx, AV_LOG_WARNING,
+               "unexpected stereo packet size, expected 34, got %d\n",
+               avpkt->size);
+    }
+
+    frame->nb_samples = ((avpkt->size - avctx->channels) / avctx->channels) *
+                        (8 / avctx->bits_per_coded_sample);
+
+    /* get output buffer */
+    if ((r = ff_get_buffer(avctx, frame, 0)) < 0)
+        return r;
+
+    dst = adpcm_decode_block((int16_t *) frame->data[0], avpkt->data, argo->prev, frame->nb_samples, avctx->channels);
+
+    /* Save the previous samples for the next frame. */
+    r = avctx->channels * PREVIOUS_SAMPLE_COUNT;
+    for (int i = 0; i < r; ++i)
+        argo->prev[i] = *(dst - (r - i));
+
+    *got_frame_ptr = 1;
+    return avpkt->size;
+}
+
+const int64_t channel_layouts[] = {
+    AV_CH_LAYOUT_MONO,
+    AV_CH_LAYOUT_STEREO,
+    0
+};
+
+const enum AVSampleFormat sample_formats[] = {
+    AV_SAMPLE_FMT_S16,
+    AV_SAMPLE_FMT_NONE
+};
+
+
+AVCodec ff_adpcm_argo_decoder = {
+    .name               = "adpcm_argo",
+    .long_name          = NULL_IF_CONFIG_SMALL("ADPCM Argonaut Games"),
+    .type               = AVMEDIA_TYPE_AUDIO,
+    .id                 = AV_CODEC_ID_ADPCM_ARGO,
+    .priv_data_size     = sizeof(ADPCMArgoDecoderContext),
+    .init               = adpcm_decode_init,
+    .decode             = adpcm_decode_frame,
+    .capabilities       = AV_CODEC_CAP_DR1,
+    .sample_fmts        = sample_formats,
+    .channel_layouts    = channel_layouts
+};
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, \