summaryrefslogtreecommitdiff
path: root/utils/xbox/MakeGameData/imaadpcm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/xbox/MakeGameData/imaadpcm.cpp')
-rw-r--r--utils/xbox/MakeGameData/imaadpcm.cpp1531
1 files changed, 1531 insertions, 0 deletions
diff --git a/utils/xbox/MakeGameData/imaadpcm.cpp b/utils/xbox/MakeGameData/imaadpcm.cpp
new file mode 100644
index 0000000..6648669
--- /dev/null
+++ b/utils/xbox/MakeGameData/imaadpcm.cpp
@@ -0,0 +1,1531 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+/***************************************************************************
+ *
+ * Copyright (C) 2001 Microsoft Corporation. All Rights Reserved.
+ *
+ * File: imaadpcm.cpp
+ * Content: IMA ADPCM CODEC.
+ * History:
+ * Date By Reason
+ * ==== == ======
+ * 04/29/01 dereks Created.
+ * 06/12/01 jharding Adapted for command-line encode
+ *
+ ****************************************************************************/
+
+#include <stdio.h>
+#include <wtypes.h>
+#include <assert.h>
+#include "imaadpcm.h"
+
+// 1/2 the range of the searchable step indices
+// for a particular block when optimizing on a
+// per-block basis. Widening or narrowing this
+// range may produce better/worse encodings.
+// Experimentation may be necessary. Higher values
+// cause each block to be encoded better, but may
+// produce popping in particularly fast attacks across
+// blocks, while smaller values limit the number
+// of encodings you consider
+#define STEPINDEXSEARCHRANGE (24)
+
+/****************************************************************************
+ *
+ * CImaAdpcmCodec
+ *
+ * Description:
+ * Object constructor.
+ *
+ * Arguments:
+ * (void)
+ *
+ * Returns:
+ * (void)
+ *
+ ****************************************************************************/
+
+//
+// This array is used by NextStepIndex to determine the next step index to use.
+// The step index is an index to the m_asStep[] array, below.
+//
+
+const short CImaAdpcmCodec::m_asNextStep[16] =
+{
+ -1, -1, -1, -1, 2, 4, 6, 8,
+ -1, -1, -1, -1, 2, 4, 6, 8
+};
+
+//
+// This array contains the array of step sizes used to encode the ADPCM
+// samples. The step index in each ADPCM block is an index to this array.
+//
+
+const short CImaAdpcmCodec::m_asStep[89] =
+{
+ 7, 8, 9, 10, 11, 12, 13,
+ 14, 16, 17, 19, 21, 23, 25,
+ 28, 31, 34, 37, 41, 45, 50,
+ 55, 60, 66, 73, 80, 88, 97,
+ 107, 118, 130, 143, 157, 173, 190,
+ 209, 230, 253, 279, 307, 337, 371,
+ 408, 449, 494, 544, 598, 658, 724,
+ 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749,
+ 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+ 5894, 6484, 7132, 7845, 8630, 9493, 10442,
+ 11487, 12635, 13899, 15289, 16818, 18500, 20350,
+ 22385, 24623, 27086, 29794, 32767
+};
+
+CImaAdpcmCodec::CImaAdpcmCodec
+(
+ void
+)
+{
+}
+
+
+/****************************************************************************
+ *
+ * ~CImaAdpcmCodec
+ *
+ * Description:
+ * Object destructor.
+ *
+ * Arguments:
+ * (void)
+ *
+ * Returns:
+ * (void)
+ *
+ ****************************************************************************/
+
+CImaAdpcmCodec::~CImaAdpcmCodec
+(
+ void
+)
+{
+}
+
+
+/****************************************************************************
+ *
+ * Initialize
+ *
+ * Description:
+ * Initializes the object.
+ *
+ * Arguments:
+ * LPCIMAADPCMWAVEFORMAT [in]: encoded data format.
+ * BOOL [in]: TRUE to initialize the object as an encoder.
+ *
+ * Returns:
+ * BOOL: TRUE on success.
+ *
+ ****************************************************************************/
+
+BOOL
+CImaAdpcmCodec::Initialize
+(
+ LPCIMAADPCMWAVEFORMAT pwfxEncode,
+ CODEC_MODE cmCodecMode
+)
+{
+ static const LPFNIMAADPCMCONVERT apfnConvert[2][2] =
+ {
+ {
+ DecodeM16,
+ DecodeS16
+ },
+ {
+ EncodeM16,
+ EncodeS16
+ }
+ };
+
+ if(!IsValidImaAdpcmFormat(pwfxEncode))
+ {
+ return FALSE;
+ }
+
+ //
+ // Save the format data
+ //
+
+ m_wfxEncode = *pwfxEncode;
+ m_cmCodecMode = cmCodecMode;
+
+ //
+ // Set up the conversion function
+ //
+
+ m_pfnConvert = apfnConvert[!(m_cmCodecMode == CODEC_MODE_DECODE)][m_wfxEncode.wfx.nChannels - 1];
+
+ //
+ // Initialize the stepping indices
+ //
+
+ m_nStepIndexL = m_nStepIndexR = 0;
+
+ return TRUE;
+}
+
+
+/****************************************************************************
+ *
+ * Convert
+ *
+ * Description:
+ * Converts data from the source to destination format.
+ *
+ * Arguments:
+ * LPCVOID [in]: source buffer.
+ * LPVOID [out]: destination buffer.
+ * UINT [in]: block count.
+ *
+ * Returns:
+ * BOOL: TRUE on success.
+ *
+ ****************************************************************************/
+
+BOOL
+CImaAdpcmCodec::Convert
+(
+ LPCVOID pvSrc,
+ LPVOID pvDst,
+ UINT cBlocks
+)
+{
+ // Array of decoders
+ static const LPFNIMAADPCMCONVERT apfnDecoders[2] =
+ {
+ DecodeM16,
+ DecodeS16
+ };
+
+ // Both destination and source block sizes
+ DWORD dwSrcBlockSize = m_wfxEncode.wfx.nChannels * m_wfxEncode.wSamplesPerBlock * sizeof(short);
+ DWORD dwDstBlockSize = m_wfxEncode.wfx.nBlockAlign;
+
+ // Zero out the output
+ ZeroMemory( pvDst, cBlocks * dwDstBlockSize );
+
+ switch( m_cmCodecMode )
+ {
+ case CODEC_MODE_DECODE:
+ // If we are decoding, just do it
+ return m_pfnConvert( (LPBYTE)pvSrc, (LPBYTE)pvDst, cBlocks, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR );
+
+ case CODEC_MODE_ENCODE_NORMAL:
+ // Normal encode
+ // We have some output right now, so this becomes a separate case.
+ // Otherwise, it would be the same as CODEC_MODE_DECODE
+ {
+ printf("Using normal encoding...\n");
+
+ // Allocate temporary buffers
+ LPBYTE pvDecoded = new BYTE[cBlocks * dwSrcBlockSize];
+ if( !pvDecoded )
+ return FALSE;
+
+ // Find the decoder
+ LPFNIMAADPCMCONVERT pfnOppConvert;
+ pfnOppConvert = apfnDecoders[m_wfxEncode.wfx.nChannels - 1];
+
+ // Encode the stream
+ if( !m_pfnConvert( (LPBYTE)pvSrc, (LPBYTE)pvDst, cBlocks, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ) )
+ {
+ delete[] pvDecoded;
+ return FALSE;
+ }
+
+ // Decode it back
+ if( !pfnOppConvert( (LPBYTE)pvDst, pvDecoded, cBlocks, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ) )
+ {
+ delete[] pvDecoded;
+ return FALSE;
+ }
+
+ // Report the normal difference
+ printf( "Difference between original and decoded streams: 0x%I64x\n",
+ CalcDifference( (LPBYTE)pvSrc, pvDecoded, cBlocks, cBlocks, dwSrcBlockSize ) );
+
+ delete[] pvDecoded;
+ }
+ break;
+
+ case CODEC_MODE_ENCODE_OPTIMIZE_WHOLE_FILE:
+ // Optimize whole file encode
+ // Encode the file with each possible starting step index
+ // and pick the best one
+ {
+ printf("Using whole file encoding...\n");
+
+ // Allocate temporary buffers
+ LPBYTE pvTempDst = new BYTE[cBlocks * dwDstBlockSize];
+ if(!pvTempDst)
+ return FALSE;
+
+ LPBYTE pvDecoded = new BYTE[cBlocks * dwSrcBlockSize];
+ if(!pvDecoded)
+ {
+ delete[] pvTempDst;
+ return FALSE;
+ }
+
+ // Find the decoder
+ LPFNIMAADPCMCONVERT pfnOppConvert;
+ pfnOppConvert = apfnDecoders[m_wfxEncode.wfx.nChannels - 1];
+
+ // Keep track of the best encoding, as well as the chosen step index
+ ULONGLONG ullLeastDiff = (ULONGLONG)-1;
+ UINT uChosen = (UINT)-1;
+
+ // Encode the entire stream with each step index and choose the best one
+ for( UINT i = 0; i < ARRAYSIZE(m_asStep); ++i )
+ {
+ ZeroMemory( pvTempDst, cBlocks * dwDstBlockSize );
+ ZeroMemory( pvDecoded, cBlocks * dwSrcBlockSize );
+
+ // Encode
+ m_nStepIndexL = m_nStepIndexR = i;
+ if( !m_pfnConvert( (LPBYTE)pvSrc, pvTempDst, cBlocks, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ) )
+ continue;
+
+ // Decode
+ if( !pfnOppConvert( pvTempDst, pvDecoded, cBlocks, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ) )
+ continue;
+
+ // Diff
+ ULONGLONG ullDiff = CalcDifference( (LPBYTE)pvSrc, pvDecoded, cBlocks, cBlocks, dwSrcBlockSize );
+ if( ullDiff < ullLeastDiff )
+ {
+ ullLeastDiff = ullDiff;
+ uChosen = i;
+ CopyMemory( (LPBYTE)pvDst, pvTempDst, cBlocks * dwDstBlockSize );
+ }
+ }
+
+ // Report the optimized difference
+ printf( "Difference between original and decoded streams: 0x%I64x\n", ullLeastDiff );
+ printf( "Step index chosen: %d\n", uChosen );
+
+ delete[] pvTempDst;
+ delete[] pvDecoded;
+ }
+ break;
+
+ case CODEC_MODE_ENCODE_OPTIMIZE_EACH_BLOCK:
+ // Optimize per block encode
+ // Encode each block within the file with each
+ // possible starting step index and pick the
+ // best one for each block
+ {
+ printf("Using per-block encoding\n\n");
+
+ // Allocate temporary buffers
+ LPBYTE pvTempDst = new BYTE[dwDstBlockSize];
+ if( !pvTempDst )
+ return FALSE;
+
+ LPBYTE pvDecoded = new BYTE[dwSrcBlockSize];
+ if( !pvDecoded )
+ {
+ delete[] pvTempDst;
+ return FALSE;
+ }
+
+ // Find the decoder
+ LPFNIMAADPCMCONVERT pfnOppConvert;
+ pfnOppConvert = apfnDecoders[m_wfxEncode.wfx.nChannels - 1];
+
+ // We keep track of the best step index of the previous block
+ // This enables us to search a small range of values close to
+ // this value (the size of 2*STEPINDEXSEARCHRANGE)
+ // To begin, the previous block's best step index was -1.
+ INT iPreviousBestStepIndex = -1;
+
+ for( UINT c = 0; c < cBlocks; ++c )
+ {
+ ULONGLONG ullLeastDiff = (ULONGLONG)-1;
+ INT iThisBestStepIndex = -1;
+ INT iStartIndex, iStopIndex;
+
+ // Setup the start/stop indices properly
+ if( iPreviousBestStepIndex == -1 )
+ {
+ // If the previous best step index is -1,
+ // then we haven't yet encoded a block. Search
+ // through the entire range of step indices,
+ // rather than just in a limited range
+ iStartIndex = 0;
+ iStopIndex = ARRAYSIZE( m_asStep );
+ }
+ else
+ {
+ // Keep the range of indices to search limited
+ // to around the previously chosen step index
+ iStartIndex = iPreviousBestStepIndex - STEPINDEXSEARCHRANGE;
+ iStopIndex = iPreviousBestStepIndex + STEPINDEXSEARCHRANGE + 1;
+ }
+
+ // Try each step index in the searchable range and choose the best one
+ // for this block
+ for( INT i = iStartIndex; i < iStopIndex; ++i )
+ {
+ // Don't consider anything out of range
+ if( i < 0 || i >= ARRAYSIZE( m_asStep ) )
+ continue;
+
+ // Zero out the temporary buffers
+ ZeroMemory( pvTempDst, dwDstBlockSize );
+ ZeroMemory( pvDecoded, dwSrcBlockSize );
+
+ // Encode
+ m_nStepIndexL = m_nStepIndexR = i;
+ if( !m_pfnConvert( (LPBYTE)pvSrc + c*dwSrcBlockSize, pvTempDst, 1, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ) )
+ continue;
+
+ // Decode
+ if( !pfnOppConvert( pvTempDst, pvDecoded, 1, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ) )
+ continue;
+
+ // Diff
+ ULONGLONG ullDiff = CalcDifference( (LPBYTE)pvSrc + c*dwSrcBlockSize, pvDecoded, 1, cBlocks, dwSrcBlockSize );
+ if( ullDiff < ullLeastDiff )
+ {
+ ullLeastDiff = ullDiff;
+ iThisBestStepIndex = i;
+ CopyMemory( (LPBYTE)pvDst + c*dwDstBlockSize, (LPBYTE)pvTempDst, dwDstBlockSize );
+ }
+ }
+
+ // Save the best step index for this block
+ iPreviousBestStepIndex = iThisBestStepIndex;
+ }
+
+ delete[] pvTempDst;
+ delete[] pvDecoded;
+
+ // Report on the optimized difference
+ pvDecoded = new BYTE[cBlocks * dwSrcBlockSize];
+ pfnOppConvert( (LPBYTE)pvDst, pvDecoded, cBlocks, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR );
+ printf( "Difference between original and decoded streams: 0x%I64x\n", CalcDifference( (LPBYTE)pvSrc, pvDecoded, cBlocks, cBlocks, dwSrcBlockSize ) );
+
+ delete[] pvDecoded;
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+
+/****************************************************************************
+ *
+ * Reset
+ *
+ * Description:
+ * Resets the conversion operation.
+ *
+ * Arguments:
+ * (void)
+ *
+ * Returns:
+ * (void)
+ *
+ ****************************************************************************/
+
+void
+CImaAdpcmCodec::Reset
+(
+ void
+)
+{
+ //
+ // Reset the stepping indices
+ //
+
+ m_nStepIndexL = m_nStepIndexR = 0;
+}
+
+
+/****************************************************************************
+ *
+ * GetEncodeAlignment
+ *
+ * Description:
+ * Gets the alignment of an encoded buffer.
+ *
+ * Arguments:
+ * (void)
+ *
+ * Returns:
+ * WORD: alignment, in bytes.
+ *
+ ****************************************************************************/
+
+WORD
+CImaAdpcmCodec::GetEncodeAlignment
+(
+ void
+)
+{
+ return m_wfxEncode.wfx.nBlockAlign;
+}
+
+
+/****************************************************************************
+ *
+ * GetDecodeAlignment
+ *
+ * Description:
+ * Gets the alignment of a decoded buffer.
+ *
+ * Arguments:
+ * (void)
+ *
+ * Returns:
+ * DWORD: alignment, in bytes.
+ *
+ ****************************************************************************/
+
+WORD
+CImaAdpcmCodec::GetDecodeAlignment
+(
+ void
+)
+{
+ return m_wfxEncode.wSamplesPerBlock * m_wfxEncode.wfx.nChannels * IMAADPCM_PCM_BITS_PER_SAMPLE / 8;
+}
+
+
+/****************************************************************************
+ *
+ * CalculateEncodeAlignment
+ *
+ * Description:
+ * Calculates an encoded data block alignment based on a PCM sample
+ * count and an alignment multiplier.
+ *
+ * Arguments:
+ * WORD [in]: channel count.
+ * WORD [in]: PCM samples per block.
+ *
+ * Returns:
+ * WORD: alignment, in bytes.
+ *
+ ****************************************************************************/
+
+WORD
+CImaAdpcmCodec::CalculateEncodeAlignment
+(
+ WORD nChannels,
+ WORD nSamplesPerBlock
+)
+{
+ const WORD nEncodedSampleBits = nChannels * IMAADPCM_BITS_PER_SAMPLE;
+ const WORD nHeaderBytes = nChannels * IMAADPCM_HEADER_LENGTH;
+ INT nBlockAlign;
+
+ //
+ // Calculate the raw block alignment that nSamplesPerBlock dictates. This
+ // value may include a partial encoded sample, so be sure to round up.
+ //
+ // Start with the samples-per-block, minus 1. The first sample is actually
+ // stored in the header.
+ //
+
+ nBlockAlign = nSamplesPerBlock - 1;
+
+ //
+ // Convert to encoded sample size
+ //
+
+ nBlockAlign *= nEncodedSampleBits;
+ nBlockAlign += 7;
+ nBlockAlign /= 8;
+
+ //
+ // The stereo encoder requires that there be at least two DWORDs to process
+ //
+
+ nBlockAlign += 7;
+ nBlockAlign /= 8;
+ nBlockAlign *= 8;
+
+ //
+ // Add the header
+ //
+
+ nBlockAlign += nHeaderBytes;
+
+ // We used an INT temporarily, but the final result should fit into a WORD
+ assert( nBlockAlign < 0xFFFF );
+ return (WORD)nBlockAlign;
+}
+
+
+/****************************************************************************
+ *
+ * CreatePcmFormat
+ *
+ * Description:
+ * Creates a PCM format descriptor.
+ *
+ * Arguments:
+ * WORD [in]: channel count.
+ * DWORD [in]: sampling rate.
+ * LPWAVEFORMATEX [out]: format descriptor.
+ *
+ * Returns:
+ * (void)
+ *
+ ****************************************************************************/
+
+void
+CImaAdpcmCodec::CreatePcmFormat
+(
+ WORD nChannels,
+ DWORD nSamplesPerSec,
+ LPWAVEFORMATEX pwfx
+)
+{
+ pwfx->wFormatTag = WAVE_FORMAT_PCM;
+ pwfx->nChannels = nChannels;
+ pwfx->nSamplesPerSec = nSamplesPerSec;
+ pwfx->nBlockAlign = nChannels * IMAADPCM_PCM_BITS_PER_SAMPLE / 8;
+ pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
+ pwfx->wBitsPerSample = IMAADPCM_PCM_BITS_PER_SAMPLE;
+}
+
+
+/****************************************************************************
+ *
+ * CreateImaAdpcmFormat
+ *
+ * Description:
+ * Creates an IMA ADPCM format descriptor.
+ *
+ * Arguments:
+ * WORD [in]: channel count.
+ * DWORD [in]: sampling rate.
+ * LPIMAADPCMWAVEFORMAT [out]: format descriptor.
+ *
+ * Returns:
+ * (void)
+ *
+ ****************************************************************************/
+
+void
+CImaAdpcmCodec::CreateImaAdpcmFormat
+(
+ WORD nChannels,
+ DWORD nSamplesPerSec,
+ WORD nSamplesPerBlock,
+ LPIMAADPCMWAVEFORMAT pwfx
+)
+{
+ pwfx->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM;
+ pwfx->wfx.nChannels = nChannels;
+ pwfx->wfx.nSamplesPerSec = nSamplesPerSec;
+ pwfx->wfx.nBlockAlign = CalculateEncodeAlignment(nChannels, nSamplesPerBlock);
+ pwfx->wfx.nAvgBytesPerSec = nSamplesPerSec * pwfx->wfx.nBlockAlign / nSamplesPerBlock;
+ pwfx->wfx.wBitsPerSample = IMAADPCM_BITS_PER_SAMPLE;
+ pwfx->wfx.cbSize = sizeof(*pwfx) - sizeof(pwfx->wfx);
+ pwfx->wSamplesPerBlock = nSamplesPerBlock;
+}
+
+
+/****************************************************************************
+ *
+ * IsValidPcmFormat
+ *
+ * Description:
+ * Validates a format structure.
+ *
+ * Arguments:
+ * LPCWAVEFORMATEX [in]: format.
+ *
+ * Returns:
+ * BOOL: TRUE on success.
+ *
+ ****************************************************************************/
+
+BOOL
+CImaAdpcmCodec::IsValidPcmFormat
+(
+ LPCWAVEFORMATEX pwfx
+)
+{
+ if(WAVE_FORMAT_PCM != pwfx->wFormatTag)
+ {
+ return FALSE;
+ }
+
+ if((pwfx->nChannels < 1) || (pwfx->nChannels > IMAADPCM_MAX_CHANNELS))
+ {
+ return FALSE;
+ }
+
+ if(IMAADPCM_PCM_BITS_PER_SAMPLE != pwfx->wBitsPerSample)
+ {
+ return FALSE;
+ }
+
+ if(pwfx->nChannels * pwfx->wBitsPerSample / 8 != pwfx->nBlockAlign)
+ {
+ return FALSE;
+ }
+
+ if(pwfx->nBlockAlign * pwfx->nSamplesPerSec != pwfx->nAvgBytesPerSec)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/****************************************************************************
+ *
+ * IsValidXboxAdpcmFormat
+ *
+ * Description:
+ * Validates a format structure.
+ *
+ * Arguments:
+ * LPCIMAADPCMWAVEFORMAT [in]: format.
+ *
+ * Returns:
+ * BOOL: TRUE on success.
+ *
+ ****************************************************************************/
+
+BOOL
+CImaAdpcmCodec::IsValidImaAdpcmFormat
+(
+ LPCIMAADPCMWAVEFORMAT pwfx
+)
+{
+ if(WAVE_FORMAT_XBOX_ADPCM != pwfx->wfx.wFormatTag)
+ {
+ return FALSE;
+ }
+
+ if(sizeof(*pwfx) - sizeof(pwfx->wfx) != pwfx->wfx.cbSize)
+ {
+ return FALSE;
+ }
+
+ if((pwfx->wfx.nChannels < 1) || (pwfx->wfx.nChannels > IMAADPCM_MAX_CHANNELS))
+ {
+ return FALSE;
+ }
+
+ if(IMAADPCM_BITS_PER_SAMPLE != pwfx->wfx.wBitsPerSample)
+ {
+ return FALSE;
+ }
+
+ if(CalculateEncodeAlignment(pwfx->wfx.nChannels, pwfx->wSamplesPerBlock) != pwfx->wfx.nBlockAlign)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/****************************************************************************
+ *
+ * CalcDifference
+ *
+ * Description:
+ * Calculates the error between two audio buffers. The error is clamped
+ * at (ULONGLONG)-1. Also, the error of a block acts as a percentage of
+ * the maximum possible contribution of a block.
+ *
+ * Arguments:
+ * LPBYTE [in]: First buffer
+ * LPBYTE [in]: Second buffer
+ * UINT [in]: Number of blocks-worth to compare
+ * UINT [in]: Total number of blocks being converted
+ * DWORD [in]: Size of a single block in bytes
+ *
+ * Returns:
+ * ULONGLONG: Difference of the two buffers
+ *
+ ****************************************************************************/
+ULONGLONG CImaAdpcmCodec::CalcDifference(LPBYTE pvBuffer1, LPBYTE pvBuffer2, UINT cBlocks, UINT cTotalBlocks, DWORD dwBlockSize)
+{
+ ULONGLONG ullDiff = 0;
+
+ // Each block worth of error can contribute a maximum of this value
+ const ULONGLONG ullMaxBlockContribution = ( (ULONGLONG)-1 / cTotalBlocks );
+
+ // The maximum error in a block is
+ // (2^16)^2 * m_wfxEncode.wSamplesPerBlock
+ // = ( 1 << 32 ) * m_wfxEncode.wSamplesPerBlock
+ const ULONGLONG ullMaxBlockDiff = ( (ULONGLONG)1 << 32 ) * m_wfxEncode.wSamplesPerBlock;
+
+ // Now we go through the buffers sample by sample and find the difference
+ // on a block-by-block basis. The factored difference of a block is
+ // ullBlockDiff / ullMaxBlockDiff * ullMaxBlockContribution
+ for( UINT i = 0; i < cBlocks; ++i )
+ {
+ PSHORT pSamples1 = (PSHORT)(pvBuffer1 + i * dwBlockSize);
+ PSHORT pSamples2 = (PSHORT)(pvBuffer2 + i * dwBlockSize);
+ ULONGLONG ullBlockDiff = 0;
+
+ // Find the block difference
+ for( UINT j = 0; j < m_wfxEncode.wSamplesPerBlock; ++j )
+ {
+ ULONGLONG ullSampleDiff = (ULONGLONG)(pSamples2[j]) - (ULONGLONG)(pSamples1[j]);
+ ullBlockDiff += ( ullSampleDiff * ullSampleDiff );
+ }
+
+ // Assert that we didn't go over the maximum possible
+ assert( ullBlockDiff <= ullMaxBlockDiff );
+
+ // Add the contribution of this block to the error
+ ullDiff += (ULONGLONG)( ( (DOUBLE)ullBlockDiff / (DOUBLE)ullMaxBlockDiff ) * ullMaxBlockContribution );
+ }
+
+ assert( ullDiff <= cBlocks * ullMaxBlockContribution );
+
+ return ullDiff;
+}
+
+
+/****************************************************************************
+ *
+ * EncodeSample
+ *
+ * Description:
+ * Encodes a sample.
+ *
+ * Arguments:
+ * int [in]: the sample to be encoded.
+ * LPINT [in/out]: the predicted value of the sample.
+ * int [in]: the quantization step size used to encode the sample.
+ *
+ * Returns:
+ * int: the encoded ADPCM sample.
+ *
+ ****************************************************************************/
+
+int
+CImaAdpcmCodec::EncodeSample
+(
+ int nInputSample,
+ LPINT pnPredictedSample,
+ int nStepSize
+)
+{
+ int nPredictedSample;
+ LONG lDifference;
+ int nEncodedSample;
+
+ nPredictedSample = *pnPredictedSample;
+
+ lDifference = nInputSample - nPredictedSample;
+ nEncodedSample = 0;
+
+ if(lDifference < 0)
+ {
+ nEncodedSample = 8;
+ lDifference = -lDifference;
+ }
+
+ if(lDifference >= nStepSize)
+ {
+ nEncodedSample |= 4;
+ lDifference -= nStepSize;
+ }
+
+ nStepSize >>= 1;
+
+ if(lDifference >= nStepSize)
+ {
+ nEncodedSample |= 2;
+ lDifference -= nStepSize;
+ }
+
+ nStepSize >>= 1;
+
+ if(lDifference >= nStepSize)
+ {
+ nEncodedSample |= 1;
+ lDifference -= nStepSize;
+ }
+
+ if(nEncodedSample & 8)
+ {
+ nPredictedSample = nInputSample + lDifference - (nStepSize >> 1);
+ }
+ else
+ {
+ nPredictedSample = nInputSample - lDifference + (nStepSize >> 1);
+ }
+
+ if(nPredictedSample > 32767)
+ {
+ nPredictedSample = 32767;
+ }
+ else if(nPredictedSample < -32768)
+ {
+ nPredictedSample = -32768;
+ }
+
+ *pnPredictedSample = nPredictedSample;
+
+ return nEncodedSample;
+}
+
+
+/****************************************************************************
+ *
+ * DecodeSample
+ *
+ * Description:
+ * Decodes an encoded sample.
+ *
+ * Arguments:
+ * int [in]: the sample to be decoded.
+ * int [in]: the predicted value of the sample.
+ * int [i]: the quantization step size used to encode the sample.
+ *
+ * Returns:
+ * int: the decoded PCM sample.
+ *
+ ****************************************************************************/
+
+int
+CImaAdpcmCodec::DecodeSample
+(
+ int nEncodedSample,
+ int nPredictedSample,
+ int nStepSize
+)
+{
+ LONG lDifference;
+ LONG lNewSample;
+
+ lDifference = nStepSize >> 3;
+
+ if(nEncodedSample & 4)
+ {
+ lDifference += nStepSize;
+ }
+
+ if(nEncodedSample & 2)
+ {
+ lDifference += nStepSize >> 1;
+ }
+
+ if(nEncodedSample & 1)
+ {
+ lDifference += nStepSize >> 2;
+ }
+
+ if(nEncodedSample & 8)
+ {
+ lDifference = -lDifference;
+ }
+
+ lNewSample = nPredictedSample + lDifference;
+
+ if((LONG)(short)lNewSample != lNewSample)
+ {
+ if(lNewSample < -32768)
+ {
+ lNewSample = -32768;
+ }
+ else
+ {
+ lNewSample = 32767;
+ }
+ }
+
+ return (int)lNewSample;
+}
+
+
+/****************************************************************************
+ *
+ * Conversion Routines
+ *
+ * Description:
+ * Converts a PCM buffer to ADPCM, or the reverse.
+ *
+ * Arguments:
+ * LPBYTE [in]: source buffer.
+ * LPBYTE [out]: destination buffer.
+ * UINT [in]: block count.
+ * UINT [in]: block alignment of the ADPCM data, in bytes.
+ * UINT [in]: the number of samples in each ADPCM block (not used in
+ * decoding).
+ * LPINT [in/out]: left-channel stepping index.
+ * LPINT [in/out]: right-channel stepping index.
+ *
+ * Returns:
+ * BOOL: TRUE on success.
+ *
+ ****************************************************************************/
+
+BOOL
+CImaAdpcmCodec::EncodeM16
+(
+ LPBYTE pbSrc,
+ LPBYTE pbDst,
+ UINT cBlocks,
+ UINT nBlockAlignment,
+ UINT cSamplesPerBlock,
+ LPINT pnStepIndexL,
+ LPINT
+)
+{
+ LPBYTE pbBlock;
+ UINT cSamples;
+ int nSample;
+ int nStepSize;
+ int nEncSample1;
+ int nEncSample2;
+ int nPredSample;
+ int nStepIndex;
+
+ //
+ // Save a local copy of the step index so we're not constantly
+ // dereferencing a pointer.
+ //
+
+ nStepIndex = *pnStepIndexL;
+
+ //
+ // Enter the main loop
+ //
+
+ while(cBlocks--)
+ {
+ pbBlock = pbDst;
+ cSamples = cSamplesPerBlock - 1;
+
+ //
+ // Block header
+ //
+
+ nPredSample = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+
+ *(LONG *)pbBlock = MAKELONG(nPredSample, nStepIndex);
+ pbBlock += sizeof(LONG);
+
+ //
+ // We have written the header for this block--now write the data
+ // chunk (which consists of a bunch of encoded nibbles). Note
+ // that if we don't have enough data to fill a complete byte, then
+ // we add a 0 nibble on the end.
+ //
+
+ while(cSamples)
+ {
+ //
+ // Sample 1
+ //
+
+ nSample = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+ cSamples--;
+
+ nStepSize = m_asStep[nStepIndex];
+ nEncSample1 = EncodeSample(nSample, &nPredSample, nStepSize);
+ nStepIndex = NextStepIndex(nEncSample1, nStepIndex);
+
+ //
+ // Sample 2
+ //
+
+ if(cSamples)
+ {
+ nSample = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+ cSamples--;
+
+ nStepSize = m_asStep[nStepIndex];
+ nEncSample2 = EncodeSample(nSample, &nPredSample, nStepSize);
+ nStepIndex = NextStepIndex(nEncSample2, nStepIndex);
+ }
+ else
+ {
+ nEncSample2 = 0;
+ }
+
+ //
+ // Write out encoded byte.
+ //
+
+ *pbBlock++ = (BYTE)(nEncSample1 | (nEncSample2 << 4));
+ }
+
+ //
+ // Skip padding
+ //
+
+ pbDst += nBlockAlignment;
+ }
+
+ //
+ // Restore the value of the step index to be used on the next buffer.
+ //
+
+ *pnStepIndexL = nStepIndex;
+
+ return TRUE;
+}
+
+
+BOOL
+CImaAdpcmCodec::EncodeS16
+(
+ LPBYTE pbSrc,
+ LPBYTE pbDst,
+ UINT cBlocks,
+ UINT nBlockAlignment,
+ UINT cSamplesPerBlock,
+ LPINT pnStepIndexL,
+ LPINT pnStepIndexR
+)
+{
+ LPBYTE pbBlock;
+ UINT cSamples;
+ UINT cSubSamples;
+ int nSample;
+ int nStepSize;
+ DWORD dwLeft;
+ DWORD dwRight;
+ int nEncSampleL;
+ int nPredSampleL;
+ int nStepIndexL;
+ int nEncSampleR;
+ int nPredSampleR;
+ int nStepIndexR;
+ UINT i;
+
+ //
+ // Save a local copy of the step indices so we're not constantly
+ // dereferencing a pointer.
+ //
+
+ nStepIndexL = *pnStepIndexL;
+ nStepIndexR = *pnStepIndexR;
+
+ //
+ // Enter the main loop
+ //
+
+ while(cBlocks--)
+ {
+ pbBlock = pbDst;
+ cSamples = cSamplesPerBlock - 1;
+
+ //
+ // LEFT channel block header
+ //
+
+ nPredSampleL = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+
+ *(LONG *)pbBlock = MAKELONG(nPredSampleL, nStepIndexL);
+ pbBlock += sizeof(LONG);
+
+ //
+ // RIGHT channel block header
+ //
+
+ nPredSampleR = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+
+ *(LONG *)pbBlock = MAKELONG(nPredSampleR, nStepIndexR);
+ pbBlock += sizeof(LONG);
+
+ //
+ // We have written the header for this block--now write the data
+ // chunk. This consists of 8 left samples (one DWORD of output)
+ // followed by 8 right samples (also one DWORD). Since the input
+ // samples are interleaved, we create the left and right DWORDs
+ // sample by sample, and then write them both out.
+ //
+
+ while(cSamples)
+ {
+ dwLeft = 0;
+ dwRight = 0;
+
+ cSubSamples = min(cSamples, 8);
+
+ for(i = 0; i < cSubSamples; i++)
+ {
+ //
+ // LEFT channel
+ //
+
+ nSample = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+
+ nStepSize = m_asStep[nStepIndexL];
+
+ nEncSampleL = EncodeSample(nSample, &nPredSampleL, nStepSize);
+
+ nStepIndexL = NextStepIndex(nEncSampleL, nStepIndexL);
+ dwLeft |= (DWORD)nEncSampleL << (4 * i);
+
+ //
+ // RIGHT channel
+ //
+
+ nSample = *(short *)pbSrc;
+ pbSrc += sizeof(short);
+
+ nStepSize = m_asStep[nStepIndexR];
+
+ nEncSampleR = EncodeSample(nSample, &nPredSampleR, nStepSize);
+
+ nStepIndexR = NextStepIndex(nEncSampleR, nStepIndexR);
+ dwRight |= (DWORD)nEncSampleR << (4 * i);
+ }
+
+ //
+ // Write out encoded DWORDs.
+ //
+
+ *(LPDWORD)pbBlock = dwLeft;
+ pbBlock += sizeof(DWORD);
+
+ *(LPDWORD)pbBlock = dwRight;
+ pbBlock += sizeof(DWORD);
+
+ cSamples -= cSubSamples;
+ }
+
+ //
+ // Skip padding
+ //
+
+ pbDst += nBlockAlignment;
+ }
+
+ //
+ // Restore the value of the step index to be used on the next buffer.
+ //
+
+ *pnStepIndexL = nStepIndexL;
+ *pnStepIndexR = nStepIndexR;
+
+ return TRUE;
+
+}
+
+
+BOOL
+CImaAdpcmCodec::DecodeM16
+(
+ LPBYTE pbSrc,
+ LPBYTE pbDst,
+ UINT cBlocks,
+ UINT nBlockAlignment,
+ UINT cSamplesPerBlock,
+ LPINT,
+ LPINT
+)
+{
+ BOOL fSuccess = TRUE;
+ LPBYTE pbBlock;
+ UINT cSamples;
+ BYTE bSample;
+ int nStepSize;
+ int nEncSample;
+ int nPredSample;
+ int nStepIndex;
+ DWORD dwHeader;
+
+ //
+ // Enter the main loop
+ //
+
+ while(cBlocks--)
+ {
+ pbBlock = pbSrc;
+ cSamples = cSamplesPerBlock - 1;
+
+ //
+ // Block header
+ //
+
+ dwHeader = *(LPDWORD)pbBlock;
+ pbBlock += sizeof(DWORD);
+
+ nPredSample = (int)(short)LOWORD(dwHeader);
+ nStepIndex = (int)(BYTE)HIWORD(dwHeader);
+
+ if(!ValidStepIndex(nStepIndex))
+ {
+ //
+ // The step index is out of range - this is considered a fatal
+ // error as the input stream is corrupted. We fail by returning
+ // zero bytes converted.
+ //
+
+ fSuccess = FALSE;
+ break;
+ }
+
+ //
+ // Write out first sample
+ //
+
+ *(short *)pbDst = (short)nPredSample;
+ pbDst += sizeof(short);
+
+ //
+ // Enter the block loop
+ //
+
+ while(cSamples)
+ {
+ bSample = *pbBlock++;
+
+ //
+ // Sample 1
+ //
+
+ nEncSample = (bSample & (BYTE)0x0F);
+ nStepSize = m_asStep[nStepIndex];
+ nPredSample = DecodeSample(nEncSample, nPredSample, nStepSize);
+ nStepIndex = NextStepIndex(nEncSample, nStepIndex);
+
+ *(short *)pbDst = (short)nPredSample;
+ pbDst += sizeof(short);
+
+ cSamples--;
+
+ //
+ // Sample 2
+ //
+
+ if(cSamples)
+ {
+ nEncSample = (bSample >> 4);
+ nStepSize = m_asStep[nStepIndex];
+ nPredSample = DecodeSample(nEncSample, nPredSample, nStepSize);
+ nStepIndex = NextStepIndex(nEncSample, nStepIndex);
+
+ *(short *)pbDst = (short)nPredSample;
+ pbDst += sizeof(short);
+
+ cSamples--;
+ }
+ }
+
+ //
+ // Skip padding
+ //
+
+ pbSrc += nBlockAlignment;
+ }
+
+ return fSuccess;
+}
+
+
+BOOL
+CImaAdpcmCodec::DecodeS16
+(
+ LPBYTE pbSrc,
+ LPBYTE pbDst,
+ UINT cBlocks,
+ UINT nBlockAlignment,
+ UINT cSamplesPerBlock,
+ LPINT,
+ LPINT
+)
+{
+ BOOL fSuccess = TRUE;
+ LPBYTE pbBlock;
+ UINT cSamples;
+ UINT cSubSamples;
+ int nStepSize;
+ DWORD dwHeader;
+ DWORD dwLeft;
+ DWORD dwRight;
+ int nEncSampleL;
+ int nPredSampleL;
+ int nStepIndexL;
+ int nEncSampleR;
+ int nPredSampleR;
+ int nStepIndexR;
+ UINT i;
+
+ //
+ // Enter the main loop
+ //
+
+ while(cBlocks--)
+ {
+ pbBlock = pbSrc;
+ cSamples = cSamplesPerBlock - 1;
+
+ //
+ // LEFT channel header
+ //
+
+ dwHeader = *(LPDWORD)pbBlock;
+ pbBlock += sizeof(DWORD);
+
+ nPredSampleL = (int)(short)LOWORD(dwHeader);
+ nStepIndexL = (int)(BYTE)HIWORD(dwHeader);
+
+ if(!ValidStepIndex(nStepIndexL))
+ {
+ //
+ // The step index is out of range - this is considered a fatal
+ // error as the input stream is corrupted. We fail by returning
+ // zero bytes converted.
+ //
+
+ fSuccess = FALSE;
+ break;
+ }
+
+ //
+ // RIGHT channel header
+ //
+
+ dwHeader = *(LPDWORD)pbBlock;
+ pbBlock += sizeof(DWORD);
+
+ nPredSampleR = (int)(short)LOWORD(dwHeader);
+ nStepIndexR = (int)(BYTE)HIWORD(dwHeader);
+
+ if(!ValidStepIndex(nStepIndexR))
+ {
+ //
+ // The step index is out of range - this is considered a fatal
+ // error as the input stream is corrupted. We fail by returning
+ // zero bytes converted.
+ //
+
+ fSuccess = FALSE;
+ break;
+ }
+
+ //
+ // Write out first sample
+ //
+
+ *(LPDWORD)pbDst = MAKELONG(nPredSampleL, nPredSampleR);
+ pbDst += sizeof(DWORD);
+
+ //
+ // The first DWORD contains 4 left samples, the second DWORD
+ // contains 4 right samples. We process the source in 8-byte
+ // chunks to make it easy to interleave the output correctly.
+ //
+
+ while(cSamples)
+ {
+ dwLeft = *(LPDWORD)pbBlock;
+ pbBlock += sizeof(DWORD);
+ dwRight = *(LPDWORD)pbBlock;
+ pbBlock += sizeof(DWORD);
+
+ cSubSamples = min(cSamples, 8);
+
+ for(i = 0; i < cSubSamples; i++)
+ {
+ //
+ // LEFT channel
+ //
+
+ nEncSampleL = (dwLeft & 0x0F);
+ nStepSize = m_asStep[nStepIndexL];
+ nPredSampleL = DecodeSample(nEncSampleL, nPredSampleL, nStepSize);
+ nStepIndexL = NextStepIndex(nEncSampleL, nStepIndexL);
+
+ //
+ // RIGHT channel
+ //
+
+ nEncSampleR = (dwRight & 0x0F);
+ nStepSize = m_asStep[nStepIndexR];
+ nPredSampleR = DecodeSample(nEncSampleR, nPredSampleR, nStepSize);
+ nStepIndexR = NextStepIndex(nEncSampleR, nStepIndexR);
+
+ //
+ // Write out sample
+ //
+
+ *(LPDWORD)pbDst = MAKELONG(nPredSampleL, nPredSampleR);
+ pbDst += sizeof(DWORD);
+
+ //
+ // Shift the next input sample into the low-order 4 bits.
+ //
+
+ dwLeft >>= 4;
+ dwRight >>= 4;
+ }
+
+ cSamples -= cSubSamples;
+ }
+
+ //
+ // Skip padding
+ //
+
+ pbSrc += nBlockAlignment;
+ }
+
+ return fSuccess;
+}
+
+
+int XboxADPCMSize( int sampleCount, int channelCount, int sampleRate )
+{
+ CImaAdpcmCodec codec;
+ IMAADPCMWAVEFORMAT wfxEncode;
+
+ // Create an APDCM format structure based off the source format
+ codec.CreateImaAdpcmFormat( (WORD)channelCount, sampleRate, XBOX_ADPCM_SAMPLES_PER_BLOCK, &wfxEncode );
+
+ // Calculate number of ADPCM blocks and length of ADPCM data
+ DWORD dwDestBlocks = sampleCount / XBOX_ADPCM_SAMPLES_PER_BLOCK;
+ DWORD dwDestLength = dwDestBlocks * wfxEncode.wfx.nBlockAlign;
+
+ return dwDestLength;
+}
+
+void Convert16ToXboxADPCM( const short *pInputBuffer, byte *pOutputBuffer, byte *pOutFormat, int sampleCount, int channelCount, int sampleRate )
+{
+ CImaAdpcmCodec codec;
+ IMAADPCMWAVEFORMAT wfxEncode;
+
+ // Create an APDCM format structure based off the source format
+ codec.CreateImaAdpcmFormat( (WORD)channelCount, sampleRate, XBOX_ADPCM_SAMPLES_PER_BLOCK, &wfxEncode );
+ if ( pOutFormat )
+ {
+ memcpy( pOutFormat, &wfxEncode, sizeof(wfxEncode) );
+ }
+
+ // Initialize the codec
+ if ( FALSE == codec.Initialize( &wfxEncode, CODEC_MODE_ENCODE_OPTIMIZE_EACH_BLOCK ) )
+ {
+ printf( "Couldn't initialize codec.\n" );
+ return;
+ }
+
+ // Convert the data
+ DWORD dwDestBlocks = sampleCount / XBOX_ADPCM_SAMPLES_PER_BLOCK;
+ if ( FALSE == codec.Convert( (const byte *)pInputBuffer, pOutputBuffer, dwDestBlocks ) )
+ return;
+}