PR #24735 opened by Peter Ross (pross)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24735
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24735.patch

Citrix ADPCM decoder and fate test case.

```fate-samples
citrix/citrix-mono-2bit-8000.wav
```


>From 0b57af56337b74fe1ac53ccfea61f3e07797fed1 Mon Sep 17 00:00:00 2001
From: Peter Ross <[email protected]>
Date: Tue, 8 Jul 2025 14:06:47 +1000
Subject: [PATCH 1/3] avcodec/adpcm: Citrix ADPCM decoder

---
 libavcodec/Makefile     |  1 +
 libavcodec/adpcm.c      | 56 +++++++++++++++++++++++++++++++++++++++++
 libavcodec/allcodecs.c  |  1 +
 libavcodec/codec_desc.c |  7 ++++++
 libavcodec/codec_id.h   |  1 +
 5 files changed, 66 insertions(+)

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index e2c60e9051..6c5e17427d 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 74af2cb4d3..a60f1103db 100644
--- a/libavcodec/adpcm.c
+++ b/libavcodec/adpcm.c
@@ -18,6 +18,7 @@
  * High Voltage Software ALP decoder by Zane van Iperen 
([email protected])
  * Cunning Developments decoder by Zane van Iperen ([email protected])
  * Sanyo LD-ADPCM decoder by Peter Ross ([email protected])
+ * Citrix ADPCM decoder by Peter Ross ([email protected])
  *
  * This file is part of FFmpeg.
  *
@@ -241,6 +242,10 @@ static const int8_t mtf_index_table[16] = {
     -1, -1, -1, -1,  2,  4,  6,  8,
 };
 
+static const int8_t citrix_index_table[4] = {
+    -1, 1, -1, 1,
+};
+
 /* end of tables */
 
 typedef struct ADPCMDecodeContext {
@@ -332,6 +337,10 @@ 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)
+            return AVERROR_INVALIDDATA;
+        break;
     default:
         break;
     }
@@ -1149,6 +1158,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
@@ -1430,6 +1452,9 @@ 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:
+        nb_samples = (buf_size / avctx->block_align) * (1 + 
((avctx->block_align / ch) - 4) * 4);
+        break;
     }
 
     /* validate coded sample count */
@@ -2886,6 +2911,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");
     }
@@ -2987,6 +3042,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 ca20c76fe6..46630133ae 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 dc20b57df4..7ee20b3b6e 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2698,6 +2698,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("ADPCM IMA Acorn Escape"),
         .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 841aa28d20..2757afcdbf 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -430,6 +430,7 @@ enum AVCodecID {
     AV_CODEC_ID_ADPCM_PSXC,
     AV_CODEC_ID_ADPCM_CIRCUS,
     AV_CODEC_ID_ADPCM_IMA_ESCAPE,
+    AV_CODEC_ID_ADPCM_IMA_CITRIX,
 
     /* AMR */
     AV_CODEC_ID_AMR_NB = 0x12000,
-- 
2.52.0


>From 868c315e1dc4f298289e239c1487b77f3fb66697 Mon Sep 17 00:00:00 2001
From: Peter Ross <[email protected]>
Date: Sun, 27 Sep 2026 09:36:05 +1000
Subject: [PATCH 2/3] avformat/riff: Citrix ADPCM decoder

---
 libavformat/riff.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libavformat/riff.c b/libavformat/riff.c
index 21de951e3a..4cb2763827 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -582,6 +582,7 @@ const AVCodecTag ff_codec_wav_tags[] = {
     { AV_CODEC_ID_MSNSIREN,        0x028E },
     { AV_CODEC_ID_ADPCM_G722,      0x028F },
     { AV_CODEC_ID_MISC4,           0x0350 },
+    { AV_CODEC_ID_ADPCM_IMA_CITRIX, 0x0400 },
     { AV_CODEC_ID_IMC,             0x0401 },
     { AV_CODEC_ID_IAC,             0x0402 },
     { AV_CODEC_ID_ON2AVC,          0x0500 },
-- 
2.52.0


>From 696edeec49e915a12b3ba8f085c5a4df7b7886ba Mon Sep 17 00:00:00 2001
From: Peter Ross <[email protected]>
Date: Tue, 8 Jul 2025 14:29:00 +1000
Subject: [PATCH 3/3] tests/fate/adpcm: Citrix ADPCM test case

---
 tests/fate/adpcm.mak            |  3 +++
 tests/ref/fate/adpcm-ima-citrix | 47 +++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+)
 create mode 100644 tests/ref/fate/adpcm-ima-citrix

diff --git a/tests/fate/adpcm.mak b/tests/fate/adpcm.mak
index 7ad62312dd..c66c9c831a 100644
--- a/tests/fate/adpcm.mak
+++ b/tests/fate/adpcm.mak
@@ -43,6 +43,9 @@ fate-adpcm-ima-amv: CMD = framecrc -i 
$(TARGET_SAMPLES)/amv/MTV_high_res_320x240
 FATE_ADPCM-$(call DEMDEC, APC, ADPCM_IMA_APC) += fate-adpcm-ima-apc
 fate-adpcm-ima-apc: CMD = md5 -i $(TARGET_SAMPLES)/cryo-apc/cine007.APC -f 
s16le
 
+FATE_ADPCM-$(call FRAMECRC, WAV, ADPCM_IMA_CITRIX, ARESAMPLE_FILTER) += 
fate-adpcm-ima-citrix
+fate-adpcm-ima-citrix: CMD = framecrc -i 
$(TARGET_SAMPLES)/citrix/citrix-mono-2bit-8000.wav -f s16le -af aresample
+
 FATE_ADPCM-$(call DEMDEC, AVI, ADPCM_IMA_DK3) += fate-adpcm-ima-dk3
 fate-adpcm-ima-dk3: CMD = md5 -i $(TARGET_SAMPLES)/duck/sop-audio-only.avi -f 
s16le
 
diff --git a/tests/ref/fate/adpcm-ima-citrix b/tests/ref/fate/adpcm-ima-citrix
new file mode 100644
index 0000000000..348b6127c4
--- /dev/null
+++ b/tests/ref/fate/adpcm-ima-citrix
@@ -0,0 +1,47 @@
+#tb 0: 1/8000
+#media_type 0: audio
+#codec_id 0: pcm_s16le
+#sample_rate 0: 8000
+#channel_layout_name 0: mono
+0,          0,          0,      497,      994, 0xa79b02e8
+0,        497,        497,      497,      994, 0x265cad20
+0,        994,        994,      497,      994, 0xc9f50298
+0,       1491,       1491,      497,      994, 0xe838ef24
+0,       1988,       1988,      497,      994, 0x07f02c62
+0,       2485,       2485,      497,      994, 0x4adc0334
+0,       2982,       2982,      497,      994, 0x6a9deb05
+0,       3479,       3479,      497,      994, 0xbf4bfc73
+0,       3976,       3976,      497,      994, 0xa942ff2d
+0,       4473,       4473,      497,      994, 0x7538ea62
+0,       4970,       4970,      497,      994, 0x228ed53a
+0,       5467,       5467,      497,      994, 0x3538e336
+0,       5964,       5964,      497,      994, 0xebec17fb
+0,       6461,       6461,      497,      994, 0x7ba516a4
+0,       6958,       6958,      497,      994, 0x6ed0e896
+0,       7455,       7455,      497,      994, 0x0fd6e65e
+0,       7952,       7952,      497,      994, 0x2a1803a3
+0,       8449,       8449,      497,      994, 0x7ac50c89
+0,       8946,       8946,      497,      994, 0xd0360d8b
+0,       9443,       9443,      497,      994, 0x425605bd
+0,       9940,       9940,      497,      994, 0x0c6ffb20
+0,      10437,      10437,      497,      994, 0x2341f1f9
+0,      10934,      10934,      497,      994, 0xaa98e622
+0,      11431,      11431,      497,      994, 0x6081f62a
+0,      11928,      11928,      497,      994, 0x2b0ecbb5
+0,      12425,      12425,      497,      994, 0x595300fa
+0,      12922,      12922,      497,      994, 0x3c310aae
+0,      13419,      13419,      497,      994, 0x98d213a2
+0,      13916,      13916,      497,      994, 0x38672288
+0,      14413,      14413,      497,      994, 0xd8920cd3
+0,      14910,      14910,      497,      994, 0xc1acef44
+0,      15407,      15407,      497,      994, 0x304ce24a
+0,      15904,      15904,      497,      994, 0x0f7ffba8
+0,      16401,      16401,      497,      994, 0x7981e376
+0,      16898,      16898,      497,      994, 0xbd7bf915
+0,      17395,      17395,      497,      994, 0x67b9ecdc
+0,      17892,      17892,      497,      994, 0xb849ff06
+0,      18389,      18389,      497,      994, 0x1987ca8f
+0,      18886,      18886,      497,      994, 0xacd1ab92
+0,      19383,      19383,      497,      994, 0x72b1ee2b
+0,      19880,      19880,      497,      994, 0xb2acc537
+0,      20377,      20377,      497,      994, 0xa79b02e8
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to