Files
FC1/CrySoundSystem/ADPCMDecoder.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

558 lines
16 KiB
C++

#include "StdAfx.h"
#include <CrySizer.h>
#include <ISound.h>
#include <ISystem.h>
#include <ICryPak.h>
#include "AdpcmDecoder.h"
template< typename T, typename S >
inline void lsbshortldi( T& x, S& p )
{
x = (T) ( (int) p[ 0 ] + ( (int) p[ 1 ] << 8 ) );
p += 2;
}
static const int stepAdjustTable[] =
{
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
};
//////////////////////////////////////////////////////////////////////////
CADPCMDecoder::CADPCMDecoder(IMusicSystem *pMusicSystem) : m_bOpen(false)
{
m_pMusicSystem=pMusicSystem;
ISystem *pSystem=m_pMusicSystem->GetSystem();
ASSERT(pSystem);
m_pPak=pSystem->GetIPak();
ASSERT(m_pPak);
}
//////////////////////////////////////////////////////////////////////////
inline CADPCMDecoder::~CADPCMDecoder()
{
Close();
}
inline const bool CADPCMDecoder::IsOpen()const
{
return m_bOpen;
}
inline FILE* CADPCMDecoder::GetNewFileHandle()
{
FILE *pNewFile = m_pPak->FOpen(m_szFilename.c_str(), "rb");
if (!pNewFile)
return NULL;
return pNewFile;
}
inline void CADPCMDecoder::CloseFileHandle(FILE*& pFile)
{
if (pFile)
{
m_pPak->FClose(pFile);
pFile=NULL;
}
}
inline void CADPCMDecoder::SetFileInfo(const unsigned int cuiSampleCount, const bool cbIs44KHz)
{
m_FileInfo.nSamples = cuiSampleCount;//already doubled up
m_b44KHz = cbIs44KHz;
m_bOpen = true;
}
//////////////////////////////////////////////////////////////////////////
inline bool CADPCMDecoder::Open(const char *pszFilename)
{
m_FileInfo.sFilename = pszFilename;
m_szFilename = pszFilename;//damn it, but how can i get a const char* from the other string type, why do we have an own?
m_FileInfo.nHeaderSize = scuiSizeOfWavHeader;
return m_bOpen;
}
//////////////////////////////////////////////////////////////////////////
inline bool CADPCMDecoder::Close()
{
m_bOpen = false;
return true;
}
inline const char* CADPCMDecoder::FileName() const
{
return m_szFilename.c_str();
}
//////////////////////////////////////////////////////////////////////////
inline bool CADPCMDecoder::GetFileInfo(SMusicPatternFileInfo &FileInfo)
{
FileInfo=m_FileInfo;
return true;
}
//////////////////////////////////////////////////////////////////////////
inline void CADPCMDecoder::GetMemoryUsage(class ICrySizer* pSizer)
{
if (!pSizer->Add(*this))
return;
}
inline int CADPCMDecoder::AdpcmDecode( int c, MsState& rState, int sample1, int sample2 )
{
// Compute next step value
int step( rState.step );
int nstep( ( stepAdjustTable[ c ] * step ) >> 8 );
rState.step = ( nstep < 16 ) ? 16 : nstep;
// make linear prediction for next sample
int vlin( ( ( sample1 * rState.sCoef0 ) + ( sample2 * rState.sCoef1 ) ) >> 8 );
// then add the code * step adjustment
c -= ( c & 0x08 ) << 1;
int sample( ( c * step ) + vlin );
if( sample > 0x7fff )
sample = 0x7fff;
else if( sample < -0x8000 )
sample = -0x8000;
return( sample );
}
void CADPCMDecoder::AdpcmBlockExpandI( int nCoef, const short* iCoef, const unsigned char* ibuff, short* obuff, int n )
{
MsState state[ 2 ]; // One decompressor state for each channel
// Read the four-byte header for each channel
const unsigned char* ip( ibuff );
unsigned char bpred( *ip++ );
state[ 0 ].sCoef0 = iCoef[ (int) bpred * 2 + 0 ];
state[ 0 ].sCoef1 = iCoef[ (int) bpred * 2 + 1 ];
bpred = ( *ip++ );
state[ 1 ].sCoef0 = iCoef[ (int) bpred * 2 + 0 ];
state[ 1 ].sCoef1 = iCoef[ (int) bpred * 2 + 1 ];
lsbshortldi( state[0].step, ip );
lsbshortldi( state[1].step, ip );
// sample1's directly into obuff
lsbshortldi( obuff[ 2 ], ip );
lsbshortldi( obuff[ 2 + 1 ], ip );
// sample2's directly into obuff
lsbshortldi( obuff[ 0 ], ip );
lsbshortldi( obuff[ 1 ], ip );
// already have 1st 2 samples from block-header
short* op( obuff + 2 * 2 );
short* top( obuff + n * 2 );
{
while( op < top )
{
unsigned char b( *ip++ );
short* tmp( op );
*op++ = AdpcmDecode( b >> 4, state[0], tmp[ -2 ], tmp[ -2 * 2 ] );
tmp = op;
*op++ = AdpcmDecode( b & 0x0f, state[1], tmp[ -2 ], tmp[ -2 * 2 ] );
}
}
}
//////////////////////////////////////////////////////////////////////////
inline IMusicPatternDecoderInstance* CADPCMDecoder::CreateInstance()
{
return new CADPCMDecoderInstance(this);
}
//////////////////////////////////////////////////////////////////////////
/*** INSTANCE ***/
//////////////////////////////////////////////////////////////////////////
inline CADPCMDecoderInstance::CADPCMDecoderInstance(CADPCMDecoder *pDecoder)
: m_iFilePos(0), m_pFile(NULL),m_nPos(0), m_uiDataStartPos(0),m_uiNumSamples(0),m_sCoefs(7),m_psSamplePtr(NULL),
m_bInitialized(false), m_bCopyFromLastFrame(false),m_uiCurrentBlockSize(scuiBlockSize),m_uiCurrentSamplesPerBlock(scuiSamplesPerBlock)
{
assert(pDecoder);
assert(pDecoder->m_pPak);
m_pDecoder = pDecoder;
//init wav stream
if(!InitStreamWAV())
{
m_bInitialized = false;
}
else
{
m_bInitialized = true;
}
}
CADPCMDecoderInstance::~CADPCMDecoderInstance()
{
Close();//let the filehandle be released
}
inline void CADPCMDecoderInstance::Close()
{
if (m_pFile)
{
m_pDecoder->CloseFileHandle(m_pFile);
}
m_bInitialized = false;
}
//////////////////////////////////////////////////////////////////////////
inline bool CADPCMDecoderInstance::Seek0(int nDelay)
{
m_nPos=-nDelay; //new sample pos
m_bCopyFromLastFrame = false;
if (!m_pFile || m_uiDataStartPos == 0)
return false;
m_pDecoder->m_pPak->FSeek(m_pFile,m_uiDataStartPos,SEEK_SET);
m_iFilePos = 0;
return true;
}
inline const unsigned int CADPCMDecoderInstance::Samples()
{
return m_uiNumSamples;
}
//////////////////////////////////////////////////////////////////////////
inline int CADPCMDecoderInstance::GetPos()
{
if (!m_pFile)
return -1;
return m_nPos;
}
const bool CADPCMDecoderInstance::InitStreamWAV()
{
memset(m_aEncodedBlock,0,scuiBlockSize);
SADPCMWaveHdr adpcmHeader; //adpcm secific header
//get file handle
if(!m_pFile)
{
m_pFile = m_pDecoder->GetNewFileHandle();
if(!m_pFile)
return false;
}
ICryPak& pPak = *m_pDecoder->m_pPak; //get reference to pak file
//retrieve total size of file
pPak.FRead(&adpcmHeader,1, sizeof(SADPCMWaveHdr),m_pFile);
pPak.FRead(&m_sCoefs, 1, sizeof(short), m_pFile);
if(adpcmHeader.FormatTag == WAVE_FORMAT_PCM)
{
#ifdef LOG_MUSICFILES
m_pDecoder->m_pMusicSystem->LogMsg( "Uncompressed pcm data, use pcm decoder %s\n", m_pDecoder->FileName());
#else
TRACE("uncompressed pcm data, use pcm decoder %s\n", m_pDecoder->FileName());
#endif
return false;
}
if(adpcmHeader.FormatTag != WAVE_FORMAT_ADPCM || adpcmHeader.wChnls != 2)
{
#ifdef LOG_MUSICFILES
m_pDecoder->m_pMusicSystem->LogMsg("No stereo adpcm file: %s\n", m_pDecoder->FileName());
#else
TRACE("no stereo adpcm file: %s\n", m_pDecoder->FileName());
#endif
return false;
}
//set block alignments
m_uiCurrentBlockSize = adpcmHeader.wBlkAlign;
m_uiCurrentSamplesPerBlock = adpcmHeader.SamplesPerBlock;
if(m_sCoefs != scuiNumberOfCoefs || m_uiCurrentSamplesPerBlock > scuiSamplesPerBlock || m_uiCurrentBlockSize > scuiBlockSize)
{
#ifdef LOG_MUSICFILES
m_pDecoder->m_pMusicSystem->LogMsg("Unsupported adpcm format %s\n", m_pDecoder->FileName());
#else
TRACE("unsupported adpcm format %s\n", m_pDecoder->FileName());
#endif
return false;
}
pPak.FRead(m_aCoefs, 1, m_sCoefs/*7*/ * 2/*stereo->2 channels*/ * sizeof(short), m_pFile);
//now seek to start position (next to "data")
unsigned int uiDataLeft = 0;
for(;;)
{
char pcMagic[ 4 ];
pPak.FRead( pcMagic, 1, sizeof( unsigned char ) * 4, m_pFile );
if(0 != pPak.FEof( m_pFile ))
return false;
pPak.FRead( &uiDataLeft, 1, sizeof( unsigned int ), m_pFile );
if( 0 == strncmp( "data", pcMagic, 4 ) )
break;
pPak.FSeek( m_pFile, uiDataLeft, SEEK_CUR);
}
m_uiDataStartPos = m_pDecoder->m_pPak->FTell(m_pFile);//to make seek0 work properly, data start position
//compute number of samples contained
int m = (uiDataLeft % m_uiCurrentBlockSize);
m_uiNumSamples = (uiDataLeft / m_uiCurrentBlockSize) * m_uiCurrentSamplesPerBlock;//usually block aligned and not necessary
m -= scuiNumberOfCoefs * 2; // bytes beyond block-header
m += 2; // nibbles / channels + 2 in header
if(m > 0) //there are some samples contained in an unaligned block
{
if( m > (int)m_uiCurrentSamplesPerBlock )
{
m = (int)m_uiCurrentSamplesPerBlock;
}
m_uiNumSamples += m;
}
Seek0();
//since the total sample count is not known by the decoder, tell him
const bool cbIs44KHz = (adpcmHeader.dwSRate == 44100);
if(!cbIs44KHz)
m_uiNumSamples *= 2;
m_pDecoder->SetFileInfo(m_uiNumSamples, cbIs44KHz);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CADPCMDecoderInstance::GetPCMData(signed long *pDataOut, int nSamples, bool bLoop)
{
//prevent any asynchonisation in terms of file opening
if (!m_pFile || !m_bInitialized)
{
//try it again
if(m_pDecoder->IsOpen())
{
if(!InitStreamWAV())
return false;
}
else
return false;
}
int nOfs=0;
if (m_nPos<0)
{
if (-m_nPos >= nSamples)
{
memset(pDataOut, 0, nSamples*m_pDecoder->m_pMusicSystem->GetBytesPerSample());
m_nPos+=nSamples;
return true;
}else
{
nOfs=-m_nPos;
m_nPos=0;
Seek0();//set back to start
nSamples-=nOfs;
memset(pDataOut, 0, nOfs*m_pDecoder->m_pMusicSystem->GetBytesPerSample());
}
}
int nSamplesToRead;
for (;;)
{
if ((m_nPos+nSamples) > (int)m_uiNumSamples)
{
nSamplesToRead = m_uiNumSamples - m_nPos;
}
else
nSamplesToRead=nSamples;
if(m_pDecoder->Is44KHz())
{
if (!FillPCMBuffer(&(pDataOut[nOfs]), nSamplesToRead))
return false;
}
else
{
if (!FillPCMBuffer22KHz(&(pDataOut[nOfs]), nSamplesToRead))
return false;
}
m_nPos+=nSamplesToRead;
if (nSamplesToRead==nSamples)
break;
nOfs+=nSamplesToRead;
if (!bLoop)
{
memset(&(pDataOut[nOfs]), 0, (nSamples-nSamplesToRead)*m_pDecoder->m_pMusicSystem->GetBytesPerSample());
break;
}
nSamples-=nSamplesToRead;
Seek0();
}
return (true);
}
// AdpcmReadBlock - Grab and decode complete block of samples
inline unsigned short CADPCMDecoderInstance::AdpcmReadBlock(short* pDest)
{
//read block
unsigned short bytesRead( (unsigned short) m_pDecoder->m_pPak->FRead( m_aEncodedBlock, 1, m_uiCurrentBlockSize, m_pFile ) );
m_iFilePos += bytesRead; //internal file pos counter, counted from m_uiDataStartPos on
unsigned int samplesThisBlock = m_uiCurrentSamplesPerBlock;
if( bytesRead < m_uiCurrentBlockSize )
{
//this shouldnt be the case, but handle it just in case (most programs do block align)
samplesThisBlock = 0;
unsigned int m = bytesRead;
//more data found than just coefs
if( m >= (unsigned int) ( scuiNumberOfCoefs * 2 ) )
{
samplesThisBlock = m - scuiNumberOfCoefs * 2 + 2;// bytes beyond block-header
}
else
return( 0 );
}
//now decode read stuff
if(pDest == NULL)
CADPCMDecoder::AdpcmBlockExpandI( m_sCoefs, m_aCoefs, m_aEncodedBlock, m_aDecodedBlock, samplesThisBlock );
else
CADPCMDecoder::AdpcmBlockExpandI( m_sCoefs, m_aCoefs, m_aEncodedBlock, pDest, samplesThisBlock );
return( samplesThisBlock );
}
const bool CADPCMDecoderInstance::FillPCMBuffer(signed long *pBuffer, int nSamples)
{
//since there get always complete blocks read, fill buffer with already compressed samples
const unsigned int cuiModulo = m_nPos % m_uiCurrentSamplesPerBlock;
if(cuiModulo != 0 && m_iFilePos > 0)//to not let break anything
{ //we are not on a block boundary
unsigned int uiLeft = min((unsigned int)nSamples, (m_uiCurrentSamplesPerBlock - cuiModulo));
if(uiLeft != 0)//read as many sampels as left decompressed and not more than requested
{
//copy decompressed data
signed long *pslDecompressed = reinterpret_cast<signed long*>(m_aDecodedBlock) + cuiModulo;
for(unsigned int i=0; i<uiLeft;i++)
{
*pBuffer++ = *pslDecompressed++;
}
//set new number of remaining samples
nSamples -= uiLeft;
}
}
short *pCurrentDest = reinterpret_cast<short*>(pBuffer); //need a short buffer to decode each channel indivually (remember: 16 bit)
if(nSamples > 0)
{
while(nSamples > 0)
{
//read as many blocks as necessary
//write straight to output buffer if complete block has to read in
if(nSamples >= (int)m_uiCurrentSamplesPerBlock)
{
bool bEndOfFile = (AdpcmReadBlock( pCurrentDest ) != m_uiCurrentSamplesPerBlock);
if(bEndOfFile) //shouldnt happen
return false;
nSamples -= m_uiCurrentSamplesPerBlock;
pCurrentDest += m_uiCurrentSamplesPerBlock * 2;//two channels decoded
}
else
{
//decompress to static buffer
unsigned int uiReadSamples = AdpcmReadBlock();
if(uiReadSamples != m_uiCurrentSamplesPerBlock)
{
//we are supposed to be on the end of the file, fill with 0's
memset(m_aDecodedBlock + uiReadSamples * 2/*stereo*/, 0, m_uiCurrentSamplesPerBlock - uiReadSamples);
}
//read requested samples
pBuffer = reinterpret_cast<signed long*>(pCurrentDest);
signed long *pDecoded = reinterpret_cast<signed long*>(m_aDecodedBlock);
//if for bad reason not enough samples have been read, it will get automatically filled with 0's
for(int i=0; i<nSamples;i++)
{
*pBuffer++ = *pDecoded++;
}
nSamples = 0;
}
}
}
return true;
}
const bool CADPCMDecoderInstance::FillPCMBuffer22KHz(signed long *pBuffer, int nSamples)
{
unsigned uStartPos = (m_nPos/2/*22KHz*/);
//first check for copying some data from last frame (if not at starting position)
if(m_bCopyFromLastFrame && m_iFilePos > 0)/*22KHz*/
{
*pBuffer++ = m_lLastSample;//contains sample to start with from last encoding
nSamples -= 1;
m_bCopyFromLastFrame = false;
uStartPos++;
}
//since there get always complete blocks read, fill buffer with already compressed samples
const unsigned int cuiModulo = uStartPos % m_uiCurrentSamplesPerBlock;
unsigned int uiLeft = 0;
if(cuiModulo != 0 && m_iFilePos > 0)//to not let break anything
{ //we are not on a block boundary
uiLeft = min((unsigned int)nSamples/2, (m_uiCurrentSamplesPerBlock - cuiModulo));/*22KHz*/
signed long *pslDecompressed = reinterpret_cast<signed long*>(m_aDecodedBlock) + cuiModulo;
if(uiLeft != 0)//read as many sampels as left decompressed and not more than requested
{
//copy decompressed data
for(unsigned int i=0; i<uiLeft;i++)
{
*pBuffer++ = *pslDecompressed;/*22KHz*/
*pBuffer++ = *pslDecompressed++;
}
//set new number of remaining samples
nSamples -= 2*uiLeft;
}
if(nSamples == 1)/*22KHz*/
{
//there is one sample left
m_bCopyFromLastFrame = true;
*pBuffer++ = m_lLastSample = *pslDecompressed; //remember last sample, has to start with in next request
nSamples = 0;
}
}
short *pCurrentDest = reinterpret_cast<short*>(pBuffer); //need a short buffer to decode each channel indivually (remember: 16 bit)
if(nSamples > 0)
{
while(nSamples > 0)
{
//read as many blocks as necessary
//write straight to output buffer if complete block has to read in
if(nSamples >= (int)m_uiCurrentSamplesPerBlock*2)
{
bool bEndOfFile = (AdpcmReadBlock() != m_uiCurrentSamplesPerBlock);
if(bEndOfFile) //shouldnt happen
{
memset(pBuffer, 0, nSamples*4);//fill with 0's
return false;
}
pBuffer = reinterpret_cast<signed long*>(pCurrentDest);
signed long *pDecoded = reinterpret_cast<signed long*>(m_aDecodedBlock);
//if for bad reason not enough samples have been read, it will get automatically filled with 0's
for(unsigned int i=0; i<m_uiCurrentSamplesPerBlock;i++)
{
*pBuffer++ = *pDecoded;
*pBuffer++ = *pDecoded++;
}
nSamples -= 2*m_uiCurrentSamplesPerBlock; /*22KHz*/
pCurrentDest = reinterpret_cast<short*>(pBuffer);
}
else
{
//decompress to static buffer
unsigned int uiReadSamples = AdpcmReadBlock();
if(uiReadSamples != m_uiCurrentSamplesPerBlock)
{
//we are supposed to be on the end of the file, fill with 0's
memset(m_aDecodedBlock + uiReadSamples * 2/*stereo*/, 0, m_uiCurrentSamplesPerBlock - uiReadSamples);
}
//read requested samples
pBuffer = reinterpret_cast<signed long*>(pCurrentDest);
signed long *pDecoded = reinterpret_cast<signed long*>(m_aDecodedBlock);
//if for some bad reason not enough samples have been read, it will get automatically filled with 0's
for(int i=0; i<nSamples/2;i++)
{
*pBuffer++ = *pDecoded;
*pBuffer++ = *pDecoded++;
}
m_bCopyFromLastFrame = false; //just to make sure
if(nSamples & 0x1)
{
m_bCopyFromLastFrame = true;
//read one sample more
*pBuffer++ = m_lLastSample = *pDecoded; //remember last sample, has to start with in next request
}
nSamples = 0;
}
}
}
return true;
}