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.

4155 lines
124 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //===========================================================================//
  8. #include "audio_pch.h"
  9. #include "utllinkedlist.h"
  10. #include "utldict.h"
  11. #include "filesystem/IQueuedLoader.h"
  12. #include "cdll_int.h"
  13. #include "mempool.h"
  14. #include "memstack.h"
  15. #include "ienginetoolinternal.h"
  16. #include "vstdlib/jobthread.h"
  17. #include "tier3/tier3.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. extern IVEngineClient *engineClient;
  21. extern IFileSystem *g_pFileSystem;
  22. extern double realtime;
  23. // console streaming buffer implementation, appropriate for high latency and low memory
  24. // shift this many buffers through the wave
  25. // We need to be careful with these settings as depending of the values, this may reduce the number we can play simultaneously.
  26. // If per sound, 2 buffers of 32 Kb are allocated, then we can have 64 different sounds simultaneously.
  27. // If per sound, 4 buffers of 64 Kb are allocated, then we can have 16 different sounds simultaneously.
  28. // For non-looped sounds, only 2 buffers are needed for streaming.
  29. // For looped sounds, we may need up to 4 buffers. (assuming that the last sound frame is at the beginning of the buffer, and the first loop sound frame is at the end of a buffer).
  30. #if IsX360()
  31. # define STREAM_BUFFER_COUNT 2
  32. #if !defined( CSTRIKE15 )
  33. # define STREAM_BUFFER_DATASIZE ( 2 * XBOX_DVD_ECC_SIZE ) // On X360 DVD, to reduce the number of seeks we actually read 64 Kb at a time instead of 32 Kb
  34. // This increases the memory in term of temp buffer, it reduces the number of different sounds stored in the cache
  35. #else
  36. # define STREAM_BUFFER_DATASIZE ( 1 * XBOX_DVD_ECC_SIZE ) // CSTRIKE15 is not shipping on a DVD
  37. #endif
  38. // XBox has enough memory, and needs to lighten the i/o for the DVD (not using the HDD on this product)
  39. // NOTE: in the case of asian languages, we need to spend an extra 7MB loading a large font file - this is taken from the audio cache.
  40. # define CONSOLE_STREAMING_AUDIO_PORTAL2 ( 20 * 1024 * 1024 )
  41. # define ASIAN_FONT_MEMORY_USAGE ( 7 * 1024 * 1024 )
  42. #elif IsPS3()
  43. # define STREAM_BUFFER_COUNT 4 // Don't use memory for 4 buffers but 3, as in most case it is enough. In rare cases, we may have a latency issue.
  44. // However due to the latency introduced by the saving mechanism (also using the HHD), let's use a bit more for looped sounds.
  45. // Note that looped sounds on PS3 are not MP3 encoded but WAV encoded, so they are much bigger, we'll use 4 instead of 3 due to the save latency.
  46. # define STREAM_BUFFER_DATASIZE ( 32 * 1024 ) // Blu-Ray has a block size of 64 Kb. We could reduce this to 32 Kb to allow more different sounds, but this would make the loading less optimal.
  47. // We actually reduced it to 32 Kb (like for X360) as we are now using FIOS for caching on the HDD. 32 Kb is a good size for the HDD.
  48. // We should be good in term of memory on PS3 now.
  49. # define CONSOLE_STREAMING_AUDIO_PORTAL2 ( 4 * 1024 * 1024 )
  50. # define ASIAN_FONT_MEMORY_USAGE ( 0 )
  51. #else
  52. # define STREAM_BUFFER_COUNT 4
  53. # define STREAM_BUFFER_DATASIZE ( 64 * 1024 )
  54. # define CONSOLE_STREAMING_AUDIO_PORTAL2 ( 4 * 1024 * 1024 )
  55. # define ASIAN_FONT_MEMORY_USAGE ( 0 )
  56. #endif
  57. // PC single buffering implementation
  58. // UNDONE: Allocate this in cache instead?
  59. #define SINGLE_BUFFER_SIZE ( 16 * 1024 )
  60. // console streaming pool
  61. #define CONSOLE_STREAMING_AUDIO_DEFAULT ( 4 * 1024 * 1024 )
  62. #define CONSOLE_STREAMING_AUDIO_TF ( 12 * 1024 * 1024 )
  63. #define CONSOLE_STREAMING_AUDIO_LEFT4DEAD ( 10 * 1024 * 1024 )
  64. #define CONSOLE_STREAMING_AUDIO_LEFT4DEAD_DVD ( 12 * 1024 * 1024 )
  65. #define CONSOLE_STREAMING_AUDIO_CSTRIKE15 ( 9 * 1024 * 1024 )
  66. // console static pool
  67. #define CONSOLE_STATIC_AUDIO_DEFAULT ( 8.7 * 1024 * 1024 )
  68. #define CONSOLE_STATIC_AUDIO_PORTAL2 ( 2 * 1024 * 1024 )
  69. #define CONSOLE_STATIC_AUDIO_CSTRIKE15 ( 0.3 * 1024 * 1024 )
  70. #define DEFAULT_WAV_MEMORY_CACHE ( 20 * 1024 * 1024 )
  71. ConVar snd_async_spew_blocking( "snd_async_spew_blocking", "0", 0, "Spew message to console any time async sound loading blocks on file i/o." );
  72. ConVar snd_async_fullyasync( "snd_async_fullyasync", "1", 0, "All playback is fully async (sound doesn't play until data arrives)." );
  73. ConVar snd_async_stream_spew( "snd_async_stream_spew", "0", 0, "Spew streaming info ( 0=Off, 1=streams, 2=buffers" );
  74. ConVar snd_async_stream_fail( "snd_async_stream_fail", "0", 0, "Spew stream pool failures." );
  75. ConVar snd_async_stream_purges( "snd_async_stream_purges", "0", 0, "Spew stream pool purges." );
  76. ConVar snd_async_stream_static_alloc( "snd_async_stream_static_alloc", "0", 0, "If 1, spews allocations on the static alloc pool. Set to 0 for no spew." );
  77. ConVar snd_async_stream_recover_from_exhausted_stream( "snd_async_stream_recover_from_exhausted_stream", "1", 0, "If 1, recovers when the stream is exhausted when playing PCM sounds (prevents music or ambiance sounds to stop if too many sounds are played). Set to 0, to stop the sound otherwise." );
  78. ConVar snd_async_stream_spew_exhausted_buffer( "snd_async_stream_spew_exhausted_buffer", IsCert() ? "0" : "1", 0, "If 1, spews warnings when the buffer is exhausted (recommended). Set to 0 for no spew (for debugging purpose only)." );
  79. ConVar snd_async_stream_spew_exhausted_buffer_time( "snd_async_stream_spew_exhausted_buffer_time", "1000", 0, "Number of milliseconds between each exhausted buffer spew." );
  80. ConVar snd_async_stream_spew_delayed_start_time( "snd_async_stream_spew_delayed_start_time", "500", 0, "Spew any asynchronous sound that starts with more than N milliseconds delay. By default spew when there is more than 500 ms delay." );
  81. ConVar snd_async_stream_spew_delayed_start_filter( "snd_async_stream_spew_delayed_start_filter", "vo", 0, "Filter used to spew sounds that starts late. Use an empty string \"\" to display all sounds. By default only the VO are displayed.");
  82. extern ConVar snd_report_loop_sound;
  83. #define SndAlignReads() 1
  84. void MaybeReportMissingWav( char const *wav );
  85. // xbox pools its transient fixed sized streaming buffers
  86. static CUtlMemoryPool *g_pAudioStreamPool;
  87. static CMemoryStack *g_pAudioStaticPool;
  88. //-----------------------------------------------------------------------------
  89. // Purpose:
  90. //-----------------------------------------------------------------------------
  91. struct asyncwaveparams_t
  92. {
  93. asyncwaveparams_t() :
  94. bPrefetch( false ),
  95. bCanBeQueued( false ),
  96. bIsTransient( false ),
  97. bIsStaticPooled( false )
  98. {}
  99. FileNameHandle_t hFilename; // handle to sound item name (i.e. not with sound\ prefix)
  100. int datasize;
  101. int seekpos;
  102. int alignment;
  103. unsigned int bPrefetch : 1;
  104. unsigned int bCanBeQueued : 1;
  105. unsigned int bIsTransient : 1;
  106. unsigned int bIsStaticPooled : 1;
  107. };
  108. //-----------------------------------------------------------------------------
  109. // Purpose: Builds a cache of the data bytes for a specific .wav file
  110. //-----------------------------------------------------------------------------
  111. class CAsyncWaveData
  112. {
  113. public:
  114. explicit CAsyncWaveData();
  115. void DestroyResource();
  116. CAsyncWaveData *GetData();
  117. unsigned int Size();
  118. static void AsyncCallback( const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err );
  119. static void QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError );
  120. static CAsyncWaveData *CreateResource( const asyncwaveparams_t &params );
  121. static unsigned int EstimatedSize( const asyncwaveparams_t &params );
  122. void OnAsyncCompleted( const FileAsyncRequest_t* asyncFilePtr, int numReadBytes, FSAsyncStatus_t err );
  123. bool BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count );
  124. bool BlockingGetDataPointer( void **ppData );
  125. void SetAsyncPriority( int priority );
  126. void StartAsyncLoading( const asyncwaveparams_t& params );
  127. bool GetPostProcessed();
  128. void SetPostProcessed( bool proc );
  129. bool IsCurrentlyLoading();
  130. char const *GetFileName();
  131. // Data
  132. public:
  133. int m_nDataSize; // bytes requested
  134. int m_nReadSize; // bytes actually read
  135. void *m_pvData; // target buffer
  136. void *m_pAlloc; // memory of buffer (base may not match)
  137. FileAsyncRequest_t m_async;
  138. FSAsyncControl_t m_hAsyncControl;
  139. float m_start; // time at request invocation
  140. float m_arrival; // time at data arrival
  141. FileNameHandle_t m_hFileNameHandle;
  142. int m_nBufferBytes; // size of any pre-allocated target buffer
  143. BufferHandle_t m_hBuffer; // used to dequeue the buffer after lru
  144. unsigned int m_bLoaded : 1;
  145. unsigned int m_bMissing : 1;
  146. unsigned int m_bPostProcessed : 1;
  147. unsigned int m_bIsTransient : 1;
  148. unsigned int m_bIsStaticPooled : 1;
  149. };
  150. enum WaveCacheAddFlags_t
  151. {
  152. WCAF_LOCK = ( 1 << 0 ),
  153. WCAF_DEFAULT = 0,
  154. };
  155. struct WaveCacheStatus_t
  156. {
  157. int nBytes;
  158. };
  159. struct WaveCacheLimits_t
  160. {
  161. int nMaxBytes;
  162. };
  163. struct WaveCache_t
  164. {
  165. CAsyncWaveData *m_pWaveData;
  166. unsigned int m_nDataSize;
  167. unsigned int m_nAgeStamp;
  168. unsigned int m_nLockCount;
  169. int m_hUnlock;
  170. };
  171. // Replacement for wavedata hosted by datacache which had some costlier aspects. When small purges needed
  172. // to occur for the console streaming solution, the datacache's global lru list would need to be iterated.
  173. // This replacement class attempts to provide the minimal necessary thread safe behavior to provide just
  174. // the service that the wavedatacache uses. The lru list (only unlocked resources) is maintained by an age
  175. // count, not link order.
  176. class CWaveCache
  177. {
  178. public:
  179. CWaveCache()
  180. {
  181. m_nCurrentMemorySize = 0;
  182. m_nMaxMemorySize = UINT_MAX;
  183. m_nAgeStamp = 1;
  184. // reserve the 0 entry, want to use 0 as invalid to remain compliant to datacache code
  185. // which used 0 as an invalid handle
  186. WaveCacheHandle_t hData = m_HandleTable.AddHandle();
  187. Assert( hData == 0 );
  188. hData;
  189. }
  190. void Init( unsigned int nMemorySize )
  191. {
  192. #if defined(LINUX) && !defined(DEDICATED)
  193. m_nMaxMemorySize = -1;
  194. #else
  195. // can be -1 (i.e. unlimited, no implicit purge occurs via create)
  196. m_nMaxMemorySize = nMemorySize;
  197. #endif
  198. }
  199. void Shutdown()
  200. {
  201. }
  202. CAsyncWaveData *CacheGet( WaveCacheHandle_t hData )
  203. {
  204. if ( hData == INVALID_WAVECACHE_HANDLE )
  205. {
  206. // must trap, the 0 entry is a valid index
  207. return NULL;
  208. }
  209. AUTO_LOCK_FM( m_WaveCacheMutex );
  210. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  211. if ( !pCacheData )
  212. return NULL;
  213. pCacheData->m_nAgeStamp = m_nAgeStamp++;
  214. return pCacheData->m_pWaveData;
  215. }
  216. CAsyncWaveData *CacheGetNoTouch( WaveCacheHandle_t hData )
  217. {
  218. if ( hData == INVALID_WAVECACHE_HANDLE )
  219. {
  220. // must trap, the 0 entry is a valid index
  221. return NULL;
  222. }
  223. AUTO_LOCK_FM( m_WaveCacheMutex );
  224. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  225. if ( !pCacheData )
  226. return NULL;
  227. return pCacheData->m_pWaveData;
  228. }
  229. CAsyncWaveData *CacheLock( WaveCacheHandle_t hData )
  230. {
  231. if ( hData == INVALID_WAVECACHE_HANDLE )
  232. {
  233. // must trap, the 0 entry is a valid index
  234. return NULL;
  235. }
  236. AUTO_LOCK_FM( m_WaveCacheMutex );
  237. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  238. if ( !pCacheData )
  239. return NULL;
  240. pCacheData->m_nAgeStamp = m_nAgeStamp++;
  241. pCacheData->m_nLockCount++;
  242. if ( pCacheData->m_nLockCount == 1 && pCacheData->m_hUnlock != m_UnlockedList.InvalidIndex() )
  243. {
  244. // remove from unlocked list
  245. m_UnlockedList.Remove( pCacheData->m_hUnlock );
  246. pCacheData->m_hUnlock = m_UnlockedList.InvalidIndex();
  247. }
  248. return pCacheData->m_pWaveData;
  249. }
  250. int CacheUnlock( WaveCacheHandle_t hData )
  251. {
  252. if ( hData == INVALID_WAVECACHE_HANDLE )
  253. {
  254. // must trap, the 0 entry is a valid index
  255. return 0;
  256. }
  257. AUTO_LOCK_FM( m_WaveCacheMutex );
  258. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  259. if ( !pCacheData )
  260. return 0;
  261. if ( pCacheData->m_nLockCount > 0 )
  262. {
  263. pCacheData->m_nLockCount--;
  264. if ( pCacheData->m_nLockCount == 0 )
  265. {
  266. // add to unlocked list
  267. pCacheData->m_hUnlock = m_UnlockedList.AddToTail( hData );
  268. }
  269. }
  270. return pCacheData->m_nLockCount;
  271. }
  272. WaveCacheHandle_t CacheCreate( asyncwaveparams_t params, WaveCacheAddFlags_t flags = WCAF_DEFAULT )
  273. {
  274. AUTO_LOCK_FM( m_WaveCacheMutex );
  275. if ( m_nMaxMemorySize != UINT_MAX )
  276. {
  277. unsigned int nBytesToPurge = CAsyncWaveData::EstimatedSize( params );
  278. if ( m_nCurrentMemorySize + nBytesToPurge > m_nMaxMemorySize )
  279. {
  280. Purge( nBytesToPurge );
  281. if ( m_nCurrentMemorySize + nBytesToPurge > m_nMaxMemorySize )
  282. {
  283. // not enough memory
  284. return INVALID_WAVECACHE_HANDLE;
  285. }
  286. }
  287. }
  288. CAsyncWaveData *pWaveData = CAsyncWaveData::CreateResource( params );
  289. if ( !pWaveData )
  290. {
  291. return INVALID_WAVECACHE_HANDLE;
  292. }
  293. WaveCache_t *pCacheData = new WaveCache_t;
  294. pCacheData->m_pWaveData = pWaveData;
  295. pCacheData->m_nDataSize = pWaveData->Size();
  296. pCacheData->m_nAgeStamp = m_nAgeStamp++;
  297. m_nCurrentMemorySize += pCacheData->m_nDataSize;
  298. WaveCacheHandle_t hData = m_HandleTable.AddHandle();
  299. if ( flags & WCAF_LOCK )
  300. {
  301. pCacheData->m_nLockCount = 1;
  302. pCacheData->m_hUnlock = m_UnlockedList.InvalidIndex();
  303. }
  304. else
  305. {
  306. pCacheData->m_nLockCount = 0;
  307. pCacheData->m_hUnlock = m_UnlockedList.AddToTail( hData );
  308. }
  309. m_HandleTable.SetHandle( hData, pCacheData );
  310. return hData;
  311. }
  312. bool CacheRemove( WaveCacheHandle_t hData )
  313. {
  314. if ( hData == INVALID_WAVECACHE_HANDLE )
  315. {
  316. // must trap, the 0 entry is a valid index
  317. return false;
  318. }
  319. AUTO_LOCK_FM( m_WaveCacheMutex );
  320. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  321. if ( !pCacheData || pCacheData->m_nLockCount != 0 )
  322. return false;
  323. if ( snd_async_stream_purges.GetBool() )
  324. {
  325. Msg( "CacheRemove: Age:%d %s\n", pCacheData->m_nAgeStamp, pCacheData->m_pWaveData->GetFileName() );
  326. }
  327. pCacheData->m_pWaveData->DestroyResource();
  328. m_nCurrentMemorySize -= pCacheData->m_nDataSize;
  329. if ( pCacheData->m_hUnlock != m_UnlockedList.InvalidIndex() )
  330. {
  331. m_UnlockedList.Remove( pCacheData->m_hUnlock );
  332. }
  333. m_HandleTable.RemoveHandle( hData );
  334. delete pCacheData;
  335. return true;
  336. }
  337. void BreakLock( WaveCacheHandle_t hData )
  338. {
  339. if ( hData == INVALID_WAVECACHE_HANDLE )
  340. {
  341. // must trap, the 0 entry is a valid index
  342. return;
  343. }
  344. AUTO_LOCK_FM( m_WaveCacheMutex );
  345. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  346. if ( !pCacheData )
  347. return;
  348. if ( pCacheData->m_nLockCount != 0 )
  349. {
  350. pCacheData->m_nLockCount = 0;
  351. // add to unlocked list
  352. pCacheData->m_hUnlock = m_UnlockedList.AddToTail( hData );
  353. }
  354. }
  355. void Age( WaveCacheHandle_t hData )
  356. {
  357. if ( hData == INVALID_WAVECACHE_HANDLE )
  358. {
  359. // must trap, the 0 entry is a valid index
  360. return;
  361. }
  362. AUTO_LOCK_FM( m_WaveCacheMutex );
  363. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  364. if ( !pCacheData )
  365. return;
  366. // the oldest sounds are ones that have not been touched
  367. pCacheData->m_nAgeStamp = 0;
  368. }
  369. int GetLockCount( WaveCacheHandle_t hData )
  370. {
  371. if ( hData == INVALID_WAVECACHE_HANDLE )
  372. {
  373. // must trap, the 0 entry is a valid index
  374. return 0;
  375. }
  376. AUTO_LOCK_FM( m_WaveCacheMutex );
  377. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  378. if ( !pCacheData )
  379. return 0;
  380. return pCacheData->m_nLockCount;
  381. }
  382. unsigned int GetAgeStamp( WaveCacheHandle_t hData )
  383. {
  384. if ( hData == INVALID_WAVECACHE_HANDLE )
  385. {
  386. // must trap, the 0 entry is a valid index
  387. return 0;
  388. }
  389. AUTO_LOCK_FM( m_WaveCacheMutex );
  390. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  391. if ( !pCacheData )
  392. return 0;
  393. return pCacheData->m_nAgeStamp;
  394. }
  395. void Purge( unsigned int nBytesToPurge )
  396. {
  397. AUTO_LOCK_FM( m_WaveCacheMutex );
  398. // keep purging the oldest unlocked until desired memory is available
  399. // trying to keep this as cheap as possible, so no sorting, just an iteration to find oldest
  400. unsigned int nBytesPurged = 0;
  401. while ( nBytesPurged < nBytesToPurge )
  402. {
  403. unsigned int nCandidateSize = 0;
  404. unsigned int nCandidateAge = UINT_MAX;
  405. WaveCacheHandle_t hCandidate = INVALID_WAVECACHE_HANDLE;
  406. for ( int hUnlock = m_UnlockedList.Head(); hUnlock != m_UnlockedList.InvalidIndex(); hUnlock = m_UnlockedList.Next( hUnlock ) )
  407. {
  408. WaveCacheHandle_t hData = m_UnlockedList[hUnlock];
  409. WaveCache_t *pCacheData = m_HandleTable.GetHandle( hData );
  410. if ( pCacheData && pCacheData->m_nAgeStamp < nCandidateAge )
  411. {
  412. nCandidateAge = pCacheData->m_nAgeStamp;
  413. nCandidateSize = pCacheData->m_nDataSize;
  414. hCandidate = hData;
  415. }
  416. }
  417. if ( hCandidate != INVALID_WAVECACHE_HANDLE && CacheRemove( hCandidate ) )
  418. {
  419. nBytesPurged += nCandidateSize;
  420. }
  421. else
  422. {
  423. // there are no unlocked candidates
  424. // or the qualified remove failed
  425. break;
  426. }
  427. }
  428. }
  429. void Flush()
  430. {
  431. AUTO_LOCK_FM( m_WaveCacheMutex );
  432. CUtlVector< WaveCacheHandle_t > purgeList( 0, m_UnlockedList.Count() );
  433. for ( int hUnlock = m_UnlockedList.Head(); hUnlock != m_UnlockedList.InvalidIndex(); hUnlock = m_UnlockedList.Next( hUnlock ) )
  434. {
  435. purgeList.AddToTail( m_UnlockedList[hUnlock] );
  436. }
  437. // remove all unlocked resources
  438. for ( int i = 0; i < purgeList.Count(); i++ )
  439. {
  440. CacheRemove( purgeList[i] );
  441. }
  442. }
  443. void CacheLockMutex()
  444. {
  445. m_WaveCacheMutex.Lock();
  446. }
  447. void CacheUnlockMutex()
  448. {
  449. m_WaveCacheMutex.Unlock();
  450. }
  451. void GetStatus( WaveCacheStatus_t *pStatus, WaveCacheLimits_t *pLimits )
  452. {
  453. pStatus->nBytes = m_nCurrentMemorySize;
  454. pLimits->nMaxBytes = m_nMaxMemorySize;
  455. }
  456. private:
  457. unsigned int m_nCurrentMemorySize;
  458. unsigned int m_nMaxMemorySize;
  459. unsigned int m_nAgeStamp;
  460. CThreadFastMutex m_WaveCacheMutex;
  461. // 2048 simultaneous sound handles in the cache
  462. CUtlHandleTable< WaveCache_t, 11 > m_HandleTable;
  463. CUtlLinkedList< WaveCacheHandle_t > m_UnlockedList;
  464. };
  465. CWaveCache s_WaveCache;
  466. //-----------------------------------------------------------------------------
  467. // Purpose: C'tor
  468. //-----------------------------------------------------------------------------
  469. CAsyncWaveData::CAsyncWaveData() :
  470. m_nDataSize( 0 ),
  471. m_nReadSize( 0 ),
  472. m_pvData( 0 ),
  473. m_pAlloc( 0 ),
  474. m_hBuffer( INVALID_BUFFER_HANDLE ),
  475. m_nBufferBytes( 0 ),
  476. m_hAsyncControl( NULL ),
  477. m_bLoaded( false ),
  478. m_bMissing( false ),
  479. m_start( 0.0 ),
  480. m_arrival( 0.0 ),
  481. m_bPostProcessed( false ),
  482. m_bIsTransient( false ),
  483. m_bIsStaticPooled( false ),
  484. m_hFileNameHandle( 0 )
  485. {
  486. }
  487. //-----------------------------------------------------------------------------
  488. // Purpose: // APIS required by CDataLRU
  489. //-----------------------------------------------------------------------------
  490. void CAsyncWaveData::DestroyResource()
  491. {
  492. if ( IsPC() )
  493. {
  494. if ( m_hAsyncControl )
  495. {
  496. if ( !m_bLoaded && !m_bMissing )
  497. {
  498. // NOTE: We CANNOT call AsyncAbort since if the file is actually being read we'll end
  499. // up still getting a callback, but our this ptr (deleted below) will be feeefeee and we'll trash the heap
  500. // pretty bad. So we call AsyncFinish, which will do a blocking read and will definitely succeed
  501. // Block until we are finished
  502. g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
  503. }
  504. g_pFileSystem->AsyncRelease( m_hAsyncControl );
  505. m_hAsyncControl = NULL;
  506. }
  507. }
  508. if ( IsGameConsole() )
  509. {
  510. if ( m_hAsyncControl )
  511. {
  512. if ( !m_bLoaded && !m_bMissing )
  513. {
  514. // force an abort
  515. int errStatus = g_pFileSystem->AsyncAbort( m_hAsyncControl );
  516. if ( errStatus != FSASYNC_ERR_UNKNOWNID )
  517. {
  518. // must wait for abort to finish before deallocating data
  519. g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
  520. }
  521. }
  522. g_pFileSystem->AsyncRelease( m_hAsyncControl );
  523. m_hAsyncControl = NULL;
  524. }
  525. if ( m_hBuffer != INVALID_BUFFER_HANDLE )
  526. {
  527. // hint the manager that this tracked buffer is invalid
  528. wavedatacache->MarkBufferDiscarded( m_hBuffer );
  529. }
  530. }
  531. // delete buffers
  532. if ( IsPC() )
  533. {
  534. g_pFileSystem->FreeOptimalReadBuffer( m_pAlloc );
  535. }
  536. if ( IsGameConsole() )
  537. {
  538. if ( m_bIsTransient )
  539. {
  540. g_pAudioStreamPool->Free( m_pAlloc );
  541. }
  542. else if ( m_bIsStaticPooled )
  543. {
  544. // freed as part of pool purge
  545. }
  546. else
  547. {
  548. free( m_pAlloc );
  549. }
  550. }
  551. delete this;
  552. }
  553. //-----------------------------------------------------------------------------
  554. // Purpose:
  555. // Output : char const
  556. //-----------------------------------------------------------------------------
  557. char const *CAsyncWaveData::GetFileName()
  558. {
  559. static char sz[MAX_PATH];
  560. if ( m_hFileNameHandle )
  561. {
  562. if ( g_pFileSystem->String( m_hFileNameHandle, sz, sizeof( sz ) ) )
  563. {
  564. return sz;
  565. }
  566. }
  567. Assert( 0 );
  568. return "";
  569. }
  570. //-----------------------------------------------------------------------------
  571. // Purpose:
  572. // Output : CAsyncWaveData
  573. //-----------------------------------------------------------------------------
  574. CAsyncWaveData *CAsyncWaveData::GetData()
  575. {
  576. return this;
  577. }
  578. //-----------------------------------------------------------------------------
  579. // Purpose:
  580. // Output : unsigned int
  581. //-----------------------------------------------------------------------------
  582. unsigned int CAsyncWaveData::Size()
  583. {
  584. int size;
  585. if ( IsPC() )
  586. {
  587. size = sizeof( *this ) + m_nDataSize;
  588. }
  589. if ( IsGameConsole() )
  590. {
  591. // the data size is volatile and shrinks during streaming near end of file
  592. // report the real constant size of this object's allocation
  593. size = m_nBufferBytes;
  594. }
  595. return size;
  596. }
  597. //-----------------------------------------------------------------------------
  598. // Purpose: Static method for CDataLRU
  599. // Input : &params -
  600. // Output : CAsyncWaveData
  601. //-----------------------------------------------------------------------------
  602. CAsyncWaveData *CAsyncWaveData::CreateResource( const asyncwaveparams_t &params )
  603. {
  604. MEM_ALLOC_CREDIT_( "CAsyncWaveData::CreateResource" );
  605. CAsyncWaveData *pData = NULL;
  606. if ( IsGameConsole() )
  607. {
  608. // create buffers now for re-use during streaming process
  609. void *pBuffer = NULL;
  610. int bufferSize;
  611. bool bIsStaticPooled = params.bIsStaticPooled;
  612. if ( params.bIsTransient )
  613. {
  614. // streaming transient sounds pool their fixed size dynamic buffers to lighten fragmentation
  615. bufferSize = STREAM_BUFFER_DATASIZE;
  616. pBuffer = g_pAudioStreamPool->Alloc();
  617. if ( !pBuffer )
  618. {
  619. // pool is empty, purge required
  620. // failure case detected by create logic, will drive purge and retry
  621. return NULL;
  622. }
  623. }
  624. else
  625. {
  626. // non-streaming sounds have buffers that are only transient during map transitions
  627. bufferSize = AlignValue( params.datasize, params.alignment );
  628. if ( bIsStaticPooled )
  629. {
  630. // pool these sounds
  631. pBuffer = g_pAudioStaticPool->Alloc( bufferSize );
  632. if ( snd_async_stream_static_alloc.GetBool() )
  633. {
  634. Msg( "CAsyncWavDataCache: Static Pool: %.2f MB used of %.2f MB\n", g_pAudioStaticPool->GetUsed() / ( 1024.0f * 1024.0f ), g_pAudioStaticPool->GetSize() / ( 1024.0f * 1024.0f ) );
  635. }
  636. if ( !pBuffer )
  637. {
  638. Warning( "CAsyncWaveData:: Static Pool OVERFLOW, failing to standard heap!\n" );
  639. // flip and fail to other heap
  640. bIsStaticPooled = false;
  641. }
  642. }
  643. if ( !bIsStaticPooled )
  644. {
  645. // use the standard heap for non-streaming non-pooled buffers
  646. pBuffer = new byte[bufferSize];
  647. }
  648. }
  649. pData = new CAsyncWaveData;
  650. pData->m_nBufferBytes = bufferSize;
  651. pData->m_pAlloc = pData->m_pvData = pBuffer;
  652. pData->m_bIsTransient = params.bIsTransient;
  653. pData->m_bIsStaticPooled = bIsStaticPooled;
  654. }
  655. else
  656. {
  657. pData = new CAsyncWaveData;
  658. Assert( pData );
  659. }
  660. if ( pData )
  661. {
  662. pData->StartAsyncLoading( params );
  663. }
  664. return pData;
  665. }
  666. //-----------------------------------------------------------------------------
  667. // Purpose: Static method
  668. // Input : &params -
  669. // Output : static unsigned int
  670. //-----------------------------------------------------------------------------
  671. unsigned int CAsyncWaveData::EstimatedSize( const asyncwaveparams_t &params )
  672. {
  673. int size;
  674. if ( IsPC() )
  675. {
  676. size = sizeof( CAsyncWaveData ) + params.datasize;
  677. }
  678. if ( IsGameConsole() )
  679. {
  680. // the expected size of this object's allocations
  681. if ( params.bIsTransient )
  682. {
  683. size = STREAM_BUFFER_DATASIZE;
  684. }
  685. else
  686. {
  687. size = AlignValue( params.datasize, params.alignment );
  688. }
  689. }
  690. return size;
  691. }
  692. //-----------------------------------------------------------------------------
  693. // Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!!
  694. // Input : asyncFilePtr -
  695. // numReadBytes -
  696. // err -
  697. //-----------------------------------------------------------------------------
  698. void CAsyncWaveData::AsyncCallback(const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err )
  699. {
  700. CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( asyncRequest.pContext );
  701. Assert( pObject );
  702. if ( pObject )
  703. {
  704. pObject->OnAsyncCompleted( &asyncRequest, numReadBytes, err );
  705. }
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!!
  709. //-----------------------------------------------------------------------------
  710. void CAsyncWaveData::QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError )
  711. {
  712. CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( pContext );
  713. Assert( pObject );
  714. pObject->OnAsyncCompleted( NULL, nSize, loaderError == LOADERERROR_NONE ? FSASYNC_OK : FSASYNC_ERR_FILEOPEN );
  715. }
  716. //-----------------------------------------------------------------------------
  717. // Purpose: NOTE: THIS IS CALLED FROM A THREAD SO YOU CAN'T CALL INTO ANYTHING NON-THREADSAFE
  718. // such as CUtlSymbolTable/CUtlDict (many of the CUtl* are non-thread safe)!!!
  719. // Input : asyncFilePtr -
  720. // numReadBytes -
  721. // err -
  722. //-----------------------------------------------------------------------------
  723. void CAsyncWaveData::OnAsyncCompleted( const FileAsyncRequest_t *asyncFilePtr, int numReadBytes, FSAsyncStatus_t err )
  724. {
  725. if ( IsPC() )
  726. {
  727. // Take hold of pointer (we can just use delete[] across .dlls because we are using a shared memory allocator...)
  728. if ( err == FSASYNC_OK || err == FSASYNC_ERR_READING )
  729. {
  730. m_arrival = ( float )Plat_FloatTime();
  731. // Take over ptr
  732. m_pAlloc = asyncFilePtr->pData;
  733. if ( SndAlignReads() )
  734. {
  735. m_async.nOffset = ( m_async.nBytes - m_nDataSize );
  736. m_async.nBytes -= m_async.nOffset;
  737. m_pvData = ((byte *)m_pAlloc) + m_async.nOffset;
  738. m_nReadSize = numReadBytes - m_async.nOffset;
  739. }
  740. else
  741. {
  742. m_pvData = m_pAlloc;
  743. m_nReadSize = numReadBytes;
  744. }
  745. // Needs to be post-processed
  746. m_bPostProcessed = false;
  747. // Finished loading
  748. m_bLoaded = true;
  749. }
  750. else if ( err == FSASYNC_ERR_FILEOPEN )
  751. {
  752. // SEE NOTE IN FUNCTION COMMENT ABOVE!!!
  753. // Tracker 22905, et al.
  754. // Because this api gets called from the other thread, don't spew warning here as it can
  755. // cause a crash in searching CUtlSymbolTables since they use a global var for a LessFunc context!!!
  756. m_bMissing = true;
  757. }
  758. }
  759. if ( IsGameConsole() )
  760. {
  761. m_arrival = (float)Plat_FloatTime();
  762. // possibly reading more than intended due to alignment restriction
  763. m_nReadSize = numReadBytes;
  764. if ( m_nReadSize > m_nDataSize )
  765. {
  766. // clamp to expected, extra data is unreliable
  767. m_nReadSize = m_nDataSize;
  768. }
  769. if ( err != FSASYNC_OK )
  770. {
  771. // track as any error
  772. m_bMissing = true;
  773. }
  774. if ( err != FSASYNC_ERR_FILEOPEN )
  775. {
  776. // some data got loaded
  777. m_bLoaded = true;
  778. }
  779. }
  780. }
  781. //-----------------------------------------------------------------------------
  782. // Purpose:
  783. // Input : *destbuffer -
  784. // destbufsize -
  785. // startoffset -
  786. // count -
  787. // Output : Returns true on success, false on failure.
  788. //-----------------------------------------------------------------------------
  789. bool CAsyncWaveData::BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count )
  790. {
  791. if ( !m_bLoaded )
  792. {
  793. Assert( m_hAsyncControl );
  794. // Force it to finish
  795. // It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal
  796. if ( snd_async_spew_blocking.GetBool() )
  797. {
  798. // Force it to finish
  799. float st = ( float )Plat_FloatTime();
  800. g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
  801. float ed = ( float )Plat_FloatTime();
  802. DevMsg( "%f BCD: Async I/O Force %s (%8.2f msec / %8.2f msec total)\n", realtime, GetFileName(), 1000.0f * (float)( ed - st ), 1000.0f * (float)( m_arrival - m_start ) );
  803. }
  804. else
  805. {
  806. g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
  807. }
  808. }
  809. // notify on any error
  810. if ( m_bMissing )
  811. {
  812. // Only warn once
  813. m_bMissing = false;
  814. char fn[MAX_PATH];
  815. if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) )
  816. {
  817. MaybeReportMissingWav( fn );
  818. }
  819. }
  820. if ( !m_bLoaded )
  821. {
  822. return false;
  823. }
  824. else if ( m_arrival != 0 && snd_async_spew_blocking.GetInt() >= 2 )
  825. {
  826. DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) );
  827. m_arrival = 0;
  828. }
  829. // clamp requested to available
  830. if ( count > m_nReadSize )
  831. {
  832. count = m_nReadSize - startoffset;
  833. }
  834. if ( count < 0 )
  835. {
  836. return false;
  837. }
  838. // Copy data from stream buffer
  839. Q_memcpy( destbuffer, (char *)m_pvData + ( startoffset - m_async.nOffset ), count );
  840. g_pFileSystem->AsyncRelease( m_hAsyncControl );
  841. m_hAsyncControl = NULL;
  842. return true;
  843. }
  844. bool CAsyncWaveData::IsCurrentlyLoading()
  845. {
  846. if ( m_bLoaded )
  847. return true;
  848. if ( m_bMissing )
  849. return false;
  850. FSAsyncStatus_t status = g_pFileSystem->AsyncStatus( m_hAsyncControl );
  851. if ( status == FSASYNC_STATUS_INPROGRESS || status == FSASYNC_OK )
  852. return true;
  853. return false;
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose:
  857. // Input : **ppData -
  858. // Output : Returns true on success, false on failure.
  859. //-----------------------------------------------------------------------------
  860. bool CAsyncWaveData::BlockingGetDataPointer( void **ppData )
  861. {
  862. Assert( ppData );
  863. if ( !m_bLoaded )
  864. {
  865. // Force it to finish
  866. // It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal
  867. if ( snd_async_spew_blocking.GetBool() )
  868. {
  869. float st = ( float )Plat_FloatTime();
  870. g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
  871. float ed = ( float )Plat_FloatTime();
  872. DevMsg( "%f BlockingGetDataPointer: Async I/O Force %s (%8.2f msec / %8.2f msec total )\n", realtime, GetFileName(), 1000.0f * (float)( ed - st ), 1000.0f * (float)( m_arrival - m_start ) );
  873. }
  874. else
  875. {
  876. g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
  877. }
  878. }
  879. // notify on any error
  880. if ( m_bMissing )
  881. {
  882. // Only warn once
  883. m_bMissing = false;
  884. char fn[MAX_PATH];
  885. if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) )
  886. {
  887. MaybeReportMissingWav( fn );
  888. }
  889. }
  890. if ( !m_bLoaded )
  891. {
  892. return false;
  893. }
  894. else if ( m_arrival != 0 && snd_async_spew_blocking.GetInt() >= 2 )
  895. {
  896. DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) );
  897. m_arrival = 0;
  898. }
  899. *ppData = m_pvData;
  900. g_pFileSystem->AsyncRelease( m_hAsyncControl );
  901. m_hAsyncControl = NULL;
  902. return true;
  903. }
  904. void CAsyncWaveData::SetAsyncPriority( int priority )
  905. {
  906. if ( m_async.priority != priority )
  907. {
  908. m_async.priority = priority;
  909. g_pFileSystem->AsyncSetPriority( m_hAsyncControl, m_async.priority );
  910. if ( snd_async_spew_blocking.GetInt() >= 2 )
  911. {
  912. DevMsg( "%f Async I/O Bumped priority for %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( Plat_FloatTime() - m_start ) );
  913. }
  914. }
  915. }
  916. //-----------------------------------------------------------------------------
  917. // Purpose:
  918. // Input : params -
  919. //-----------------------------------------------------------------------------
  920. void CAsyncWaveData::StartAsyncLoading( const asyncwaveparams_t& params )
  921. {
  922. Assert( IsGameConsole() || ( IsPC() && !m_bLoaded ) );
  923. // expected to be relative to the sound\ dir
  924. m_hFileNameHandle = params.hFilename;
  925. // build the real filename
  926. char szFilename[MAX_PATH];
  927. Q_snprintf( szFilename, sizeof( szFilename ), "sound\\%s", GetFileName() );
  928. int nPriority = 1;
  929. if ( params.bPrefetch )
  930. {
  931. // lower the priority of prefetched sounds, so they don't block immediate sounds from being loaded
  932. nPriority = 0;
  933. }
  934. if ( !IsGameConsole() )
  935. {
  936. m_async.pData = NULL;
  937. if ( SndAlignReads() )
  938. {
  939. m_async.nOffset = 0;
  940. m_async.nBytes = params.seekpos + params.datasize;
  941. }
  942. else
  943. {
  944. m_async.nOffset = params.seekpos;
  945. m_async.nBytes = params.datasize;
  946. }
  947. }
  948. else
  949. {
  950. Assert( params.datasize > 0 );
  951. // using explicit allocated buffer on xbox
  952. m_async.pData = m_pvData;
  953. m_async.nOffset = params.seekpos;
  954. m_async.nBytes = AlignValue( params.datasize, params.alignment );
  955. }
  956. m_async.pfnCallback = AsyncCallback; // optional completion callback
  957. m_async.pContext = (void *)this; // caller's unique context
  958. m_async.priority = nPriority; // inter list priority, 0=lowest
  959. m_async.flags = IsGameConsole() ? 0 : FSASYNC_FLAGS_ALLOCNOFREE;
  960. m_async.pszPathID = "GAME";
  961. m_bLoaded = false;
  962. m_bMissing = false;
  963. m_nDataSize = params.datasize;
  964. m_start = (float)Plat_FloatTime();
  965. m_arrival = 0;
  966. m_nReadSize = 0;
  967. m_bPostProcessed = false;
  968. // The async layer creates a copy of this string, ok to send a local reference
  969. m_async.pszFilename = szFilename;
  970. char szFullName[MAX_PATH];
  971. if ( IsGameConsole() )
  972. {
  973. // all audio is expected be in zips
  974. // resolve to absolute name now, where path can be filtered to just the zips (fast find, no real i/o)
  975. // otherwise the dvd will do a costly seek for each zip miss due to search path fall through
  976. PathTypeQuery_t pathType;
  977. if ( !g_pFileSystem->RelativePathToFullPath( m_async.pszFilename, m_async.pszPathID, szFullName, sizeof( szFullName ), GetAudioPathFilter(), &pathType ) )
  978. {
  979. // not found, do callback now to handle error
  980. m_async.pfnCallback( m_async, 0, FSASYNC_ERR_FILEOPEN );
  981. return;
  982. }
  983. m_async.pszFilename = szFullName;
  984. }
  985. if ( IsGameConsole() && params.bCanBeQueued )
  986. {
  987. // queued loader takes over
  988. LoaderJob_t loaderJob;
  989. loaderJob.m_pFilename = m_async.pszFilename;
  990. loaderJob.m_pPathID = m_async.pszPathID;
  991. loaderJob.m_pCallback = QueuedLoaderCallback;
  992. loaderJob.m_pContext = (void *)this;
  993. loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD;
  994. loaderJob.m_pTargetData = m_async.pData;
  995. loaderJob.m_nBytesToRead = m_async.nBytes;
  996. loaderJob.m_nStartOffset = m_async.nOffset;
  997. g_pQueuedLoader->AddJob( &loaderJob );
  998. return;
  999. }
  1000. MEM_ALLOC_CREDIT();
  1001. // Commence async I/O
  1002. Assert( !m_hAsyncControl );
  1003. g_pFileSystem->AsyncRead( m_async, &m_hAsyncControl );
  1004. }
  1005. //-----------------------------------------------------------------------------
  1006. // Purpose:
  1007. // Output : Returns true on success, false on failure.
  1008. //-----------------------------------------------------------------------------
  1009. bool CAsyncWaveData::GetPostProcessed()
  1010. {
  1011. return m_bPostProcessed;
  1012. }
  1013. //-----------------------------------------------------------------------------
  1014. // Purpose:
  1015. // Input : proc -
  1016. //-----------------------------------------------------------------------------
  1017. void CAsyncWaveData::SetPostProcessed( bool proc )
  1018. {
  1019. m_bPostProcessed = proc;
  1020. }
  1021. //-----------------------------------------------------------------------------
  1022. // Purpose: Implements a cache of .wav / .mp3 data based on filename
  1023. //-----------------------------------------------------------------------------
  1024. class CAsyncWavDataCache : public IAsyncWavDataCache
  1025. {
  1026. public:
  1027. CAsyncWavDataCache();
  1028. ~CAsyncWavDataCache() {}
  1029. virtual bool Init( unsigned int memSize );
  1030. virtual void Shutdown();
  1031. // implementation that treats file as monolithic
  1032. virtual WaveCacheHandle_t AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch = false );
  1033. virtual void PrefetchCache( char const *filename, int datasize, int startpos );
  1034. virtual bool CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed );
  1035. virtual bool CopyDataIntoMemory( WaveCacheHandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed );
  1036. virtual void SetPostProcessed( WaveCacheHandle_t handle, bool proc );
  1037. virtual void Unload( WaveCacheHandle_t handle );
  1038. virtual bool GetDataPointer( WaveCacheHandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed );
  1039. virtual bool IsDataLoadCompleted( WaveCacheHandle_t handle, bool *pIsValid, bool *pIsMissing );
  1040. virtual void RestartDataLoad( WaveCacheHandle_t* handle, char const *filename, int datasize, int startpos );
  1041. virtual bool IsDataLoadInProgress( WaveCacheHandle_t handle );
  1042. // Xbox: alternate multi-buffer streaming implementation
  1043. virtual StreamHandle_t OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags, SoundError &soundError );
  1044. virtual void CloseStreamedLoad( StreamHandle_t hStream );
  1045. virtual int CopyStreamedDataIntoMemory( StreamHandle_t hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy );
  1046. virtual bool IsStreamedDataReady( StreamHandle_t hStream );
  1047. virtual void MarkBufferDiscarded( BufferHandle_t hBuffer );
  1048. virtual void *GetStreamedDataPointer( StreamHandle_t hStream, bool bSync );
  1049. virtual void Flush( bool bTearDownStaticPool = false );
  1050. virtual void UpdateLoopPosition( StreamHandle_t hStream, int nLoopPosition );
  1051. enum MemoryUsageType
  1052. {
  1053. SPEW_BASIC = 0,
  1054. SPEW_MUSIC_NONSTREAMING,
  1055. SPEW_NONSTREAMING,
  1056. SPEW_ALL,
  1057. };
  1058. void SpewMemoryUsage( MemoryUsageType level );
  1059. // Cache helpers
  1060. bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen );
  1061. private:
  1062. void Clear();
  1063. struct CacheEntry_t
  1064. {
  1065. CacheEntry_t() :
  1066. name( 0 ),
  1067. handle( 0 )
  1068. {
  1069. }
  1070. FileNameHandle_t name;
  1071. WaveCacheHandle_t handle;
  1072. };
  1073. // tags the signature of a buffer inside a rb tree for faster than linear find
  1074. struct BufferEntry_t
  1075. {
  1076. FileNameHandle_t m_hName;
  1077. WaveCacheHandle_t m_hWaveData;
  1078. int m_StartPos;
  1079. unsigned int m_bIsTransient : 1;
  1080. unsigned int m_bCanBeShared : 1;
  1081. };
  1082. static bool BufferHandleLessFunc( const BufferEntry_t& lhs, const BufferEntry_t& rhs )
  1083. {
  1084. if ( lhs.m_bIsTransient != rhs.m_bIsTransient )
  1085. {
  1086. return lhs.m_bIsTransient < rhs.m_bIsTransient;
  1087. }
  1088. if ( lhs.m_hName != rhs.m_hName )
  1089. {
  1090. return lhs.m_hName < rhs.m_hName;
  1091. }
  1092. if ( lhs.m_StartPos != rhs.m_StartPos )
  1093. {
  1094. return lhs.m_StartPos < rhs.m_StartPos;
  1095. }
  1096. return lhs.m_bCanBeShared < rhs.m_bCanBeShared;
  1097. }
  1098. CUtlRBTree< BufferEntry_t, BufferHandle_t > m_BufferList;
  1099. // encapsulates (n) buffers for a streamed wave object
  1100. struct StreamedEntry_t
  1101. {
  1102. FileNameHandle_t m_hName;
  1103. WaveCacheHandle_t m_hWaveData[STREAM_BUFFER_COUNT];
  1104. int m_Front; // buffer index, forever incrementing
  1105. int m_NextStartPos; // predicted offset if mixing linearly
  1106. int m_DataSize; // length of the data set in bytes
  1107. int m_DataStart; // file offset where data set starts
  1108. int m_LoopStart; // offset in data set where loop starts
  1109. int m_BufferSize; // size of the buffer in bytes
  1110. int m_numBuffers; // number of buffers (1 or 2) to march through
  1111. int m_SectorSize; // size of sector on stream device
  1112. bool m_bSinglePlay; // hint to keep same buffers
  1113. bool m_bIsTransient; // hint for buffer lifetime
  1114. };
  1115. CUtlLinkedList< StreamedEntry_t, StreamHandle_t > m_StreamedHandles;
  1116. static bool CacheHandleLessFunc( const CacheEntry_t& lhs, const CacheEntry_t& rhs )
  1117. {
  1118. return lhs.name < rhs.name;
  1119. }
  1120. CUtlRBTree< CacheEntry_t, int > m_CacheHandles;
  1121. // these are buffers that were still in-flight at time of stream closure
  1122. // we track these until we can remove an outstanding lock
  1123. // some of these buffers are allowed to be reclaimed
  1124. struct DeadBufferEntry_t
  1125. {
  1126. WaveCacheHandle_t hWaveData;
  1127. bool bSinglePlay;
  1128. };
  1129. CUtlVector< DeadBufferEntry_t > m_DeadBuffers;
  1130. WaveCacheHandle_t FindOrCreateBuffer( asyncwaveparams_t &params, bool bFind );
  1131. void CleanupDeadBuffers( bool bSync );
  1132. bool m_bInitialized;
  1133. struct StreamData_t
  1134. {
  1135. int actualCopied;
  1136. CAsyncWaveData *pWaveData[STREAM_BUFFER_COUNT];
  1137. int index;
  1138. bool bWaiting;
  1139. int copyStartPos;
  1140. };
  1141. bool InitializeStreamData( const StreamedEntry_t &streamedEntry, StreamData_t & streamData, int copyStartPos );
  1142. void CopyFromCurrentBuffers( StreamedEntry_t &streamedEntry, StreamData_t & streamData, StreamHandle_t hStream, void *pBuffer, int bufferSize, int bytesToCopy );
  1143. void PrefetchNextBuffers( StreamedEntry_t &streamedEntry, StreamData_t & streamData );
  1144. };
  1145. //-----------------------------------------------------------------------------
  1146. // Purpose:
  1147. //-----------------------------------------------------------------------------
  1148. CAsyncWavDataCache::CAsyncWavDataCache() :
  1149. m_CacheHandles( 0, 0, CacheHandleLessFunc ),
  1150. m_BufferList( 0, 0, BufferHandleLessFunc ),
  1151. m_bInitialized( false )
  1152. {
  1153. }
  1154. //-----------------------------------------------------------------------------
  1155. // Purpose:
  1156. // Output : Returns true on success, false on failure.
  1157. //-----------------------------------------------------------------------------
  1158. bool CAsyncWavDataCache::Init( unsigned int memSize )
  1159. {
  1160. if ( m_bInitialized )
  1161. return true;
  1162. if ( IsGameConsole() )
  1163. {
  1164. // xbox non-streaming audio is uncapped, all these sounds allocate as required
  1165. // streaming audio is constrained, explicitly managed, and pooled
  1166. unsigned int nStaticPoolSize = CONSOLE_STATIC_AUDIO_DEFAULT;
  1167. const char *pGame = engineClient->GetGameDirectory();
  1168. if ( !Q_stricmp( Q_UnqualifiedFileName( pGame ), "tf" ) )
  1169. {
  1170. memSize = CONSOLE_STREAMING_AUDIO_TF;
  1171. }
  1172. else if ( StringHasPrefix( Q_UnqualifiedFileName( pGame ), "left4dead2") )
  1173. {
  1174. if ( g_pFullFileSystem->IsDVDHosted() )
  1175. {
  1176. memSize = CONSOLE_STREAMING_AUDIO_LEFT4DEAD_DVD;
  1177. }
  1178. else
  1179. {
  1180. memSize = CONSOLE_STREAMING_AUDIO_LEFT4DEAD;
  1181. }
  1182. }
  1183. else if ( StringHasPrefix( Q_UnqualifiedFileName( pGame ), "portal2") )
  1184. {
  1185. nStaticPoolSize = CONSOLE_STATIC_AUDIO_PORTAL2;
  1186. memSize = CONSOLE_STREAMING_AUDIO_PORTAL2;
  1187. if ( IsX360() && XBX_IsRestrictiveLanguage() )
  1188. {
  1189. COMPILE_TIME_ASSERT( !IsX360() || ( CONSOLE_STREAMING_AUDIO_PORTAL2 == 20*1024*1024 ) ); // Might want to revisit this tradeoff if this changes
  1190. memSize -= ASIAN_FONT_MEMORY_USAGE;
  1191. }
  1192. }
  1193. else if ( StringHasPrefix( Q_UnqualifiedFileName( pGame ), "csgo" ) )
  1194. {
  1195. nStaticPoolSize = CONSOLE_STATIC_AUDIO_CSTRIKE15;
  1196. memSize = CONSOLE_STREAMING_AUDIO_CSTRIKE15;
  1197. }
  1198. else
  1199. {
  1200. memSize = CONSOLE_STREAMING_AUDIO_DEFAULT;
  1201. }
  1202. // needs to be integral
  1203. Assert( memSize % STREAM_BUFFER_DATASIZE == 0 );
  1204. g_pAudioStreamPool = new CUtlMemoryPool( STREAM_BUFFER_DATASIZE, memSize/STREAM_BUFFER_DATASIZE, CUtlMemoryPool::GROW_NONE, "CAsyncWavDataCache::AudioStreamPool" );
  1205. // force the actual pool allocation to occur on first alloc
  1206. g_pAudioStreamPool->Clear();
  1207. // create a pool to hold the non-streaming static data
  1208. g_pAudioStaticPool = new CMemoryStack;
  1209. g_pAudioStaticPool->Init( "g_pAudioStaticPool", nStaticPoolSize, 0, nStaticPoolSize );
  1210. // NOTE!!!!
  1211. // console audio section is unlimited, purges are *explicitly* invoked via FindOrCreateBuffer()
  1212. // the create will cause the resource to be created which checks the pools, fails, and then the explicit purge occurs
  1213. memSize = (unsigned int)-1;
  1214. }
  1215. else
  1216. {
  1217. if ( memSize < DEFAULT_WAV_MEMORY_CACHE )
  1218. {
  1219. memSize = DEFAULT_WAV_MEMORY_CACHE;
  1220. }
  1221. }
  1222. s_WaveCache.Init( memSize );
  1223. m_bInitialized = true;
  1224. return true;
  1225. }
  1226. //-----------------------------------------------------------------------------
  1227. // Purpose:
  1228. //-----------------------------------------------------------------------------
  1229. void CAsyncWavDataCache::Shutdown()
  1230. {
  1231. if ( !m_bInitialized )
  1232. {
  1233. return;
  1234. }
  1235. Clear();
  1236. s_WaveCache.Shutdown();
  1237. delete g_pAudioStreamPool;
  1238. m_bInitialized = false;
  1239. }
  1240. //-----------------------------------------------------------------------------
  1241. // Purpose: Creates initial cache object if it doesn't already exist, starts async loading the actual data
  1242. // in any case.
  1243. // Input : *filename -
  1244. // datasize -
  1245. // startpos -
  1246. // Output : WaveCacheHandle_t
  1247. //-----------------------------------------------------------------------------
  1248. WaveCacheHandle_t CAsyncWavDataCache::AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch )
  1249. {
  1250. VPROF( "CAsyncWavDataCache::AsyncLoadCache" );
  1251. FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
  1252. CacheEntry_t search;
  1253. search.name = fnh;
  1254. search.handle = 0;
  1255. // find or create the handle
  1256. int idx = m_CacheHandles.Find( search );
  1257. if ( idx == m_CacheHandles.InvalidIndex() )
  1258. {
  1259. idx = m_CacheHandles.Insert( search );
  1260. Assert( idx != m_CacheHandles.InvalidIndex() );
  1261. }
  1262. CacheEntry_t &entry = m_CacheHandles[idx];
  1263. // Try and pull it into cache
  1264. CAsyncWaveData *data = s_WaveCache.CacheGet( entry.handle );
  1265. if ( !data )
  1266. {
  1267. // Try and reload it
  1268. asyncwaveparams_t params;
  1269. params.hFilename = fnh;
  1270. params.datasize = datasize;
  1271. params.seekpos = startpos;
  1272. params.bPrefetch = bIsPrefetch;
  1273. entry.handle = s_WaveCache.CacheCreate( params );
  1274. }
  1275. return entry.handle;
  1276. }
  1277. //-----------------------------------------------------------------------------
  1278. // Purpose: Reclaim a buffer. A reclaimed resident buffer is ready for play.
  1279. //-----------------------------------------------------------------------------
  1280. WaveCacheHandle_t CAsyncWavDataCache::FindOrCreateBuffer( asyncwaveparams_t &params, bool bFind )
  1281. {
  1282. CAsyncWaveData *pWaveData;
  1283. BufferEntry_t search;
  1284. BufferHandle_t hBuffer;
  1285. search.m_hName = params.hFilename;
  1286. search.m_StartPos = params.seekpos;
  1287. search.m_bIsTransient = params.bIsTransient;
  1288. search.m_bCanBeShared = bFind;
  1289. search.m_hWaveData = INVALID_WAVECACHE_HANDLE;
  1290. if ( bFind )
  1291. {
  1292. // look for an existing buffer that matches signature (same file, offset, etc)
  1293. int hBuffer = m_BufferList.Find( search );
  1294. if ( hBuffer != m_BufferList.InvalidIndex() )
  1295. {
  1296. // found
  1297. search.m_hWaveData = m_BufferList[hBuffer].m_hWaveData;
  1298. if ( snd_async_stream_spew.GetInt() >= 2 )
  1299. {
  1300. char tempBuff[MAX_PATH];
  1301. g_pFileSystem->String( params.hFilename, tempBuff, sizeof( tempBuff ) );
  1302. Msg( "Found Buffer: %s, offset: %d\n", tempBuff, params.seekpos );
  1303. }
  1304. }
  1305. }
  1306. // each resource buffer stays locked (valid) while in use
  1307. // a buffering stream is not subject to lru and can rely on it's buffers
  1308. // a buffering stream may obsolete it's buffers by reducing the lock count, allowing for lru
  1309. pWaveData = s_WaveCache.CacheLock( search.m_hWaveData );
  1310. if ( !pWaveData )
  1311. {
  1312. // not in cache, create and lock
  1313. // not found, create buffer and fill with data
  1314. int numFails = 0;
  1315. while ( 1 )
  1316. {
  1317. search.m_hWaveData = s_WaveCache.CacheCreate( params, WCAF_LOCK );
  1318. if ( search.m_hWaveData != INVALID_WAVECACHE_HANDLE )
  1319. {
  1320. break;
  1321. }
  1322. if ( !params.bIsTransient )
  1323. {
  1324. // yikes!, creation can fail if out of system memory or the stream memory pool was full
  1325. // only transient streams would fail if the stream pool was full
  1326. Assert( 0 );
  1327. return INVALID_WAVECACHE_HANDLE;
  1328. }
  1329. numFails++;
  1330. if ( numFails >= 2 )
  1331. {
  1332. // second attempt
  1333. // yikes!, stream pool isn't getting purged, all must be locked
  1334. Assert( 0 );
  1335. if ( snd_async_stream_fail.GetBool() )
  1336. {
  1337. Warning( "Stream pool: No buffers available! (dead:%d)\n", m_DeadBuffers.Count() );
  1338. }
  1339. return INVALID_WAVECACHE_HANDLE;
  1340. }
  1341. if ( numFails == 1 )
  1342. {
  1343. // first time failure
  1344. // try and reclaim dead buffers before the purge
  1345. CleanupDeadBuffers( false );
  1346. }
  1347. // lru purge the stream pool and retry
  1348. s_WaveCache.Purge( STREAM_BUFFER_DATASIZE );
  1349. }
  1350. // add the buffer to our managed list
  1351. hBuffer = m_BufferList.Insert( search );
  1352. Assert( hBuffer != m_BufferList.InvalidIndex() );
  1353. // store the handle into our managed list
  1354. // used during a lru discard as a means to keep the list in-sync
  1355. pWaveData = s_WaveCache.CacheGet( search.m_hWaveData );
  1356. pWaveData->m_hBuffer = hBuffer;
  1357. }
  1358. else
  1359. {
  1360. // still in cache
  1361. // same as requesting it and having it arrive instantly
  1362. pWaveData->m_start = pWaveData->m_arrival = (float)Plat_FloatTime();
  1363. }
  1364. return search.m_hWaveData;
  1365. }
  1366. //-----------------------------------------------------------------------------
  1367. // Purpose: Load data asynchronously via multi-buffers, returns specialized handle
  1368. //-----------------------------------------------------------------------------
  1369. StreamHandle_t CAsyncWavDataCache::OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags, SoundError &soundError )
  1370. {
  1371. VPROF( "CAsyncWavDataCache::OpenStreamedLoad" );
  1372. StreamedEntry_t streamedEntry;
  1373. StreamHandle_t hStream;
  1374. asyncwaveparams_t params;
  1375. Assert( numBuffers > 0 && numBuffers <= STREAM_BUFFER_COUNT );
  1376. if ( flags & STREAMED_QUEUEDLOAD )
  1377. {
  1378. if ( numBuffers != 1 )
  1379. {
  1380. // queued load mandates one buffer, caller has violated code expectations
  1381. Assert( 0 );
  1382. numBuffers = 1;
  1383. }
  1384. if ( flags & STREAMED_TRANSIENT )
  1385. {
  1386. // not allowed, queued loads are for static resident sounds
  1387. Assert( 0 );
  1388. flags &= ~STREAMED_TRANSIENT;
  1389. }
  1390. }
  1391. if ( ( flags & STREAMED_TRANSIENT ) && ( bufferSize != STREAM_BUFFER_DATASIZE ) )
  1392. {
  1393. // stream pool mandates the pool block size, caller has violated code expectations
  1394. Assert( 0 );
  1395. bufferSize = STREAM_BUFFER_DATASIZE;
  1396. }
  1397. if ( !( flags & STREAMED_TRANSIENT ) && ( flags & STREAMED_SINGLEPLAY ) )
  1398. {
  1399. // singleplay is a streaming concept requiring transient buffers
  1400. // nonsense concept if used by memory resident wavs
  1401. // caller has violated code expectations
  1402. Assert( 0 );
  1403. flags &= ~STREAMED_SINGLEPLAY;
  1404. }
  1405. streamedEntry.m_hName = g_pFileSystem->FindOrAddFileName( pFileName );
  1406. streamedEntry.m_Front = 0;
  1407. streamedEntry.m_DataSize = dataSize;
  1408. streamedEntry.m_DataStart = dataStart;
  1409. streamedEntry.m_NextStartPos = startPos + numBuffers * bufferSize;
  1410. streamedEntry.m_LoopStart = loopPos;
  1411. streamedEntry.m_BufferSize = bufferSize;
  1412. streamedEntry.m_numBuffers = numBuffers;
  1413. streamedEntry.m_bSinglePlay = ( flags & STREAMED_SINGLEPLAY ) != 0;
  1414. streamedEntry.m_SectorSize = ( IsGameConsole() && ( flags & STREAMED_FROMDVD ) ) ? XBOX_DVD_SECTORSIZE : 1;
  1415. streamedEntry.m_bIsTransient = IsGameConsole() && ( flags & STREAMED_TRANSIENT ) != 0;
  1416. bool bFindBuffer;
  1417. if ( !( flags & STREAMED_TRANSIENT ) && !( flags & STREAMED_QUEUEDLOAD ) )
  1418. {
  1419. // static sounds created outside the queued loader end up in the standard heap
  1420. // these would be UI sounds or other engine startup sounds
  1421. // for simplicity and stability, they don't alias
  1422. bFindBuffer = false;
  1423. }
  1424. else
  1425. {
  1426. // single play streams expect to uniquely own and thus recycle their buffers though the data
  1427. // single play streams are guaranteed that their buffers are private and cannot be shared
  1428. // a non-single play stream wants persisting buffers and attempts to reclaim a matching buffer
  1429. bFindBuffer = ( streamedEntry.m_bSinglePlay == false );
  1430. }
  1431. // initial load populates buffers
  1432. // mixing starts after front buffer viable
  1433. // buffer rotation occurs after front buffer consumed
  1434. // there should be no blocking
  1435. params.hFilename = streamedEntry.m_hName;
  1436. params.datasize = bufferSize;
  1437. params.alignment = streamedEntry.m_SectorSize;
  1438. params.bCanBeQueued = ( flags & STREAMED_QUEUEDLOAD ) != 0;
  1439. params.bIsTransient = streamedEntry.m_bIsTransient;
  1440. params.bIsStaticPooled = ( flags & STREAMED_QUEUEDLOAD ) != 0;
  1441. bool bFailed = false;
  1442. for ( int i = 0; i < numBuffers; ++i )
  1443. {
  1444. int nOffset = streamedEntry.m_SectorSize * ( ( startPos + i * bufferSize ) / streamedEntry.m_SectorSize ); // So it matches the alignment of the streamer after
  1445. params.seekpos = dataStart + nOffset;
  1446. WaveCacheHandle_t hWaveData = INVALID_WAVECACHE_HANDLE;
  1447. if ( !bFailed )
  1448. {
  1449. hWaveData = FindOrCreateBuffer( params, bFindBuffer );
  1450. bFailed = ( hWaveData == INVALID_WAVECACHE_HANDLE );
  1451. }
  1452. streamedEntry.m_hWaveData[i] = hWaveData;
  1453. }
  1454. // get a unique handle for each stream request
  1455. hStream = m_StreamedHandles.AddToTail( streamedEntry );
  1456. Assert( hStream != m_StreamedHandles.InvalidIndex() );
  1457. if ( bFailed )
  1458. {
  1459. // partial failure, didn't get all the buffers required
  1460. // have to do cleanup, cannot leave dangling buffers
  1461. CloseStreamedLoad( hStream );
  1462. // sorry, this sound will not play, system is unable to provide streaming buffers
  1463. // streaming buffers are should have been available
  1464. hStream = INVALID_STREAM_HANDLE;
  1465. soundError = SE_NO_STREAM_BUFFER;
  1466. }
  1467. else
  1468. {
  1469. soundError = SE_OK;
  1470. }
  1471. return hStream;
  1472. }
  1473. //-----------------------------------------------------------------------------
  1474. // Purpose: Cleanup a streamed load's resources.
  1475. //-----------------------------------------------------------------------------
  1476. void CAsyncWavDataCache::CloseStreamedLoad( StreamHandle_t hStream )
  1477. {
  1478. VPROF( "CAsyncWavDataCache::CloseStreamedLoad" );
  1479. if ( hStream == INVALID_STREAM_HANDLE )
  1480. {
  1481. return;
  1482. }
  1483. int lockCount;
  1484. StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
  1485. for ( int i=0; i<streamedEntry.m_numBuffers; ++i )
  1486. {
  1487. WaveCacheHandle_t hWaveData = streamedEntry.m_hWaveData[i];
  1488. if ( hWaveData == INVALID_WAVECACHE_HANDLE )
  1489. {
  1490. // cleaning up unexpected bad entry
  1491. // skip over logic that would otherwise assert
  1492. continue;
  1493. }
  1494. // multiple streams could be using the same buffer, keeping the lock count nonzero
  1495. lockCount = s_WaveCache.GetLockCount( hWaveData );
  1496. Assert( lockCount >= 1 );
  1497. if ( lockCount > 1 )
  1498. {
  1499. // just remove our lock, it will eventuall free when the last consumer releases
  1500. s_WaveCache.CacheUnlock( hWaveData );
  1501. continue;
  1502. }
  1503. CAsyncWaveData *pBuffer = s_WaveCache.CacheGetNoTouch( hWaveData );
  1504. if ( pBuffer )
  1505. {
  1506. // going to a zero lock count with an async operation in flight would cause memory corruption
  1507. if ( !pBuffer->m_bMissing && !pBuffer->m_bLoaded )
  1508. {
  1509. // still in flight, add to list of dead buffers
  1510. // have to now track these to remove our lock when they async-finish
  1511. int iEntryIndex = m_DeadBuffers.AddToTail();
  1512. m_DeadBuffers[iEntryIndex].hWaveData = hWaveData;
  1513. m_DeadBuffers[iEntryIndex].bSinglePlay = streamedEntry.m_bSinglePlay;
  1514. continue;
  1515. }
  1516. }
  1517. lockCount = s_WaveCache.CacheUnlock( hWaveData );
  1518. if ( streamedEntry.m_bSinglePlay )
  1519. {
  1520. // a buffering single play stream has no reason to reuse its own buffers and destroys them
  1521. // these buffers are uniquely owned, so the lock count can/should only be 0 at this point
  1522. // if !=0 the remove will respect the lock and do nothing and the buffer becomes a zombie
  1523. Assert( lockCount == 0 );
  1524. s_WaveCache.CacheRemove( hWaveData );
  1525. }
  1526. }
  1527. m_StreamedHandles.Remove( hStream );
  1528. }
  1529. //-----------------------------------------------------------------------------
  1530. // Cleanup any streaming buffers that could not be released during the close
  1531. // because they had async data in-flight. These buffers may have become owned
  1532. // by another stream. We do this polling to avoid having to sync stop the
  1533. // buffers which would cause the game to hitch.
  1534. //-----------------------------------------------------------------------------
  1535. void CAsyncWavDataCache::CleanupDeadBuffers( bool bSync )
  1536. {
  1537. if ( !IsGameConsole() )
  1538. {
  1539. return;
  1540. }
  1541. for ( int iEntryIndex = 0; iEntryIndex < m_DeadBuffers.Count(); )
  1542. {
  1543. WaveCacheHandle_t hWaveData = m_DeadBuffers[iEntryIndex].hWaveData;
  1544. bool bSinglePlay = m_DeadBuffers[iEntryIndex].bSinglePlay;
  1545. int lockCount = s_WaveCache.GetLockCount( hWaveData );
  1546. Assert( lockCount >= 1 );
  1547. if ( lockCount > 1 )
  1548. {
  1549. // this buffer got re-claimed by some stream
  1550. // just remove the oustanding lock that should have occurred during the initial close but was prevented
  1551. // this buffer will eventually free when the last consumer releases
  1552. s_WaveCache.CacheUnlock( hWaveData );
  1553. // no longer tracking
  1554. m_DeadBuffers.Remove( iEntryIndex );
  1555. continue;
  1556. }
  1557. // going to a zero lock count with an async operation in flight would cause memory corruption
  1558. // check the buffer to ensure it has completed
  1559. CAsyncWaveData *pBuffer = s_WaveCache.CacheGetNoTouch( hWaveData );
  1560. if ( pBuffer )
  1561. {
  1562. if ( !pBuffer->m_bMissing && !pBuffer->m_bLoaded )
  1563. {
  1564. if ( !bSync )
  1565. {
  1566. // still in flight, it will eventually finish
  1567. // keep tracking
  1568. iEntryIndex++;
  1569. continue;
  1570. }
  1571. else
  1572. {
  1573. // cause a sync operation to force the async operation to finish
  1574. void *pData = NULL;
  1575. pBuffer->BlockingGetDataPointer( &pData );
  1576. }
  1577. }
  1578. }
  1579. // remove the outstanding lock
  1580. lockCount = s_WaveCache.CacheUnlock( hWaveData );
  1581. if ( bSinglePlay )
  1582. {
  1583. // a buffering single play stream has no reason to reuse its own buffers and destroys them
  1584. // these buffers are uniquely owned, so the lock count can/should only be 0 at this point
  1585. // if !=0 the remove will respect the lock and do nothing and the buffer becomes a zombie
  1586. Assert( lockCount == 0 );
  1587. s_WaveCache.CacheRemove( hWaveData );
  1588. }
  1589. // no longer tracking
  1590. m_DeadBuffers.Remove( iEntryIndex );
  1591. }
  1592. }
  1593. //-----------------------------------------------------------------------------
  1594. // Purpose:
  1595. // Input : *filename -
  1596. // datasize -
  1597. // startpos -
  1598. //-----------------------------------------------------------------------------
  1599. void CAsyncWavDataCache::PrefetchCache( char const *filename, int datasize, int startpos )
  1600. {
  1601. // Just do an async load, but don't get cache handle
  1602. AsyncLoadCache( filename, datasize, startpos, true );
  1603. }
  1604. //-----------------------------------------------------------------------------
  1605. // Purpose:
  1606. // Input : *filename -
  1607. // datasize -
  1608. // startpos -
  1609. // *buffer -
  1610. // bufsize -
  1611. // copystartpos -
  1612. // bytestocopy -
  1613. // Output : Returns true on success, false on failure.
  1614. //-----------------------------------------------------------------------------
  1615. bool CAsyncWavDataCache::CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed )
  1616. {
  1617. VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" );
  1618. bool bret = false;
  1619. // Add to caching system
  1620. AsyncLoadCache( filename, datasize, startpos );
  1621. FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
  1622. CacheEntry_t search;
  1623. search.name = fnh;
  1624. search.handle = 0;
  1625. // Now look it up, it should be in the system
  1626. int idx = m_CacheHandles.Find( search );
  1627. if ( idx == m_CacheHandles.InvalidIndex() )
  1628. {
  1629. Assert( 0 );
  1630. return bret;
  1631. }
  1632. // Now see if the handle has been paged out...
  1633. return CopyDataIntoMemory( m_CacheHandles[ idx ].handle, filename, datasize, startpos, buffer, bufsize, copystartpos, bytestocopy, pbPostProcessed );
  1634. }
  1635. //-----------------------------------------------------------------------------
  1636. // Purpose:
  1637. // Input : handle -
  1638. // *filename -
  1639. // datasize -
  1640. // startpos -
  1641. // *buffer -
  1642. // bufsize -
  1643. // copystartpos -
  1644. // bytestocopy -
  1645. // Output : Returns true on success, false on failure.
  1646. //-----------------------------------------------------------------------------
  1647. bool CAsyncWavDataCache::CopyDataIntoMemory( WaveCacheHandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed )
  1648. {
  1649. VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" );
  1650. *pbPostProcessed = false;
  1651. bool bret = false;
  1652. CAsyncWaveData *data = s_WaveCache.CacheLock( handle );
  1653. if ( !data )
  1654. {
  1655. FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
  1656. CacheEntry_t search;
  1657. search.name = fnh;
  1658. search.handle = 0;
  1659. // Now look it up, it should be in the system
  1660. int idx = m_CacheHandles.Find( search );
  1661. if ( idx == m_CacheHandles.InvalidIndex() )
  1662. {
  1663. Assert( 0 );
  1664. return false;
  1665. }
  1666. // Try and reload it
  1667. asyncwaveparams_t params;
  1668. params.hFilename = fnh;
  1669. params.datasize = datasize;
  1670. params.seekpos = startpos;
  1671. handle = m_CacheHandles[ idx ].handle = s_WaveCache.CacheCreate( params );
  1672. data = s_WaveCache.CacheLock( handle );
  1673. if ( !data )
  1674. {
  1675. return bret;
  1676. }
  1677. }
  1678. // Cache entry exists, but if filesize == 0 then the file itself wasn't on disk...
  1679. if ( data->m_nDataSize != 0 )
  1680. {
  1681. bret = data->BlockingCopyData( buffer, bufsize, copystartpos, bytestocopy );
  1682. }
  1683. *pbPostProcessed = data->GetPostProcessed();
  1684. // Release lock
  1685. s_WaveCache.CacheUnlock( handle );
  1686. return bret;
  1687. }
  1688. bool CAsyncWavDataCache::InitializeStreamData( const StreamedEntry_t &streamedEntry, StreamData_t & streamData, int copyStartPos )
  1689. {
  1690. for ( int i=0; i<streamedEntry.m_numBuffers; ++i )
  1691. {
  1692. streamData.pWaveData[i] = s_WaveCache.CacheGetNoTouch( streamedEntry.m_hWaveData[i] );
  1693. Assert( streamData.pWaveData[i] );
  1694. if ( streamData.pWaveData[i] == NULL )
  1695. {
  1696. // oops, where are our locked buffers?
  1697. // The buffers can go away in midst of streaming if the streaming buffer pool is filled,
  1698. // no buffers can be lru'd, and then can't provide the next stream buffer to fill.
  1699. // There is no choice but to abort this streaming sound.
  1700. return false;
  1701. }
  1702. }
  1703. streamData.index = streamedEntry.m_Front;
  1704. streamData.actualCopied = 0;
  1705. streamData.bWaiting = false;
  1706. streamData.copyStartPos = copyStartPos;
  1707. return true;
  1708. }
  1709. void CAsyncWavDataCache::CopyFromCurrentBuffers( StreamedEntry_t &streamedEntry, StreamData_t & streamData, StreamHandle_t hStream, void *pBuffer, int bufferSize, int bytesToCopy )
  1710. {
  1711. int nRemainingBytesToCopy = bytesToCopy;
  1712. while ( 1 )
  1713. {
  1714. // try to satisfy from the front
  1715. CAsyncWaveData *pFront = streamData.pWaveData[streamData.index % streamedEntry.m_numBuffers];
  1716. int bufferPos = streamData.copyStartPos - pFront->m_async.nOffset;
  1717. // cache atomic async completion signal off to avoid coherency issues
  1718. bool bCompleted = pFront->m_bLoaded || pFront->m_bMissing;
  1719. if ( snd_async_stream_spew.GetInt() >= 1 )
  1720. {
  1721. // interval is the audio block clock rate, the block must be available within this interval
  1722. // a faster audio rate or smaller block size implies a smaller interval
  1723. // latency is the actual block delivery time
  1724. // latency must not exceed the delivery interval or starving occurs and audio pops
  1725. float nowTime = Plat_FloatTime();
  1726. int interval = (int)(1000.0f*(nowTime-pFront->m_start));
  1727. int latency;
  1728. if ( bCompleted && pFront->m_bLoaded )
  1729. {
  1730. latency = (int)(1000.0f*(pFront->m_arrival-pFront->m_start));
  1731. }
  1732. else
  1733. {
  1734. // buffer has not arrived yet
  1735. latency = -1;
  1736. }
  1737. Msg( "Stream:%2d interval:%5dms latency:%5dms offset:%d length:%d (%s)\n", hStream, interval, latency, pFront->m_async.nOffset, pFront->m_nReadSize, pFront->GetFileName() );
  1738. }
  1739. if ( bCompleted && pFront->m_hAsyncControl && ( pFront->m_bLoaded || pFront->m_bMissing) )
  1740. {
  1741. g_pFileSystem->AsyncRelease( pFront->m_hAsyncControl );
  1742. pFront->m_hAsyncControl = NULL;
  1743. }
  1744. if ( bCompleted && pFront->m_bLoaded )
  1745. {
  1746. if ( bufferPos >= 0 && bufferPos < pFront->m_nReadSize )
  1747. {
  1748. int count = nRemainingBytesToCopy;
  1749. if ( bufferPos + count > pFront->m_nReadSize )
  1750. {
  1751. // clamp requested to actual available
  1752. count = pFront->m_nReadSize - bufferPos;
  1753. }
  1754. if ( bufferPos + count > bufferSize )
  1755. {
  1756. // clamp requested to caller's buffer dimension
  1757. count = bufferSize - bufferPos;
  1758. }
  1759. if ( count > 0 )
  1760. {
  1761. // We have to test for count as in some cases it could be negative (as BufferSize gets reduced if it spans over more than one loop).
  1762. // This is actually very rare though.
  1763. Q_memcpy( pBuffer, (char *)pFront->m_pvData + bufferPos, count );
  1764. // advance past consumed bytes
  1765. streamData.actualCopied += count;
  1766. streamData.copyStartPos += count;
  1767. nRemainingBytesToCopy -= count;
  1768. bufferPos += count; // Once we copied it, update bufferPos so we can look at the next buffer if needed.
  1769. pBuffer = (void *)((intp)pBuffer + count); // Skip written bytes
  1770. bufferSize -= count;
  1771. }
  1772. else
  1773. {
  1774. // Nothing else can be done. We'll see with next request.
  1775. Warning( "%s(%d): Protecting against negative memcpy. BufferSize = %d. Buffer Pos = %d. Count = %d.\n", __FILE__, __LINE__, bufferSize, bufferPos, count );
  1776. return;
  1777. }
  1778. }
  1779. }
  1780. else if ( bCompleted && pFront->m_bMissing )
  1781. {
  1782. // notify on any error
  1783. MaybeReportMissingWav( pFront->GetFileName() );
  1784. break;
  1785. }
  1786. else
  1787. {
  1788. // data not available
  1789. streamData.bWaiting = true;
  1790. break;
  1791. }
  1792. // cycle past obsolete or consumed buffers
  1793. if ( bufferPos < 0 || bufferPos >= pFront->m_nReadSize )
  1794. {
  1795. // move to next buffer
  1796. streamData.index++;
  1797. if ( streamData.index - streamedEntry.m_Front >= streamedEntry.m_numBuffers )
  1798. {
  1799. // out of buffers
  1800. break;
  1801. }
  1802. }
  1803. if ( streamData.actualCopied == bytesToCopy )
  1804. {
  1805. // satisfied request
  1806. return;
  1807. }
  1808. }
  1809. // If the request is not satisfied, let's make sure that at least the buffers are in the range asked.
  1810. // If that's not the case, then we need to make sure that the next pos retrieved is in the range.
  1811. bool bInRange = false;
  1812. for ( int i = 0 ; i < streamedEntry.m_numBuffers ; ++i )
  1813. {
  1814. CAsyncWaveData *pWaveData = streamData.pWaveData[i];
  1815. if ( pWaveData->m_bMissing )
  1816. {
  1817. continue;
  1818. }
  1819. int bufferPos = streamData.copyStartPos - pWaveData->m_async.nOffset;
  1820. if ( ( bufferPos >= 0 ) && ( bufferPos < pWaveData->m_nDataSize ) )
  1821. {
  1822. bInRange = true;
  1823. }
  1824. }
  1825. if ( bInRange == false )
  1826. {
  1827. // None on the buffers are in range, make sure next buffer is the one we are currently requesting.
  1828. streamedEntry.m_NextStartPos = streamData.copyStartPos - streamedEntry.m_DataStart;
  1829. }
  1830. }
  1831. void CAsyncWavDataCache::PrefetchNextBuffers( StreamedEntry_t &streamedEntry, StreamData_t & streamData )
  1832. {
  1833. if ( streamedEntry.m_numBuffers > 1 )
  1834. {
  1835. int nextStartPos;
  1836. // restart consumed buffers
  1837. while ( streamedEntry.m_Front < streamData.index )
  1838. {
  1839. if ( !streamData.actualCopied && !streamData.bWaiting )
  1840. {
  1841. // couldn't return any data because the buffers aren't in the right location
  1842. nextStartPos = streamData.copyStartPos - streamedEntry.m_DataStart;
  1843. }
  1844. else
  1845. {
  1846. // get the next forecast read location
  1847. nextStartPos = streamedEntry.m_NextStartPos;
  1848. }
  1849. if ( nextStartPos >= streamedEntry.m_DataSize )
  1850. {
  1851. // next buffer is at or past end of file
  1852. if ( streamedEntry.m_LoopStart >= 0 )
  1853. {
  1854. // wrap back around to loop position
  1855. nextStartPos = streamedEntry.m_LoopStart;
  1856. }
  1857. else
  1858. {
  1859. // advance past consumed buffer
  1860. streamedEntry.m_Front++;
  1861. // start no further buffers
  1862. break;
  1863. }
  1864. }
  1865. // still valid data left to read
  1866. // snap the buffer position to required alignment
  1867. nextStartPos = streamedEntry.m_SectorSize * (nextStartPos/streamedEntry.m_SectorSize);
  1868. // start loading back buffer at future location
  1869. asyncwaveparams_t params;
  1870. params.hFilename = streamedEntry.m_hName;
  1871. params.seekpos = streamedEntry.m_DataStart + nextStartPos;
  1872. params.datasize = streamedEntry.m_DataSize - nextStartPos;
  1873. params.alignment = streamedEntry.m_SectorSize;
  1874. params.bIsTransient = streamedEntry.m_bIsTransient;
  1875. if ( params.datasize > streamedEntry.m_BufferSize )
  1876. {
  1877. // clamp to buffer size
  1878. params.datasize = streamedEntry.m_BufferSize;
  1879. }
  1880. // save next start position
  1881. streamedEntry.m_NextStartPos = nextStartPos + params.datasize;
  1882. int which = streamedEntry.m_Front % streamedEntry.m_numBuffers;
  1883. if ( streamedEntry.m_bSinglePlay )
  1884. {
  1885. // a single play wave has no reason to persist its buffers into the lru
  1886. // reuse buffer and restart until finished
  1887. streamData.pWaveData[which]->StartAsyncLoading( params );
  1888. }
  1889. else
  1890. {
  1891. // release obsolete buffer to lru management
  1892. s_WaveCache.CacheUnlock( streamedEntry.m_hWaveData[which] );
  1893. // reclaim or create/load the desired buffer
  1894. WaveCacheHandle_t hWaveData = FindOrCreateBuffer( params, true );
  1895. streamedEntry.m_hWaveData[which] = hWaveData;
  1896. if ( hWaveData == INVALID_WAVECACHE_HANDLE )
  1897. {
  1898. // very bad, failed to get an expected buffer
  1899. // return whatever we have
  1900. // retry logic will eventually get a 0 and cease sound
  1901. return;
  1902. }
  1903. }
  1904. streamData.bWaiting = true;
  1905. streamedEntry.m_Front++;
  1906. // Then if there is more stream buffer available, let's continue at the next position
  1907. nextStartPos = streamData.copyStartPos - streamedEntry.m_DataStart + streamedEntry.m_BufferSize;
  1908. streamData.copyStartPos += streamedEntry.m_BufferSize;
  1909. }
  1910. if ( streamData.bWaiting )
  1911. {
  1912. // oh no! data needed is not yet available in front buffer
  1913. // caller requesting data faster than can be provided or caller skipped
  1914. // can only return what has been copied thus far (could be 0)
  1915. return;
  1916. }
  1917. }
  1918. }
  1919. //-----------------------------------------------------------------------------
  1920. // Purpose: Copy from streaming buffers into target memory, never blocks.
  1921. //-----------------------------------------------------------------------------
  1922. int CAsyncWavDataCache::CopyStreamedDataIntoMemory( StreamHandle_t hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy )
  1923. {
  1924. VPROF( "CAsyncWavDataCache::CopyStreamedDataIntoMemory" );
  1925. StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
  1926. if ( copyStartPos >= streamedEntry.m_DataStart + streamedEntry.m_DataSize )
  1927. {
  1928. // at or past end of file
  1929. return 0;
  1930. }
  1931. StreamData_t streamData;
  1932. if ( InitializeStreamData( streamedEntry, streamData, copyStartPos ) == false )
  1933. {
  1934. return 0;
  1935. }
  1936. CopyFromCurrentBuffers( streamedEntry, streamData, hStream, pBuffer, bufferSize, bytesToCopy );
  1937. PrefetchNextBuffers( streamedEntry, streamData );
  1938. return streamData.actualCopied;
  1939. }
  1940. //-----------------------------------------------------------------------------
  1941. // Purpose: Get the front buffer, optionally block.
  1942. // Intended for user of a single buffer stream.
  1943. //-----------------------------------------------------------------------------
  1944. void *CAsyncWavDataCache::GetStreamedDataPointer( StreamHandle_t hStream, bool bSync )
  1945. {
  1946. void *pData = NULL;
  1947. CAsyncWaveData *pFront;
  1948. int index;
  1949. StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
  1950. index = streamedEntry.m_Front % streamedEntry.m_numBuffers;
  1951. pFront = s_WaveCache.CacheGetNoTouch( streamedEntry.m_hWaveData[index] );
  1952. Assert( pFront );
  1953. if ( !pFront )
  1954. {
  1955. // shouldn't happen
  1956. return NULL;
  1957. }
  1958. if ( !pFront->m_bMissing && pFront->m_bLoaded )
  1959. {
  1960. return pFront->m_pvData;
  1961. }
  1962. if ( bSync && pFront->BlockingGetDataPointer( &pData ) )
  1963. {
  1964. return pData;
  1965. }
  1966. return NULL;
  1967. }
  1968. //-----------------------------------------------------------------------------
  1969. // Purpose: The front buffer must be valid
  1970. //-----------------------------------------------------------------------------
  1971. bool CAsyncWavDataCache::IsStreamedDataReady( int hStream )
  1972. {
  1973. VPROF( "CAsyncWavDataCache::IsStreamedDataReady" );
  1974. if ( hStream == INVALID_STREAM_HANDLE )
  1975. {
  1976. return false;
  1977. }
  1978. StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
  1979. if ( streamedEntry.m_Front )
  1980. {
  1981. // already streaming, the buffers better be arriving as expected
  1982. return true;
  1983. }
  1984. // only the first front buffer must be present
  1985. CAsyncWaveData *pFront = s_WaveCache.CacheGetNoTouch( streamedEntry.m_hWaveData[0] );
  1986. Assert( pFront );
  1987. if ( !pFront )
  1988. {
  1989. // shouldn't happen
  1990. // let the caller think data is ready, so stream can shutdown
  1991. return true;
  1992. }
  1993. // regardless of any errors
  1994. // errors handled during data fetch
  1995. return pFront->m_bLoaded || pFront->m_bMissing;
  1996. }
  1997. //-----------------------------------------------------------------------------
  1998. // Purpose: Dequeue the buffer entry (backdoor for list management)
  1999. //-----------------------------------------------------------------------------
  2000. void CAsyncWavDataCache::MarkBufferDiscarded( BufferHandle_t hBuffer )
  2001. {
  2002. m_BufferList.RemoveAt( hBuffer );
  2003. }
  2004. //-----------------------------------------------------------------------------
  2005. // Purpose:
  2006. // Input : handle -
  2007. // proc -
  2008. //-----------------------------------------------------------------------------
  2009. void CAsyncWavDataCache::SetPostProcessed( WaveCacheHandle_t handle, bool proc )
  2010. {
  2011. CAsyncWaveData *data = s_WaveCache.CacheGet( handle );
  2012. if ( data )
  2013. {
  2014. data->SetPostProcessed( proc );
  2015. }
  2016. }
  2017. //-----------------------------------------------------------------------------
  2018. // Purpose:
  2019. // Input : handle -
  2020. //-----------------------------------------------------------------------------
  2021. void CAsyncWavDataCache::Unload( WaveCacheHandle_t handle )
  2022. {
  2023. // Don't actually unload, just mark it as stale
  2024. s_WaveCache.Age( handle );
  2025. }
  2026. //-----------------------------------------------------------------------------
  2027. // Purpose:
  2028. // Input : handle -
  2029. // *filename -
  2030. // datasize -
  2031. // startpos -
  2032. // **pData -
  2033. // copystartpos -
  2034. // *pbPostProcessed -
  2035. // Output : Returns true on success, false on failure.
  2036. //-----------------------------------------------------------------------------
  2037. bool CAsyncWavDataCache::GetDataPointer( WaveCacheHandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed )
  2038. {
  2039. VPROF( "CAsyncWavDataCache::GetDataPointer" );
  2040. Assert( pbPostProcessed );
  2041. Assert( pData );
  2042. *pbPostProcessed = false;
  2043. bool bret = false;
  2044. *pData = NULL;
  2045. CAsyncWaveData *data = s_WaveCache.CacheLock( handle );
  2046. if ( !data )
  2047. {
  2048. FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
  2049. CacheEntry_t search;
  2050. search.name = fnh;
  2051. search.handle = 0;
  2052. int idx = m_CacheHandles.Find( search );
  2053. if ( idx == m_CacheHandles.InvalidIndex() )
  2054. {
  2055. Assert( 0 );
  2056. return bret;
  2057. }
  2058. // Try and reload it
  2059. asyncwaveparams_t params;
  2060. params.hFilename = fnh;
  2061. params.datasize = datasize;
  2062. params.seekpos = startpos;
  2063. handle = m_CacheHandles[ idx ].handle = s_WaveCache.CacheCreate( params );
  2064. data = s_WaveCache.CacheLock( handle );
  2065. if ( !data )
  2066. {
  2067. return bret;
  2068. }
  2069. }
  2070. // Cache entry exists, but if filesize == 0 then the file itself wasn't on disk...
  2071. if ( data->m_nDataSize != 0 && copystartpos < data->m_nDataSize )
  2072. {
  2073. if ( data->BlockingGetDataPointer( pData ) )
  2074. {
  2075. *pData = (char *)*pData + copystartpos;
  2076. bret = true;
  2077. }
  2078. }
  2079. *pbPostProcessed = data->GetPostProcessed();
  2080. // Release lock
  2081. s_WaveCache.CacheUnlock( handle );
  2082. return bret;
  2083. }
  2084. //-----------------------------------------------------------------------------
  2085. // Purpose:
  2086. // Input : handle -
  2087. // *filename -
  2088. // datasize -
  2089. // startpos -
  2090. // Output : Returns true on success, false on failure.
  2091. //-----------------------------------------------------------------------------
  2092. bool CAsyncWavDataCache::IsDataLoadCompleted( WaveCacheHandle_t handle, bool *pIsValid, bool *pIsMissing )
  2093. {
  2094. VPROF( "CAsyncWavDataCache::IsDataLoadCompleted" );
  2095. CAsyncWaveData *data = s_WaveCache.CacheGet( handle );
  2096. if ( !data )
  2097. {
  2098. *pIsValid = false;
  2099. return false;
  2100. }
  2101. *pIsValid = true;
  2102. if ( pIsMissing )
  2103. {
  2104. *pIsMissing = data->m_bMissing;
  2105. }
  2106. // bump the priority
  2107. data->SetAsyncPriority( 1 );
  2108. return data->m_bLoaded;
  2109. }
  2110. void CAsyncWavDataCache::RestartDataLoad( WaveCacheHandle_t *pHandle, const char *pFilename, int dataSize, int startpos )
  2111. {
  2112. CAsyncWaveData *data = s_WaveCache.CacheGet( *pHandle );
  2113. if ( !data )
  2114. {
  2115. *pHandle = AsyncLoadCache( pFilename, dataSize, startpos );
  2116. }
  2117. }
  2118. bool CAsyncWavDataCache::IsDataLoadInProgress( WaveCacheHandle_t handle )
  2119. {
  2120. CAsyncWaveData *data = s_WaveCache.CacheGet( handle );
  2121. if ( data )
  2122. {
  2123. return data->IsCurrentlyLoading();
  2124. }
  2125. return false;
  2126. }
  2127. //-----------------------------------------------------------------------------
  2128. // Purpose:
  2129. //-----------------------------------------------------------------------------
  2130. void CAsyncWavDataCache::Flush( bool bTearDownStaticPool )
  2131. {
  2132. if ( !m_bInitialized )
  2133. {
  2134. return;
  2135. }
  2136. if ( IsGameConsole() )
  2137. {
  2138. // this will sync stop (and unlock) any buffers that could not be unlocked at stream closure
  2139. CleanupDeadBuffers( true );
  2140. }
  2141. // purge all unlocked resources
  2142. s_WaveCache.Flush();
  2143. MemoryUsageType spewType = SPEW_BASIC;
  2144. if ( IsGameConsole() )
  2145. {
  2146. if ( bTearDownStaticPool )
  2147. {
  2148. // The caller has unlocked all static resources that should have been in this pool
  2149. // and flush should have released them.
  2150. // This is VERY scary, with this technique there is no way to ensure all resources from this pool
  2151. // are really unlocked unless we scan the cache section annd make queries. And if they aren't,
  2152. // nothing can be done because this is a stack, so either way it's just fatal.
  2153. // Tear the pool down.
  2154. g_pAudioStaticPool->FreeAll( false );
  2155. }
  2156. // a flush nominally occurs during level transitions to free up memory
  2157. // the underlying pool's blob needs to get freed to use for level transition work
  2158. // the next first allocation will re-establish the blob
  2159. if ( !g_pAudioStreamPool->Count() )
  2160. {
  2161. g_pAudioStreamPool->Clear();
  2162. }
  2163. else
  2164. {
  2165. // this is used between levels, no sounds should be occuring, lock counts *should* be 0
  2166. // the flush should have caused the stream buffers to destroy and thus the pool to be emptied
  2167. // if sounds are not playing, buffers that are remaining are locked and would be zombied
  2168. // zombied buffers would just accumulate until the stream pool couldn't play any more sounds
  2169. // spewing more details into the log to scan after playtests to track (if any) down
  2170. Warning( "CAsyncWavDataCache: Failed to clear stream pool during flush\n" );
  2171. // need more detailed breakdown
  2172. spewType = SPEW_ALL;
  2173. }
  2174. }
  2175. SpewMemoryUsage( spewType );
  2176. }
  2177. //-----------------------------------------------------------------------------
  2178. // Purpose: Update loop position if a more accurate value has been found.
  2179. //-----------------------------------------------------------------------------
  2180. void CAsyncWavDataCache::UpdateLoopPosition( StreamHandle_t hStream, int nLoopPosition )
  2181. {
  2182. StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
  2183. streamedEntry.m_LoopStart = nLoopPosition;
  2184. }
  2185. //-----------------------------------------------------------------------------
  2186. // Purpose:
  2187. //-----------------------------------------------------------------------------
  2188. bool CAsyncWavDataCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen )
  2189. {
  2190. CAsyncWaveData *pWaveData = (CAsyncWaveData *)pItem;
  2191. Q_strncpy( pDest, pWaveData->GetFileName(), nMaxLen );
  2192. return true;
  2193. }
  2194. //-----------------------------------------------------------------------------
  2195. // Purpose: Spew a cache summary to the console
  2196. //-----------------------------------------------------------------------------
  2197. void CAsyncWavDataCache::SpewMemoryUsage( MemoryUsageType level )
  2198. {
  2199. WaveCacheStatus_t status;
  2200. WaveCacheLimits_t limits;
  2201. s_WaveCache.GetStatus( &status, &limits );
  2202. int bytesUsed = status.nBytes;
  2203. int bytesTotal = limits.nMaxBytes;
  2204. if ( IsPC() )
  2205. {
  2206. if ( level != SPEW_BASIC )
  2207. {
  2208. for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) )
  2209. {
  2210. char name[MAX_PATH];
  2211. if ( !g_pFileSystem->String( m_CacheHandles[ i ].name, name, sizeof( name ) ) )
  2212. {
  2213. Assert( 0 );
  2214. continue;
  2215. }
  2216. if ( level == SPEW_MUSIC_NONSTREAMING && V_stristr( name, "music" ) == NULL )
  2217. continue;
  2218. WaveCacheHandle_t &handle = m_CacheHandles[ i ].handle;
  2219. CAsyncWaveData *data = s_WaveCache.CacheGetNoTouch( handle );
  2220. if ( data )
  2221. {
  2222. Msg( "\t%16.16s : %s\n", Q_pretifymem(data->Size()),name);
  2223. }
  2224. else
  2225. {
  2226. Msg( "\t%16.16s : %s\n", "not resident",name);
  2227. }
  2228. }
  2229. }
  2230. float percent;
  2231. if ( bytesTotal <= 0 )
  2232. {
  2233. // unbounded, indeterminate
  2234. percent = 0;
  2235. bytesTotal = 0;
  2236. }
  2237. else
  2238. {
  2239. percent = 100.0f * (float)bytesUsed / (float)bytesTotal;
  2240. }
  2241. Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent );
  2242. }
  2243. if ( IsGameConsole() )
  2244. {
  2245. if ( level == SPEW_BASIC )
  2246. {
  2247. // basic spew memory usage is the total of all the outstanding buffers
  2248. // this isn't intended as an entirely accurate memory usage report
  2249. Msg( "CAsyncWavDataCache: %.2f MB used\n", bytesUsed / ( 1024.0f * 1024.0f ) );
  2250. Msg( "CAsyncWavDataCache: Static Pool: %.2f MB used of %.2f MB\n", g_pAudioStaticPool->GetUsed() / ( 1024.0f * 1024.0f ), g_pAudioStaticPool->GetSize() / ( 1024.0f * 1024.0f ) );
  2251. Msg( "CAsyncWavDataCache: Stream Pool: %.2f MB used of %.2f MB\n", ( g_pAudioStreamPool->Count() * g_pAudioStreamPool->BlockSize() ) / ( 1024.0f * 1024.0f ), g_pAudioStreamPool->Size() / ( 1024.0f * 1024.0f ) );
  2252. Msg( "CAsyncWavDataCache: Dead Buffers: %d\n", m_DeadBuffers.Count() );
  2253. }
  2254. else
  2255. {
  2256. // detailed spew breaks the stream buffers into resident (pooled or standard) or streaming types
  2257. // iterate non-stream buffers
  2258. int nonStreamBytesUsedPooled = 0;
  2259. Msg( "\nCAsyncWavDataCache: Non-Stream Buffer List (Pooled Heap):\n" );
  2260. for ( BufferHandle_t h = m_BufferList.FirstInorder(); h != m_BufferList.InvalidIndex(); h = m_BufferList.NextInorder( h ) )
  2261. {
  2262. BufferEntry_t *pBuffer = &m_BufferList[h];
  2263. CAsyncWaveData *pData = s_WaveCache.CacheGetNoTouch( pBuffer->m_hWaveData );
  2264. s_WaveCache.CacheLockMutex();
  2265. if ( pData && !pData->m_bIsTransient && pData->m_bIsStaticPooled )
  2266. {
  2267. int lockCount = s_WaveCache.GetLockCount( pBuffer->m_hWaveData );
  2268. unsigned int ageStamp = s_WaveCache.GetAgeStamp( pBuffer->m_hWaveData );
  2269. Msg( "Start:%7d Size:%7d Lock:%3d Age:%4d %s\n", pBuffer->m_StartPos, pData->m_nBufferBytes, lockCount, ageStamp, pData->GetFileName() );
  2270. nonStreamBytesUsedPooled += pData->m_nBufferBytes;
  2271. }
  2272. s_WaveCache.CacheUnlockMutex();
  2273. }
  2274. Msg( "CAsyncWavDataCache: Non-Stream Buffers (Pooled): %.2f MB used\n", (float)nonStreamBytesUsedPooled / ( 1024.0f * 1024.0f ) );
  2275. int nonStreamBytesUsedStandard = 0;
  2276. Msg( "\nCAsyncWavDataCache: Non-Stream Buffer List (Standard Heap):\n" );
  2277. for ( BufferHandle_t h = m_BufferList.FirstInorder(); h != m_BufferList.InvalidIndex(); h = m_BufferList.NextInorder( h ) )
  2278. {
  2279. BufferEntry_t *pBuffer = &m_BufferList[h];
  2280. CAsyncWaveData *pData = s_WaveCache.CacheGetNoTouch( pBuffer->m_hWaveData );
  2281. s_WaveCache.CacheLockMutex();
  2282. if ( pData && !pData->m_bIsTransient && !pData->m_bIsStaticPooled )
  2283. {
  2284. int lockCount = s_WaveCache.GetLockCount( pBuffer->m_hWaveData );
  2285. unsigned int ageStamp = s_WaveCache.GetAgeStamp( pBuffer->m_hWaveData );
  2286. Msg( "Start:%7d Size:%7d Lock:%3d Age:%4d %s\n", pBuffer->m_StartPos, pData->m_nBufferBytes, lockCount, ageStamp, pData->GetFileName() );
  2287. nonStreamBytesUsedStandard += pData->m_nBufferBytes;
  2288. }
  2289. s_WaveCache.CacheUnlockMutex();
  2290. }
  2291. Msg( "CAsyncWavDataCache: Non-Stream Buffers (Standard): %.2f MB used\n", (float)nonStreamBytesUsedStandard / ( 1024.0f * 1024.0f ) );
  2292. // iterate stream buffers
  2293. int streamBytesUsed = 0;
  2294. Msg( "\nCAsyncWavDataCache: Stream Buffer List:\n" );
  2295. for ( BufferHandle_t h = m_BufferList.FirstInorder(); h != m_BufferList.InvalidIndex(); h = m_BufferList.NextInorder( h ) )
  2296. {
  2297. BufferEntry_t *pBuffer = &m_BufferList[h];
  2298. CAsyncWaveData *pData = s_WaveCache.CacheGetNoTouch( pBuffer->m_hWaveData );
  2299. s_WaveCache.CacheLockMutex();
  2300. if ( pData && pData->m_bIsTransient )
  2301. {
  2302. int lockCount = s_WaveCache.GetLockCount( pBuffer->m_hWaveData );
  2303. unsigned int ageStamp = s_WaveCache.GetAgeStamp( pBuffer->m_hWaveData );
  2304. Msg( "Start:%7d Size:%7d Lock:%3d Age:%4d %s\n", pBuffer->m_StartPos, pData->m_nBufferBytes, lockCount, ageStamp, pData->GetFileName() );
  2305. streamBytesUsed += pData->m_nBufferBytes;
  2306. }
  2307. s_WaveCache.CacheUnlockMutex();
  2308. }
  2309. Msg( "CAsyncWavDataCache: Stream Buffers: %.2f MB used\n", (float)streamBytesUsed / ( 1024.0f * 1024.0f ) );
  2310. // the stream pool usage should match exactly with the streaming buffer iteration results
  2311. Msg( "\nCAsyncWavDataCache: Stream Pool\n" );
  2312. Msg( " Block Size: %d bytes\n", g_pAudioStreamPool->BlockSize() );
  2313. Msg( " Blocks: %d\n", g_pAudioStreamPool->Count() );
  2314. Msg( " Allocated: %.2f MB \n", ( g_pAudioStreamPool->Count() * g_pAudioStreamPool->BlockSize() ) / ( 1024.0f * 1024.0f ) );
  2315. Msg( " Pool: %.2f MB \n", g_pAudioStreamPool->Size() / ( 1024.0f * 1024.0f ) );
  2316. Msg( " Dead: %d\n", m_DeadBuffers.Count() );
  2317. }
  2318. }
  2319. }
  2320. //-----------------------------------------------------------------------------
  2321. // This is a scary function. It doesn't check for possible outstanding async operations
  2322. // It is part of shutdown.
  2323. //-----------------------------------------------------------------------------
  2324. void CAsyncWavDataCache::Clear()
  2325. {
  2326. int i;
  2327. for ( i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) )
  2328. {
  2329. CacheEntry_t& dat = m_CacheHandles[i];
  2330. s_WaveCache.CacheRemove( dat.handle );
  2331. }
  2332. m_CacheHandles.RemoveAll();
  2333. FOR_EACH_LL( m_StreamedHandles, i )
  2334. {
  2335. StreamedEntry_t &dat = m_StreamedHandles[i];
  2336. for ( int j=0; j<dat.m_numBuffers; ++j )
  2337. {
  2338. s_WaveCache.BreakLock( dat.m_hWaveData[j] );
  2339. s_WaveCache.CacheRemove( dat.m_hWaveData[j] );
  2340. }
  2341. }
  2342. m_StreamedHandles.RemoveAll();
  2343. for ( int i = 0; i < m_DeadBuffers.Count(); i++ )
  2344. {
  2345. s_WaveCache.BreakLock( m_DeadBuffers[i].hWaveData );
  2346. s_WaveCache.CacheRemove( m_DeadBuffers[i].hWaveData );
  2347. }
  2348. m_DeadBuffers.Purge();
  2349. m_BufferList.RemoveAll();
  2350. }
  2351. static CAsyncWavDataCache g_AsyncWaveDataCache;
  2352. IAsyncWavDataCache *wavedatacache = &g_AsyncWaveDataCache;
  2353. CON_COMMAND( snd_async_flush, "Flush all unlocked async audio data" )
  2354. {
  2355. g_AsyncWaveDataCache.Flush();
  2356. }
  2357. CON_COMMAND( snd_async_showmem, "Show async memory stats" )
  2358. {
  2359. g_AsyncWaveDataCache.SpewMemoryUsage( CAsyncWavDataCache::SPEW_ALL );
  2360. }
  2361. CON_COMMAND( snd_async_showmem_summary, "Show brief async memory stats" )
  2362. {
  2363. g_AsyncWaveDataCache.SpewMemoryUsage( CAsyncWavDataCache::SPEW_BASIC );
  2364. }
  2365. CON_COMMAND( snd_async_showmem_music, "Show async memory stats for just non-streamed music" )
  2366. {
  2367. g_AsyncWaveDataCache.SpewMemoryUsage( CAsyncWavDataCache::SPEW_MUSIC_NONSTREAMING );
  2368. }
  2369. //-----------------------------------------------------------------------------
  2370. // Purpose:
  2371. // Input : *pFileName -
  2372. // dataOffset -
  2373. // dataSize -
  2374. //-----------------------------------------------------------------------------
  2375. void PrefetchDataStream( const char *pFileName, int dataOffset, int dataSize )
  2376. {
  2377. if ( IsGameConsole() )
  2378. {
  2379. // Xbox streaming buffer implementation does not support this "hinting"
  2380. return;
  2381. }
  2382. wavedatacache->PrefetchCache( pFileName, dataSize, dataOffset );
  2383. }
  2384. //-----------------------------------------------------------------------------
  2385. // Purpose: This is an instance of a stream.
  2386. // This contains the file handle and streaming buffer
  2387. // The mixer doesn't know the file is streaming. The IWaveData
  2388. // abstracts the data access. The mixer abstracts data encoding/format
  2389. //-----------------------------------------------------------------------------
  2390. class CWaveDataStreamAsync : public IWaveData
  2391. {
  2392. public:
  2393. CWaveDataStreamAsync( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int fileStart, int fileSize, CSfxTable *sfx, int startOffset, SoundError &soundError );
  2394. ~CWaveDataStreamAsync( void );
  2395. // return the source pointer (mixer needs this to determine some things like sampling rate)
  2396. CAudioSource &Source( void ) { return m_source; }
  2397. // Read data from the source - this is the primary function of a IWaveData subclass
  2398. // Get the data from the buffer (or reload from disk)
  2399. virtual int ReadSourceData( void **pData, int64 sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
  2400. bool IsValid() { return m_bValid; }
  2401. virtual bool IsReadyToMix();
  2402. virtual void UpdateLoopPosition( int nLoopPosition );
  2403. private:
  2404. CWaveDataStreamAsync( const CWaveDataStreamAsync & );
  2405. //-----------------------------------------------------------------------------
  2406. // Purpose:
  2407. // Output : byte
  2408. //-----------------------------------------------------------------------------
  2409. inline byte *GetCachedDataPointer()
  2410. {
  2411. VPROF( "CWaveDataStreamAsync::GetCachedDataPointer" );
  2412. CAudioSourceCachedInfo *info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
  2413. if ( !info )
  2414. {
  2415. Assert( !"CAudioSourceWave::GetCachedDataPointer info == NULL" );
  2416. return NULL;
  2417. }
  2418. return (byte *)info->CachedData();
  2419. }
  2420. char const *GetFileName();
  2421. CAudioSource &m_source; // wave source
  2422. IWaveStreamSource *m_pStreamSource; // streaming
  2423. int m_sampleSize; // size of a sample in bytes
  2424. int m_waveSize; // total number of samples in the file
  2425. int m_bufferSize; // size of transfer buffer in samples
  2426. char *m_pBuffer; // transfer buffer
  2427. int64 m_sampleIndex;
  2428. int m_bufferCount;
  2429. int m_dataStart;
  2430. int m_dataSize;
  2431. WaveCacheHandle_t m_hCache;
  2432. StreamHandle_t m_hStream;
  2433. FileNameHandle_t m_hFileName;
  2434. CAudioSourceCachedInfoHandle_t m_AudioCacheHandle;
  2435. int m_nCachedDataSize;
  2436. CSfxTable *m_pSfx;
  2437. // These members are used to handle more gracefully exhausted streaming
  2438. static const int SIZE_LAST_SAMPLE = 8; // Support up to stereo 32 bits sample
  2439. uint8 m_LastSample[SIZE_LAST_SAMPLE];
  2440. unsigned int m_bValid : 1;
  2441. };
  2442. CWaveDataStreamAsync::CWaveDataStreamAsync
  2443. (
  2444. CAudioSource &source,
  2445. IWaveStreamSource *pStreamSource,
  2446. const char *pFileName,
  2447. int fileStart,
  2448. int fileSize,
  2449. CSfxTable *sfx,
  2450. int startOffset,
  2451. SoundError &soundError
  2452. ) :
  2453. m_source( source ),
  2454. m_dataStart( fileStart ),
  2455. m_dataSize( fileSize ),
  2456. m_pStreamSource( pStreamSource ),
  2457. m_bValid( false ),
  2458. m_hCache( 0 ),
  2459. m_hStream( INVALID_STREAM_HANDLE ),
  2460. m_hFileName( 0 ),
  2461. m_pSfx( sfx ),
  2462. m_pBuffer( NULL )
  2463. {
  2464. soundError = SE_OK;
  2465. m_hFileName = g_pFileSystem->FindOrAddFileName( pFileName );
  2466. // nothing in the buffer yet
  2467. m_sampleIndex = 0;
  2468. m_bufferCount = 0;
  2469. m_nCachedDataSize = 0;
  2470. if ( m_dataSize <= 0 )
  2471. {
  2472. DevMsg( 1, "Can't find streaming wav file: sound\\%s\n", GetFileName() );
  2473. soundError = SE_FILE_NOT_FOUND;
  2474. return;
  2475. }
  2476. if ( IsPC() )
  2477. {
  2478. m_hCache = wavedatacache->AsyncLoadCache( GetFileName(), m_dataSize, m_dataStart );
  2479. m_pBuffer = new char[SINGLE_BUFFER_SIZE];
  2480. Q_memset( m_pBuffer, 0, SINGLE_BUFFER_SIZE );
  2481. // size of a sample
  2482. m_sampleSize = source.SampleSize();
  2483. // size in samples of the buffer
  2484. m_bufferSize = SINGLE_BUFFER_SIZE / m_sampleSize;
  2485. // size in samples (not bytes) of the wave itself
  2486. m_waveSize = fileSize / m_sampleSize;
  2487. m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
  2488. }
  2489. if ( IsGameConsole() )
  2490. {
  2491. // size of a sample
  2492. m_sampleSize = source.SampleSize();
  2493. // size in samples (not bytes) of the wave itself
  2494. m_waveSize = fileSize / m_sampleSize;
  2495. // mark as transient to let streamer better configure the buffers for a streaming wave
  2496. // absence of transient indicates a memory resident wave
  2497. streamFlags_t flags = STREAMED_FROMDVD | STREAMED_TRANSIENT;
  2498. if ( IsGameConsole() )
  2499. {
  2500. char cleanName[MAX_PATH];
  2501. V_strncpy( cleanName, pFileName, sizeof( cleanName ) );
  2502. V_FixSlashes( cleanName, '/' );
  2503. bool bForceToSinglePlay = false;
  2504. if ( bForceToSinglePlay ||
  2505. V_stristr( cleanName, "music/" ) ||
  2506. V_stristr( cleanName, "commentary/" ) ||
  2507. V_stristr( cleanName, "playonce/" ))
  2508. {
  2509. // music discards and cycles its buffers
  2510. flags |= STREAMED_SINGLEPLAY;
  2511. }
  2512. else if ( !source.IsSentenceWord() && V_stristr( cleanName, "vo/" ) )
  2513. {
  2514. // vo discards and cycles its buffers, except for sentence sources which recur
  2515. flags |= STREAMED_SINGLEPLAY;
  2516. }
  2517. }
  2518. #if IsX360()
  2519. // the xma mixer expects quantum xma blocks
  2520. COMPILE_TIME_ASSERT( ( STREAM_BUFFER_DATASIZE % XMA_BLOCK_SIZE ) == 0 );
  2521. // streaming buffers must be sector compliant
  2522. COMPILE_TIME_ASSERT( ( STREAM_BUFFER_DATASIZE % XBOX_DVD_SECTORSIZE ) == 0 );
  2523. #endif
  2524. int transferSize = STREAM_BUFFER_DATASIZE;
  2525. int nAlignedDataSize = AlignValue( m_dataSize, transferSize );
  2526. int numBuffers = nAlignedDataSize / transferSize; // Number of buffers that we would need at max
  2527. int nNecessaryBuffers = source.IsLooped() ? 4 : 2; // By default we need 2 buffers, but when looping the safe number is 4
  2528. nNecessaryBuffers = MIN( nNecessaryBuffers, STREAM_BUFFER_COUNT ); // Max numbers of buffers allowed on that platform
  2529. numBuffers = MIN( numBuffers, nNecessaryBuffers ); // Don't allocate more buffers than necessary to load completely the sound
  2530. if ( numBuffers == 1 )
  2531. {
  2532. // the transfer buffer can be exact sized
  2533. // [oliviern] I kept the old behavior but it seems to me that this would fragment the memory (and would not help in worst case anyway)
  2534. transferSize = m_dataSize;
  2535. }
  2536. // allocate a transfer buffer
  2537. // when multiple buffering, exactly matches the size of the streaming buffer
  2538. // ensures that a streaming buffer can be entirely consumed and requeued during the transfer
  2539. m_pBuffer = new char[transferSize];
  2540. // size in samples of the transfer buffer
  2541. m_bufferSize = transferSize / m_sampleSize;
  2542. int loopStart;
  2543. if ( source.IsLooped() )
  2544. {
  2545. int loopBlock;
  2546. loopStart = m_pStreamSource->GetLoopingInfo( &loopBlock, NULL, NULL ) * m_sampleSize;
  2547. // Note that the loop start is inaccurate for some format (like WAVE_FORMAT_MP3).
  2548. // It is a bytes position in uncompressed samples, but this is not valid for MP3.
  2549. // We actually update the loopStart when we parse the sound (while playing it) and we find the MP3 frame that contains the loop position.
  2550. // We then call UpdateLoopPosition() with the corresponding MP3 frame.
  2551. // In most cases, the full MP3 sound is going to fit in the streaming buffers. For long sounds when the looping points is far (like at the beginning),
  2552. // loopStart is going to be updated before it is actually used.
  2553. // There is a potential for rare cases where we will start streaming with an incorrect loop position, then update the loop point at a later time.
  2554. // In this case, we may stream some memory for nothing or in some extreme cases, have the streamed buffers not ready in time.
  2555. // This would happen only for the first loop.
  2556. // The XMA format fixes that by approximating loopStart with loopBlock (because compressed samples are fortunately aligned on 2048 bytes).
  2557. #if defined( _GAMECONSOLE )
  2558. switch ( source.Format() )
  2559. {
  2560. #if IsX360()
  2561. case WAVE_FORMAT_XMA:
  2562. // xma works in blocks, mixer handles inter-block accurate loop positioning
  2563. // block streaming will cycle from the block where the loop occurs
  2564. loopStart = loopBlock * XMA_BLOCK_SIZE;
  2565. break;
  2566. #endif
  2567. #if IsPS3()
  2568. case WAVE_FORMAT_MP3:
  2569. loopStart /= 10; // We assume that we have a compression factor of 10.
  2570. // With the streamed buffer, it is better to start before than after (as the current impl. reads several buffers forward).
  2571. if ( loopStart > fileSize )
  2572. {
  2573. // Make sure we are in a reasonable range
  2574. loopStart = fileSize - transferSize;
  2575. if ( loopStart < 0 )
  2576. {
  2577. loopStart = 0;
  2578. }
  2579. }
  2580. break;
  2581. case WAVE_FORMAT_TEMP:
  2582. // Uncompressed, so keep it roughly the same
  2583. break;
  2584. #endif
  2585. default:
  2586. // Nothing to fix up
  2587. break;
  2588. }
  2589. #endif
  2590. }
  2591. else
  2592. {
  2593. // sample not looped
  2594. loopStart = -1;
  2595. }
  2596. // load the file piecewise through a buffering implementation
  2597. m_hStream = wavedatacache->OpenStreamedLoad( pFileName, m_dataSize, m_dataStart, startOffset, loopStart, STREAM_BUFFER_DATASIZE, numBuffers, flags, soundError );
  2598. if ( m_hStream == INVALID_STREAM_HANDLE )
  2599. {
  2600. if ( soundError == SE_NO_STREAM_BUFFER )
  2601. {
  2602. DevWarning( "[Sound] Not enough stream buffer available to setup wav file: '\\%s'\n", GetFileName() );
  2603. }
  2604. else
  2605. {
  2606. DevWarning( "[Sound] Failed to setup streaming wav file: sound\\%s\n", GetFileName() );
  2607. }
  2608. m_bValid = false;
  2609. return;
  2610. }
  2611. else
  2612. {
  2613. m_sampleIndex = startOffset; // Do not forget to initialize the sample index at the correct position
  2614. // otherwise this is going to put the whole streaming algorithm off.
  2615. // The whole streaming layer in this file should be re-written by the way as it is needlessly complicated
  2616. // and only works in very specific conditions.
  2617. }
  2618. }
  2619. V_memset( m_LastSample, 0, sizeof( m_LastSample ) );
  2620. m_bValid = true;
  2621. }
  2622. //-----------------------------------------------------------------------------
  2623. // Purpose:
  2624. //-----------------------------------------------------------------------------
  2625. CWaveDataStreamAsync::~CWaveDataStreamAsync( void )
  2626. {
  2627. if ( IsPC() && m_source.IsPlayOnce() && m_source.CanDelete() )
  2628. {
  2629. m_source.SetPlayOnce( false ); // in case it gets used again
  2630. wavedatacache->Unload( m_hCache );
  2631. }
  2632. if ( IsGameConsole() )
  2633. {
  2634. wavedatacache->CloseStreamedLoad( m_hStream );
  2635. }
  2636. delete [] m_pBuffer;
  2637. }
  2638. //-----------------------------------------------------------------------------
  2639. // Purpose:
  2640. // Output : char const
  2641. //-----------------------------------------------------------------------------
  2642. char const *CWaveDataStreamAsync::GetFileName()
  2643. {
  2644. static char fn[MAX_PATH];
  2645. if ( m_hFileName )
  2646. {
  2647. if ( g_pFileSystem->String( m_hFileName, fn, sizeof( fn ) ) )
  2648. {
  2649. return fn;
  2650. }
  2651. }
  2652. Assert( 0 );
  2653. return "";
  2654. }
  2655. //-----------------------------------------------------------------------------
  2656. // Purpose:
  2657. // Output : Returns true on success, false on failure.
  2658. //-----------------------------------------------------------------------------
  2659. bool CWaveDataStreamAsync::IsReadyToMix()
  2660. {
  2661. if ( IsPC() )
  2662. {
  2663. // If not async loaded, start mixing right away
  2664. if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() )
  2665. {
  2666. return true;
  2667. }
  2668. bool bCacheValid;
  2669. bool bLoaded = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid );
  2670. if ( !bCacheValid )
  2671. {
  2672. wavedatacache->RestartDataLoad( &m_hCache, GetFileName(), m_dataSize, m_dataStart );
  2673. }
  2674. // When laying off a movie, all sound access is forced to be synchronous to get better
  2675. // synchronization (avoids async loading random delay)
  2676. if ( g_pEngineToolInternal->IsRecordingMovie() )
  2677. {
  2678. return true;
  2679. }
  2680. return bLoaded;
  2681. }
  2682. if ( IsGameConsole() )
  2683. {
  2684. return wavedatacache->IsStreamedDataReady( m_hStream );
  2685. }
  2686. return false;
  2687. }
  2688. //-----------------------------------------------------------------------------
  2689. // Purpose:
  2690. // Output : Returns true on success, false on failure.
  2691. //-----------------------------------------------------------------------------
  2692. void CWaveDataStreamAsync::UpdateLoopPosition( int nLoopPosition )
  2693. {
  2694. if ( m_hStream != INVALID_STREAM_HANDLE )
  2695. {
  2696. wavedatacache->UpdateLoopPosition( m_hStream, nLoopPosition );
  2697. }
  2698. }
  2699. //-----------------------------------------------------------------------------
  2700. // Purpose: Read data from the source - this is the primary function of a IWaveData subclass
  2701. // Get the data from the buffer (or reload from disk)
  2702. // Input : **pData -
  2703. // sampleIndex -
  2704. // sampleCount -
  2705. // copyBuf[AUDIOSOURCE_COPYBUF_SIZE] -
  2706. // Output : int
  2707. //-----------------------------------------------------------------------------
  2708. int CWaveDataStreamAsync::ReadSourceData( void **pData, int64 sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
  2709. {
  2710. int nOldBufferCount = m_bufferCount;
  2711. int nOldSampleIndex = m_sampleIndex;
  2712. // Current file position
  2713. int seekpos = m_dataStart + m_sampleIndex * m_sampleSize;
  2714. // wrap position if looping
  2715. if ( m_source.IsLooped() )
  2716. {
  2717. sampleIndex = m_pStreamSource->UpdateLoopingSamplePosition( sampleIndex );
  2718. if ( sampleIndex < m_sampleIndex )
  2719. {
  2720. if ( snd_report_loop_sound.GetBool() )
  2721. {
  2722. Warning( "[Sound] Sound \"%s\" just looped.\n", GetFileName( ) );
  2723. }
  2724. // looped back, buffer has no samples yet
  2725. m_sampleIndex = sampleIndex;
  2726. m_bufferCount = 0;
  2727. // update file position
  2728. seekpos = m_dataStart + sampleIndex * m_sampleSize;
  2729. }
  2730. }
  2731. // UNDONE: This is an error!!
  2732. // The mixer playing back the stream tried to go backwards!?!?!
  2733. // BUGBUG: Just play the beginning of the buffer until we get to a valid linear position
  2734. if ( sampleIndex < m_sampleIndex )
  2735. sampleIndex = m_sampleIndex;
  2736. // calc sample position relative to the current buffer
  2737. // m_sampleIndex is the sample position of the first byte of the buffer
  2738. sampleIndex -= m_sampleIndex;
  2739. // out of range? refresh buffer
  2740. if ( sampleIndex >= m_bufferCount )
  2741. {
  2742. // advance one buffer (the file is positioned here)
  2743. m_sampleIndex += m_bufferCount;
  2744. // next sample to load
  2745. sampleIndex -= m_bufferCount;
  2746. // if the remainder is greater than one buffer size, seek over it. Otherwise, read the next chunk
  2747. // and leave the remainder as an offset.
  2748. // number of buffers to "skip" (as in the case where we are starting a streaming sound not at the beginning)
  2749. int skips = sampleIndex / m_bufferSize;
  2750. // If we are skipping over a buffer, do it with a seek instead of a read.
  2751. if ( skips )
  2752. {
  2753. // skip directly to next position
  2754. m_sampleIndex += sampleIndex;
  2755. sampleIndex = 0;
  2756. }
  2757. // move the file to the new position
  2758. seekpos = m_dataStart + (m_sampleIndex * m_sampleSize);
  2759. // This is the maximum number of samples we could read from the file
  2760. m_bufferCount = m_waveSize - m_sampleIndex;
  2761. // past the end of the file? stop the wave.
  2762. if ( m_bufferCount <= 0 )
  2763. return 0;
  2764. // clamp available samples to buffer size
  2765. if ( m_bufferCount > m_bufferSize )
  2766. m_bufferCount = m_bufferSize;
  2767. if ( IsPC() )
  2768. {
  2769. // See if we can load in the initial data right out of the cached data lump instead.
  2770. int cacheddatastartpos = ( seekpos - m_dataStart );
  2771. // FastGet doesn't call into IsPrecachedSound if the handle appears valid...
  2772. CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
  2773. if ( !info )
  2774. {
  2775. // Full recache
  2776. info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
  2777. }
  2778. bool startupCacheUsed = false;
  2779. if ( info &&
  2780. ( m_nCachedDataSize > 0 ) &&
  2781. ( cacheddatastartpos < m_nCachedDataSize ) )
  2782. {
  2783. // Get a ptr to the cached data
  2784. const byte *cacheddata = info->CachedData();
  2785. if ( cacheddata )
  2786. {
  2787. // See how many samples of cached data are available (cacheddatastartpos is zero on the first read)
  2788. int availSamples = ( m_nCachedDataSize - cacheddatastartpos ) / m_sampleSize;
  2789. // Clamp to size of our internal buffer
  2790. if ( availSamples > m_bufferSize )
  2791. {
  2792. availSamples = m_bufferSize;
  2793. }
  2794. // Mark how many we are returning
  2795. m_bufferCount = availSamples;
  2796. // Copy raw sample data directly out of cache
  2797. Q_memcpy( m_pBuffer, ( char * )cacheddata + cacheddatastartpos, availSamples * m_sampleSize );
  2798. startupCacheUsed = true;
  2799. }
  2800. }
  2801. // Not in startup cache, grab data from async cache loader (will block if data hasn't arrived yet)
  2802. if ( !startupCacheUsed )
  2803. {
  2804. bool postprocessed = false;
  2805. // read in the max bufferable, available samples
  2806. if ( !wavedatacache->CopyDataIntoMemory(
  2807. m_hCache,
  2808. GetFileName(),
  2809. m_dataSize,
  2810. m_dataStart,
  2811. m_pBuffer,
  2812. m_bufferSize * m_sampleSize,
  2813. seekpos,
  2814. m_bufferCount * m_sampleSize,
  2815. &postprocessed ) )
  2816. {
  2817. return 0;
  2818. }
  2819. // do any conversion the source needs (mixer will decode/decompress)
  2820. if ( !postprocessed )
  2821. {
  2822. // Note that we don't set the postprocessed flag on the underlying data, since for streaming we're copying the
  2823. // original data into this buffer instead.
  2824. m_pStreamSource->UpdateSamples( m_pBuffer, m_bufferCount );
  2825. }
  2826. }
  2827. }
  2828. if ( IsGameConsole() )
  2829. {
  2830. if ( m_hStream != INVALID_STREAM_HANDLE )
  2831. {
  2832. // request available data, may get less
  2833. // drives the buffering
  2834. int nBytesFilled = wavedatacache->CopyStreamedDataIntoMemory(
  2835. m_hStream,
  2836. m_pBuffer,
  2837. m_bufferSize * m_sampleSize,
  2838. seekpos,
  2839. m_bufferCount * m_sampleSize );
  2840. if ( nBytesFilled == 0 )
  2841. {
  2842. // If we reach here, it means the streamer is behind
  2843. // Let's try to recover so the sound does not stop abruptly. It only works for PCM sounds though,
  2844. // for XMA and MP3 sounds, we have to do this at their own level (as we can't send simulated data for them).
  2845. if ( snd_async_stream_recover_from_exhausted_stream.GetBool() && ( m_source.Format() == WAVE_FORMAT_PCM ) )
  2846. {
  2847. if ( m_sampleSize <= SIZE_LAST_SAMPLE )
  2848. {
  2849. if ( snd_async_stream_spew_exhausted_buffer.GetBool() && ( g_pQueuedLoader->IsMapLoading() == false ) )
  2850. {
  2851. static uint sOldTime = 0;
  2852. uint nCurrentTime = Plat_MSTime();
  2853. if ( nCurrentTime >= sOldTime + snd_async_stream_spew_exhausted_buffer_time.GetInt() )
  2854. {
  2855. Warning( "[Sound] The stream buffer is exhausted for sound '%s'. Except after loading, fill a bug to have the number of sounds played reduced.\n", GetFileName() );
  2856. sOldTime = nCurrentTime;
  2857. }
  2858. }
  2859. // This code is not optimized, but hopefully never executed.
  2860. int nSamplesToFill = imin( m_bufferCount, AUDIOSOURCE_COPYBUF_SIZE / m_sampleSize );
  2861. nSamplesToFill = imin( nSamplesToFill, sampleCount );
  2862. if ( copyBuf != NULL )
  2863. {
  2864. for ( int i = 0 ; i < nSamplesToFill ; ++i )
  2865. {
  2866. V_memcpy( &copyBuf[ i * m_sampleSize ], &m_LastSample, m_sampleSize );
  2867. }
  2868. }
  2869. *pData = copyBuf;
  2870. m_bufferCount = nOldBufferCount; // Put back the values to the state before any change were applied.
  2871. m_sampleIndex = nOldSampleIndex; // In some cases, we could actually have a buffer filled with static if the sound was smaller than one buffer long.
  2872. return nSamplesToFill;
  2873. }
  2874. }
  2875. }
  2876. else
  2877. {
  2878. if ( m_sampleSize <= SIZE_LAST_SAMPLE )
  2879. {
  2880. // Let's copy the last sample, in case next time the streamer is too late. We fill with the last sample instead of zero to reduce potential pops.
  2881. V_memcpy( &m_LastSample, &m_pBuffer[ nBytesFilled - m_sampleSize ], m_sampleSize );
  2882. }
  2883. }
  2884. // convert to number of samples in the buffer
  2885. m_bufferCount = nBytesFilled / m_sampleSize;
  2886. }
  2887. else
  2888. {
  2889. return 0;
  2890. }
  2891. // do any conversion now the source needs (mixer will decode/decompress) on this buffer
  2892. m_pStreamSource->UpdateSamples( m_pBuffer, m_bufferCount );
  2893. }
  2894. }
  2895. // If we have some samples in the buffer that are within range of the request
  2896. if ( sampleIndex < m_bufferCount )
  2897. {
  2898. // Get the desired starting sample
  2899. *pData = (void *)&m_pBuffer[sampleIndex * m_sampleSize];
  2900. // max available
  2901. int available = m_bufferCount - sampleIndex;
  2902. // clamp available to max requested
  2903. if ( available > sampleCount )
  2904. available = sampleCount;
  2905. return available;
  2906. }
  2907. return 0;
  2908. }
  2909. //-----------------------------------------------------------------------------
  2910. // Purpose: Iterator for wave data (this is to abstract streaming/buffering)
  2911. //-----------------------------------------------------------------------------
  2912. class CWaveDataMemoryAsync : public IWaveData
  2913. {
  2914. public:
  2915. CWaveDataMemoryAsync( CAudioSource &source );
  2916. ~CWaveDataMemoryAsync( void ) {}
  2917. CAudioSource &Source( void ) { return m_source; }
  2918. virtual int ReadSourceData( void **pData, int64 sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
  2919. virtual bool IsReadyToMix();
  2920. virtual void UpdateLoopPosition( int nLoopPosition );
  2921. private:
  2922. CAudioSource &m_source; // pointer to source
  2923. };
  2924. //-----------------------------------------------------------------------------
  2925. // Purpose:
  2926. // Input : &source -
  2927. //-----------------------------------------------------------------------------
  2928. CWaveDataMemoryAsync::CWaveDataMemoryAsync( CAudioSource &source ) :
  2929. m_source(source)
  2930. {
  2931. }
  2932. //-----------------------------------------------------------------------------
  2933. // Purpose:
  2934. // Input : **pData -
  2935. // sampleIndex -
  2936. // sampleCount -
  2937. // copyBuf[AUDIOSOURCE_COPYBUF_SIZE] -
  2938. // Output : int
  2939. //-----------------------------------------------------------------------------
  2940. int CWaveDataMemoryAsync::ReadSourceData( void **pData, int64 sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
  2941. {
  2942. return m_source.GetOutputData( pData, sampleIndex, sampleCount, copyBuf );
  2943. }
  2944. //-----------------------------------------------------------------------------
  2945. // Purpose:
  2946. // Output : Returns true on success, false on failure.
  2947. //-----------------------------------------------------------------------------
  2948. bool CWaveDataMemoryAsync::IsReadyToMix()
  2949. {
  2950. if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() )
  2951. {
  2952. // Wait until we're pending at least
  2953. if ( m_source.GetCacheStatus() == CAudioSource::AUDIO_NOT_LOADED || m_source.GetCacheStatus() == CAudioSource::AUDIO_ERROR_LOADING )
  2954. {
  2955. // When laying off a movie, all sound access is forced to be synchronous to get better
  2956. // synchronization (avoids async loading random delay)
  2957. if ( g_pEngineToolInternal->IsRecordingMovie() )
  2958. {
  2959. return true;
  2960. }
  2961. return false;
  2962. }
  2963. return true;
  2964. }
  2965. if ( m_source.IsCached() )
  2966. {
  2967. return true;
  2968. }
  2969. if ( IsPC() )
  2970. {
  2971. // Msg( "Waiting for data '%s'\n", m_source.GetFileName() );
  2972. m_source.CacheLoad();
  2973. }
  2974. if ( IsGameConsole() )
  2975. {
  2976. // expected to be resident and valid, otherwise being called prior to load
  2977. Assert( 0 );
  2978. }
  2979. return false;
  2980. }
  2981. void CWaveDataMemoryAsync::UpdateLoopPosition( int nLoopPosition )
  2982. {
  2983. // Should not be necessary in this implementation...
  2984. Assert( false );
  2985. }
  2986. //-----------------------------------------------------------------------------
  2987. // Purpose:
  2988. // Input : &source -
  2989. // *pStreamSource -
  2990. // &io -
  2991. // *pFileName -
  2992. // dataOffset -
  2993. // dataSize -
  2994. // Output : IWaveData
  2995. //-----------------------------------------------------------------------------
  2996. IWaveData *CreateWaveDataStream( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int dataStart, int dataSize, CSfxTable *pSfx, int startOffset, int skipInitialSamples, SoundError &soundError )
  2997. {
  2998. CWaveDataStreamAsync *pStream = new CWaveDataStreamAsync( source, pStreamSource, pFileName, dataStart, dataSize, pSfx, startOffset, soundError );
  2999. if ( !pStream || !pStream->IsValid() )
  3000. {
  3001. delete pStream;
  3002. pStream = NULL;
  3003. }
  3004. return pStream;
  3005. }
  3006. //-----------------------------------------------------------------------------
  3007. // Purpose:
  3008. // Input : &source -
  3009. // Output : IWaveData
  3010. //-----------------------------------------------------------------------------
  3011. IWaveData *CreateWaveDataMemory( CAudioSource &source )
  3012. {
  3013. CWaveDataMemoryAsync *mem = new CWaveDataMemoryAsync( source );
  3014. return mem;
  3015. }
  3016. // set this to zero to revert to the previous scalar code
  3017. #define PHONON_USE_SIMD 0
  3018. namespace
  3019. {
  3020. const int PHONON_FRAME_SIZE = 1024 * 2;
  3021. ThreadHandle_t g_hPhononThread;
  3022. bool g_bPhononThreadExit;
  3023. CThreadFastMutex g_phononMutex;
  3024. IPLAudioFormat g_phononInputFormat, g_phononOutputFormat;
  3025. IPLhandle g_phononContext = 0;
  3026. static const int PHONON_SAMPLE_SIZE = 4;
  3027. struct PhononJob
  3028. {
  3029. ~PhononJob()
  3030. {
  3031. if (phononEffect)
  3032. {
  3033. iplDestroyBinauralEffect(&phononEffect);
  3034. }
  3035. }
  3036. IPLHrtfInterpolation interpolation;
  3037. int nChannels;
  3038. int nDataWidth;
  3039. Vector vec;
  3040. float lerp;
  3041. IPLhandle phononEffect;
  3042. bool bComplete;
  3043. CThreadFastMutex mutex;
  3044. char inputBuf[PHONON_FRAME_SIZE * PHONON_SAMPLE_SIZE];
  3045. short outputBuf[PHONON_FRAME_SIZE * 2];
  3046. float fSubmitTime, fCompleteTime;
  3047. };
  3048. void ProcessPhononJob(PhononJob& job)
  3049. {
  3050. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "ProcessPhononJob %d", PHONON_FRAME_SIZE );
  3051. if (!job.phononEffect)
  3052. {
  3053. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "iplCreateBinauralEffect" );
  3054. IPLerror error = iplCreateBinauralEffect(g_phononContext, g_phononInputFormat, g_phononOutputFormat, &job.phononEffect);
  3055. if (error)
  3056. {
  3057. ConVarRef snd_use_hrtf("snd_use_hrtf");
  3058. snd_use_hrtf.SetValue(false);
  3059. DevMsg("Failed to create binaural effect: %d. Turning off binaural rendering\n", (int)error);
  3060. memset( job.outputBuf, 0, sizeof( job.outputBuf ) );
  3061. return;
  3062. }
  3063. }
  3064. float ALIGN16 phononInputBuf[PHONON_FRAME_SIZE];
  3065. // stereo inputs are scaled by 0.5 to avoid distortion
  3066. float flStereoScale = 0.5f;
  3067. const short *in = (const short*)job.inputBuf;
  3068. // base scalar code for clarity
  3069. #if !PHONON_USE_SIMD
  3070. // if ( !PHONON_USE_SIMD )
  3071. {
  3072. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "ConvertMultiToFloat" );
  3073. for ( int n = 0; n != PHONON_FRAME_SIZE; ++n )
  3074. {
  3075. int value = 0;
  3076. for ( int i = 0; i != job.nChannels; ++i )
  3077. {
  3078. value += *in / job.nChannels;
  3079. ++in;
  3080. }
  3081. phononInputBuf[ n ] = value / ( 65536.0 * 0.5 );
  3082. }
  3083. }
  3084. #else //!PHONON_USE_SIMD
  3085. // else
  3086. {
  3087. const float flSigned16ToUnitScale = 1.0f / 32768.0f;
  3088. if ( job.nChannels == 1 )
  3089. {
  3090. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "ConvertShortToFloat" );
  3091. ConvertShortToFloat( phononInputBuf, in, PHONON_FRAME_SIZE, flSigned16ToUnitScale );
  3092. }
  3093. else
  3094. {
  3095. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "SumStereoShortToFloat" );
  3096. SumStereoShortToFloat( phononInputBuf, in, PHONON_FRAME_SIZE, flSigned16ToUnitScale * flStereoScale );
  3097. }
  3098. }
  3099. #endif //!PHONON_USE_SIMD
  3100. float ALIGN16 phononOutputBuf[PHONON_FRAME_SIZE * 2];
  3101. memset( phononOutputBuf, 0, sizeof( phononOutputBuf ) );
  3102. IPLAudioBuffer input_buffer;
  3103. input_buffer.format = g_phononInputFormat;
  3104. input_buffer.numSamples = PHONON_FRAME_SIZE;
  3105. input_buffer.interleavedBuffer = phononInputBuf;
  3106. input_buffer.deinterleavedBuffer = nullptr;
  3107. IPLAudioBuffer output_buffer;
  3108. output_buffer.format = g_phononOutputFormat;
  3109. output_buffer.numSamples = PHONON_FRAME_SIZE;
  3110. output_buffer.interleavedBuffer = phononOutputBuf;
  3111. output_buffer.deinterleavedBuffer = nullptr;
  3112. IPLVector3 iplvec;
  3113. iplvec.x = job.vec.x;
  3114. iplvec.y = job.vec.y;
  3115. iplvec.z = job.vec.z;
  3116. {
  3117. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "iplApplyBinauralEffect" );
  3118. iplApplyBinauralEffect( job.phononEffect, input_buffer, iplvec, job.interpolation, output_buffer );
  3119. }
  3120. #if !PHONON_USE_SIMD
  3121. //if ( !PHONON_USE_SIMD )
  3122. {
  3123. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "Convert" );
  3124. for ( int n = 0; n < PHONON_FRAME_SIZE * 2; ++n )
  3125. {
  3126. Assert( phononOutputBuf[ n ] > -8.0 && phononOutputBuf[ n ] < 8.0 );
  3127. int out = static_cast<int>( phononOutputBuf[ n ] * 65536.0 / 16.0 );
  3128. job.outputBuf[ n ] = static_cast<short>( out );
  3129. }
  3130. }
  3131. #else //!PHONON_USE_SIMD
  3132. //else
  3133. {
  3134. TM_ZONE( TELEMETRY_LEVEL0, TMZF_NONE, "ConvertSIMD" );
  3135. // convert from float [-1,1] to short [-32767, 32768], but phonon may have amplified so reduce to 0.125f
  3136. float flScaleFactorFloat = ( 32768.0f ) * ( 0.125f );
  3137. ConvertFloatToShort( job.outputBuf, &phononOutputBuf[ 0 ], PHONON_FRAME_SIZE * 2, flScaleFactorFloat );
  3138. }
  3139. #endif //!PHONON_USE_SIMD
  3140. }
  3141. void SetupPhononContext()
  3142. {
  3143. if (g_phononContext)
  3144. {
  3145. return;
  3146. }
  3147. ConVarRef snd_use_hrtf("snd_use_hrtf");
  3148. IPLGlobalContext iplContext;
  3149. memset(&iplContext, 0, sizeof(iplContext));
  3150. iplContext.logCallback = nullptr;
  3151. IPLDspParams dspParams;
  3152. dspParams.samplingRate = 44100;
  3153. dspParams.frameSize = PHONON_FRAME_SIZE;
  3154. IPLerror phononError = iplCreate3DContext(iplContext, dspParams, nullptr, &g_phononContext);
  3155. if (phononError != IPL_STATUS_SUCCESS)
  3156. {
  3157. DevMsg( "Failed to init phonon: %d\n", (int)phononError );
  3158. snd_use_hrtf.SetValue(false);
  3159. return;
  3160. }
  3161. g_phononInputFormat.channelLayoutType = IPL_CHANNELLAYOUTTYPE_SPEAKERS;
  3162. g_phononInputFormat.channelLayout = IPL_CHANNELLAYOUT_MONO;
  3163. g_phononInputFormat.numSpeakers = 1;
  3164. g_phononInputFormat.speakerDirections = nullptr;
  3165. g_phononInputFormat.channelOrder = IPL_CHANNELORDER_INTERLEAVED;
  3166. g_phononOutputFormat = g_phononInputFormat;
  3167. g_phononOutputFormat.channelLayout = IPL_CHANNELLAYOUT_STEREO;
  3168. g_phononOutputFormat.numSpeakers = 2;
  3169. }
  3170. CTSQueue<PhononJob*> g_phononJobQueue, g_phononDeleteQueue;
  3171. CThreadEvent g_eventPhononThread;
  3172. int g_benchSecond = 0;
  3173. float g_sleepTime = 0.0;
  3174. float g_fMaxJobTime = 0.0;
  3175. float g_fBenchRunway = -1.0f;
  3176. int g_nBenchSkips = 0;
  3177. int g_nBenchNumSounds = 0;
  3178. int g_nBenchMaxNumSounds = 0;
  3179. CThreadFastMutex g_mutexBenchSkips;
  3180. ConVar snd_hrtf_benchmark("snd_hrtf_benchmark", "0", FCVAR_NONE, "");
  3181. void PhononThread()
  3182. {
  3183. SetupPhononContext();
  3184. while (!g_bPhononThreadExit)
  3185. {
  3186. PhononJob *job = nullptr;
  3187. while (g_phononJobQueue.PopItem(&job))
  3188. {
  3189. // float t = Plat_FloatTime();
  3190. ProcessPhononJob(*job);
  3191. // DevMsg("HRTF: Processed HRTF job %p in %f\n", job, Plat_FloatTime() - t);
  3192. if (snd_hrtf_benchmark.GetBool())
  3193. {
  3194. float fTime = Plat_FloatTime();
  3195. float fElapsed = fTime - job->fSubmitTime;
  3196. job->fCompleteTime = fTime;
  3197. if (fElapsed > g_fMaxJobTime)
  3198. {
  3199. g_fMaxJobTime = fElapsed;
  3200. }
  3201. }
  3202. job->mutex.Lock();
  3203. job->bComplete = true;
  3204. job->mutex.Unlock();
  3205. }
  3206. while (g_phononDeleteQueue.Count() > 0 && g_phononJobQueue.Count() == 0)
  3207. {
  3208. if (g_phononDeleteQueue.PopItem(&job))
  3209. {
  3210. delete job;
  3211. }
  3212. }
  3213. if (snd_hrtf_benchmark.GetBool())
  3214. {
  3215. float start_sleep = Plat_FloatTime();
  3216. g_eventPhononThread.Wait(10);
  3217. float end_sleep = Plat_FloatTime();
  3218. int sec = (int)end_sleep;
  3219. if (sec != g_benchSecond)
  3220. {
  3221. g_sleepTime += float(sec) - start_sleep;
  3222. g_mutexBenchSkips.Lock();
  3223. const int nSkips = g_nBenchSkips;
  3224. const float fRunway = g_fBenchRunway;
  3225. g_nBenchSkips = 0;
  3226. g_fBenchRunway = -1.0f;
  3227. const int nSounds = g_nBenchMaxNumSounds;
  3228. g_nBenchMaxNumSounds = g_nBenchNumSounds;
  3229. g_mutexBenchSkips.Unlock();
  3230. float busy = (1.0 - g_sleepTime)*100.0f;
  3231. DevMsg("HRTF: sounds: %d busy: %0.2f%% Max latency: %0.3fms runway: %0.3fms skips: %d\n", nSounds, busy, g_fMaxJobTime*1000.0f, fRunway*1000.0f, nSkips);
  3232. g_sleepTime = end_sleep - float(sec);
  3233. g_fMaxJobTime = 0.0f;
  3234. g_benchSecond = sec;
  3235. }
  3236. else
  3237. {
  3238. g_sleepTime += end_sleep - start_sleep;
  3239. }
  3240. }
  3241. else
  3242. {
  3243. //Wait for a signal that there is more work or at
  3244. //most 10ms.
  3245. g_eventPhononThread.Wait(10);
  3246. }
  3247. }
  3248. }
  3249. }
  3250. ConVar snd_hrtf_passthrough("snd_hrtf_passthrough", "0", FCVAR_NONE, "");
  3251. ConVar snd_hrtf_async("snd_hrtf_async", "1", FCVAR_NONE, "");
  3252. ConVar snd_hrtf_lerp_method("snd_hrtf_lerp_method", "0", FCVAR_NONE, "");
  3253. void StartPhononThread()
  3254. {
  3255. if (!snd_hrtf_async.GetBool())
  3256. {
  3257. SetupPhononContext();
  3258. return;
  3259. }
  3260. if (!g_hPhononThread )
  3261. {
  3262. g_bPhononThreadExit = false;
  3263. g_hPhononThread = ThreadExecuteSolo("SndMixPhonon", PhononThread);
  3264. if (!g_hPhononThread)
  3265. {
  3266. DevMsg("Failed to start phonon thread\n");
  3267. }
  3268. }
  3269. }
  3270. void ShutdownPhononThread()
  3271. {
  3272. if (g_hPhononThread)
  3273. {
  3274. g_bPhononThreadExit = true;
  3275. g_eventPhononThread.Set();
  3276. ThreadJoin(g_hPhononThread);
  3277. ReleaseThreadHandle(g_hPhononThread);
  3278. g_hPhononThread = NULL;
  3279. }
  3280. }
  3281. class CWaveDataHRTF : public IWaveData
  3282. {
  3283. public:
  3284. CWaveDataHRTF(IWaveData* pData, hrtf_info_t* dir);
  3285. ~CWaveDataHRTF();
  3286. CAudioSource &Source() { return m_pData->Source(); }
  3287. virtual int ReadSourceData(void **pData, int64 sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE]);
  3288. virtual bool IsReadyToMix()
  3289. {
  3290. if (snd_hrtf_passthrough.GetBool())
  3291. {
  3292. return m_pData->IsReadyToMix();
  3293. }
  3294. if (m_bAsync == false)
  3295. {
  3296. return m_pData->IsReadyToMix() || SamplesAvailableToRead() >= 512;
  3297. }
  3298. TryCompleteJob();
  3299. bool bResult = m_bEOF || SamplesAvailableToRead() > 0;
  3300. if (snd_hrtf_benchmark.GetBool())
  3301. {
  3302. if (bResult && m_bHasStartedMixing == false)
  3303. {
  3304. m_bHasStartedMixing = true;
  3305. }
  3306. else if (bResult == false && m_bHasStartedMixing && !m_bEOF)
  3307. {
  3308. g_mutexBenchSkips.Lock();
  3309. ++g_nBenchSkips;
  3310. g_mutexBenchSkips.Unlock();
  3311. }
  3312. else if (bResult && m_bHasStartedMixing && SamplesAvailableToRead() < 512 + PHONON_FRAME_SIZE)
  3313. {
  3314. float fRunway = Plat_FloatTime() - m_pJob->fCompleteTime;
  3315. g_mutexBenchSkips.Lock();
  3316. if (fRunway < g_fBenchRunway || g_fBenchRunway == -1.0f)
  3317. {
  3318. g_fBenchRunway = fRunway;
  3319. }
  3320. g_mutexBenchSkips.Unlock();
  3321. }
  3322. }
  3323. TrySubmitJob();
  3324. TryCompleteJob();
  3325. return bResult;
  3326. }
  3327. virtual void UpdateLoopPosition(int nLoopPosition)
  3328. {
  3329. m_pData->UpdateLoopPosition(nLoopPosition);
  3330. }
  3331. private:
  3332. IWaveData* m_pData;
  3333. hrtf_info_t* m_pDir;
  3334. void TryCompleteJob();
  3335. void TrySubmitJob();
  3336. PhononJob *m_pJob;
  3337. bool m_bJobPending;
  3338. int m_nChannels;
  3339. int m_nDataWidth;
  3340. int64 m_nDataStreamReadPos;
  3341. bool m_bEOF;
  3342. bool m_bAsync;
  3343. int64 m_readIndex;
  3344. char m_outputBuf[PHONON_FRAME_SIZE * PHONON_SAMPLE_SIZE * 2];
  3345. char* m_readPos;
  3346. char* m_writePos;
  3347. bool m_bHasStartedMixing;
  3348. bool m_bBenchmarking;
  3349. int SamplesAvailableToRead() const
  3350. {
  3351. if (m_readPos <= m_writePos)
  3352. {
  3353. return (m_writePos - m_readPos)/PHONON_SAMPLE_SIZE;
  3354. }
  3355. else
  3356. {
  3357. const char *end = m_outputBuf + sizeof(m_outputBuf);
  3358. return ((end - m_readPos) + (m_writePos - m_outputBuf)) / PHONON_SAMPLE_SIZE;
  3359. }
  3360. }
  3361. int SamplesAvailableToWrite() const
  3362. {
  3363. return sizeof(m_outputBuf) / PHONON_SAMPLE_SIZE - SamplesAvailableToRead();
  3364. }
  3365. CWaveDataHRTF(const CWaveDataHRTF&);
  3366. void operator=(const CWaveDataHRTF&);
  3367. };
  3368. CWaveDataHRTF::CWaveDataHRTF(IWaveData* pData, hrtf_info_t* dir) : m_pData(pData), m_pDir(dir), m_bJobPending(false), m_nDataStreamReadPos(0), m_bEOF(false), m_bAsync(snd_hrtf_async.GetBool()), m_bHasStartedMixing(false), m_bBenchmarking(snd_hrtf_benchmark.GetBool())
  3369. {
  3370. if ( m_bBenchmarking )
  3371. {
  3372. g_mutexBenchSkips.Lock();
  3373. g_nBenchNumSounds++;
  3374. if ( g_nBenchNumSounds > g_nBenchMaxNumSounds )
  3375. {
  3376. g_nBenchMaxNumSounds = g_nBenchNumSounds;
  3377. }
  3378. g_mutexBenchSkips.Unlock();
  3379. }
  3380. m_pJob = new PhononJob;
  3381. memset(m_pJob, 0, sizeof(*m_pJob));
  3382. m_readIndex = 0;
  3383. m_readPos = m_writePos = m_outputBuf;
  3384. if (pData->Source().IsStereoWav())
  3385. {
  3386. m_nChannels = 2;
  3387. }
  3388. else
  3389. {
  3390. m_nChannels = 1;
  3391. }
  3392. m_nDataWidth = 2;
  3393. }
  3394. CWaveDataHRTF::~CWaveDataHRTF()
  3395. {
  3396. if ( m_bBenchmarking )
  3397. {
  3398. g_mutexBenchSkips.Lock();
  3399. g_nBenchNumSounds--;
  3400. g_mutexBenchSkips.Unlock();
  3401. }
  3402. if (m_bAsync)
  3403. {
  3404. g_phononDeleteQueue.PushItem(m_pJob);
  3405. g_eventPhononThread.Set();
  3406. }
  3407. else
  3408. delete m_pJob;
  3409. delete m_pData;
  3410. }
  3411. int CWaveDataHRTF::ReadSourceData(void **pData, int64 sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE])
  3412. {
  3413. if (snd_hrtf_passthrough.GetBool())
  3414. {
  3415. return m_pData->ReadSourceData(pData, sampleIndex, sampleCount, copyBuf);
  3416. }
  3417. if (m_bAsync)
  3418. {
  3419. TryCompleteJob();
  3420. }
  3421. else
  3422. {
  3423. int nLastAvail = -1;
  3424. while (SamplesAvailableToRead() < sampleCount && SamplesAvailableToRead() > nLastAvail && SamplesAvailableToWrite() > PHONON_FRAME_SIZE && m_pData->IsReadyToMix())
  3425. {
  3426. nLastAvail = SamplesAvailableToRead();
  3427. TrySubmitJob();
  3428. TryCompleteJob();
  3429. }
  3430. }
  3431. const int nAvail = SamplesAvailableToRead();
  3432. if (nAvail == 0 && m_bEOF)
  3433. {
  3434. *pData = nullptr;
  3435. return 0;
  3436. }
  3437. int nRead = sampleCount < nAvail ? sampleCount : nAvail;
  3438. if (nAvail < sampleCount && !m_bEOF)
  3439. {
  3440. DevMsg("HRTF: Not enough data, wanted %d, got %d\n", sampleCount, nAvail);
  3441. g_mutexBenchSkips.Lock();
  3442. ++g_nBenchSkips;
  3443. g_mutexBenchSkips.Unlock();
  3444. }
  3445. if (nRead > AUDIOSOURCE_COPYBUF_SIZE / PHONON_SAMPLE_SIZE)
  3446. {
  3447. nRead = AUDIOSOURCE_COPYBUF_SIZE / PHONON_SAMPLE_SIZE;
  3448. }
  3449. if (m_readIndex != sampleIndex)
  3450. {
  3451. Warning("Read index mismatch sampleIndex: %d vs %d\n", (int)m_readIndex, (int)sampleIndex);
  3452. }
  3453. m_readIndex += nRead;
  3454. if ((m_outputBuf + sizeof(m_outputBuf)) - m_readPos >= nRead * PHONON_SAMPLE_SIZE)
  3455. {
  3456. memcpy(copyBuf, m_readPos, nRead*PHONON_SAMPLE_SIZE);
  3457. m_readPos += nRead * PHONON_SAMPLE_SIZE;
  3458. }
  3459. else
  3460. {
  3461. char* out = copyBuf;
  3462. const int at_end = (m_outputBuf + sizeof(m_outputBuf)) - m_readPos;
  3463. const int at_begin = nRead*PHONON_SAMPLE_SIZE - at_end;
  3464. memcpy(out, m_readPos, at_end);
  3465. out += at_end;
  3466. m_readPos = m_outputBuf;
  3467. memcpy(out, m_readPos, at_begin);
  3468. m_readPos += at_begin;
  3469. }
  3470. *pData = (void*)copyBuf;
  3471. TrySubmitJob();
  3472. return nRead;
  3473. }
  3474. void CWaveDataHRTF::TryCompleteJob()
  3475. {
  3476. if (!m_bAsync && m_bJobPending)
  3477. {
  3478. ProcessPhononJob(*m_pJob);
  3479. m_pJob->bComplete = true;
  3480. }
  3481. if (!m_bJobPending )
  3482. {
  3483. return;
  3484. }
  3485. m_pJob->mutex.Lock();
  3486. const bool bComplete = m_pJob->bComplete;
  3487. m_pJob->mutex.Unlock();
  3488. if (!bComplete)
  3489. {
  3490. return;
  3491. }
  3492. m_pJob->bComplete = false;
  3493. m_bJobPending = false;
  3494. memcpy(m_writePos, m_pJob->outputBuf, sizeof( m_pJob->outputBuf ) );
  3495. m_writePos += sizeof( m_pJob->outputBuf );
  3496. if (m_writePos == m_outputBuf + sizeof(m_outputBuf))
  3497. {
  3498. m_writePos = m_outputBuf;
  3499. }
  3500. }
  3501. void CWaveDataHRTF::TrySubmitJob()
  3502. {
  3503. TryCompleteJob();
  3504. if (m_bJobPending || SamplesAvailableToWrite() <= PHONON_FRAME_SIZE || m_bEOF)
  3505. {
  3506. return;
  3507. }
  3508. if (!m_pData->IsReadyToMix())
  3509. {
  3510. return;
  3511. }
  3512. char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
  3513. char* buf = nullptr;
  3514. int nLoaded = m_pData->ReadSourceData((void**)&buf, m_nDataStreamReadPos, PHONON_FRAME_SIZE, copyBuf);
  3515. m_nDataStreamReadPos += nLoaded;
  3516. if (nLoaded <= 0)
  3517. {
  3518. m_bEOF = true;
  3519. return;
  3520. }
  3521. const int nBytesLoaded = nLoaded * sizeof(short) * m_nChannels;
  3522. if (nLoaded < PHONON_FRAME_SIZE)
  3523. {
  3524. memcpy(m_pJob->inputBuf, buf, nBytesLoaded);
  3525. memset(m_pJob->inputBuf + nBytesLoaded, 0, sizeof(m_pJob->inputBuf) - nBytesLoaded);
  3526. m_bEOF = true;
  3527. }
  3528. else if (nLoaded == PHONON_FRAME_SIZE)
  3529. {
  3530. memcpy(m_pJob->inputBuf, buf, nBytesLoaded);
  3531. }
  3532. else
  3533. {
  3534. DevMsg("HRTF: Unexpected too many bytes from ReadSourceData: %d vs %d\n", PHONON_FRAME_SIZE, nLoaded);
  3535. return;
  3536. }
  3537. m_pJob->nChannels = m_nChannels;
  3538. m_pJob->nDataWidth = m_nDataWidth;
  3539. m_pJob->interpolation = snd_hrtf_lerp_method.GetInt() == 0 && m_pDir->bilinear_filtering == false ? IPL_HRTFINTERPOLATION_NEAREST : IPL_HRTFINTERPOLATION_BILINEAR;
  3540. m_pJob->vec = m_pDir->vec;
  3541. m_pJob->lerp = m_pDir->lerp;
  3542. m_pJob->bComplete = false;
  3543. if (snd_hrtf_benchmark.GetBool())
  3544. {
  3545. m_pJob->fSubmitTime = Plat_FloatTime();
  3546. }
  3547. m_bJobPending = true;
  3548. if (m_bAsync)
  3549. {
  3550. g_phononJobQueue.PushItem(m_pJob);
  3551. g_eventPhononThread.Set();
  3552. }
  3553. else
  3554. {
  3555. TryCompleteJob();
  3556. }
  3557. }
  3558. IWaveData *CreateWaveDataHRTF(IWaveData* pData, hrtf_info_t* dir)
  3559. {
  3560. if (dir == nullptr)
  3561. return pData;
  3562. CWaveDataHRTF* res = new CWaveDataHRTF(pData, dir);
  3563. return res;
  3564. }