In my previous blog post about Decoding Audio With Media Foundation we looked at how we can play compressed formats like MP3 which can help a lot if our app is size-constrained. This time we are going to look at another compressed format called ADPCM. What's good about ADPCM is that it is natively supported by XAudio2 and we do not have to worry about decoding the data ourselves like we did in the previous blog post. ADPCM is the format that we are actually using in our product Elite Interval Training which is a fitness app that is available for both Windows Phone and Windows.
The ADPCM FormatAs I have mentioned, ADPCM is a lossy compression format that is natively supported by XAudio2. Depending on the compression parameters that you choose, ADPCM can achive compression ratios of up to 4:1, which is not bad at all. For example, before we switched to ADPCM the audio content of Elite Interval Training was about 30MB of uncompressed WAVE files and now it is about 8MB of compressed ADPCM files - the users are quite happy.
ADPCM File StructureADPCM files have a standard RIFF file structure and contain all of the chunks that we have looked at previously like RIFF, fmt, and data. Basically we do not need to do any modifications to our RiffReader and UniversalAudioPlayer classes to be able to play ADPCM. What's different is that ADPCM can contain optional chunks for samples which will actualy use to support more acurate looping.
The Problem With Compressed FormatsFrom Wikipedia article about compression artifacts:
Lossy audio compression schemes that are based on overlapping time/frequency transforms add a small amount of padding silence to the beginning and end of each track. These silences increase the playtime of the compressed audio data. If not trimmed off upon playback, the two silences played consecutively over a track boundary will appear as a pause in the original audio content. Lossless formats are not prone to this problem.ADPCM is no different and it suffers from similar problems - silence will probably be added to the encoded audio data which will obviously make it a tad longer than the original. This means that if you are using looping extensively the audio sample may get out of sync due to silence being added to the compressed audio data. Fortunately, the tool that we are going to use to convert our standard WAVE file to ADPCM will add loop information to our files and we will have more acurate loops.
Converting to ADPCMIn order to use ADPCM we should of course first convert the original audio data into ADPCM. I encourage you to use the AdpcmEncode command-line tool for this task. The tool is part of Windows SDK for Windows 8.1. The great thing about this tool is that it will take into account the extra padding that is used by the compression algorithm and provide us with smpl chuck that contains more acurate information about the actual length of our audio sample.
Now that we know a thing or two about ADPCM and its benefits let's take a look at the changes that we have to make to implement ADPCM support in our universal audio component.
Updating the Universal Audio ComponentAs I have mentioned, we do not have to do any changes to the component if we simply want to play ADPCM but since we want to read and use the loop information provided by AdpcmEncode we have to make some adjustments.
First we add two additional fields to the struct that carries information about our audio data. The fields will control where our audio data starts and where it ends - this will allow us to skip the extra padding that is added by the conversion process.
We now modify our riffReader class to read the sample information and pass it along:
We have added a new method RiffReader::SetLoopData(IBuffer^ buffer, AudioData& data) that searches for the smpl chunk and extracts the audio sample length if available. We simply use this method in RiffReader::Read(IBuffer^ buffer).
If loop information is available, data.loopLength specifies the length of the audio sample and data.loopStart specifies where the actual audio data starts. Since some padding data will be added by the compression process, you will notice that the compressed audio file has more samples than the original file. This is where data.LoopLength is heplufl as it will ignore the extra padding and provide a value that is much closer to the original sample length.
Now we use the loop information when we play an audio sample.
UniversalAudioPlayer::CreateAudioBuffer(AudioData data) is where the audio data that will be submitted to XAudio2 is configured. Here we set LoopLength and LoopBegin if they were provided by RiffReader.
Almost done. Now we can add ADPCM audio support to our app which can run on any Windows Phone 8.1 or Windows 8 device.
The AppThe only change here is to discard the use of the AudioDecoder that we have built last time since it is not needed in this scenario.
Download the full source code of the updated audio player and the app.