Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: .360.WAV Creation
  4. //
  5. //=====================================================================================//
  6. #include "MakeGameData.h"
  7. #ifndef NO_X360_XDK
  8. #include <XMAEncoder.h>
  9. #endif
  10. #include "datamap.h"
  11. #include "sentence.h"
  12. #include "tier2/riff.h"
  13. #include "resample.h"
  14. #include "xwvfile.h"
  15. // all files are built for streaming compliance
  16. // allows for fastest runtime loading path
  17. // actual streaming or static state is determined by engine
  18. #define XBOX_DVD_SECTORSIZE 2048
  19. #define XMA_BLOCK_SIZE 2048 // must be aligned to 1024
  20. #define MAX_CHUNKS 256
  21. // [0,100]
  22. #define XMA_HIGH_QUALITY 90
  23. #define XMA_DEFAULT_QUALITY 75
  24. #define XMA_MEDIUM_QUALITY 50
  25. #define XMA_LOW_QUALITY 25
  26. typedef struct
  27. {
  28. unsigned int id;
  29. int size;
  30. byte *pData;
  31. } chunk_t;
  32. struct conversion_t
  33. {
  34. const char *pSubDir;
  35. int quality;
  36. bool bForceTo22K;
  37. };
  38. // default conversion rules
  39. conversion_t g_defaultConversionRules[] =
  40. {
  41. // subdir quality 22Khz
  42. { "", XMA_DEFAULT_QUALITY, false }, // default settings
  43. { "weapons", XMA_DEFAULT_QUALITY, false },
  44. { "music", XMA_DEFAULT_QUALITY, false },
  45. { "vo", XMA_MEDIUM_QUALITY, false },
  46. { "npc", XMA_MEDIUM_QUALITY, false },
  47. { "ambient", XMA_DEFAULT_QUALITY, false },
  48. { "commentary", XMA_LOW_QUALITY, true },
  49. { NULL },
  50. };
  51. // portal conversion rules
  52. conversion_t g_portalConversionRules[] =
  53. {
  54. // subdir quality 22Khz
  55. { "", XMA_DEFAULT_QUALITY, false }, // default settings
  56. { "commentary", XMA_LOW_QUALITY, true },
  57. { NULL },
  58. };
  59. chunk_t g_chunks[MAX_CHUNKS];
  60. int g_numChunks;
  61. extern IFileReadBinary *g_pSndIO;
  62. //-----------------------------------------------------------------------------
  63. // Purpose: chunk printer
  64. //-----------------------------------------------------------------------------
  65. void PrintChunk( unsigned int chunkName, int size )
  66. {
  67. char c[4];
  68. for ( int i=0; i<4; i++ )
  69. {
  70. c[i] = ( chunkName >> i*8 ) & 0xFF;
  71. if ( !c[i] )
  72. c[i] = ' ';
  73. }
  74. Msg( "%c%c%c%c: %d bytes\n", c[0], c[1], c[2], c[3], size );
  75. }
  76. //-----------------------------------------------------------------------------
  77. // Purpose: which chunks are supported, false to ignore
  78. //-----------------------------------------------------------------------------
  79. bool IsValidChunk( unsigned int chunkName )
  80. {
  81. switch ( chunkName )
  82. {
  83. case WAVE_DATA:
  84. case WAVE_CUE:
  85. case WAVE_SAMPLER:
  86. case WAVE_VALVEDATA:
  87. case WAVE_FMT:
  88. return true;
  89. }
  90. return false;
  91. }
  92. //-----------------------------------------------------------------------------
  93. // Purpose: align buffer
  94. //-----------------------------------------------------------------------------
  95. int AlignToBoundary( CUtlBuffer &buf, int alignment )
  96. {
  97. int curPosition;
  98. int newPosition;
  99. byte padByte = 0;
  100. buf.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
  101. curPosition = buf.TellPut();
  102. if ( alignment <= 1 )
  103. return curPosition;
  104. // advance to aligned position
  105. newPosition = AlignValue( curPosition, alignment );
  106. buf.EnsureCapacity( newPosition );
  107. // write empty
  108. for ( int i=0; i<newPosition-curPosition; i++ )
  109. {
  110. buf.Put( &padByte, 1 );
  111. }
  112. return newPosition;
  113. }
  114. //--------------------------------------------------------------------------------------
  115. // SampleToXMABlockOffset
  116. //
  117. // Description: converts from a sample index to a block index + the number of samples
  118. // to offset from the beginning of the block.
  119. //
  120. // Parameters:
  121. // dwSampleIndex: sample index to convert
  122. // pdwSeekTable: pointer to the file's XMA2 seek table
  123. // nEntries: number of DWORD entries in the seek table
  124. // out_pBlockIndex: index of block where the desired sample lives
  125. // out_pOffset: number of samples in the block before the desired sample
  126. //--------------------------------------------------------------------------------------
  127. bool SampleToXMABlockOffset( DWORD dwSampleIndex, const DWORD *pdwSeekTable, DWORD nEntries, DWORD *out_pBlockIndex, DWORD *out_pOffset )
  128. {
  129. // Run through the seek table to find the block closest to the desired sample.
  130. // Each seek table entry is the index (counting from the beginning of the file)
  131. // of the first sample in the corresponding block, but there's no entry for the
  132. // first block (since the index would always be zero).
  133. bool bFound = false;
  134. for ( DWORD i = 0; !bFound && i < nEntries; ++i )
  135. {
  136. if ( dwSampleIndex < BigLong( pdwSeekTable[i] ) )
  137. {
  138. *out_pBlockIndex = i;
  139. bFound = true;
  140. }
  141. }
  142. // Calculate the sample offset by figuring out what the sample index of the first sample
  143. // in the block is, then subtracting that from dwSampleIndex.
  144. if ( bFound )
  145. {
  146. DWORD dwStartOfBlock = (*out_pBlockIndex == 0) ? 0 : BigLong( pdwSeekTable[*out_pBlockIndex - 1] );
  147. *out_pOffset = dwSampleIndex - dwStartOfBlock;
  148. }
  149. return bFound;
  150. }
  151. //-----------------------------------------------------------------------------
  152. // Compile and compress vdat
  153. //-----------------------------------------------------------------------------
  154. bool CompressVDAT( chunk_t *pChunk )
  155. {
  156. CSentence *pSentence;
  157. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  158. buf.EnsureCapacity( pChunk->size );
  159. memcpy( buf.Base(), pChunk->pData, pChunk->size );
  160. buf.SeekPut( CUtlBuffer::SEEK_HEAD, pChunk->size );
  161. pSentence = new CSentence();
  162. // Make binary version of VDAT
  163. // Throws all phonemes into one word, discards sentence memory, etc.
  164. pSentence->InitFromDataChunk( buf.Base(), buf.TellPut() );
  165. pSentence->MakeRuntimeOnly();
  166. CUtlBuffer binaryBuffer( 0, 0, 0 );
  167. binaryBuffer.SetBigEndian( true );
  168. pSentence->CacheSaveToBuffer( binaryBuffer, CACHED_SENTENCE_VERSION_ALIGNED );
  169. delete pSentence;
  170. unsigned int compressedSize = 0;
  171. unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)binaryBuffer.Base(),
  172. binaryBuffer.TellPut(), &compressedSize );
  173. if ( pCompressedOutput )
  174. {
  175. if ( !g_bQuiet )
  176. {
  177. Msg( "CompressVDAT: Compressed %d to %d\n", binaryBuffer.TellPut(), compressedSize );
  178. }
  179. free( pChunk->pData );
  180. pChunk->size = compressedSize;
  181. pChunk->pData = pCompressedOutput;
  182. }
  183. else
  184. {
  185. // save binary VDAT as-is
  186. free( pChunk->pData );
  187. pChunk->size = binaryBuffer.TellPut();
  188. pChunk->pData = (byte *)malloc( pChunk->size );
  189. memcpy( pChunk->pData, binaryBuffer.Base(), pChunk->size );
  190. }
  191. // success
  192. return true;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: read chunks into provided array
  196. //-----------------------------------------------------------------------------
  197. bool ReadChunks( const char *pFileName, int &numChunks, chunk_t chunks[MAX_CHUNKS] )
  198. {
  199. numChunks = 0;
  200. InFileRIFF riff( pFileName, *g_pSndIO );
  201. if ( riff.RIFFName() != RIFF_WAVE )
  202. {
  203. return false;
  204. }
  205. IterateRIFF walk( riff, riff.RIFFSize() );
  206. while ( walk.ChunkAvailable() )
  207. {
  208. chunks[numChunks].id = walk.ChunkName();
  209. chunks[numChunks].size = walk.ChunkSize();
  210. int size = chunks[numChunks].size;
  211. if ( walk.ChunkName() == WAVE_FMT && size < sizeof( WAVEFORMATEXTENSIBLE ) )
  212. {
  213. // format chunks are variable and cast to different structures
  214. // ensure the data footprint is at least the structure we want to manipulate
  215. size = sizeof( WAVEFORMATEXTENSIBLE );
  216. }
  217. chunks[numChunks].pData = (byte *)malloc( size );
  218. memset( chunks[numChunks].pData, 0, size );
  219. walk.ChunkRead( chunks[numChunks].pData );
  220. numChunks++;
  221. if ( numChunks >= MAX_CHUNKS )
  222. return false;
  223. walk.ChunkNext();
  224. }
  225. // success
  226. return true;
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Purpose: promote pcm 8 bit to 16 bit pcm
  230. //-----------------------------------------------------------------------------
  231. void ConvertPCMDataChunk8To16( chunk_t *pFormatChunk, chunk_t *pDataChunk )
  232. {
  233. WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData;
  234. int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3;
  235. int sampleCount = pDataChunk->size / sampleSize;
  236. int outputSize = sizeof( short ) * ( sampleCount * pFormat->nChannels );
  237. short *pOut = (short *)malloc( outputSize );
  238. // in-place convert data from 8-bits to 16-bits
  239. Convert8To16( pDataChunk->pData, pOut, sampleCount, pFormat->nChannels );
  240. free( pDataChunk->pData );
  241. pDataChunk->pData = (byte *)pOut;
  242. pDataChunk->size = outputSize;
  243. pFormat->wFormatTag = WAVE_FORMAT_PCM;
  244. pFormat->nBlockAlign = 2 * pFormat->nChannels;
  245. pFormat->wBitsPerSample = 16;
  246. pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels;
  247. }
  248. //-----------------------------------------------------------------------------
  249. // Purpose: convert adpcm to 16 bit pcm
  250. //-----------------------------------------------------------------------------
  251. void ConvertADPCMDataChunkTo16( chunk_t *pFormatChunk, chunk_t *pDataChunk )
  252. {
  253. WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData;
  254. int sampleCount = ADPCMSampleCount( (byte *)pFormat, pDataChunk->pData, pDataChunk->size );
  255. int outputSize = sizeof( short ) * sampleCount * pFormat->nChannels;
  256. short *pOut = (short *)malloc( outputSize );
  257. // convert to PCM 16bit format
  258. DecompressADPCMSamples( (byte*)pFormat, (byte*)pDataChunk->pData, pDataChunk->size, pOut );
  259. free( pDataChunk->pData );
  260. pDataChunk->pData = (byte *)pOut;
  261. pDataChunk->size = outputSize;
  262. pFormat->wFormatTag = WAVE_FORMAT_PCM;
  263. pFormat->nBlockAlign = 2 * pFormat->nChannels;
  264. pFormat->wBitsPerSample = 16;
  265. pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels;
  266. pFormatChunk->size = 16;
  267. }
  268. //-----------------------------------------------------------------------------
  269. // Purpose: Decimate to 22K
  270. //-----------------------------------------------------------------------------
  271. void ConvertPCMDataChunk16To22K( chunk_t *pFormatChunk, chunk_t *pDataChunk )
  272. {
  273. WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData;
  274. if ( pFormat->nSamplesPerSec != 44100 || pFormat->wBitsPerSample != 16 || pFormat->wFormatTag != WAVE_FORMAT_PCM )
  275. {
  276. // not in expected format
  277. return;
  278. }
  279. int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3;
  280. int sampleCount = pDataChunk->size / sampleSize;
  281. short *pOut = (short *)malloc( sizeof( short ) * ( sampleCount * pFormat->nChannels ) );
  282. DecimateSampleRateBy2_16( (short *)pDataChunk->pData, pOut, sampleCount, pFormat->nChannels );
  283. free( pDataChunk->pData );
  284. pDataChunk->pData = (byte *)pOut;
  285. pDataChunk->size = sizeof( short ) * ( sampleCount/2 * pFormat->nChannels );
  286. pFormat->nSamplesPerSec = 22050;
  287. pFormat->nBlockAlign = 2 * pFormat->nChannels;
  288. pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels;
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Purpose: determine loop start
  292. //-----------------------------------------------------------------------------
  293. int FindLoopStart( int samplerChunk, int cueChunk )
  294. {
  295. int loopStartFromCue = -1;
  296. int loopStartFromSampler = -1;
  297. if ( cueChunk != -1 )
  298. {
  299. struct cuechunk_t
  300. {
  301. unsigned int dwName;
  302. unsigned int dwPosition;
  303. unsigned int fccChunk;
  304. unsigned int dwChunkStart;
  305. unsigned int dwBlockStart;
  306. unsigned int dwSampleOffset;
  307. };
  308. struct cueRIFF_t
  309. {
  310. int cueCount;
  311. cuechunk_t cues[1];
  312. };
  313. cueRIFF_t *pCue = (cueRIFF_t *)g_chunks[cueChunk].pData;
  314. if ( pCue->cueCount > 0 )
  315. {
  316. loopStartFromCue = pCue->cues[0].dwSampleOffset;
  317. }
  318. }
  319. if ( samplerChunk != -1 )
  320. {
  321. struct SampleLoop
  322. {
  323. unsigned int dwIdentifier;
  324. unsigned int dwType;
  325. unsigned int dwStart;
  326. unsigned int dwEnd;
  327. unsigned int dwFraction;
  328. unsigned int dwPlayCount;
  329. };
  330. struct samplerchunk_t
  331. {
  332. unsigned int dwManufacturer;
  333. unsigned int dwProduct;
  334. unsigned int dwSamplePeriod;
  335. unsigned int dwMIDIUnityNote;
  336. unsigned int dwMIDIPitchFraction;
  337. unsigned int dwSMPTEFormat;
  338. unsigned int dwSMPTEOffset;
  339. unsigned int cSampleLoops;
  340. unsigned int cbSamplerData;
  341. struct SampleLoop Loops[1];
  342. };
  343. // assume that the loop end is the sample end
  344. // assume that only the first loop is relevant
  345. samplerchunk_t *pSampler = (samplerchunk_t *)g_chunks[samplerChunk].pData;
  346. if ( pSampler->cSampleLoops > 0 )
  347. {
  348. // only support normal forward loops
  349. if ( pSampler->Loops[0].dwType == 0 )
  350. {
  351. loopStartFromSampler = pSampler->Loops[0].dwStart;
  352. }
  353. }
  354. }
  355. return ( max( loopStartFromCue, loopStartFromSampler ) );
  356. }
  357. //-----------------------------------------------------------------------------
  358. // Purpose: returns chunk, -1 if not found
  359. //-----------------------------------------------------------------------------
  360. int FindChunk( unsigned int id )
  361. {
  362. int i;
  363. for ( i=0; i<g_numChunks; i++ )
  364. {
  365. if ( g_chunks[i].id == id )
  366. {
  367. return i;
  368. }
  369. }
  370. // not found
  371. return - 1;
  372. }
  373. bool EncodeAsXMA( const char *pDebugName, CUtlBuffer &targetBuff, int quality, bool bIsVoiceOver )
  374. {
  375. #ifdef NO_X360_XDK
  376. return false;
  377. #else
  378. int formatChunk = FindChunk( WAVE_FMT );
  379. int dataChunk = FindChunk( WAVE_DATA );
  380. if ( formatChunk == -1 || dataChunk == -1 )
  381. {
  382. // huh? these should have been pre-validated
  383. return false;
  384. }
  385. int vdatSize = 0;
  386. int vdatChunk = FindChunk( WAVE_VALVEDATA );
  387. if ( vdatChunk != -1 )
  388. {
  389. vdatSize = g_chunks[vdatChunk].size;
  390. }
  391. int loopStart = FindLoopStart( FindChunk( WAVE_SAMPLER ), FindChunk( WAVE_CUE ) );
  392. // format structure must be expected 16 bit PCM, otherwise encoder crashes
  393. WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData;
  394. pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nChannels * 2;
  395. pFormat->nBlockAlign = 2 * pFormat->nChannels;
  396. pFormat->cbSize = 0;
  397. XMAENCODERSTREAM inputStream = { 0 };
  398. WAVEFORMATEXTENSIBLE wfx;
  399. Assert( g_chunks[formatChunk].size <= sizeof( WAVEFORMATEXTENSIBLE ) );
  400. memcpy( &wfx, g_chunks[formatChunk].pData, g_chunks[formatChunk].size );
  401. if ( g_chunks[formatChunk].size < sizeof( WAVEFORMATEXTENSIBLE ) )
  402. {
  403. memset( (unsigned char*)&wfx + g_chunks[formatChunk].size, 0, sizeof( WAVEFORMATEXTENSIBLE ) - g_chunks[formatChunk].size );
  404. }
  405. memcpy( &inputStream.Format, &wfx, sizeof( WAVEFORMATEX ) );
  406. inputStream.pBuffer = g_chunks[dataChunk].pData;
  407. inputStream.BufferSize = g_chunks[dataChunk].size;
  408. if ( loopStart != -1 )
  409. {
  410. // can only support a single loop point until end of file
  411. inputStream.LoopStart = loopStart;
  412. inputStream.LoopLength = inputStream.BufferSize / ( pFormat->nChannels * sizeof( short ) ) - loopStart;
  413. }
  414. void *pXMAData = NULL;
  415. DWORD XMADataSize = 0;
  416. XMA2WAVEFORMAT *pXMA2Format = NULL;
  417. DWORD XMA2FormatSize = 0;
  418. DWORD *pXMASeekTable = NULL;
  419. DWORD XMASeekTableSize = 0;
  420. HRESULT hr = S_OK;
  421. DWORD xmaFlags = XMAENCODER_NOFILTER;
  422. if ( loopStart != -1 )
  423. {
  424. xmaFlags |= XMAENCODER_LOOP;
  425. }
  426. int numAttempts = 1;
  427. while ( numAttempts < 10 )
  428. {
  429. hr = XMA2InMemoryEncoder( 1, &inputStream, quality, xmaFlags, XMA_BLOCK_SIZE/1024, &pXMAData, &XMADataSize, &pXMA2Format, &XMA2FormatSize, &pXMASeekTable, &XMASeekTableSize );
  430. if ( !FAILED( hr ) )
  431. break;
  432. // make small jumps
  433. quality += 5;
  434. if ( quality > 100 )
  435. quality = 100;
  436. if ( !g_bQuiet )
  437. {
  438. Msg( "XMA Encoding Error on '%s', Attempting increasing quality to %d\n", pDebugName, quality );
  439. }
  440. numAttempts++;
  441. pXMAData = NULL;
  442. XMADataSize = 0;
  443. pXMA2Format = NULL;
  444. XMA2FormatSize = 0;
  445. pXMASeekTable = NULL;
  446. XMASeekTableSize = 0;
  447. }
  448. if ( FAILED( hr ) )
  449. {
  450. // unrecoverable
  451. return false;
  452. }
  453. else if ( numAttempts > 1 )
  454. {
  455. if ( !g_bQuiet )
  456. {
  457. Msg( "XMA Encoding Success on '%s' at quality %d\n", pDebugName, quality );
  458. }
  459. }
  460. DWORD loopBlock = 0;
  461. DWORD numLeadingSamples = 0;
  462. DWORD numTrailingSamples = 0;
  463. if ( loopStart != -1 )
  464. {
  465. // calculate start block/offset
  466. DWORD loopBlockStartIndex = 0;
  467. DWORD loopBlockStartOffset = 0;
  468. if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopBegin ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockStartIndex, &loopBlockStartOffset ) )
  469. {
  470. // could not determine loop point, out of range of encoded samples
  471. Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart );
  472. return false;
  473. }
  474. loopBlock = loopBlockStartIndex;
  475. numLeadingSamples = loopBlockStartOffset;
  476. if ( BigLong( pXMA2Format->LoopEnd ) < BigLong( pXMA2Format->SamplesEncoded ) )
  477. {
  478. // calculate end block/offset
  479. DWORD loopBlockEndIndex = 0;
  480. DWORD loopBlockEndOffset = 0;
  481. if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopEnd ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockEndIndex, &loopBlockEndOffset ) )
  482. {
  483. // could not determine loop point, out of range of encoded samples
  484. Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart );
  485. return false;
  486. }
  487. if ( loopBlockEndIndex != BigLong( pXMA2Format->BlockCount ) - 1 )
  488. {
  489. // end block MUST be last block
  490. Msg( "XMA Loop Encoding Error on '%s', block end is %d/%d\n", pDebugName, loopBlockEndOffset, BigLong( pXMA2Format->BlockCount ) );
  491. return false;
  492. }
  493. numTrailingSamples = BigLong( pXMA2Format->SamplesEncoded ) - BigLong( pXMA2Format->LoopEnd );
  494. }
  495. // check for proper encoding range
  496. if ( loopBlock > 32767 )
  497. {
  498. Msg( "XMA Loop Encoding Error on '%s', loop block exceeds 16 bits %d\n", pDebugName, loopBlock );
  499. return false;
  500. }
  501. if ( numLeadingSamples > 32767 )
  502. {
  503. Msg( "XMA Loop Encoding Error on '%s', leading samples exceeds 16 bits %d\n", pDebugName, numLeadingSamples );
  504. return false;
  505. }
  506. if ( numTrailingSamples > 32767 )
  507. {
  508. Msg( "XMA Loop Encoding Error on '%s', trailing samples exceeds 16 bits %d\n", pDebugName, numTrailingSamples );
  509. return false;
  510. }
  511. }
  512. xwvHeader_t header;
  513. memset( &header, 0, sizeof( xwvHeader_t ) );
  514. int seekTableSize = 0;
  515. if ( vdatSize || bIsVoiceOver )
  516. {
  517. // save the optional seek table only for vdat or vo
  518. // the seek table size is expected to be derived by this calculation
  519. seekTableSize = ( XMADataSize / XMA_BYTES_PER_PACKET ) * sizeof( int );
  520. if ( seekTableSize != XMASeekTableSize )
  521. {
  522. Msg( "XMA Error: Unexpected seek table calculation in '%s'!", pDebugName );
  523. return false;
  524. }
  525. }
  526. if ( loopStart != -1 && ( vdatSize || bIsVoiceOver ) )
  527. {
  528. Msg( "XMA Warning: Unexpected loop in vo data '%s'!", pDebugName );
  529. // do not write the seek table for looping sounds
  530. seekTableSize = 0;
  531. }
  532. header.id = BigLong( XWV_ID );
  533. header.version = BigLong( XWV_VERSION );
  534. header.headerSize = BigLong( sizeof( xwvHeader_t ) );
  535. header.staticDataSize = BigLong( seekTableSize + vdatSize );
  536. header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + seekTableSize + vdatSize, XBOX_DVD_SECTORSIZE ) );
  537. header.dataSize = BigLong( XMADataSize );
  538. // track the XMA number of samples that will get decoded
  539. // which is NOT the same as what the source actually encoded
  540. header.numDecodedSamples = pXMA2Format->SamplesEncoded;
  541. if ( loopStart != -1 )
  542. {
  543. // the loop start is in source space (now meaningless), need the loop in XMA decoding sample space
  544. header.loopStart = pXMA2Format->LoopBegin;
  545. }
  546. else
  547. {
  548. header.loopStart = BigLong( -1 );
  549. }
  550. header.loopBlock = BigShort( (unsigned short)loopBlock );
  551. header.numLeadingSamples = BigShort( (unsigned short)numLeadingSamples );
  552. header.numTrailingSamples = BigShort( (unsigned short)numTrailingSamples );
  553. header.vdatSize = BigShort( (short)vdatSize );
  554. header.format = XWV_FORMAT_XMA;
  555. header.bitsPerSample = 16;
  556. header.SetSampleRate( pFormat->nSamplesPerSec );
  557. header.SetChannels( pFormat->nChannels );
  558. header.quality = quality;
  559. header.bHasSeekTable = ( seekTableSize != 0 );
  560. // output header
  561. targetBuff.Put( &header, sizeof( xwvHeader_t ) );
  562. // output optional seek table
  563. if ( seekTableSize )
  564. {
  565. // seek table is already in big-endian format
  566. targetBuff.Put( pXMASeekTable, seekTableSize );
  567. }
  568. // output vdat
  569. if ( vdatSize )
  570. {
  571. targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size );
  572. }
  573. AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
  574. // write data
  575. targetBuff.Put( pXMAData, XMADataSize );
  576. // pad to EOF
  577. AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
  578. free( pXMAData );
  579. free( pXMA2Format );
  580. free( pXMASeekTable );
  581. // xma encoder leaves its temporary files, we'll delete
  582. scriptlib->DeleteTemporaryFiles( "LoopStrm*" );
  583. scriptlib->DeleteTemporaryFiles( "EncStrm*" );
  584. return true;
  585. #endif
  586. }
  587. bool EncodeAsPCM( const char *pTargetName, CUtlBuffer &targetBuff )
  588. {
  589. int formatChunk = FindChunk( WAVE_FMT );
  590. int dataChunk = FindChunk( WAVE_DATA );
  591. if ( formatChunk == -1 || dataChunk == -1 )
  592. {
  593. // huh? these should have been pre-validated
  594. return false;
  595. }
  596. WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData;
  597. if ( pFormat->wBitsPerSample != 16 )
  598. {
  599. // huh? the input is expeted to be 16 bit PCM
  600. return false;
  601. }
  602. int vdatSize = 0;
  603. int vdatChunk = FindChunk( WAVE_VALVEDATA );
  604. if ( vdatChunk != -1 )
  605. {
  606. vdatSize = g_chunks[vdatChunk].size;
  607. }
  608. chunk_t *pDataChunk = &g_chunks[dataChunk];
  609. xwvHeader_t header;
  610. memset( &header, 0, sizeof( xwvHeader_t ) );
  611. int sampleSize = pFormat->nChannels * sizeof( short );
  612. int sampleCount = pDataChunk->size / sampleSize;
  613. header.id = BigLong( XWV_ID );
  614. header.version = BigLong( XWV_VERSION );
  615. header.headerSize = BigLong( sizeof( xwvHeader_t ) );
  616. header.staticDataSize = BigLong( vdatSize );
  617. header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + vdatSize, XBOX_DVD_SECTORSIZE ) );
  618. header.dataSize = BigLong( pDataChunk->size );
  619. header.numDecodedSamples = BigLong( sampleCount );
  620. header.loopStart = BigLong( -1 );
  621. header.loopBlock = 0;
  622. header.numLeadingSamples = 0;
  623. header.numTrailingSamples = 0;
  624. header.vdatSize = BigShort( (short)vdatSize );
  625. header.format = XWV_FORMAT_PCM;
  626. header.bitsPerSample = 16;
  627. header.SetSampleRate( pFormat->nSamplesPerSec );
  628. header.SetChannels( pFormat->nChannels );
  629. header.quality = 100;
  630. // output header
  631. targetBuff.Put( &header, sizeof( xwvHeader_t ) );
  632. // output vdat
  633. if ( vdatSize )
  634. {
  635. targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size );
  636. }
  637. AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
  638. for ( int i = 0; i < sampleCount * pFormat->nChannels; i++ )
  639. {
  640. ((short *)pDataChunk->pData)[i] = BigShort( ((short *)pDataChunk->pData)[i] );
  641. }
  642. // write data
  643. targetBuff.Put( pDataChunk->pData, pDataChunk->size );
  644. // pad to EOF
  645. AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
  646. return true;
  647. }
  648. //-----------------------------------------------------------------------------
  649. // Purpose: read source, do work, and write to target
  650. //-----------------------------------------------------------------------------
  651. bool CreateTargetFile_WAV( const char *pSourceName, const char *pTargetName, bool bWriteToZip )
  652. {
  653. g_numChunks = 0;
  654. // resolve relative source to absolute path
  655. char fullSourcePath[MAX_PATH];
  656. if ( _fullpath( fullSourcePath, pSourceName, sizeof( fullSourcePath ) ) )
  657. {
  658. pSourceName = fullSourcePath;
  659. }
  660. if ( !ReadChunks( pSourceName, g_numChunks, g_chunks ) )
  661. {
  662. Msg( "No RIFF Chunks on '%s'\n", pSourceName );
  663. return false;
  664. }
  665. int formatChunk = FindChunk( WAVE_FMT );
  666. if ( formatChunk == -1 )
  667. {
  668. Msg( "RIFF Format Chunk not found on '%s'\n", pSourceName );
  669. return false;
  670. }
  671. int dataChunk = FindChunk( WAVE_DATA );
  672. if ( dataChunk == -1 )
  673. {
  674. Msg( "RIFF Data Chunk not found on '%s'\n", pSourceName );
  675. return false;
  676. }
  677. // get the conversion rules
  678. conversion_t *pConversion = g_defaultConversionRules;
  679. if ( V_stristr( g_szModPath, "\\portal" ) )
  680. {
  681. pConversion = g_portalConversionRules;
  682. }
  683. // conversion rules are based on matching subdir
  684. for ( int i=1; ;i++ )
  685. {
  686. char subString[MAX_PATH];
  687. if ( !pConversion[i].pSubDir )
  688. {
  689. // end of list
  690. break;
  691. }
  692. sprintf( subString, "\\%s\\", pConversion[i].pSubDir );
  693. if ( V_stristr( pSourceName, subString ) )
  694. {
  695. // use matched conversion rules
  696. pConversion = &pConversion[i];
  697. break;
  698. }
  699. }
  700. bool bForceTo22K = pConversion->bForceTo22K;
  701. int quality = pConversion->quality;
  702. // cannot trust the localization depots to have matched their sources
  703. // cannot allow 44K
  704. if ( IsLocalizedFile( pSourceName ) )
  705. {
  706. bForceTo22K = true;
  707. }
  708. // classify strict vo from /sound/vo only
  709. bool bIsVoiceOver = V_stristr( pSourceName, "\\sound\\vo\\" ) != NULL;
  710. // can override default settings
  711. quality = CommandLine()->ParmValue( "-xmaquality", quality );
  712. if ( quality < 0 )
  713. quality = 0;
  714. else if ( quality > 100 )
  715. quality = 100;
  716. if ( !g_bQuiet )
  717. {
  718. Msg( "Encoding quality: %d on '%s'\n", quality, pSourceName );
  719. }
  720. int vdatSize = 0;
  721. int vdatChunk = FindChunk( WAVE_VALVEDATA );
  722. if ( vdatChunk != -1 )
  723. {
  724. // compile to optimal block
  725. if ( !CompressVDAT( &g_chunks[vdatChunk] ) )
  726. {
  727. Msg( "Compress VDAT Error on '%s'\n", pSourceName );
  728. return false;
  729. }
  730. vdatSize = g_chunks[vdatChunk].size;
  731. }
  732. // for safety (not trusting their decoding) and simplicity convert all data to 16 bit PCM before encoding
  733. WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData;
  734. if ( ( pFormat->wFormatTag == WAVE_FORMAT_PCM ) )
  735. {
  736. if ( pFormat->wBitsPerSample == 8 )
  737. {
  738. ConvertPCMDataChunk8To16( &g_chunks[formatChunk], &g_chunks[dataChunk] );
  739. }
  740. }
  741. else if ( pFormat->wFormatTag == WAVE_FORMAT_ADPCM )
  742. {
  743. ConvertADPCMDataChunkTo16( &g_chunks[formatChunk], &g_chunks[dataChunk] );
  744. }
  745. else
  746. {
  747. Msg( "Unknown RIFF Format on '%s'\n", pSourceName );
  748. return false;
  749. }
  750. // optionally decimate to 22K
  751. if ( pFormat->nSamplesPerSec == 44100 && bForceTo22K )
  752. {
  753. if ( !g_bQuiet )
  754. {
  755. Msg( "Converting to 22K '%s'\n", pSourceName );
  756. }
  757. ConvertPCMDataChunk16To22K( &g_chunks[formatChunk], &g_chunks[dataChunk] );
  758. }
  759. CUtlBuffer targetBuff;
  760. bool bSuccess;
  761. bSuccess = EncodeAsXMA( pSourceName, targetBuff, quality, bIsVoiceOver );
  762. if ( bSuccess )
  763. {
  764. WriteBufferToFile( pTargetName, targetBuff, bWriteToZip, g_WriteModeForConversions );
  765. }
  766. // release data
  767. for ( int i = 0; i < g_numChunks; i++ )
  768. {
  769. free( g_chunks[i].pData );
  770. }
  771. return bSuccess;
  772. }
  773. //-----------------------------------------------------------------------------
  774. // Purpose: MP3's are already pre-converted into .360.wav
  775. //-----------------------------------------------------------------------------
  776. bool CreateTargetFile_MP3( const char *pSourceName, const char *pTargetName, bool bWriteToZip )
  777. {
  778. CUtlBuffer targetBuffer;
  779. // ignore the .mp3 source, the .360.wav target should have been pre-converted, checked in, and exist
  780. // use the expected target as the source
  781. if ( !scriptlib->ReadFileToBuffer( pTargetName, targetBuffer ) )
  782. {
  783. // the .360.wav target does not exist
  784. // try again using a .wav version and convert from that
  785. char wavFilename[MAX_PATH];
  786. V_StripExtension( pSourceName, wavFilename, sizeof( wavFilename ) );
  787. V_SetExtension( wavFilename, ".wav", sizeof( wavFilename ) );
  788. if ( scriptlib->DoesFileExist( wavFilename ) )
  789. {
  790. if ( CreateTargetFile_WAV( wavFilename, pTargetName, bWriteToZip ) )
  791. {
  792. return true;
  793. }
  794. }
  795. return false;
  796. }
  797. // no conversion to write, but possibly zipped
  798. bool bSuccess = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, WRITE_TO_DISK_NEVER );
  799. return bSuccess;
  800. }
  801. //-----------------------------------------------------------------------------
  802. // Get the preload data for a wav file
  803. //-----------------------------------------------------------------------------
  804. bool GetPreloadData_WAV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut )
  805. {
  806. xwvHeader_t *pHeader = ( xwvHeader_t * )fileBufferIn.Base();
  807. if ( pHeader->id != ( unsigned int )BigLong( XWV_ID ) ||
  808. pHeader->version != ( unsigned int )BigLong( XWV_VERSION ) ||
  809. pHeader->headerSize != BigLong( sizeof( xwvHeader_t ) ) )
  810. {
  811. // bad version
  812. Msg( "Can't preload: '%s', has bad version\n", pFilename );
  813. return false;
  814. }
  815. // ensure caller's buffer is clean
  816. // caller determines preload size, via TellMaxPut()
  817. preloadBufferOut.Purge();
  818. unsigned int preloadSize = BigLong( pHeader->headerSize ) + BigLong( pHeader->staticDataSize );
  819. preloadBufferOut.Put( fileBufferIn.Base(), preloadSize );
  820. return true;
  821. }