Commit c979371025 for ffmpeg
commit c97937102501320924088d5a11cb64e3a9b57c7c
Author: Peter Ross <pross@xvid.org>
Date: Tue Jul 8 14:06:47 2025 +1000
avcodec/adpcm: ADPCM IMA Citrix decoder
diff --git a/Changelog b/Changelog
index 56ecd0b5d6..876497f2e1 100644
--- a/Changelog
+++ b/Changelog
@@ -15,6 +15,7 @@ version <next>:
- H.264 data partitioning support
- DSD (dsd_msbf) encoder
- Rhetorex ADPCM decoder
+- ADPCM IMA Citrix decoder
version 9.0:
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index 509317c83a..597314ac04 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1318,6 +1318,7 @@ following image formats are supported:
@item ADPCM IMA Acorn Replay @tab @tab X
@item ADPCM IMA AMV @tab X @tab X
@tab Used in AMV files
+@item ADPCM IMA Citrix @tab @tab X
@item ADPCM IMA Cunning Developments @tab @tab X
@item ADPCM IMA Electronic Arts EACS @tab @tab X
@item ADPCM IMA Electronic Arts SEAD @tab @tab X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 5ee0c00bc6..ece43a46d6 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -990,6 +990,7 @@ OBJS-$(CONFIG_ADPCM_IMA_ALP_ENCODER) += adpcmenc.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_APC_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_APM_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_APM_ENCODER) += adpcmenc.o adpcm_data.o
+OBJS-$(CONFIG_ADPCM_IMA_CITRIX_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_CUNNING_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_DAT4_DECODER) += adpcm.o adpcm_data.o
OBJS-$(CONFIG_ADPCM_IMA_DK3_DECODER) += adpcm.o adpcm_data.o
diff --git a/libavcodec/adpcm.c b/libavcodec/adpcm.c
index a53a4c7c57..df25b665a4 100644
--- a/libavcodec/adpcm.c
+++ b/libavcodec/adpcm.c
@@ -264,6 +264,10 @@ static const int16_t rhetorex_index[8] = {
-83, -55, -27, 163, 374, 677, 1864, 3408,
};
+static const int8_t citrix_index_table[4] = {
+ -1, 1, -1, 1,
+};
+
/* end of tables */
typedef struct ADPCMDecodeContext {
@@ -356,6 +360,12 @@ static av_cold int adpcm_decode_init(AVCodecContext * avctx)
if (avctx->bits_per_coded_sample != 8)
return AVERROR_INVALIDDATA;
break;
+ case AV_CODEC_ID_ADPCM_IMA_CITRIX:
+ if (avctx->bits_per_coded_sample != 2 ||
+ avctx->block_align < 4 * avctx->ch_layout.nb_channels ||
+ (avctx->ch_layout.nb_channels == 2 && avctx->block_align % 8))
+ return AVERROR_INVALIDDATA;
+ break;
default:
break;
}
@@ -1201,6 +1211,19 @@ static int adpcm_sanyo_expand5(ADPCMChannelStatus *c, int bits)
return c->predictor;
}
+static int adpcm_citrix_expand(ADPCMChannelStatus *c, int bits)
+{
+ int step = ff_adpcm_step_table[c->step_index];
+ int add = step >> 1;
+ if ((bits & 1))
+ add += step;
+ if ((bits & 2))
+ add = -add;
+ c->step_index = av_clip(c->step_index + citrix_index_table[bits], 0, 88);
+ c->predictor = av_clip_int16(c->predictor + add);
+ return c->predictor;
+}
+
/**
* Get the number of samples (per channel) that will be decoded from the packet.
* In one case, this is actually the maximum number of samples possible to
@@ -1485,6 +1508,11 @@ static int get_nb_samples(AVCodecContext *avctx, GetByteContext *gb,
return AVERROR_INVALIDDATA;
nb_samples = AV_RL16(avctx->extradata);
break;
+ case AV_CODEC_ID_ADPCM_IMA_CITRIX:
+ if (buf_size < avctx->block_align)
+ return 0;
+ nb_samples = (buf_size / avctx->block_align) * (1 + ((avctx->block_align / ch) - 4) * 4);
+ break;
}
/* validate coded sample count */
@@ -2948,6 +2976,36 @@ static int adpcm_decode_frame(AVCodecContext *avctx, AVFrame *frame,
samples += channels;
}
) /* End of CASE */
+ CASE(ADPCM_IMA_CITRIX,
+ while (bytestream2_get_bytes_left(&gb) >= avctx->block_align) {
+ for (int ch = 0; ch < channels; ch++) {
+ *samples++ = c->status[ch].predictor = sign_extend(bytestream2_get_le16(&gb), 16);
+ c->status[ch].step_index = bytestream2_get_byte(&gb);
+ if (c->status[ch].step_index > 88)
+ return AVERROR_INVALIDDATA;
+ bytestream2_skip(&gb, 1);
+ }
+ if (channels == 1) {
+ for (int block = 0; block < avctx->block_align - 4; block++) {
+ uint8_t bits = bytestream2_get_byteu(&gb);
+ samples[0] = adpcm_citrix_expand(&c->status[0], bits & 3);
+ samples[1] = adpcm_citrix_expand(&c->status[0], (bits >> 2) & 3);
+ samples[2] = adpcm_citrix_expand(&c->status[0], (bits >> 4) & 3);
+ samples[3] = adpcm_citrix_expand(&c->status[0], (bits >> 6) & 3);
+ samples += 4;
+ }
+ } else { // channels == 2
+ for (int block = 0; block < (avctx->block_align - 4*channels) / (4*channels); block++) {
+ for (int ch = 0; ch < channels; ch++) {
+ uint32_t bits = bytestream2_get_le32(&gb);
+ for (int j = 0; j < 16; j++)
+ samples[j*channels + ch] = adpcm_citrix_expand(&c->status[ch], (bits >> (2*j)) & 3);
+ }
+ samples += 16*channels;
+ }
+ }
+ }
+ ) /* End of CASE */
default:
av_unreachable("There are cases for all codec ids using adpcm_decode_frame");
}
@@ -3049,6 +3107,7 @@ ADPCM_DECODER(ADPCM_IMA_ACORN, adpcm_ima_acorn, "ADPCM IMA Acorn Replay")
ADPCM_DECODER(ADPCM_IMA_AMV, adpcm_ima_amv, "ADPCM IMA AMV")
ADPCM_DECODER(ADPCM_IMA_APC, adpcm_ima_apc, "ADPCM IMA CRYO APC")
ADPCM_DECODER(ADPCM_IMA_APM, adpcm_ima_apm, "ADPCM IMA Ubisoft APM")
+ADPCM_DECODER(ADPCM_IMA_CITRIX, adpcm_ima_citrix, "ADPCM IMA Citrix")
ADPCM_DECODER(ADPCM_IMA_CUNNING, adpcm_ima_cunning, "ADPCM IMA Cunning Developments")
ADPCM_DECODER(ADPCM_IMA_DAT4, adpcm_ima_dat4, "ADPCM IMA Eurocom DAT4")
ADPCM_DECODER(ADPCM_IMA_DK3, adpcm_ima_dk3, "ADPCM IMA Duck DK3")
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 81536329d0..9c3d62f058 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -667,6 +667,7 @@ extern const FFCodec ff_adpcm_ima_alp_encoder;
extern const FFCodec ff_adpcm_ima_apc_decoder;
extern const FFCodec ff_adpcm_ima_apm_decoder;
extern const FFCodec ff_adpcm_ima_apm_encoder;
+extern const FFCodec ff_adpcm_ima_citrix_decoder;
extern const FFCodec ff_adpcm_ima_cunning_decoder;
extern const FFCodec ff_adpcm_ima_dat4_decoder;
extern const FFCodec ff_adpcm_ima_dk3_decoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 37dafd47f4..c02b48ac01 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2705,6 +2705,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
.long_name = NULL_IF_CONFIG_SMALL("ADPCM Rhetorex"),
.props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
},
+ {
+ .id = AV_CODEC_ID_ADPCM_IMA_CITRIX,
+ .type = AVMEDIA_TYPE_AUDIO,
+ .name = "adpcm_citrix",
+ .long_name = NULL_IF_CONFIG_SMALL("ADPCM IMA Citrix"),
+ .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
+ },
/* AMR */
{
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 5fe44bbf90..0b388594b9 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -431,6 +431,7 @@ enum AVCodecID {
AV_CODEC_ID_ADPCM_CIRCUS,
AV_CODEC_ID_ADPCM_IMA_ESCAPE,
AV_CODEC_ID_ADPCM_RHETOREX,
+ AV_CODEC_ID_ADPCM_IMA_CITRIX,
/* AMR */
AV_CODEC_ID_AMR_NB = 0x12000,
diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index f87fc13873..306696ecc5 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -559,6 +559,7 @@ int av_get_bits_per_sample(enum AVCodecID codec_id)
switch (codec_id) {
case AV_CODEC_ID_DFPWM:
return 1;
+ case AV_CODEC_ID_ADPCM_IMA_CITRIX:
case AV_CODEC_ID_ADPCM_SBPRO_2:
case AV_CODEC_ID_G728:
return 2;
@@ -768,6 +769,11 @@ static int get_audio_frame_duration(enum AVCodecID id, int sr, int ch, int ba,
case AV_CODEC_ID_ADPCM_XMD:
tmp = blocks * 32;
break;
+ case AV_CODEC_ID_ADPCM_IMA_CITRIX:
+ if (bps != 2 || ch > 2 || ba < 4 * ch || (ch == 2 && ba % 8))
+ return 0;
+ tmp = blocks * (1LL + (ba / ch - 4) * 4LL);
+ break;
}
if (tmp) {
if (tmp != (int)tmp)
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 82a86fe9d9..2618016a83 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
#include "version_major.h"
-#define LIBAVCODEC_VERSION_MINOR 16
+#define LIBAVCODEC_VERSION_MINOR 17
#define LIBAVCODEC_VERSION_MICRO 100
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \