diff options
Diffstat (limited to 'utils/xbox/MakeGameData/imaadpcm.cpp')
| -rw-r--r-- | utils/xbox/MakeGameData/imaadpcm.cpp | 1531 |
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; +} |