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, \