[FFmpeg-devel] [PATCH 1/2] avcodec: add decoder for argonaut games' adpcm codec
Zane van Iperen
zane at zanevaniperen.com
Sun Jan 19 05:11:30 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 | 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 --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
+ 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..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 at 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, \
--
2.17.1
More information about the ffmpeg-devel
mailing list