[FFmpeg-devel] [PATCH] avcodec: add ADPCM IMA Ubisoft decoder
Zane van Iperen
zane at zanevaniperen.com
Tue Dec 28 07:37:34 EET 2021
A simple, interleaved variant, but with initial state and
extra, uncompressed samples. Found in Ubisoft soundbanks from
early-2000's games (Splinter Cell, RS3, etc.)
Signed-off-by: Zane van Iperen <zane at zanevaniperen.com>
---
Changelog | 1 +
doc/general_contents.texi | 1 +
libavcodec/Makefile | 1 +
libavcodec/adpcm.c | 69 +++++++++++++++++++++++++++++++++++++++
libavcodec/allcodecs.c | 1 +
libavcodec/codec_desc.c | 7 ++++
libavcodec/codec_id.h | 1 +
7 files changed, 81 insertions(+)
diff --git a/Changelog b/Changelog
index edb4152d0f..58be0b9da5 100644
--- a/Changelog
+++ b/Changelog
@@ -44,6 +44,7 @@ version <next>:
- yadif_videotoolbox filter
- VideoToolbox ProRes encoder
- anlmf audio filter
+- ADPCM IMA Ubisoft decoder
version 4.4:
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index df1692c8df..80506e8ab4 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1139,6 +1139,7 @@ following image formats are supported:
@item ADPCM IMA High Voltage Software ALP @tab X @tab X
@item ADPCM IMA QuickTime @tab X @tab X
@item ADPCM IMA Simon & Schuster Interactive @tab X @tab X
+ at item ADPCM IMA Ubisoft @tab @tab X
@item ADPCM IMA Ubisoft APM @tab X @tab X
@item ADPCM IMA Loki SDL MJPEG @tab @tab X
@item ADPCM IMA WAV @tab X @tab X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 9577062eec..52839e1994 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -899,6 +899,7 @@ OBJS-$(CONFIG_ADPCM_IMA_RAD_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_SSI_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_SSI_ENCODER) += adpcmenc.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_SMJPEG_DECODER) += adpcm.o adpcm_data.o
+OBJS-$(CONFIG_ADPCM_IMA_UBISOFT_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_WAV_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_WAV_ENCODER) += adpcmenc.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_WS_DECODER) += adpcm.o adpcm_data.o
diff --git a/libavcodec/adpcm.c b/libavcodec/adpcm.c
index cfde5f58b9..410fea8e21 100644
--- a/libavcodec/adpcm.c
+++ b/libavcodec/adpcm.c
@@ -239,6 +239,7 @@ static const int8_t mtf_index_table[16] = {
typedef struct ADPCMDecodeContext {
ADPCMChannelStatus status[14];
int vqa_version; /**< VQA version. Used for ADPCM_IMA_WS */
+ int extra_count; /**< Number of raw PCM samples to send */
int has_status; /**< Status flag. Reset to 0 after a flush. */
} ADPCMDecodeContext;
@@ -301,6 +302,13 @@ static av_cold int adpcm_decode_init(AVCodecContext * avctx)
if (avctx->bits_per_coded_sample != 4 || avctx->block_align != 17 * avctx->channels)
return AVERROR_INVALIDDATA;
break;
+ case AV_CODEC_ID_ADPCM_IMA_UBISOFT:
+ if (c->extra_count < 0)
+ return AVERROR_INVALIDDATA;
+
+ if (c->extra_count > 0 && c->extra_count % avctx->channels != 0)
+ return AVERROR_INVALIDDATA;
+ break;
case AV_CODEC_ID_ADPCM_ZORK:
if (avctx->bits_per_coded_sample != 8)
return AVERROR_INVALIDDATA;
@@ -877,6 +885,10 @@ static int get_nb_samples(AVCodecContext *avctx, GetByteContext *gb,
case AV_CODEC_ID_ADPCM_IMA_MTF:
nb_samples = buf_size * 2 / ch;
break;
+ /* simple 4-bit adpcm, with extra uncompressed samples */
+ case AV_CODEC_ID_ADPCM_IMA_UBISOFT:
+ nb_samples = (buf_size * 2 + s->extra_count) / ch;
+ break;
}
if (nb_samples)
return nb_samples;
@@ -1460,6 +1472,35 @@ static int adpcm_decode_frame(AVCodecContext *avctx, void *data,
*samples++ = adpcm_ima_qt_expand_nibble(&c->status[st], v & 0x0F);
}
) /* End of CASE */
+ CASE(ADPCM_IMA_UBISOFT,
+ if (c->extra_count) {
+ int offset = avctx->extradata[0] == 6 ? 36 : 28;
+ nb_samples -= c->extra_count / avctx->channels;
+
+ for (uint8_t *extra = avctx->extradata + offset; c->extra_count--; extra += 2) {
+ if (avctx->extradata[0] == 3)
+ *samples++ = AV_RB16(extra);
+ else
+ *samples++ = AV_RL16(extra);
+ }
+
+ /* NB: This is enforced above. */
+ if (avctx->channels == 1) {
+ c->status[0].predictor = samples[-1];
+ } else {
+ c->status[0].predictor = samples[-2];
+ c->status[1].predictor = samples[-1];
+ }
+
+ c->extra_count = 0;
+ }
+
+ for (int n = nb_samples >> (1 - st); n > 0; n--) {
+ int v = bytestream2_get_byteu(&gb);
+ *samples++ = adpcm_ima_expand_nibble(&c->status[0], v >> 4, 3);
+ *samples++ = adpcm_ima_expand_nibble(&c->status[st], v & 0x0F, 3);
+ }
+ ) /* End of CASE */
CASE(ADPCM_IMA_APM,
for (int n = nb_samples / 2; n > 0; n--) {
for (int channel = 0; channel < avctx->channels; channel++) {
@@ -2257,6 +2298,33 @@ static void adpcm_flush(AVCodecContext *avctx)
if (avctx->extradata && avctx->extradata_size >= 2)
c->vqa_version = AV_RL16(avctx->extradata);
break;
+
+ case AV_CODEC_ID_ADPCM_IMA_UBISOFT: {
+ if (avctx->extradata && avctx->extradata_size >= 28) {
+ uint8_t version = avctx->extradata[0];
+ uint32_t sample_offset = version == 6 ? 36 : 28;
+
+ if (version == 3) {
+ c->extra_count = AV_RB16(avctx->extradata + 14);
+ c->status[0].predictor = AV_RB16(avctx->extradata + 16);
+ c->status[1].predictor = AV_RB16(avctx->extradata + 20);
+ } else {
+ c->extra_count = AV_RL16(avctx->extradata + 14);
+ c->status[0].predictor = AV_RL16(avctx->extradata + 16);
+ c->status[1].predictor = AV_RL16(avctx->extradata + 20);
+ }
+
+ c->status[0].step_index = av_clip(avctx->extradata[18], 0, 88);
+ c->status[1].step_index = av_clip(avctx->extradata[22], 0, 88);
+
+ c->extra_count = FFMIN(
+ c->extra_count,
+ (avctx->extradata_size - sample_offset) / 2 / sizeof(int16_t)
+ );
+ }
+ break;
+ }
+
default:
/* Other codecs may want to handle this during decoding. */
c->has_status = 0;
@@ -2330,6 +2398,7 @@ ADPCM_DECODER(ADPCM_IMA_QT, sample_fmts_s16p, adpcm_ima_qt, "ADPCM IMA
ADPCM_DECODER(ADPCM_IMA_RAD, sample_fmts_s16, adpcm_ima_rad, "ADPCM IMA Radical")
ADPCM_DECODER(ADPCM_IMA_SSI, sample_fmts_s16, adpcm_ima_ssi, "ADPCM IMA Simon & Schuster Interactive")
ADPCM_DECODER(ADPCM_IMA_SMJPEG, sample_fmts_s16, adpcm_ima_smjpeg, "ADPCM IMA Loki SDL MJPEG")
+ADPCM_DECODER(ADPCM_IMA_UBISOFT, sample_fmts_s16, adpcm_ima_ubisoft, "ADPCM IMA Ubisoft");
ADPCM_DECODER(ADPCM_IMA_ALP, sample_fmts_s16, adpcm_ima_alp, "ADPCM IMA High Voltage Software ALP")
ADPCM_DECODER(ADPCM_IMA_WAV, sample_fmts_s16p, adpcm_ima_wav, "ADPCM IMA WAV")
ADPCM_DECODER(ADPCM_IMA_WS, sample_fmts_both, adpcm_ima_ws, "ADPCM IMA Westwood")
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index d1e10197de..2a07888a8f 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -652,6 +652,7 @@ extern const AVCodec ff_adpcm_ima_rad_decoder;
extern const AVCodec ff_adpcm_ima_ssi_decoder;
extern const AVCodec ff_adpcm_ima_ssi_encoder;
extern const AVCodec ff_adpcm_ima_smjpeg_decoder;
+extern const AVCodec ff_adpcm_ima_ubisoft_decoder;
extern const AVCodec ff_adpcm_ima_wav_encoder;
extern const AVCodec ff_adpcm_ima_wav_decoder;
extern const AVCodec ff_adpcm_ima_ws_encoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 0974ee03de..a892d8f853 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2475,6 +2475,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
.long_name = NULL_IF_CONFIG_SMALL("ADPCM IMA Acorn Replay"),
.props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
},
+ {
+ .id = AV_CODEC_ID_ADPCM_IMA_UBISOFT,
+ .type = AVMEDIA_TYPE_AUDIO,
+ .name = "adpcm_ima_ubisoft",
+ .long_name = NULL_IF_CONFIG_SMALL("ADPCM IMA Ubisoft"),
+ .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
+ },
/* AMR */
{
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index ab265ec584..d7b47f88ce 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -401,6 +401,7 @@ enum AVCodecID {
AV_CODEC_ID_ADPCM_IMA_CUNNING,
AV_CODEC_ID_ADPCM_IMA_MOFLEX,
AV_CODEC_ID_ADPCM_IMA_ACORN,
+ AV_CODEC_ID_ADPCM_IMA_UBISOFT,
/* AMR */
AV_CODEC_ID_AMR_NB = 0x12000,
--
I do have a demuxer for this, but it's a mess. I'll send it through eventually...
More information about the ffmpeg-devel
mailing list