[FFmpeg-trac] #6690(avformat:open): Raw Electronic Arts ADPCM support (.XA)

FFmpeg trac at avcodec.org
Fri Sep 29 20:27:52 EEST 2017


#6690: Raw Electronic Arts ADPCM support (.XA)
-------------------------------------+-------------------------------------
             Reporter:  koops        |                    Owner:
                 Type:  enhancement  |                   Status:  open
             Priority:  wish         |                Component:  avformat
              Version:  git-master   |               Resolution:
             Keywords:               |               Blocked By:
  electronicarts                     |  Reproduced by developer:  0
             Blocking:               |
Analyzed by developer:  0            |
-------------------------------------+-------------------------------------

Old description:

> Summary of the bug:
> How to reproduce:
> {{{
> $ ffmpeg -i sound.XA sound.wav
> sound.XA: Invalid data found when processing input
> }}}
>
> I would like support for headerless Electronic Arts ADPCM files to be
> added.
> These files are common in some Electronic Arts games, like Harry Potter
> and the Chamber of Secrets and Brother Bear.
>
> The format is similar to adpcm_ea_r1, but the file is always mono, there
> is no channels header. ADPCM history is always initialized to [0, 0].
> File size is always a multiple of 15, there is no raw sample mode (0xEE).
>
> Sample rate is 22050Hz by default.
>
> Files can be encoded into this format using sx.exe:
> {{{
> > sx -raw -eaxa_blk sample.wav -=sample.XA
> }}}
>
> I attached a test case, a XA file and the corresponding decoded raw file.
>
> This is decoder code in C# (I release it under CC0):
>
> {{{
> using System;
> using System.IO;
> using System.Text;
>
> namespace KoopsAudioDecoder
> {
>   class Program
>   {
>     static int coeff1, coeff2, shift, adpcmHistory1 = 0, adpcmHistory2 =
> 0;
>
>     static readonly int[] EA_XA_TABLE = new int[] {
>         0,    0,
>       240,    0,
>       460, -208,
>       392, -220,
>     };
>
>     static void DecodeSingleFrame(BinaryReader stream, BinaryWriter
> outbuf)
>     {
>       int frameInfo = stream.ReadByte();
>
>       coeff1 = EA_XA_TABLE[((frameInfo >> 4) & 15) << 1];
>       coeff2 = EA_XA_TABLE[(((frameInfo >> 4) & 15) << 1) + 1];
>       shift = (frameInfo & 15) + 8;
>
>       for (int i = 0; i < 14; i++)
>       {
>         int sample_byte = stream.ReadByte();
>
>         int[] nibbles = { sample_byte >> 4, sample_byte & 15 };
>
>         foreach (int nibble in nibbles)
>         {
>           int sample = GetSample(nibble);
>
>           outbuf.Write(Clamp16(sample));
>         }
>       }
>     }
>
>     private static int GetSample(int nibble)
>     {
>       int sample = (nibble << 28 >> shift) + (coeff1 * adpcmHistory1) +
> (coeff2 * adpcmHistory2) >> 8;
>
>       adpcmHistory2 = adpcmHistory1;
>       adpcmHistory1 = sample;
>
>       return sample;
>     }
>
>     static private short Clamp16(int sample)
>     {
>       if (sample > 32767)
>       {
>         return 32767;
>       }
>       else if (sample < -32768)
>       {
>         return -32768;
>       }
>       else
>       {
>         return (short) sample;
>       }
>     }
>
>     static int Main(string[] args)
>     {
>       if (args.Length != 1)
>       {
>         Console.WriteLine("This program takes only one argument, input
> file");
>         return 1;
>       }
>
>       string inputFileName = args[0], outputFileName =
> Path.ChangeExtension(inputFileName, "raw");
>       Stream inputFile = File.OpenRead(inputFileName), outputFile =
> File.OpenWrite(outputFileName);
>       BinaryReader inputFileReader = new BinaryReader(inputFile);
>       BinaryWriter outputFileWriter = new BinaryWriter(outputFile);
>
>       while (inputFile.Length - inputFile.Position >= 15)
>       {
>         DecodeSingleFrame(inputFileReader, outputFileWriter);
>       }
>
>       if (inputFile.Length - inputFile.Position > 0)
>       {
>         Console.WriteLine("File has {0} spare bytes.", inputFile.Length -
> inputFile.Position);
>       }
>
>       return 0;
>     }
>   }
> }
> }}}

New description:

 Summary of the bug:
 How to reproduce:
 {{{
 $ ffmpeg -i sound.XA sound.wav
 sound.XA: Invalid data found when processing input
 }}}

 I would like support for headerless Electronic Arts ADPCM files to be
 added.
 These files are common in some Electronic Arts games, like Harry Potter
 and the Chamber of Secrets and Brother Bear.

 The format is similar to adpcm_ea_r1, but the file is always mono, there
 is no channels header. ADPCM history is always initialized to [0, 0].
 File size is always a multiple of 15, there is no raw sample mode (0xEE).

 Sample rate is 22050Hz by default.

 Files can be encoded into this format using sx.exe:
 {{{
 > sx -raw -eaxa_blk sample.wav -=sample.XA
 }}}

 I attached a test case, a XA file and the corresponding decoded raw file.

 This is decoder code in C# (I release it under CC0):

 {{{
 using System;
 using System.IO;
 using System.Text;

 namespace KoopsAudioDecoder
 {
   class Program
   {
     static int coeff1, coeff2, shift, adpcmHistory1 = 0, adpcmHistory2 =
 0;

     static readonly int[] EA_XA_TABLE = new int[] {
         0,    0,
       240,    0,
       460, -208,
       392, -220,
     };

     static void DecodeSingleFrame(BinaryReader stream, BinaryWriter
 outbuf)
     {
       int frameInfo = stream.ReadByte();

       coeff1 = EA_XA_TABLE[((frameInfo >> 4) & 15) * 2];
       coeff2 = EA_XA_TABLE[((frameInfo >> 4) & 15) * 2 + 1];
       shift = (frameInfo & 15) + 8;

       for (int i = 0; i < 14; i++)
       {
         int sample_byte = stream.ReadByte();

         int[] nibbles = { sample_byte >> 4, sample_byte & 15 };

         foreach (int nibble in nibbles)
         {
           int sample = GetSample(nibble);

           outbuf.Write(Clamp16(sample));
         }
       }
     }

     private static int GetSample(int nibble)
     {
       int sample = ((nibble << 28 >> shift) + (coeff1 * adpcmHistory1) +
 (coeff2 * adpcmHistory2)) >> 8;

       adpcmHistory2 = adpcmHistory1;
       adpcmHistory1 = sample;

       return sample;
     }

     static private short Clamp16(int sample)
     {
       if (sample > 32767)
       {
         return 32767;
       }

       if (sample < -32768)
       {
         return -32768;
       }

       return (short) sample;
     }

     static int Main(string[] args)
     {
       if (args.Length != 1)
       {
         Console.WriteLine("This program takes only one argument, input
 file");
         return 1;
       }

       string inputFileName = args[0], outputFileName =
 Path.ChangeExtension(inputFileName, "raw");
       Stream inputFile = File.OpenRead(inputFileName), outputFile =
 File.OpenWrite(outputFileName);
       BinaryReader inputFileReader = new BinaryReader(inputFile);
       BinaryWriter outputFileWriter = new BinaryWriter(outputFile);

       while (inputFile.Length - inputFile.Position >= 15)
       {
         DecodeSingleFrame(inputFileReader, outputFileWriter);
       }

       if (inputFile.Length - inputFile.Position > 0)
       {
         Console.WriteLine("File has {0} spare bytes.", inputFile.Length -
 inputFile.Position);
       }

       return 0;
     }
   }
 }
 }}}

--

Comment (by cehoyos):

 The stereo file unfortunately never appeared, please attach it here.

--
Ticket URL: <https://trac.ffmpeg.org/ticket/6690#comment:12>
FFmpeg <https://ffmpeg.org>
FFmpeg issue tracker


More information about the FFmpeg-trac mailing list