XA

From Niotso Wiki
Jump to: navigation, search
Maxis XA
Type of format Audio
Filename extension .xa
Magic number XA
Byte order Little endian
First appeared Sim City 3000 (1999)
Extended from EA ADPCM
Type ID 0x1D07EB4B
Maxis XA (named after CD-ROM Extended Architecture) is a variant of the EA ADPCM lossy audio compression format. Maxis XA is used by all Maxis games from Sim City 3000 onwards.

Francois Revol is credited for reverse engineering the original EA ADPCM audio format. Dmitry Kirnocenskij is credited for reverse engineering Maxis XA.

All content on this page is public domain. A complete working Maxis XA file reading implementation can be found in the FileHandler interface of Niotso under the ISC license.

XA file header[edit]

struct XAHeader
{
    char  szID[4];
    DWORD dwOutSize;
    /* WAVEFORMATEX */
    WORD  wFormatTag;
    WORD  nChannels;
    DWORD nSamplesPerSec;
    DWORD nAvgBytesPerSec;
    WORD  nBlockAlign;
    WORD  wBitsPerSample;
};
  • szID - A null-terminated string identifier, which may be equal to "XAI" (sound, speech) or "XAJ" (music) or "XA" (untagged) ("XAS" appeared in Command & Conquer 3)
  • dwOutSize - The decompressed size of the audio stream
  • wFormatTag - The decoded audio format; set to WAVE_FORMAT_PCM (0x0001)
  • nChannels - Number of channels in the decoded audio data
  • nSamplesPerSec - Sampling rate used in the decoded audio data
  • nAvgBytesPerSec - Bytes per second consumed by the decoded audio data; equal to nChannels*nSamplesPerSec*wBitsPerSample/8 or nSamplesPerSec*nBlockAlign
  • nBlockAlign - The number of bytes consumed by an audio frame (one sample for each channel) in the decoded audio data; equal to nChannels*wBitsPerSample/8
  • wBitsPerSample - The bits per sample for one audio channel in the decoded audio data; 8-, 16-, or 24-bit, etc.

Note that the part of the header from wFormatTag below is a Microsoft WAVEFORMATEX structure, albeit without the cbSize parameter.

szID is just a hint. XAI is stored stereo signed 16-bit 22,050Hz. XAJ is stored mono signed 16-bit 22,050Hz. If szID is untagged ("XA"), it can be determined from the WAVEFORMATEX structure.

Decoding[edit]

The goal is to pack 16 bits per sample per channel into 4 without sacrificing as much quality as we would through basic scalar quantization.

Lossy Differential/Delta PCM ("DPCM") works on the principle of predicting the difference between the last sample and the current sample and storing the residual in a constricted range. The decompressor must therefore retain the last audio frame when it reads a new one. Before decompression begins, all previous-sample variables must be initialized to zero.

Prediction is applied in blocks of 15 bytes (1 control byte providing the prediction model for 28 samples) for each channel. Here is decompression code in C89 to decode a sequence of complete XA blocks.

#define HINIBBLE(byte) ((byte) >> 4)
#define LONIBBLE(byte) ((byte) & 0x0F)
 
__inline int16_t Clip16(int sample)
{
    if(sample>=32767) return 32767;
    else if(sample<=-32768) return -32768;
    else return (int16_t) sample;
}
 
static const int8_t XATable[] =
{
    0, 240,  460,  392,
    0,   0, -208, -220,
    0,   1,    3,    4,
    7,   8,   10,   11,
    0,  -1,   -3,   -4
};
 
typedef struct {
    int16_t PrevSample, CurSample;
    int divisor; /* residual right-shift value */
    int c1, c2;  /* predictor coefficients */
} channel_t;
 
void XADecode(const uint8_t *__restrict InBuffer, unsigned BlockCount, int16_t *__restrict OutBuffer, unsigned Channels)
{
    channel_t * Channel = malloc(Channels * sizeof(channel_t));
    memset(Channel, 0x00, Channels * sizeof(channel_t));
 
    while(BlockCount--){
        unsigned i;
 
        for(i=0; i<Channels; i++){
            unsigned byte      = *(InBuffer++);
            Channel[i].divisor = LONIBBLE(byte)+8;
            Channel[i].c1      = XATable[HINIBBLE(byte)];
            Channel[i].c2      = XATable[HINIBBLE(byte)+4];
        }
 
        for(i=0; i<14; i++){
            unsigned j;
            for(j=0; j<Channels; j++){
                unsigned byte = *(InBuffer++);
                int n;
                for(n=0; n<2; n++){
                    int NewValue = (n == 0) ? HINIBBLE(byte) : LONIBBLE(byte);
                    NewValue = (NewValue << 28) >> Channel[j].divisor;
                    NewValue = (NewValue + Channel[j].CurSample*Channel[j].c1 + Channel[j].PrevSample*Channel[j].c2 + 128) >> 8;
                    Channel[j].PrevSample = Channel[j].CurSample;
                    Channel[j].CurSample  = Clip16(NewValue);
                }
                *(OutBuffer++) = Channel[j].PrevSample;
            }
            for(j=0; j<Channels; j++) *(OutBuffer++) = Channel[j].CurSample;
        }
    }
 
    free(Channel);
}

A complete working Maxis XA file reading implementation can be found in the FileHandler interface of Niotso under the ISC license.

Other versions[edit]

There are several variants of EA ADPCM with decode support in FFmpeg. These are:

  • EA ADPCM
  • EA ADPCM / Maxis XA
  • EA ADPCM R1
  • EA ADPCM R2
  • EA ADPCM R3
  • EA ADPCM / XAS
  • EA IMA ADPCM / EACS
  • EA IMA ADPCM / SEAD

Source code for these can be found in libavcodec/adpcm.c.