You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
968 lines
28 KiB
968 lines
28 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: .360.WAV Creation |
|
// |
|
//=====================================================================================// |
|
|
|
#include "MakeGameData.h" |
|
#ifndef NO_X360_XDK |
|
#include <XMAEncoder.h> |
|
#endif |
|
#include "datamap.h" |
|
#include "sentence.h" |
|
#include "tier2/riff.h" |
|
#include "resample.h" |
|
#include "xwvfile.h" |
|
|
|
// all files are built for streaming compliance |
|
// allows for fastest runtime loading path |
|
// actual streaming or static state is determined by engine |
|
#define XBOX_DVD_SECTORSIZE 2048 |
|
#define XMA_BLOCK_SIZE 2048 // must be aligned to 1024 |
|
#define MAX_CHUNKS 256 |
|
|
|
// [0,100] |
|
#define XMA_HIGH_QUALITY 90 |
|
#define XMA_DEFAULT_QUALITY 75 |
|
#define XMA_MEDIUM_QUALITY 50 |
|
#define XMA_LOW_QUALITY 25 |
|
|
|
typedef struct |
|
{ |
|
unsigned int id; |
|
int size; |
|
byte *pData; |
|
} chunk_t; |
|
|
|
struct conversion_t |
|
{ |
|
const char *pSubDir; |
|
int quality; |
|
bool bForceTo22K; |
|
}; |
|
|
|
// default conversion rules |
|
conversion_t g_defaultConversionRules[] = |
|
{ |
|
// subdir quality 22Khz |
|
{ "", XMA_DEFAULT_QUALITY, false }, // default settings |
|
{ "weapons", XMA_DEFAULT_QUALITY, false }, |
|
{ "music", XMA_DEFAULT_QUALITY, false }, |
|
{ "vo", XMA_MEDIUM_QUALITY, false }, |
|
{ "npc", XMA_MEDIUM_QUALITY, false }, |
|
{ "ambient", XMA_DEFAULT_QUALITY, false }, |
|
{ "commentary", XMA_LOW_QUALITY, true }, |
|
{ NULL }, |
|
}; |
|
|
|
// portal conversion rules |
|
conversion_t g_portalConversionRules[] = |
|
{ |
|
// subdir quality 22Khz |
|
{ "", XMA_DEFAULT_QUALITY, false }, // default settings |
|
{ "commentary", XMA_LOW_QUALITY, true }, |
|
{ NULL }, |
|
}; |
|
|
|
chunk_t g_chunks[MAX_CHUNKS]; |
|
int g_numChunks; |
|
|
|
extern IFileReadBinary *g_pSndIO; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: chunk printer |
|
//----------------------------------------------------------------------------- |
|
void PrintChunk( unsigned int chunkName, int size ) |
|
{ |
|
char c[4]; |
|
|
|
for ( int i=0; i<4; i++ ) |
|
{ |
|
c[i] = ( chunkName >> i*8 ) & 0xFF; |
|
if ( !c[i] ) |
|
c[i] = ' '; |
|
} |
|
|
|
Msg( "%c%c%c%c: %d bytes\n", c[0], c[1], c[2], c[3], size ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: which chunks are supported, false to ignore |
|
//----------------------------------------------------------------------------- |
|
bool IsValidChunk( unsigned int chunkName ) |
|
{ |
|
switch ( chunkName ) |
|
{ |
|
case WAVE_DATA: |
|
case WAVE_CUE: |
|
case WAVE_SAMPLER: |
|
case WAVE_VALVEDATA: |
|
case WAVE_FMT: |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: align buffer |
|
//----------------------------------------------------------------------------- |
|
int AlignToBoundary( CUtlBuffer &buf, int alignment ) |
|
{ |
|
int curPosition; |
|
int newPosition; |
|
byte padByte = 0; |
|
|
|
buf.SeekPut( CUtlBuffer::SEEK_TAIL, 0 ); |
|
curPosition = buf.TellPut(); |
|
|
|
if ( alignment <= 1 ) |
|
return curPosition; |
|
|
|
// advance to aligned position |
|
newPosition = AlignValue( curPosition, alignment ); |
|
buf.EnsureCapacity( newPosition ); |
|
|
|
// write empty |
|
for ( int i=0; i<newPosition-curPosition; i++ ) |
|
{ |
|
buf.Put( &padByte, 1 ); |
|
} |
|
|
|
return newPosition; |
|
} |
|
|
|
//-------------------------------------------------------------------------------------- |
|
// SampleToXMABlockOffset |
|
// |
|
// Description: converts from a sample index to a block index + the number of samples |
|
// to offset from the beginning of the block. |
|
// |
|
// Parameters: |
|
// dwSampleIndex: sample index to convert |
|
// pdwSeekTable: pointer to the file's XMA2 seek table |
|
// nEntries: number of DWORD entries in the seek table |
|
// out_pBlockIndex: index of block where the desired sample lives |
|
// out_pOffset: number of samples in the block before the desired sample |
|
//-------------------------------------------------------------------------------------- |
|
bool SampleToXMABlockOffset( DWORD dwSampleIndex, const DWORD *pdwSeekTable, DWORD nEntries, DWORD *out_pBlockIndex, DWORD *out_pOffset ) |
|
{ |
|
// Run through the seek table to find the block closest to the desired sample. |
|
// Each seek table entry is the index (counting from the beginning of the file) |
|
// of the first sample in the corresponding block, but there's no entry for the |
|
// first block (since the index would always be zero). |
|
bool bFound = false; |
|
for ( DWORD i = 0; !bFound && i < nEntries; ++i ) |
|
{ |
|
if ( dwSampleIndex < BigLong( pdwSeekTable[i] ) ) |
|
{ |
|
*out_pBlockIndex = i; |
|
bFound = true; |
|
} |
|
} |
|
|
|
// Calculate the sample offset by figuring out what the sample index of the first sample |
|
// in the block is, then subtracting that from dwSampleIndex. |
|
if ( bFound ) |
|
{ |
|
DWORD dwStartOfBlock = (*out_pBlockIndex == 0) ? 0 : BigLong( pdwSeekTable[*out_pBlockIndex - 1] ); |
|
*out_pOffset = dwSampleIndex - dwStartOfBlock; |
|
} |
|
|
|
return bFound; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Compile and compress vdat |
|
//----------------------------------------------------------------------------- |
|
bool CompressVDAT( chunk_t *pChunk ) |
|
{ |
|
CSentence *pSentence; |
|
|
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
buf.EnsureCapacity( pChunk->size ); |
|
memcpy( buf.Base(), pChunk->pData, pChunk->size ); |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, pChunk->size ); |
|
|
|
pSentence = new CSentence(); |
|
|
|
// Make binary version of VDAT |
|
// Throws all phonemes into one word, discards sentence memory, etc. |
|
pSentence->InitFromDataChunk( buf.Base(), buf.TellPut() ); |
|
pSentence->MakeRuntimeOnly(); |
|
CUtlBuffer binaryBuffer( 0, 0, 0 ); |
|
binaryBuffer.SetBigEndian( true ); |
|
pSentence->CacheSaveToBuffer( binaryBuffer, CACHED_SENTENCE_VERSION_ALIGNED ); |
|
delete pSentence; |
|
|
|
unsigned int compressedSize = 0; |
|
unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)binaryBuffer.Base(), |
|
binaryBuffer.TellPut(), &compressedSize ); |
|
if ( pCompressedOutput ) |
|
{ |
|
if ( !g_bQuiet ) |
|
{ |
|
Msg( "CompressVDAT: Compressed %d to %d\n", binaryBuffer.TellPut(), compressedSize ); |
|
} |
|
|
|
free( pChunk->pData ); |
|
pChunk->size = compressedSize; |
|
pChunk->pData = pCompressedOutput; |
|
} |
|
else |
|
{ |
|
// save binary VDAT as-is |
|
free( pChunk->pData ); |
|
pChunk->size = binaryBuffer.TellPut(); |
|
pChunk->pData = (byte *)malloc( pChunk->size ); |
|
memcpy( pChunk->pData, binaryBuffer.Base(), pChunk->size ); |
|
} |
|
|
|
// success |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: read chunks into provided array |
|
//----------------------------------------------------------------------------- |
|
bool ReadChunks( const char *pFileName, int &numChunks, chunk_t chunks[MAX_CHUNKS] ) |
|
{ |
|
numChunks = 0; |
|
|
|
InFileRIFF riff( pFileName, *g_pSndIO ); |
|
if ( riff.RIFFName() != RIFF_WAVE ) |
|
{ |
|
return false; |
|
} |
|
|
|
IterateRIFF walk( riff, riff.RIFFSize() ); |
|
|
|
while ( walk.ChunkAvailable() ) |
|
{ |
|
chunks[numChunks].id = walk.ChunkName(); |
|
chunks[numChunks].size = walk.ChunkSize(); |
|
|
|
int size = chunks[numChunks].size; |
|
if ( walk.ChunkName() == WAVE_FMT && size < sizeof( WAVEFORMATEXTENSIBLE ) ) |
|
{ |
|
// format chunks are variable and cast to different structures |
|
// ensure the data footprint is at least the structure we want to manipulate |
|
size = sizeof( WAVEFORMATEXTENSIBLE ); |
|
} |
|
|
|
chunks[numChunks].pData = (byte *)malloc( size ); |
|
memset( chunks[numChunks].pData, 0, size ); |
|
|
|
walk.ChunkRead( chunks[numChunks].pData ); |
|
|
|
numChunks++; |
|
if ( numChunks >= MAX_CHUNKS ) |
|
return false; |
|
|
|
walk.ChunkNext(); |
|
} |
|
|
|
// success |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: promote pcm 8 bit to 16 bit pcm |
|
//----------------------------------------------------------------------------- |
|
void ConvertPCMDataChunk8To16( chunk_t *pFormatChunk, chunk_t *pDataChunk ) |
|
{ |
|
WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData; |
|
|
|
int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3; |
|
int sampleCount = pDataChunk->size / sampleSize; |
|
int outputSize = sizeof( short ) * ( sampleCount * pFormat->nChannels ); |
|
short *pOut = (short *)malloc( outputSize ); |
|
|
|
// in-place convert data from 8-bits to 16-bits |
|
Convert8To16( pDataChunk->pData, pOut, sampleCount, pFormat->nChannels ); |
|
|
|
free( pDataChunk->pData ); |
|
pDataChunk->pData = (byte *)pOut; |
|
pDataChunk->size = outputSize; |
|
|
|
pFormat->wFormatTag = WAVE_FORMAT_PCM; |
|
pFormat->nBlockAlign = 2 * pFormat->nChannels; |
|
pFormat->wBitsPerSample = 16; |
|
pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: convert adpcm to 16 bit pcm |
|
//----------------------------------------------------------------------------- |
|
void ConvertADPCMDataChunkTo16( chunk_t *pFormatChunk, chunk_t *pDataChunk ) |
|
{ |
|
WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData; |
|
|
|
int sampleCount = ADPCMSampleCount( (byte *)pFormat, pDataChunk->pData, pDataChunk->size ); |
|
int outputSize = sizeof( short ) * sampleCount * pFormat->nChannels; |
|
short *pOut = (short *)malloc( outputSize ); |
|
|
|
// convert to PCM 16bit format |
|
DecompressADPCMSamples( (byte*)pFormat, (byte*)pDataChunk->pData, pDataChunk->size, pOut ); |
|
|
|
free( pDataChunk->pData ); |
|
pDataChunk->pData = (byte *)pOut; |
|
pDataChunk->size = outputSize; |
|
|
|
pFormat->wFormatTag = WAVE_FORMAT_PCM; |
|
pFormat->nBlockAlign = 2 * pFormat->nChannels; |
|
pFormat->wBitsPerSample = 16; |
|
pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels; |
|
|
|
pFormatChunk->size = 16; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Decimate to 22K |
|
//----------------------------------------------------------------------------- |
|
void ConvertPCMDataChunk16To22K( chunk_t *pFormatChunk, chunk_t *pDataChunk ) |
|
{ |
|
WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData; |
|
|
|
if ( pFormat->nSamplesPerSec != 44100 || pFormat->wBitsPerSample != 16 || pFormat->wFormatTag != WAVE_FORMAT_PCM ) |
|
{ |
|
// not in expected format |
|
return; |
|
} |
|
|
|
int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3; |
|
int sampleCount = pDataChunk->size / sampleSize; |
|
short *pOut = (short *)malloc( sizeof( short ) * ( sampleCount * pFormat->nChannels ) ); |
|
|
|
DecimateSampleRateBy2_16( (short *)pDataChunk->pData, pOut, sampleCount, pFormat->nChannels ); |
|
|
|
free( pDataChunk->pData ); |
|
pDataChunk->pData = (byte *)pOut; |
|
pDataChunk->size = sizeof( short ) * ( sampleCount/2 * pFormat->nChannels ); |
|
|
|
pFormat->nSamplesPerSec = 22050; |
|
pFormat->nBlockAlign = 2 * pFormat->nChannels; |
|
pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: determine loop start |
|
//----------------------------------------------------------------------------- |
|
int FindLoopStart( int samplerChunk, int cueChunk ) |
|
{ |
|
int loopStartFromCue = -1; |
|
int loopStartFromSampler = -1; |
|
|
|
if ( cueChunk != -1 ) |
|
{ |
|
struct cuechunk_t |
|
{ |
|
unsigned int dwName; |
|
unsigned int dwPosition; |
|
unsigned int fccChunk; |
|
unsigned int dwChunkStart; |
|
unsigned int dwBlockStart; |
|
unsigned int dwSampleOffset; |
|
}; |
|
struct cueRIFF_t |
|
{ |
|
int cueCount; |
|
cuechunk_t cues[1]; |
|
}; |
|
|
|
cueRIFF_t *pCue = (cueRIFF_t *)g_chunks[cueChunk].pData; |
|
if ( pCue->cueCount > 0 ) |
|
{ |
|
loopStartFromCue = pCue->cues[0].dwSampleOffset; |
|
} |
|
} |
|
|
|
if ( samplerChunk != -1 ) |
|
{ |
|
struct SampleLoop |
|
{ |
|
unsigned int dwIdentifier; |
|
unsigned int dwType; |
|
unsigned int dwStart; |
|
unsigned int dwEnd; |
|
unsigned int dwFraction; |
|
unsigned int dwPlayCount; |
|
}; |
|
|
|
struct samplerchunk_t |
|
{ |
|
unsigned int dwManufacturer; |
|
unsigned int dwProduct; |
|
unsigned int dwSamplePeriod; |
|
unsigned int dwMIDIUnityNote; |
|
unsigned int dwMIDIPitchFraction; |
|
unsigned int dwSMPTEFormat; |
|
unsigned int dwSMPTEOffset; |
|
unsigned int cSampleLoops; |
|
unsigned int cbSamplerData; |
|
struct SampleLoop Loops[1]; |
|
}; |
|
|
|
// assume that the loop end is the sample end |
|
// assume that only the first loop is relevant |
|
samplerchunk_t *pSampler = (samplerchunk_t *)g_chunks[samplerChunk].pData; |
|
if ( pSampler->cSampleLoops > 0 ) |
|
{ |
|
// only support normal forward loops |
|
if ( pSampler->Loops[0].dwType == 0 ) |
|
{ |
|
loopStartFromSampler = pSampler->Loops[0].dwStart; |
|
} |
|
} |
|
} |
|
|
|
return ( max( loopStartFromCue, loopStartFromSampler ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns chunk, -1 if not found |
|
//----------------------------------------------------------------------------- |
|
int FindChunk( unsigned int id ) |
|
{ |
|
int i; |
|
for ( i=0; i<g_numChunks; i++ ) |
|
{ |
|
if ( g_chunks[i].id == id ) |
|
{ |
|
return i; |
|
} |
|
} |
|
|
|
// not found |
|
return - 1; |
|
} |
|
|
|
bool EncodeAsXMA( const char *pDebugName, CUtlBuffer &targetBuff, int quality, bool bIsVoiceOver ) |
|
{ |
|
#ifdef NO_X360_XDK |
|
return false; |
|
#else |
|
int formatChunk = FindChunk( WAVE_FMT ); |
|
int dataChunk = FindChunk( WAVE_DATA ); |
|
if ( formatChunk == -1 || dataChunk == -1 ) |
|
{ |
|
// huh? these should have been pre-validated |
|
return false; |
|
} |
|
|
|
int vdatSize = 0; |
|
int vdatChunk = FindChunk( WAVE_VALVEDATA ); |
|
if ( vdatChunk != -1 ) |
|
{ |
|
vdatSize = g_chunks[vdatChunk].size; |
|
} |
|
|
|
int loopStart = FindLoopStart( FindChunk( WAVE_SAMPLER ), FindChunk( WAVE_CUE ) ); |
|
|
|
// format structure must be expected 16 bit PCM, otherwise encoder crashes |
|
WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData; |
|
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nChannels * 2; |
|
pFormat->nBlockAlign = 2 * pFormat->nChannels; |
|
pFormat->cbSize = 0; |
|
|
|
XMAENCODERSTREAM inputStream = { 0 }; |
|
|
|
WAVEFORMATEXTENSIBLE wfx; |
|
Assert( g_chunks[formatChunk].size <= sizeof( WAVEFORMATEXTENSIBLE ) ); |
|
memcpy( &wfx, g_chunks[formatChunk].pData, g_chunks[formatChunk].size ); |
|
if ( g_chunks[formatChunk].size < sizeof( WAVEFORMATEXTENSIBLE ) ) |
|
{ |
|
memset( (unsigned char*)&wfx + g_chunks[formatChunk].size, 0, sizeof( WAVEFORMATEXTENSIBLE ) - g_chunks[formatChunk].size ); |
|
} |
|
|
|
memcpy( &inputStream.Format, &wfx, sizeof( WAVEFORMATEX ) ); |
|
inputStream.pBuffer = g_chunks[dataChunk].pData; |
|
inputStream.BufferSize = g_chunks[dataChunk].size; |
|
if ( loopStart != -1 ) |
|
{ |
|
// can only support a single loop point until end of file |
|
inputStream.LoopStart = loopStart; |
|
inputStream.LoopLength = inputStream.BufferSize / ( pFormat->nChannels * sizeof( short ) ) - loopStart; |
|
} |
|
|
|
void *pXMAData = NULL; |
|
DWORD XMADataSize = 0; |
|
XMA2WAVEFORMAT *pXMA2Format = NULL; |
|
DWORD XMA2FormatSize = 0; |
|
DWORD *pXMASeekTable = NULL; |
|
DWORD XMASeekTableSize = 0; |
|
HRESULT hr = S_OK; |
|
|
|
DWORD xmaFlags = XMAENCODER_NOFILTER; |
|
if ( loopStart != -1 ) |
|
{ |
|
xmaFlags |= XMAENCODER_LOOP; |
|
} |
|
|
|
int numAttempts = 1; |
|
while ( numAttempts < 10 ) |
|
{ |
|
hr = XMA2InMemoryEncoder( 1, &inputStream, quality, xmaFlags, XMA_BLOCK_SIZE/1024, &pXMAData, &XMADataSize, &pXMA2Format, &XMA2FormatSize, &pXMASeekTable, &XMASeekTableSize ); |
|
if ( !FAILED( hr ) ) |
|
break; |
|
|
|
// make small jumps |
|
quality += 5; |
|
if ( quality > 100 ) |
|
quality = 100; |
|
if ( !g_bQuiet ) |
|
{ |
|
Msg( "XMA Encoding Error on '%s', Attempting increasing quality to %d\n", pDebugName, quality ); |
|
} |
|
|
|
numAttempts++; |
|
|
|
pXMAData = NULL; |
|
XMADataSize = 0; |
|
pXMA2Format = NULL; |
|
XMA2FormatSize = 0; |
|
pXMASeekTable = NULL; |
|
XMASeekTableSize = 0; |
|
} |
|
|
|
if ( FAILED( hr ) ) |
|
{ |
|
// unrecoverable |
|
return false; |
|
} |
|
else if ( numAttempts > 1 ) |
|
{ |
|
if ( !g_bQuiet ) |
|
{ |
|
Msg( "XMA Encoding Success on '%s' at quality %d\n", pDebugName, quality ); |
|
} |
|
} |
|
|
|
DWORD loopBlock = 0; |
|
DWORD numLeadingSamples = 0; |
|
DWORD numTrailingSamples = 0; |
|
|
|
if ( loopStart != -1 ) |
|
{ |
|
// calculate start block/offset |
|
DWORD loopBlockStartIndex = 0; |
|
DWORD loopBlockStartOffset = 0; |
|
|
|
if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopBegin ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockStartIndex, &loopBlockStartOffset ) ) |
|
{ |
|
// could not determine loop point, out of range of encoded samples |
|
Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart ); |
|
return false; |
|
} |
|
|
|
loopBlock = loopBlockStartIndex; |
|
numLeadingSamples = loopBlockStartOffset; |
|
|
|
if ( BigLong( pXMA2Format->LoopEnd ) < BigLong( pXMA2Format->SamplesEncoded ) ) |
|
{ |
|
// calculate end block/offset |
|
DWORD loopBlockEndIndex = 0; |
|
DWORD loopBlockEndOffset = 0; |
|
|
|
if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopEnd ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockEndIndex, &loopBlockEndOffset ) ) |
|
{ |
|
// could not determine loop point, out of range of encoded samples |
|
Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart ); |
|
return false; |
|
} |
|
|
|
if ( loopBlockEndIndex != BigLong( pXMA2Format->BlockCount ) - 1 ) |
|
{ |
|
// end block MUST be last block |
|
Msg( "XMA Loop Encoding Error on '%s', block end is %d/%d\n", pDebugName, loopBlockEndOffset, BigLong( pXMA2Format->BlockCount ) ); |
|
return false; |
|
} |
|
|
|
numTrailingSamples = BigLong( pXMA2Format->SamplesEncoded ) - BigLong( pXMA2Format->LoopEnd ); |
|
} |
|
|
|
// check for proper encoding range |
|
if ( loopBlock > 32767 ) |
|
{ |
|
Msg( "XMA Loop Encoding Error on '%s', loop block exceeds 16 bits %d\n", pDebugName, loopBlock ); |
|
return false; |
|
} |
|
if ( numLeadingSamples > 32767 ) |
|
{ |
|
Msg( "XMA Loop Encoding Error on '%s', leading samples exceeds 16 bits %d\n", pDebugName, numLeadingSamples ); |
|
return false; |
|
} |
|
if ( numTrailingSamples > 32767 ) |
|
{ |
|
Msg( "XMA Loop Encoding Error on '%s', trailing samples exceeds 16 bits %d\n", pDebugName, numTrailingSamples ); |
|
return false; |
|
} |
|
} |
|
|
|
xwvHeader_t header; |
|
memset( &header, 0, sizeof( xwvHeader_t ) ); |
|
|
|
int seekTableSize = 0; |
|
if ( vdatSize || bIsVoiceOver ) |
|
{ |
|
// save the optional seek table only for vdat or vo |
|
// the seek table size is expected to be derived by this calculation |
|
seekTableSize = ( XMADataSize / XMA_BYTES_PER_PACKET ) * sizeof( int ); |
|
if ( seekTableSize != XMASeekTableSize ) |
|
{ |
|
Msg( "XMA Error: Unexpected seek table calculation in '%s'!", pDebugName ); |
|
return false; |
|
} |
|
} |
|
|
|
if ( loopStart != -1 && ( vdatSize || bIsVoiceOver ) ) |
|
{ |
|
Msg( "XMA Warning: Unexpected loop in vo data '%s'!", pDebugName ); |
|
|
|
// do not write the seek table for looping sounds |
|
seekTableSize = 0; |
|
} |
|
|
|
header.id = BigLong( XWV_ID ); |
|
header.version = BigLong( XWV_VERSION ); |
|
header.headerSize = BigLong( sizeof( xwvHeader_t ) ); |
|
header.staticDataSize = BigLong( seekTableSize + vdatSize ); |
|
header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + seekTableSize + vdatSize, XBOX_DVD_SECTORSIZE ) ); |
|
header.dataSize = BigLong( XMADataSize ); |
|
|
|
// track the XMA number of samples that will get decoded |
|
// which is NOT the same as what the source actually encoded |
|
header.numDecodedSamples = pXMA2Format->SamplesEncoded; |
|
|
|
if ( loopStart != -1 ) |
|
{ |
|
// the loop start is in source space (now meaningless), need the loop in XMA decoding sample space |
|
header.loopStart = pXMA2Format->LoopBegin; |
|
} |
|
else |
|
{ |
|
header.loopStart = BigLong( -1 ); |
|
} |
|
header.loopBlock = BigShort( (unsigned short)loopBlock ); |
|
header.numLeadingSamples = BigShort( (unsigned short)numLeadingSamples ); |
|
header.numTrailingSamples = BigShort( (unsigned short)numTrailingSamples ); |
|
|
|
header.vdatSize = BigShort( (short)vdatSize ); |
|
header.format = XWV_FORMAT_XMA; |
|
header.bitsPerSample = 16; |
|
header.SetSampleRate( pFormat->nSamplesPerSec ); |
|
header.SetChannels( pFormat->nChannels ); |
|
header.quality = quality; |
|
header.bHasSeekTable = ( seekTableSize != 0 ); |
|
|
|
// output header |
|
targetBuff.Put( &header, sizeof( xwvHeader_t ) ); |
|
|
|
// output optional seek table |
|
if ( seekTableSize ) |
|
{ |
|
// seek table is already in big-endian format |
|
targetBuff.Put( pXMASeekTable, seekTableSize ); |
|
} |
|
|
|
// output vdat |
|
if ( vdatSize ) |
|
{ |
|
targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size ); |
|
} |
|
|
|
AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); |
|
|
|
// write data |
|
targetBuff.Put( pXMAData, XMADataSize ); |
|
|
|
// pad to EOF |
|
AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); |
|
|
|
free( pXMAData ); |
|
free( pXMA2Format ); |
|
free( pXMASeekTable ); |
|
|
|
// xma encoder leaves its temporary files, we'll delete |
|
scriptlib->DeleteTemporaryFiles( "LoopStrm*" ); |
|
scriptlib->DeleteTemporaryFiles( "EncStrm*" ); |
|
|
|
return true; |
|
#endif |
|
} |
|
|
|
bool EncodeAsPCM( const char *pTargetName, CUtlBuffer &targetBuff ) |
|
{ |
|
int formatChunk = FindChunk( WAVE_FMT ); |
|
int dataChunk = FindChunk( WAVE_DATA ); |
|
if ( formatChunk == -1 || dataChunk == -1 ) |
|
{ |
|
// huh? these should have been pre-validated |
|
return false; |
|
} |
|
|
|
WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData; |
|
if ( pFormat->wBitsPerSample != 16 ) |
|
{ |
|
// huh? the input is expeted to be 16 bit PCM |
|
return false; |
|
} |
|
|
|
int vdatSize = 0; |
|
int vdatChunk = FindChunk( WAVE_VALVEDATA ); |
|
if ( vdatChunk != -1 ) |
|
{ |
|
vdatSize = g_chunks[vdatChunk].size; |
|
} |
|
|
|
chunk_t *pDataChunk = &g_chunks[dataChunk]; |
|
|
|
xwvHeader_t header; |
|
memset( &header, 0, sizeof( xwvHeader_t ) ); |
|
|
|
int sampleSize = pFormat->nChannels * sizeof( short ); |
|
int sampleCount = pDataChunk->size / sampleSize; |
|
|
|
header.id = BigLong( XWV_ID ); |
|
header.version = BigLong( XWV_VERSION ); |
|
header.headerSize = BigLong( sizeof( xwvHeader_t ) ); |
|
header.staticDataSize = BigLong( vdatSize ); |
|
header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + vdatSize, XBOX_DVD_SECTORSIZE ) ); |
|
header.dataSize = BigLong( pDataChunk->size ); |
|
header.numDecodedSamples = BigLong( sampleCount ); |
|
header.loopStart = BigLong( -1 ); |
|
header.loopBlock = 0; |
|
header.numLeadingSamples = 0; |
|
header.numTrailingSamples = 0; |
|
header.vdatSize = BigShort( (short)vdatSize ); |
|
header.format = XWV_FORMAT_PCM; |
|
header.bitsPerSample = 16; |
|
header.SetSampleRate( pFormat->nSamplesPerSec ); |
|
header.SetChannels( pFormat->nChannels ); |
|
header.quality = 100; |
|
|
|
// output header |
|
targetBuff.Put( &header, sizeof( xwvHeader_t ) ); |
|
|
|
// output vdat |
|
if ( vdatSize ) |
|
{ |
|
targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size ); |
|
} |
|
|
|
AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); |
|
|
|
for ( int i = 0; i < sampleCount * pFormat->nChannels; i++ ) |
|
{ |
|
((short *)pDataChunk->pData)[i] = BigShort( ((short *)pDataChunk->pData)[i] ); |
|
} |
|
|
|
// write data |
|
targetBuff.Put( pDataChunk->pData, pDataChunk->size ); |
|
|
|
// pad to EOF |
|
AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: read source, do work, and write to target |
|
//----------------------------------------------------------------------------- |
|
bool CreateTargetFile_WAV( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) |
|
{ |
|
g_numChunks = 0; |
|
|
|
// resolve relative source to absolute path |
|
char fullSourcePath[MAX_PATH]; |
|
if ( _fullpath( fullSourcePath, pSourceName, sizeof( fullSourcePath ) ) ) |
|
{ |
|
pSourceName = fullSourcePath; |
|
} |
|
|
|
if ( !ReadChunks( pSourceName, g_numChunks, g_chunks ) ) |
|
{ |
|
Msg( "No RIFF Chunks on '%s'\n", pSourceName ); |
|
return false; |
|
} |
|
|
|
int formatChunk = FindChunk( WAVE_FMT ); |
|
if ( formatChunk == -1 ) |
|
{ |
|
Msg( "RIFF Format Chunk not found on '%s'\n", pSourceName ); |
|
return false; |
|
} |
|
|
|
int dataChunk = FindChunk( WAVE_DATA ); |
|
if ( dataChunk == -1 ) |
|
{ |
|
Msg( "RIFF Data Chunk not found on '%s'\n", pSourceName ); |
|
return false; |
|
} |
|
|
|
// get the conversion rules |
|
conversion_t *pConversion = g_defaultConversionRules; |
|
if ( V_stristr( g_szModPath, "\\portal" ) ) |
|
{ |
|
pConversion = g_portalConversionRules; |
|
} |
|
|
|
// conversion rules are based on matching subdir |
|
for ( int i=1; ;i++ ) |
|
{ |
|
char subString[MAX_PATH]; |
|
if ( !pConversion[i].pSubDir ) |
|
{ |
|
// end of list |
|
break; |
|
} |
|
|
|
sprintf( subString, "\\%s\\", pConversion[i].pSubDir ); |
|
if ( V_stristr( pSourceName, subString ) ) |
|
{ |
|
// use matched conversion rules |
|
pConversion = &pConversion[i]; |
|
break; |
|
} |
|
} |
|
|
|
bool bForceTo22K = pConversion->bForceTo22K; |
|
int quality = pConversion->quality; |
|
|
|
// cannot trust the localization depots to have matched their sources |
|
// cannot allow 44K |
|
if ( IsLocalizedFile( pSourceName ) ) |
|
{ |
|
bForceTo22K = true; |
|
} |
|
|
|
// classify strict vo from /sound/vo only |
|
bool bIsVoiceOver = V_stristr( pSourceName, "\\sound\\vo\\" ) != NULL; |
|
|
|
// can override default settings |
|
quality = CommandLine()->ParmValue( "-xmaquality", quality ); |
|
if ( quality < 0 ) |
|
quality = 0; |
|
else if ( quality > 100 ) |
|
quality = 100; |
|
if ( !g_bQuiet ) |
|
{ |
|
Msg( "Encoding quality: %d on '%s'\n", quality, pSourceName ); |
|
} |
|
|
|
int vdatSize = 0; |
|
int vdatChunk = FindChunk( WAVE_VALVEDATA ); |
|
if ( vdatChunk != -1 ) |
|
{ |
|
// compile to optimal block |
|
if ( !CompressVDAT( &g_chunks[vdatChunk] ) ) |
|
{ |
|
Msg( "Compress VDAT Error on '%s'\n", pSourceName ); |
|
return false; |
|
} |
|
vdatSize = g_chunks[vdatChunk].size; |
|
} |
|
|
|
// for safety (not trusting their decoding) and simplicity convert all data to 16 bit PCM before encoding |
|
WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData; |
|
if ( ( pFormat->wFormatTag == WAVE_FORMAT_PCM ) ) |
|
{ |
|
if ( pFormat->wBitsPerSample == 8 ) |
|
{ |
|
ConvertPCMDataChunk8To16( &g_chunks[formatChunk], &g_chunks[dataChunk] ); |
|
} |
|
} |
|
else if ( pFormat->wFormatTag == WAVE_FORMAT_ADPCM ) |
|
{ |
|
ConvertADPCMDataChunkTo16( &g_chunks[formatChunk], &g_chunks[dataChunk] ); |
|
} |
|
else |
|
{ |
|
Msg( "Unknown RIFF Format on '%s'\n", pSourceName ); |
|
return false; |
|
} |
|
|
|
// optionally decimate to 22K |
|
if ( pFormat->nSamplesPerSec == 44100 && bForceTo22K ) |
|
{ |
|
if ( !g_bQuiet ) |
|
{ |
|
Msg( "Converting to 22K '%s'\n", pSourceName ); |
|
} |
|
ConvertPCMDataChunk16To22K( &g_chunks[formatChunk], &g_chunks[dataChunk] ); |
|
} |
|
|
|
CUtlBuffer targetBuff; |
|
bool bSuccess; |
|
|
|
bSuccess = EncodeAsXMA( pSourceName, targetBuff, quality, bIsVoiceOver ); |
|
if ( bSuccess ) |
|
{ |
|
WriteBufferToFile( pTargetName, targetBuff, bWriteToZip, g_WriteModeForConversions ); |
|
} |
|
|
|
// release data |
|
for ( int i = 0; i < g_numChunks; i++ ) |
|
{ |
|
free( g_chunks[i].pData ); |
|
} |
|
|
|
return bSuccess; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: MP3's are already pre-converted into .360.wav |
|
//----------------------------------------------------------------------------- |
|
bool CreateTargetFile_MP3( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) |
|
{ |
|
CUtlBuffer targetBuffer; |
|
|
|
// ignore the .mp3 source, the .360.wav target should have been pre-converted, checked in, and exist |
|
// use the expected target as the source |
|
if ( !scriptlib->ReadFileToBuffer( pTargetName, targetBuffer ) ) |
|
{ |
|
// the .360.wav target does not exist |
|
// try again using a .wav version and convert from that |
|
char wavFilename[MAX_PATH]; |
|
V_StripExtension( pSourceName, wavFilename, sizeof( wavFilename ) ); |
|
V_SetExtension( wavFilename, ".wav", sizeof( wavFilename ) ); |
|
if ( scriptlib->DoesFileExist( wavFilename ) ) |
|
{ |
|
if ( CreateTargetFile_WAV( wavFilename, pTargetName, bWriteToZip ) ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// no conversion to write, but possibly zipped |
|
bool bSuccess = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, WRITE_TO_DISK_NEVER ); |
|
return bSuccess; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Get the preload data for a wav file |
|
//----------------------------------------------------------------------------- |
|
bool GetPreloadData_WAV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) |
|
{ |
|
xwvHeader_t *pHeader = ( xwvHeader_t * )fileBufferIn.Base(); |
|
if ( pHeader->id != ( unsigned int )BigLong( XWV_ID ) || |
|
pHeader->version != ( unsigned int )BigLong( XWV_VERSION ) || |
|
pHeader->headerSize != BigLong( sizeof( xwvHeader_t ) ) ) |
|
{ |
|
// bad version |
|
Msg( "Can't preload: '%s', has bad version\n", pFilename ); |
|
return false; |
|
} |
|
|
|
// ensure caller's buffer is clean |
|
// caller determines preload size, via TellMaxPut() |
|
preloadBufferOut.Purge(); |
|
unsigned int preloadSize = BigLong( pHeader->headerSize ) + BigLong( pHeader->staticDataSize ); |
|
preloadBufferOut.Put( fileBufferIn.Base(), preloadSize ); |
|
|
|
return true; |
|
}
|
|
|