Counter Strike : Global Offensive Source Code
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.

1116 lines
38 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: XMA Decoding
  4. //
  5. //=====================================================================================//
  6. #include "audio_pch.h"
  7. #include "tier1/mempool.h"
  8. #include "tier1/circularbuffer.h"
  9. #include "tier1/utllinkedlist.h"
  10. #include "tier1/utlmap.h"
  11. #include "filesystem/iqueuedloader.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. //#define DEBUG_XMA
  15. // XMA is supposed to decode at an ideal max of 512 mono samples every 4msec.
  16. // XMA can only peel a max of 1984 stereo samples per poll request (if available).
  17. // Max is not achievable and degrades based on quality settings, stereo, etc, but using these numbers for for calcs.
  18. // 1984 stereo samples should be decoded by xma in 31 msec.
  19. // 1984 stereo samples at 44.1Khz dictates a request every 45 msec.
  20. // GetOutputData() must be clocked faster than 45 msec or samples will not be available.
  21. // However, the XMA decoder must be serviced much faster. It was designed for 5 msec.
  22. // 15 msec seems to be fast enough for XMA to decode enough to keep the smaller buffer sizes satisfied, and have slop for +/- 5 msec swings.
  23. // Need at least this amount of decoded pcm samples before mixing can commence.
  24. // This needs to be able to cover the initial mix request, while a new decode cycle is in flight.
  25. #define MIN_READYTOMIX ( ( 2 * XMA_POLL_RATE ) * 0.001f )
  26. // number of samples that xma decodes
  27. // must be 128 aligned for mono (1984 is hw max for stereo)
  28. #define XMA_MONO_OUTPUT_BUFFER_SAMPLES 2048
  29. #define XMA_STEREO_OUTPUT_BUFFER_SAMPLES 1920
  30. // for decoder input
  31. // xma blocks are fetched from the datacache into one of these hw buffers for decoding
  32. // must be in quantum units of XMA_BLOCK_SIZE
  33. #define XMA_INPUT_BUFFER_SIZE ( 8 * XMA_BLOCK_SIZE )
  34. // circular staging buffer to drain xma decoder and stage until mixer requests
  35. // must be large enough to hold the slowest expected mixing frame worth of samples
  36. #define PCM_STAGING_BUFFER_TIME 200
  37. // xma physical heap, supplies xma input buffers for hw decoder
  38. // each potential channel must be able to peel 2 buffers for driving xma decoder
  39. #define XMA_PHYSICAL_HEAP_SIZE ( 2 * MAX_CHANNELS * XMA_INPUT_BUFFER_SIZE )
  40. // in milliseconds
  41. #define MIX_IO_DATA_TIMEOUT 10000 // async i/o from dvd could be very late
  42. #define MIX_DECODER_TIMEOUT 3000 // decoder might be very busy
  43. #define MIX_DECODER_POLLING_LATENCY 5 // not faster than 5ms, or decoder will sputter
  44. // diagnostic errors
  45. #define ERROR_IO_DATA_TIMEOUT -1 // async i/o taking too long to deliver xma blocks
  46. #define ERROR_IO_TRUNCATED_BLOCK -2 // async i/o failed to deliver complete blocks
  47. #define ERROR_IO_NO_XMA_DATA -3 // async i/o failed to deliver any block
  48. #define ERROR_DECODER_TIMEOUT -4 // decoder taking too long to decode xma blocks
  49. #define ERROR_OUT_OF_MEMORY -5 // not enough physical memory for xma blocks
  50. #define ERROR_XMA_PARSE -6 // decoder barfed on xma blocks
  51. #define ERROR_XMA_CANTLOCK -7 // hw not acting as expected
  52. #define ERROR_XMA_CANTSUBMIT -8 // hw not acting as expected
  53. #define ERROR_XMA_CANTRESUME -9 // hw not acting as expected
  54. #define ERROR_XMA_NO_PCM_DATA -10 // no xma decoded pcm data ready
  55. #define ERROR_NULL_BUFFER -11 // logic flaw, expected buffer is null
  56. #define ERROR_XMA_NOPLAYBACKOBJECT -12 // failed to create xma playback object
  57. const char *g_XMAErrorStrings[] =
  58. {
  59. "Unknown Error Code",
  60. "Async I/O Data Timeout", // ERROR_IO_DATA_TIMEOUT
  61. "Async I/O Truncated Block", // ERROR_IO_TRUNCATED_BLOCK
  62. "Async I/O Data Not Ready", // ERROR_IO_NO_XMA_DATA
  63. "Decoder Timeout", // ERROR_DECODER_TIMEOUT
  64. "Out Of Memory", // ERROR_OUT_OF_MEMORY
  65. "XMA Parse", // ERROR_XMA_PARSE
  66. "XMA Cannot Lock", // ERROR_XMA_CANTLOCK
  67. "XMA Cannot Submit", // ERROR_XMA_CANTSUBMIT
  68. "XMA Cannot Resume", // ERROR_XMA_CANTRESUME
  69. "XMA No PCM Data Ready", // ERROR_XMA_NO_PCM_DATA
  70. "NULL Buffer", // ERROR_NULL_BUFFER
  71. "XMA Cannot Create Object", // ERROR_XMA_NOPLAYBACKOBJECT
  72. };
  73. MEMALLOC_DEFINE_EXTERNAL_TRACKING(CXMAAllocator);
  74. class CXMAAllocator
  75. {
  76. public:
  77. static void *Alloc( int bytes )
  78. {
  79. MEM_ALLOC_CREDIT();
  80. void *retval = XMemAlloc( bytes,
  81. MAKE_XALLOC_ATTRIBUTES(
  82. 0,
  83. false,
  84. TRUE,
  85. FALSE,
  86. eXALLOCAllocatorId_XAUDIO,
  87. XALLOC_PHYSICAL_ALIGNMENT_4K,
  88. XALLOC_MEMPROTECT_WRITECOMBINE_LARGE_PAGES,
  89. FALSE,
  90. XALLOC_MEMTYPE_PHYSICAL ) );
  91. MemAlloc_RegisterExternalAllocation( CXMAAllocator, retval, XPhysicalSize( retval ) );
  92. return retval;
  93. }
  94. static void Free( void *p )
  95. {
  96. MemAlloc_RegisterExternalDeallocation( CXMAAllocator, p, XPhysicalSize( p ) );
  97. XMemFree( p,
  98. MAKE_XALLOC_ATTRIBUTES(
  99. 0,
  100. false,
  101. TRUE,
  102. FALSE,
  103. eXALLOCAllocatorId_XAUDIO,
  104. XALLOC_PHYSICAL_ALIGNMENT_4K,
  105. XALLOC_MEMPROTECT_WRITECOMBINE_LARGE_PAGES,
  106. FALSE,
  107. XALLOC_MEMTYPE_PHYSICAL ) );
  108. }
  109. };
  110. // for XMA decoding, fixed size allocations aligned to 4K from a single physical heap
  111. CAlignedMemPool< XMA_INPUT_BUFFER_SIZE, 4096, XMA_PHYSICAL_HEAP_SIZE, CXMAAllocator > g_XMAMemoryPool;
  112. ConVar snd_xma_spew_warnings( "snd_xma_spew_warnings", "0" );
  113. ConVar snd_xma_spew_startup( "snd_xma_spew_startup", "0" );
  114. ConVar snd_xma_spew_mixers( "snd_xma_spew_mixers", "0" );
  115. ConVar snd_xma_spew_decode( "snd_xma_spew_decode", "0" );
  116. ConVar snd_xma_spew_drain( "snd_xma_spew_drain", "0" );
  117. ConVar snd_xma_recover_from_exhausted_stream( "snd_xma_recover_from_exhausted_stream", "1", 0, "If 1, recovers when the stream is exhausted when playing XMA sounds (prevents music or ambiance sounds to stop if too many sounds are played). Set to 0, to stop the sound otherwise." );
  118. #ifdef DEBUG_XMA
  119. ConVar snd_xma_record( "snd_xma_record", "0" );
  120. ConVar snd_xma_spew_errors( "snd_xma_spew_errors", "0" );
  121. #endif
  122. extern ConVar snd_report_loop_sound;
  123. extern ConVar snd_delay_for_choreo_enabled;
  124. extern ConVar snd_mixahead;
  125. extern float g_fDelayForChoreo;
  126. extern uint32 g_nDelayForChoreoLastCheckInMs;
  127. extern int g_nDelayForChoreoNumberOfSoundsPlaying;
  128. extern ConVar snd_async_stream_spew_delayed_start_time;
  129. extern ConVar snd_async_stream_spew_delayed_start_filter;
  130. extern ConVar snd_async_stream_spew_exhausted_buffer;
  131. extern ConVar snd_async_stream_spew_exhausted_buffer_time;
  132. //-----------------------------------------------------------------------------
  133. // Purpose: Mixer for ADPCM encoded audio
  134. //-----------------------------------------------------------------------------
  135. class CAudioMixerWaveXMA : public CAudioMixerWave
  136. {
  137. public:
  138. typedef CAudioMixerWave BaseClass;
  139. CAudioMixerWaveXMA( IWaveData *data, int initialStreamPosition, int skipInitialSamples, bool bUpdateDelayForChoreo );
  140. virtual ~CAudioMixerWaveXMA( void );
  141. virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress );
  142. virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
  143. virtual bool IsSetSampleStartSupported() const;
  144. virtual void SetSampleStart( int newPosition );
  145. virtual int GetPositionForSave();
  146. virtual void SetPositionFromSaved( int savedPosition );
  147. virtual int GetMixSampleSize() { return CalcSampleSize( 16, m_NumChannels ); }
  148. virtual bool IsReadyToMix();
  149. virtual bool ShouldContinueMixing();
  150. private:
  151. int GetXMABlocksAndSubmitToDecoder( bool bDecoderLocked );
  152. int UpdatePositionForLooping( int *pNumRequestedSamples );
  153. int ServiceXMADecoder( bool bForceUpdate );
  154. int GetPCMSamples( int numRequested, char *pData );
  155. bool GetPlaybackInitialized();
  156. XMAPLAYBACK *m_pXMAPlayback;
  157. // input buffers, encoded xma
  158. byte *m_pXMABuffers[2];
  159. int m_XMABufferIndex;
  160. // output buffer, decoded pcm samples, a staging circular buffer, waiting for mixer requests
  161. // due to staging nature, contains decoded samples from multiple input buffers
  162. CCircularBuffer *m_pPCMSamples;
  163. int m_SampleRate;
  164. int m_NumChannels;
  165. // maximum possible decoded samples
  166. int m_SampleCount;
  167. // decoded sample position
  168. int m_SamplePosition;
  169. // current data marker
  170. int m_LastDataOffset;
  171. int m_DataOffset;
  172. // total bytes of data
  173. int m_TotalBytes;
  174. // Numbers of samples to skip at first
  175. int m_SkipInitialSamples;
  176. // timers
  177. unsigned int m_StartTime;
  178. unsigned int m_LastSuccessfulStreamTime;
  179. unsigned int m_LastDecodingStartTime;
  180. unsigned int m_LastDrainTime;
  181. unsigned int m_LastPollTime;
  182. int m_hMixerList;
  183. int m_Error;
  184. int8 m_LastSample[4];
  185. bool m_bStartedMixing : 1;
  186. bool m_bFinished : 1;
  187. bool m_bLooped : 1;
  188. bool m_bUpdateDelayForChoreo : 1;
  189. };
  190. CUtlFixedLinkedList< CAudioMixerWaveXMA * > g_XMAMixerList;
  191. CON_COMMAND( snd_xma_info, "Spew XMA Info" )
  192. {
  193. Msg( "XMA Memory:\n" );
  194. Msg( " Blocks Allocated: %d\n", g_XMAMemoryPool.NumAllocated() );
  195. Msg( " Blocks Free: %d\n", g_XMAMemoryPool.NumFree() );
  196. Msg( " Pool Size: %.2f MB\n", g_XMAMemoryPool.BytesTotal() / ( 1024.0f * 1024.0f ) );
  197. Msg( "Active XMA Mixers: %d\n", g_XMAMixerList.Count() );
  198. char nameBuf[MAX_PATH];
  199. for ( int hMixer = g_XMAMixerList.Head(); hMixer != g_XMAMixerList.InvalidIndex(); hMixer = g_XMAMixerList.Next( hMixer ) )
  200. {
  201. CAudioMixerWaveXMA *pXMAMixer = g_XMAMixerList[hMixer];
  202. Msg( " rate:%5d ch:%1d '%s'\n", pXMAMixer->GetSource()->SampleRate(), pXMAMixer->GetSource()->IsStereoWav() ? 2 : 1, pXMAMixer->GetSource()->GetFileName(nameBuf, sizeof(nameBuf)) );
  203. }
  204. }
  205. CAudioMixerWaveXMA::CAudioMixerWaveXMA( IWaveData *data, int initialStreamPosition, int skipInitialSamples, bool bUpdateDelayForChoreo ) : CAudioMixerWave( data )
  206. {
  207. Assert( dynamic_cast<CAudioSourceWave *>(&m_pData->Source()) != NULL );
  208. m_Error = 0;
  209. m_NumChannels = m_pData->Source().IsStereoWav() ? 2 : 1;
  210. m_SampleRate = m_pData->Source().SampleRate();
  211. m_bLooped = m_pData->Source().IsLooped();
  212. m_SampleCount = m_pData->Source().SampleCount();
  213. m_TotalBytes = m_pData->Source().DataSize();
  214. m_SkipInitialSamples = skipInitialSamples;
  215. m_LastDataOffset = initialStreamPosition;
  216. m_DataOffset = initialStreamPosition;
  217. m_SamplePosition = 0;
  218. if ( initialStreamPosition )
  219. {
  220. m_SamplePosition = m_pData->Source().StreamToSamplePosition( initialStreamPosition );
  221. // IMPORTANT: When initial stream position is requested we need
  222. // to make the XMA mixer obeys rules of how CAudioMixerWave::GetSampleLoadRequest
  223. // is using its members "m_fsample_index", "m_sample_loaded_index" and "m_sample_max_loaded"
  224. // If the implementation of CAudioMixerWave::GetSampleLoadRequest changes, then
  225. // XMA mixer should be changed accordingly.
  226. CAudioMixerWave::m_fsample_index = m_SamplePosition;
  227. CAudioMixerWave::m_sample_loaded_index = m_SamplePosition;
  228. CAudioMixerWave::m_sample_max_loaded = m_SamplePosition + 1;
  229. }
  230. m_bStartedMixing = false;
  231. m_bFinished = false;
  232. m_bUpdateDelayForChoreo = bUpdateDelayForChoreo;
  233. m_StartTime = Plat_MSTime();
  234. m_LastSuccessfulStreamTime = 0;
  235. m_LastDecodingStartTime = 0;
  236. m_LastPollTime = 0;
  237. m_LastDrainTime = 0;
  238. if ( m_bUpdateDelayForChoreo ) // Do not test for enabled here as we want g_nDelayForChoreoNumberOfSoundsPlaying to be always accurate
  239. {
  240. ++g_nDelayForChoreoNumberOfSoundsPlaying;
  241. g_nDelayForChoreoLastCheckInMs = m_StartTime; // Not necessary as g_nDelayForChoreoNumberOfSoundsPlaying != 0 prevents the Reset, but does not hurt either
  242. }
  243. m_pXMAPlayback = NULL;
  244. m_pPCMSamples = NULL;
  245. m_pXMABuffers[0] = NULL;
  246. m_pXMABuffers[1] = NULL;
  247. m_XMABufferIndex = 0;
  248. V_memset( m_LastSample, 0, sizeof( m_LastSample ) );
  249. m_hMixerList = g_XMAMixerList.AddToTail( this );
  250. #ifdef DEBUG_XMA
  251. if ( snd_xma_record.GetBool() )
  252. {
  253. WaveCreateTmpFile( "debug.wav", m_SampleRate, 16, m_NumChannels );
  254. }
  255. #endif
  256. if ( snd_xma_spew_mixers.GetBool() )
  257. {
  258. char nameBuf[MAX_PATH];
  259. Msg( "XMA: 0x%8.8x (%2d), Mixer Alloc, '%s'\n", (unsigned int)this, g_XMAMixerList.Count(), m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  260. }
  261. }
  262. CAudioMixerWaveXMA::~CAudioMixerWaveXMA( void )
  263. {
  264. if ( m_bUpdateDelayForChoreo ) // Do not test for enabled here as we want g_nDelayForChoreoNumberOfSoundsPlaying to be always accurate
  265. {
  266. --g_nDelayForChoreoNumberOfSoundsPlaying;
  267. Assert( g_nDelayForChoreoNumberOfSoundsPlaying >= 0 );
  268. g_nDelayForChoreoLastCheckInMs = Plat_MSTime(); // Critical to make sure that we are reseting the latency at least N ms after the last choreo sound
  269. }
  270. if ( m_pXMAPlayback )
  271. {
  272. XMAPlaybackDestroy( m_pXMAPlayback );
  273. g_XMAMemoryPool.Free( m_pXMABuffers[0] );
  274. if ( m_pXMABuffers[1] )
  275. {
  276. g_XMAMemoryPool.Free( m_pXMABuffers[1] );
  277. }
  278. }
  279. if ( m_pPCMSamples )
  280. {
  281. FreeCircularBuffer( m_pPCMSamples );
  282. }
  283. g_XMAMixerList.Remove( m_hMixerList );
  284. if ( snd_xma_spew_mixers.GetBool() )
  285. {
  286. char nameBuf[MAX_PATH];
  287. Msg( "XMA: 0x%8.8x (%2d), Mixer Freed, '%s'\n", (unsigned int)this, g_XMAMixerList.Count(), m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  288. }
  289. }
  290. void CAudioMixerWaveXMA::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
  291. {
  292. if ( m_NumChannels == 1 )
  293. {
  294. pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
  295. }
  296. else
  297. {
  298. pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
  299. }
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Looping is achieved in two passes to provide a circular view of the linear data.
  303. // Pass1: Clamps a sample request to the end of data.
  304. // Pass2: Snaps to the loop start, and returns the number of samples to discard, could be 0,
  305. // up to the expected loop sample position.
  306. // Returns the number of samples to discard, or 0.
  307. //-----------------------------------------------------------------------------
  308. int CAudioMixerWaveXMA::UpdatePositionForLooping( int *pNumRequestedSamples )
  309. {
  310. if ( !m_bLooped )
  311. {
  312. // not looping, no fixups
  313. return 0;
  314. }
  315. int numLeadingSamples;
  316. int numTrailingSamples;
  317. CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
  318. int loopSampleStart = source.GetLoopingInfo( NULL, &numLeadingSamples, &numTrailingSamples );
  319. int numRemainingSamples = ( m_SampleCount - numTrailingSamples ) - m_SamplePosition;
  320. // possibly straddling the end of data (and thus about to loop)
  321. // want to split the straddle into two regions, due to loops possibly requiring a trailer and leader of discarded samples
  322. if ( numRemainingSamples > 0 )
  323. {
  324. // first region, all the remaining samples, clamped until end of desired data
  325. *pNumRequestedSamples = min( *pNumRequestedSamples, numRemainingSamples );
  326. // nothing to discard
  327. return 0;
  328. }
  329. else if ( numRemainingSamples == 0 )
  330. {
  331. if ( snd_report_loop_sound.GetBool() )
  332. {
  333. char nameBuf[MAX_PATH];
  334. Warning( "[Sound] Sound \"%s\" just looped.\n", m_pData->Source().GetFileName(nameBuf, sizeof( nameBuf ) ) );
  335. }
  336. // at exact end of desired data, snap the sample position back
  337. // the position will be correct AFTER discarding decoded trailing and leading samples
  338. m_SamplePosition = loopSampleStart;
  339. // clamp the request
  340. numRemainingSamples = ( m_SampleCount - numTrailingSamples ) - m_SamplePosition;
  341. *pNumRequestedSamples = min( *pNumRequestedSamples, numRemainingSamples );
  342. // flush these samples so the sample position is the real loop sample starting position
  343. return numTrailingSamples + numLeadingSamples;
  344. }
  345. return 0;
  346. }
  347. //-----------------------------------------------------------------------------
  348. // Get and submit XMA block(s). The decoder must stay blocks ahead of mixer
  349. // so the decoded samples are available for peeling.
  350. // An XMA file is thus treated as a series of fixed size large buffers (multiple xma blocks),
  351. // which are streamed in sequentially. The XMA buffers may be delayed from the
  352. // audio data cache due to async i/o latency.
  353. // Returns < 0 if error, 0 if no decode started, 1 if decode submitted.
  354. //-----------------------------------------------------------------------------
  355. int CAudioMixerWaveXMA::GetXMABlocksAndSubmitToDecoder( bool bDecoderIsLocked )
  356. {
  357. int status = 0;
  358. if ( m_DataOffset >= m_TotalBytes )
  359. {
  360. if ( !m_bLooped )
  361. {
  362. // end of file, no more data to decode
  363. // not an error, because decoder finishes long before samples drained
  364. return 0;
  365. }
  366. // start from beginning of loop
  367. CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
  368. source.GetLoopingInfo( &m_DataOffset, NULL, NULL );
  369. m_DataOffset *= XMA_BLOCK_SIZE;
  370. }
  371. HRESULT hr;
  372. bool bLocked = false;
  373. if ( !bDecoderIsLocked )
  374. {
  375. // decoder must be locked before any access
  376. hr = XMAPlaybackRequestModifyLock( m_pXMAPlayback );
  377. if ( FAILED( hr ) )
  378. {
  379. status = ERROR_XMA_CANTLOCK;
  380. goto cleanUp;
  381. }
  382. hr = XMAPlaybackWaitUntilModifyLockObtained( m_pXMAPlayback );
  383. if ( FAILED( hr ) )
  384. {
  385. status = ERROR_XMA_CANTLOCK;
  386. goto cleanUp;
  387. }
  388. bLocked = true;
  389. }
  390. // the input buffer can never be less than a single xma block (buffer size is multiple blocks)
  391. int bufferSize = min( m_TotalBytes - m_DataOffset, XMA_INPUT_BUFFER_SIZE );
  392. if ( !bufferSize )
  393. {
  394. // EOF
  395. goto cleanUp;
  396. }
  397. Assert( !( bufferSize % XMA_BLOCK_SIZE ) );
  398. byte *pXMABuffer = m_pXMABuffers[m_XMABufferIndex & 0x01];
  399. if ( !pXMABuffer )
  400. {
  401. // shouldn't happen, buffer should have been allocated
  402. Assert( 0 );
  403. status = ERROR_NULL_BUFFER;
  404. goto cleanUp;
  405. }
  406. if ( !XMAPlaybackQueryReadyForMoreData( m_pXMAPlayback, 0 ) || XMAPlaybackQueryInputDataPending( m_pXMAPlayback, 0, pXMABuffer ) )
  407. {
  408. // decoder too saturated for more data or
  409. // decoder still decoding from input hw buffer
  410. goto cleanUp;
  411. }
  412. // get xma block(s)
  413. // pump to get all of requested data
  414. char *pData;
  415. int total = 0;
  416. int nDataOffset = m_DataOffset;
  417. while ( total < bufferSize )
  418. {
  419. int available = m_pData->ReadSourceData( (void **)&pData, nDataOffset, bufferSize - total, NULL );
  420. if ( !available )
  421. {
  422. break;
  423. }
  424. // aggregate into hw buffer
  425. V_memcpy( pXMABuffer + total, pData, available );
  426. nDataOffset += available;
  427. total += available;
  428. }
  429. if ( total != bufferSize )
  430. {
  431. if ( !total )
  432. {
  433. // failed to get any data, could be async latency or file error
  434. status = ERROR_IO_NO_XMA_DATA;
  435. }
  436. else
  437. {
  438. // failed to get complete xma block(s)
  439. // Do not set an error state, we will try later.
  440. }
  441. goto cleanUp;
  442. }
  443. m_DataOffset = nDataOffset; // Now that it is successful, update m_DataOffset
  444. // It allows us to handle more gracefully partial xma blocks
  445. m_LastSuccessfulStreamTime = Plat_MSTime();
  446. // track the currently submitted offset
  447. // this is used as a cheap method for save/restore because an XMA seek table is not available
  448. m_LastDataOffset = m_DataOffset - total;
  449. // start decoding the block(s) in the hw buffer
  450. hr = XMAPlaybackSubmitData( m_pXMAPlayback, 0, pXMABuffer, bufferSize );
  451. if ( FAILED( hr ) )
  452. {
  453. // failed to start decoder
  454. status = ERROR_XMA_CANTSUBMIT;
  455. goto cleanUp;
  456. }
  457. m_LastDecodingStartTime = Plat_MSTime();
  458. // decode submitted
  459. status = 1;
  460. // advance to next buffer
  461. m_XMABufferIndex++;
  462. if ( snd_xma_spew_decode.GetBool() )
  463. {
  464. char nameBuf[MAX_PATH];
  465. Msg( "XMA: 0x%8.8x, XMABuffer: 0x%8.8x, BufferSize: %d, NextDataOffset: %d, %s\n", (unsigned int)this, pXMABuffer, bufferSize, m_DataOffset, m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  466. }
  467. cleanUp:
  468. if ( bLocked )
  469. {
  470. // release the lock and let the decoder run
  471. hr = XMAPlaybackResumePlayback( m_pXMAPlayback );
  472. if ( FAILED( hr ) )
  473. {
  474. status = ERROR_XMA_CANTRESUME;
  475. }
  476. }
  477. return status;
  478. }
  479. //-----------------------------------------------------------------------------
  480. // Drain the XMA Decoder into the staging circular buffer of PCM for mixer.
  481. // Fetch new XMA samples for the decoder.
  482. //-----------------------------------------------------------------------------
  483. int CAudioMixerWaveXMA::ServiceXMADecoder( bool bForceUpdate )
  484. {
  485. uint32 nCurrentTime = Plat_MSTime();
  486. // allow decoder to work without being polled (lock causes a decoding stall)
  487. // decoder must be allowed minimum operating latency
  488. // the buffers are sized to compensate for the operating latency
  489. if ( !bForceUpdate && ( nCurrentTime - m_LastPollTime <= MIX_DECODER_POLLING_LATENCY ) )
  490. {
  491. return 0;
  492. }
  493. m_LastPollTime = nCurrentTime;
  494. if ( GetPlaybackInitialized() == false )
  495. {
  496. return -1;
  497. }
  498. // lock and pause the decoder to gain access
  499. HRESULT hr = XMAPlaybackRequestModifyLock( m_pXMAPlayback );
  500. if ( FAILED( hr ) )
  501. {
  502. m_Error = ERROR_XMA_CANTLOCK;
  503. return -1;
  504. }
  505. hr = XMAPlaybackWaitUntilModifyLockObtained( m_pXMAPlayback );
  506. if ( FAILED( hr ) )
  507. {
  508. m_Error = ERROR_XMA_CANTLOCK;
  509. return -1;
  510. }
  511. DWORD dwParseError = XMAPlaybackGetParseError( m_pXMAPlayback, 0 );
  512. if ( dwParseError )
  513. {
  514. if ( snd_xma_spew_warnings.GetBool() )
  515. {
  516. char nameBuf[MAX_PATH];
  517. Warning( "XMA: 0x%8.8x, Decoder Error, Parse: %d, '%s'\n", (unsigned int)this, dwParseError, m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  518. }
  519. m_Error = ERROR_XMA_PARSE;
  520. return -1;
  521. }
  522. #ifdef DEBUG_XMA
  523. if ( snd_xma_spew_errors.GetBool() )
  524. {
  525. DWORD dwError = XMAPlaybackGetErrorBits( m_pXMAPlayback, 0 );
  526. if ( dwError )
  527. {
  528. char nameBuf[MAX_PATH];
  529. Warning( "XMA: 0x%8.8x, Playback Error: %d, '%s'\n", (unsigned int)this, dwError, m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  530. }
  531. }
  532. #endif
  533. int numNewSamples = XMAPlaybackQueryAvailableData( m_pXMAPlayback, 0 );
  534. int numMaxSamples = m_pPCMSamples->GetWriteAvailable()/( m_NumChannels*sizeof( short ) );
  535. int numSamples = min( numNewSamples, numMaxSamples );
  536. while ( numSamples )
  537. {
  538. char *pPCMData = NULL;
  539. int numSamplesDecoded = XMAPlaybackConsumeDecodedData( m_pXMAPlayback, 0, numSamples, (void**)&pPCMData );
  540. if ( numSamplesDecoded > m_SkipInitialSamples )
  541. {
  542. pPCMData += m_SkipInitialSamples * m_NumChannels * sizeof( short );
  543. int numSamplesToCopy = numSamplesDecoded - m_SkipInitialSamples;
  544. // put into staging buffer, ready for mixer to drain
  545. m_pPCMSamples->Write( pPCMData, numSamplesToCopy*m_NumChannels*sizeof( short ) );
  546. m_SkipInitialSamples = 0;
  547. }
  548. else
  549. {
  550. m_SkipInitialSamples -= numSamplesDecoded;
  551. }
  552. numSamples -= numSamplesDecoded;
  553. numNewSamples -= numSamplesDecoded;
  554. }
  555. // queue up more blocks for the decoder
  556. // the decoder will always finish ahead of the mixer, submit nothing, and the mixer will still be draining
  557. int decodeStatus = GetXMABlocksAndSubmitToDecoder( true );
  558. if ( decodeStatus < 0 && decodeStatus != ERROR_IO_NO_XMA_DATA )
  559. {
  560. m_Error = decodeStatus;
  561. return -1;
  562. }
  563. m_bFinished = ( numNewSamples == 0 ) && ( decodeStatus == 0 ) && XMAPlaybackIsIdle( m_pXMAPlayback, 0 );
  564. // decoder was paused for access, let the decoder run
  565. hr = XMAPlaybackResumePlayback( m_pXMAPlayback );
  566. if ( FAILED( hr ) )
  567. {
  568. m_Error = ERROR_XMA_CANTRESUME;
  569. return -1;
  570. }
  571. return 1;
  572. }
  573. //-----------------------------------------------------------------------------
  574. // Drain the PCM staging buffer.
  575. // Copy samples (numSamplesToCopy && pData). Return actual copied.
  576. // Flush Samples (numSamplesToCopy && !pData). Return actual flushed.
  577. // Query available number of samples (!numSamplesToCopy && !pData). Returns available.
  578. //-----------------------------------------------------------------------------
  579. int CAudioMixerWaveXMA::GetPCMSamples( int numSamplesToCopy, char *pData )
  580. {
  581. int numReadySamples = m_pPCMSamples->GetReadAvailable()/( m_NumChannels*sizeof( short ) );
  582. // peel sequential samples from the stream's staging buffer
  583. int numCopiedSamples = 0;
  584. int numRequestedSamples = min( numSamplesToCopy, numReadySamples );
  585. if ( numRequestedSamples )
  586. {
  587. if ( pData )
  588. {
  589. // copy to caller
  590. m_pPCMSamples->Read( pData, numRequestedSamples*m_NumChannels*sizeof( short ) );
  591. pData += numRequestedSamples*m_NumChannels*sizeof( short );
  592. }
  593. else
  594. {
  595. // flush
  596. m_pPCMSamples->Advance( numRequestedSamples*m_NumChannels*sizeof( short ) );
  597. }
  598. numCopiedSamples += numRequestedSamples;
  599. }
  600. if ( snd_xma_spew_drain.GetBool() )
  601. {
  602. char nameBuf[MAX_PATH];
  603. char *pOperation = ( numSamplesToCopy && !pData ) ? "Flushed" : "Copied";
  604. Msg( "XMA: 0x%8.8x, SamplePosition: %d, Ready: %d, Requested: %d, %s: %d, Elapsed: %d ms '%s'\n",
  605. (unsigned int)this, m_SamplePosition, numReadySamples, numSamplesToCopy, pOperation, numCopiedSamples, Plat_MSTime() - m_LastDrainTime, m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  606. }
  607. m_LastDrainTime = Plat_MSTime();
  608. if ( numSamplesToCopy )
  609. {
  610. // could be actual flushed or actual copied
  611. return numCopiedSamples;
  612. }
  613. if ( !pData )
  614. {
  615. // satify query for available
  616. return numReadySamples;
  617. }
  618. return 0;
  619. }
  620. bool CAudioMixerWaveXMA::GetPlaybackInitialized()
  621. {
  622. if ( !m_pXMAPlayback )
  623. {
  624. // first time, finish setup
  625. int numBuffers;
  626. if ( m_bLooped || m_TotalBytes > XMA_INPUT_BUFFER_SIZE )
  627. {
  628. // data will cascade through multiple buffers
  629. numBuffers = 2;
  630. }
  631. else
  632. {
  633. // data can fit into a single buffer
  634. numBuffers = 1;
  635. }
  636. // xma data must be decoded from a hw friendly buffer
  637. // pool should have buffers available
  638. if ( g_XMAMemoryPool.BytesAllocated() != numBuffers * g_XMAMemoryPool.ChunkSize() )
  639. {
  640. XMA_PLAYBACK_INIT xmaPlaybackInit = { 0 };
  641. xmaPlaybackInit.sampleRate = m_SampleRate;
  642. xmaPlaybackInit.channelCount = m_NumChannels;
  643. xmaPlaybackInit.subframesToDecode = 4;
  644. xmaPlaybackInit.outputBufferSizeInSamples = ( m_NumChannels == 2 ) ? XMA_STEREO_OUTPUT_BUFFER_SAMPLES : XMA_MONO_OUTPUT_BUFFER_SAMPLES;
  645. XMAPlaybackCreate( 1, &xmaPlaybackInit, 0, &m_pXMAPlayback );
  646. if ( !m_pXMAPlayback )
  647. {
  648. m_Error = ERROR_XMA_NOPLAYBACKOBJECT;
  649. Assert( 0 );
  650. return false;
  651. }
  652. for ( int i = 0; i < numBuffers; i++ )
  653. {
  654. m_pXMABuffers[i] = (byte*)g_XMAMemoryPool.Alloc();
  655. }
  656. int stagingSize = PCM_STAGING_BUFFER_TIME * m_SampleRate * m_NumChannels * sizeof( short ) * 0.001f;
  657. m_pPCMSamples = AllocateCircularBuffer( AlignValue( stagingSize, 4 ) );
  658. }
  659. else
  660. {
  661. // too many sounds playing, no xma buffers free
  662. m_Error = ERROR_OUT_OF_MEMORY;
  663. Assert( 0 );
  664. return false;
  665. }
  666. }
  667. return true;
  668. }
  669. //-----------------------------------------------------------------------------
  670. // Stall mixing until initial buffer of decoded samples are available.
  671. //-----------------------------------------------------------------------------
  672. bool CAudioMixerWaveXMA::IsReadyToMix()
  673. {
  674. // XMA mixing cannot be driven from the main thread
  675. Assert( ThreadInMainThread() == false );
  676. if ( m_Error )
  677. {
  678. // error has been set
  679. // let mixer try to get unavailable samples, which causes the real abort
  680. return true;
  681. }
  682. if ( m_bStartedMixing )
  683. {
  684. // decoding process has started
  685. return true;
  686. }
  687. if ( GetPlaybackInitialized() == false )
  688. {
  689. // An error has been set.
  690. return true;
  691. }
  692. // waiting for samples
  693. // allow decoder to work without being polled (lock causes a decoding stall)
  694. if ( Plat_MSTime() - m_LastPollTime <= MIX_DECODER_POLLING_LATENCY )
  695. {
  696. return false;
  697. }
  698. // must have buffers in flight before mixing can begin
  699. if ( m_DataOffset == m_LastDataOffset )
  700. {
  701. // keep trying to get data, async i/o has some allowable latency
  702. int decodeStatus = GetXMABlocksAndSubmitToDecoder( false );
  703. if ( decodeStatus < 0 && decodeStatus != ERROR_IO_NO_XMA_DATA )
  704. {
  705. m_Error = decodeStatus;
  706. return true;
  707. }
  708. else if ( !decodeStatus || decodeStatus == ERROR_IO_NO_XMA_DATA )
  709. {
  710. // async streaming latency could be to blame, check watchdog
  711. if ( m_LastSuccessfulStreamTime != 0 )
  712. {
  713. if ( Plat_MSTime() - m_LastSuccessfulStreamTime >= MIX_IO_DATA_TIMEOUT )
  714. {
  715. m_Error = ERROR_IO_DATA_TIMEOUT;
  716. }
  717. }
  718. return false;
  719. }
  720. }
  721. // get the available samples ready for immediate mixing
  722. if ( ServiceXMADecoder( true ) < 0 )
  723. {
  724. return true;
  725. }
  726. // can't mix until we have a minimum threshold of data or the decoder is finished
  727. int minSamplesNeeded = m_bFinished ? 0 : MIN_READYTOMIX * m_SampleRate;
  728. int numReadySamples = GetPCMSamples( 0, NULL );
  729. if ( numReadySamples >= minSamplesNeeded )
  730. {
  731. // decoder has samples ready for draining
  732. m_bStartedMixing = true;
  733. int nTimeSinceStart = Plat_MSTime() - m_StartTime;
  734. bool bDisplayStartupLatencyWarning = snd_xma_spew_startup.GetBool();
  735. char nameBuf[MAX_PATH];
  736. m_pData->Source().GetFileName( nameBuf, sizeof(nameBuf) );
  737. if ( nTimeSinceStart > snd_async_stream_spew_delayed_start_time.GetInt() )
  738. {
  739. const char * pFilter = snd_async_stream_spew_delayed_start_filter.GetString();
  740. if ( *pFilter != '\0' )
  741. {
  742. bDisplayStartupLatencyWarning |= ( V_stristr( nameBuf, pFilter ) != NULL );
  743. }
  744. else
  745. {
  746. bDisplayStartupLatencyWarning = true;
  747. }
  748. }
  749. if ( bDisplayStartupLatencyWarning )
  750. {
  751. Warning( "XMA: Startup Latency: %d ms, Samples Ready: %d, '%s'\n", nTimeSinceStart, numReadySamples, nameBuf );
  752. }
  753. if ( m_bUpdateDelayForChoreo && snd_delay_for_choreo_enabled.GetBool() )
  754. {
  755. // We are playing a VO, update the choreo system accordingly for any startup latency. That way future VO will be pushed back by the same amount, so cut off will not occur.
  756. float fNewValue = ( ( float )( nTimeSinceStart ) / 1000.0f );
  757. fNewValue -= snd_mixahead.GetFloat(); // Remove the mix-ahead latency so it does not accumulate over time.
  758. if ( fNewValue > 0.0f )
  759. {
  760. g_fDelayForChoreo += fNewValue; // And we accumulate the error over time.
  761. // If the 1st sound sound is late 1 second, we are going to play the 2nd sound with 1 second delay.
  762. // However, if the 2nd sound is 2 seconds late, then the 3rd sound will have to be played with a delay of 3 seconds (or cut the 2nd delay too early).
  763. // When choreo does not play sounds for a while (currently 0.5 seconds), this counter will be reset to zero.
  764. if ( bDisplayStartupLatencyWarning )
  765. {
  766. Msg( "XMA: Updated IO latency compensation for VCD to %f seconds, due to startup time for sound '%s' taking %d ms.\n", g_fDelayForChoreo, nameBuf, nTimeSinceStart );
  767. }
  768. }
  769. Assert( g_nDelayForChoreoNumberOfSoundsPlaying >= 1 ); // Let's make sure the counter is not zero, as it will prevent the reset of the latency.
  770. }
  771. return true;
  772. }
  773. if ( m_LastDecodingStartTime != 0 )
  774. {
  775. if ( Plat_MSTime() - m_LastDecodingStartTime >= MIX_DECODER_TIMEOUT )
  776. {
  777. m_Error = ERROR_DECODER_TIMEOUT;
  778. }
  779. }
  780. // on startup error, let mixer start and get unavailable samples, and abort
  781. // otherwise hold off mixing until samples arrive
  782. return ( m_Error != 0 );
  783. }
  784. //-----------------------------------------------------------------------------
  785. // Returns true to mix, false to stop mixer completely. Called after
  786. // mixer requests samples.
  787. //-----------------------------------------------------------------------------
  788. bool CAudioMixerWaveXMA::ShouldContinueMixing()
  789. {
  790. if ( m_Error && snd_xma_spew_warnings.GetBool() )
  791. {
  792. char nameBuf[MAX_PATH];
  793. const char *pErrorString;
  794. if ( m_Error < 0 && -m_Error < ARRAYSIZE( g_XMAErrorStrings ) )
  795. {
  796. pErrorString = g_XMAErrorStrings[-m_Error];
  797. }
  798. else
  799. {
  800. pErrorString = g_XMAErrorStrings[0];
  801. }
  802. Warning( "XMA: 0x%8.8x, Mixer Aborted: %s, SamplePosition: %d/%d, DataOffset: %d/%d, '%s'\n",
  803. (unsigned int)this, pErrorString, m_SamplePosition, m_SampleCount, m_DataOffset, m_TotalBytes, m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  804. }
  805. // an error condition is fatal to mixer
  806. return ( m_Error == 0 && BaseClass::ShouldContinueMixing() );
  807. }
  808. //-----------------------------------------------------------------------------
  809. // Read existing buffer or decompress a new block when necessary.
  810. // If no samples can be fetched, returns 0, which hints the mixer to a pending shutdown state.
  811. // This routines operates in large buffer quantums, and nothing smaller.
  812. // XMA decode performance severly degrades if the lock is too frequent.
  813. //-----------------------------------------------------------------------------
  814. int CAudioMixerWaveXMA::GetOutputData( void **pData, int numSamplesToCopy, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
  815. {
  816. if ( m_Error )
  817. {
  818. // mixer will eventually shutdown
  819. return 0;
  820. }
  821. // This function can get called in some cases directly when the mixing did not start (when there is a delay but no seek table in the sound).
  822. // XMA mixing cannot be driven from the main thread
  823. Assert( ThreadInMainThread() == false );
  824. // needs to be clocked at regular intervals
  825. if ( ServiceXMADecoder( false ) < 0 )
  826. {
  827. return 0;
  828. }
  829. // loopback may require flushing some decoded samples
  830. int numRequestedSamples = numSamplesToCopy;
  831. int numDiscardSamples = UpdatePositionForLooping( &numRequestedSamples );
  832. if ( numDiscardSamples > 0 )
  833. {
  834. // loopback requires discarding samples to converge to expected looppoint
  835. numDiscardSamples -= GetPCMSamples( numDiscardSamples, NULL );
  836. if ( numDiscardSamples != 0 )
  837. {
  838. // not enough decoded data ready to flush
  839. // must flush these samples to achieve looping
  840. if ( snd_xma_recover_from_exhausted_stream.GetBool() == false )
  841. {
  842. m_Error = ERROR_XMA_NO_PCM_DATA;
  843. return 0;
  844. }
  845. // If we try to recover, the sound is not going to loop properly here...
  846. }
  847. }
  848. // can only drain as much as can be copied to caller
  849. int numMaxSamples = AUDIOSOURCE_COPYBUF_SIZE/( m_NumChannels * sizeof( short ) );
  850. numRequestedSamples = min( numRequestedSamples, numMaxSamples );
  851. int numCopiedSamples = GetPCMSamples( numRequestedSamples, copyBuf );
  852. if ( numCopiedSamples )
  853. {
  854. CAudioMixerWave::m_sample_max_loaded += numCopiedSamples;
  855. CAudioMixerWave::m_sample_loaded_index += numCopiedSamples;
  856. // advance position by valid samples
  857. m_SamplePosition += numCopiedSamples;
  858. *pData = (void*)copyBuf;
  859. #ifdef DEBUG_XMA
  860. if ( snd_xma_record.GetBool() )
  861. {
  862. WaveAppendTmpFile( "debug.wav", copyBuf, 16, numCopiedSamples * m_NumChannels );
  863. WaveFixupTmpFile( "debug.wav" );
  864. }
  865. #endif
  866. // We copy the last sample in case we have to recover later of an exhausted streamer
  867. V_memcpy( &m_LastSample, &copyBuf[ ( numCopiedSamples - 1) * m_NumChannels * sizeof(short) ], m_NumChannels * sizeof(short) );
  868. }
  869. else
  870. {
  871. // no samples copied
  872. if ( !m_bFinished && numRequestedSamples )
  873. {
  874. // XMA latency error occurs when decoder not finished (not at EOF) and caller wanted samples but can't get any
  875. if ( snd_xma_recover_from_exhausted_stream.GetBool() )
  876. {
  877. // Try to recover by using the last sample (to reduce potential pop).
  878. numCopiedSamples = numRequestedSamples;
  879. // Code not optimized, but hopefully is not needed in normal cases.
  880. for ( int i = 0 ; i < numCopiedSamples ; ++i )
  881. {
  882. V_memcpy( &copyBuf[ i * m_NumChannels * sizeof(short) ], &m_LastSample, m_NumChannels * sizeof(short) );
  883. }
  884. *pData = (void*)copyBuf;
  885. // Update some information used to calculate how much sound has been updated.
  886. // When there are some constant big latencies (testing extreme cases, like 500 ms), failure to update these will
  887. // create snowballing effect and force update of more and more samples (even if they are actually discarded at the end).
  888. // A visible consequence will be major slow down of the game, getting worse over time until the sound is finished / timed out.
  889. CAudioMixerWave::m_sample_max_loaded += numCopiedSamples;
  890. CAudioMixerWave::m_sample_loaded_index += numCopiedSamples;
  891. if ( snd_async_stream_spew_exhausted_buffer.GetBool() && ( g_pQueuedLoader->IsMapLoading() == false ) )
  892. {
  893. static uint sOldTime = 0;
  894. uint nCurrentTime = Plat_MSTime();
  895. if ( nCurrentTime >= sOldTime + snd_async_stream_spew_exhausted_buffer_time.GetInt() )
  896. {
  897. char nameBuf[MAX_PATH];
  898. Warning( "XMA: The stream buffer is exhausted for sound '%s'. Except after loading, fill a bug to have the number of sounds played reduced.\n", m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  899. sOldTime = nCurrentTime;
  900. }
  901. }
  902. }
  903. else
  904. {
  905. // We do not want to recover from the late streamer, the sound is going to stop (very noticeable for music and background ambiance sound).
  906. m_Error = ERROR_XMA_NO_PCM_DATA;
  907. if ( snd_xma_spew_warnings.GetInt() )
  908. {
  909. char nameBuf[MAX_PATH];
  910. Warning( "XMA: 0x%8.8x, No Decoded Data Ready: %d samples needed, '%s'\n", (unsigned int)this, numSamplesToCopy, m_pData->Source().GetFileName(nameBuf, sizeof(nameBuf)) );
  911. }
  912. }
  913. }
  914. }
  915. return numCopiedSamples;
  916. }
  917. bool CAudioMixerWaveXMA::IsSetSampleStartSupported() const
  918. {
  919. return false;
  920. }
  921. //-----------------------------------------------------------------------------
  922. // Purpose: Seek to a new position in the file
  923. // NOTE: In most cases, only call this once, and call it before playing
  924. // any data.
  925. // Input : newPosition - new position in the sample clocks of this sample
  926. //-----------------------------------------------------------------------------
  927. void CAudioMixerWaveXMA::SetSampleStart( int newPosition )
  928. {
  929. // Implementation not supported
  930. Assert( 0 );
  931. }
  932. int CAudioMixerWaveXMA::GetPositionForSave()
  933. {
  934. if ( m_bLooped )
  935. {
  936. // A looped sample cannot be saved/restored because the decoded sample position,
  937. // which is needed for loop calc, cannot ever be correctly restored without
  938. // the XMA seek table.
  939. return 0;
  940. }
  941. // This is silly and totally wrong, but doing it anyways.
  942. // The correct thing was to have the XMA seek table and use
  943. // that to determine the correct packet. This is just a hopeful
  944. // nearby approximation. Music did not have the seek table at
  945. // the time of this code. The Seek table was added for vo
  946. // restoration later.
  947. return m_LastDataOffset;
  948. }
  949. void CAudioMixerWaveXMA::SetPositionFromSaved( int savedPosition )
  950. {
  951. // Not used here. The Mixer creation will be given the initial startup offset.
  952. }
  953. //-----------------------------------------------------------------------------
  954. // Purpose: Abstract factory function for XMA mixers
  955. //-----------------------------------------------------------------------------
  956. CAudioMixer *CreateXMAMixer( IWaveData *data, int initialStreamPosition, int skipInitialSamples, bool bUpdateDelayForChoreo )
  957. {
  958. return new CAudioMixerWaveXMA( data, initialStreamPosition, skipInitialSamples, bUpdateDelayForChoreo );
  959. }