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.
2394 lines
70 KiB
2394 lines
70 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
|
|
#include "audio_pch.h" |
|
#include "datacache/idatacache.h" |
|
#include "utllinkedlist.h" |
|
#include "utldict.h" |
|
#include "filesystem/IQueuedLoader.h" |
|
#include "cdll_int.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern IVEngineClient *engineClient; |
|
extern IFileSystem *g_pFileSystem; |
|
extern IDataCache *g_pDataCache; |
|
extern double realtime; |
|
|
|
// console streaming buffer implementation, appropriate for high latency and low memory |
|
// shift this many buffers through the wave |
|
#define STREAM_BUFFER_COUNT 2 |
|
// duration of audio samples per buffer, 200ms is 2x the worst frame rate (10Hz) |
|
// the engine then has at least 400ms to deliver a new buffer or pop (assuming 2 buffers) |
|
#define STREAM_BUFFER_TIME 0.200f |
|
// force a single buffer when streaming waves smaller than this |
|
#define STREAM_BUFFER_DATASIZE XBOX_DVD_ECC_SIZE |
|
|
|
// PC single buffering implementation |
|
// UNDONE: Allocate this in cache instead? |
|
#define SINGLE_BUFFER_SIZE 16384 |
|
|
|
// Force a small cache for debugging cache issues. |
|
// #define FORCE_SMALL_MEMORY_CACHE_SIZE ( 6 * 1024 * 1024 ) |
|
|
|
#define DEFAULT_WAV_MEMORY_CACHE ( 16 * 1024 * 1024 ) |
|
#define DEFAULT_XBOX_WAV_MEMORY_CACHE ( 16 * 1024 * 1024 ) |
|
#define TF_XBOX_WAV_MEMORY_CACHE ( 24 * 1024 * 1024 ) // Team Fortress uses a larger cache |
|
|
|
// Dev builds will be missing soundcaches and hitch sometimes, we only care if its being properly launched from steam where sound caches should be complete. |
|
ConVar snd_async_spew_blocking( "snd_async_spew_blocking", "1", 0, "Spew message to console any time async sound loading blocks on file i/o. ( 0=Off, 1=With -steam only, 2=Always" ); |
|
ConVar snd_async_spew( "snd_async_spew", "0", 0, "Spew all async sound reads, including success" ); |
|
ConVar snd_async_fullyasync( "snd_async_fullyasync", "0", 0, "All playback is fully async (sound doesn't play until data arrives)." ); |
|
ConVar snd_async_stream_spew( "snd_async_stream_spew", "0", 0, "Spew streaming info ( 0=Off, 1=streams, 2=buffers" ); |
|
|
|
static bool SndAsyncSpewBlocking() |
|
{ |
|
int pref = snd_async_spew_blocking.GetInt(); |
|
return ( pref >= 2 ) || ( pref == 1 && CommandLine()->FindParm( "-steam" ) != 0 ); |
|
} |
|
|
|
#define SndAlignReads() 1 |
|
|
|
void MaybeReportMissingWav( char const *wav ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
struct asyncwaveparams_t |
|
{ |
|
asyncwaveparams_t() : bPrefetch( false ), bCanBeQueued( false ) {} |
|
|
|
FileNameHandle_t hFilename; // handle to sound item name (i.e. not with sound\ prefix) |
|
int datasize; |
|
int seekpos; |
|
int alignment; |
|
bool bPrefetch; |
|
bool bCanBeQueued; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Builds a cache of the data bytes for a specific .wav file |
|
//----------------------------------------------------------------------------- |
|
class CAsyncWaveData |
|
{ |
|
public: |
|
explicit CAsyncWaveData(); |
|
|
|
// APIS required by CManagedDataCacheClient |
|
void DestroyResource(); |
|
CAsyncWaveData *GetData(); |
|
unsigned int Size(); |
|
|
|
static void AsyncCallback( const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err ); |
|
static void QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ); |
|
static CAsyncWaveData *CreateResource( const asyncwaveparams_t ¶ms ); |
|
static unsigned int EstimatedSize( const asyncwaveparams_t ¶ms ); |
|
|
|
void OnAsyncCompleted( const FileAsyncRequest_t* asyncFilePtr, int numReadBytes, FSAsyncStatus_t err ); |
|
bool BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count ); |
|
bool BlockingGetDataPointer( void **ppData ); |
|
void SetAsyncPriority( int priority ); |
|
void StartAsyncLoading( const asyncwaveparams_t& params ); |
|
bool GetPostProcessed(); |
|
void SetPostProcessed( bool proc ); |
|
|
|
bool IsCurrentlyLoading(); |
|
char const *GetFileName(); |
|
|
|
// Data |
|
public: |
|
int m_nDataSize; // bytes requested |
|
int m_nReadSize; // bytes actually read |
|
void *m_pvData; // target buffer |
|
byte *m_pAlloc; // memory of buffer (base may not match) |
|
FileAsyncRequest_t m_async; |
|
FSAsyncControl_t m_hAsyncControl; |
|
float m_start; // time at request invocation |
|
float m_arrival; // time at data arrival |
|
FileNameHandle_t m_hFileNameHandle; |
|
int m_nBufferBytes; // size of any pre-allocated target buffer |
|
BufferHandle_t m_hBuffer; // used to dequeue the buffer after lru |
|
unsigned int m_bLoaded : 1; |
|
unsigned int m_bMissing : 1; |
|
unsigned int m_bPostProcessed : 1; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: C'tor |
|
//----------------------------------------------------------------------------- |
|
CAsyncWaveData::CAsyncWaveData() : |
|
m_nDataSize( 0 ), |
|
m_nReadSize( 0 ), |
|
m_pvData( 0 ), |
|
m_pAlloc( 0 ), |
|
m_hBuffer( INVALID_BUFFER_HANDLE ), |
|
m_nBufferBytes( 0 ), |
|
m_hAsyncControl( NULL ), |
|
m_bLoaded( false ), |
|
m_bMissing( false ), |
|
m_start( 0.0 ), |
|
m_arrival( 0.0 ), |
|
m_bPostProcessed( false ), |
|
m_hFileNameHandle( 0 ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: // APIS required by CDataLRU |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWaveData::DestroyResource() |
|
{ |
|
if ( IsPC() ) |
|
{ |
|
if ( m_hAsyncControl ) |
|
{ |
|
if ( !m_bLoaded && !m_bMissing ) |
|
{ |
|
// NOTE: We CANNOT call AsyncAbort since if the file is actually being read we'll end |
|
// up still getting a callback, but our this ptr (deleted below) will be feeefeee and we'll trash the heap |
|
// pretty bad. So we call AsyncFinish, which will do a blocking read and will definitely succeed |
|
// Block until we are finished |
|
g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); |
|
} |
|
|
|
g_pFileSystem->AsyncRelease( m_hAsyncControl ); |
|
m_hAsyncControl = NULL; |
|
} |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
if ( m_hAsyncControl ) |
|
{ |
|
if ( !m_bLoaded && !m_bMissing ) |
|
{ |
|
// force an abort |
|
int errStatus = g_pFileSystem->AsyncAbort( m_hAsyncControl ); |
|
if ( errStatus != FSASYNC_ERR_UNKNOWNID ) |
|
{ |
|
// must wait for abort to finish before deallocating data |
|
g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); |
|
} |
|
} |
|
g_pFileSystem->AsyncRelease( m_hAsyncControl ); |
|
m_hAsyncControl = NULL; |
|
} |
|
if ( m_hBuffer != INVALID_BUFFER_HANDLE ) |
|
{ |
|
// hint the manager that this tracked buffer is invalid |
|
wavedatacache->MarkBufferDiscarded( m_hBuffer ); |
|
} |
|
} |
|
|
|
// delete buffers |
|
if ( IsPC() || !IsX360() ) |
|
{ |
|
g_pFileSystem->FreeOptimalReadBuffer( m_pAlloc ); |
|
} |
|
else |
|
{ |
|
delete [] m_pAlloc; |
|
} |
|
|
|
delete this; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : char const |
|
//----------------------------------------------------------------------------- |
|
char const *CAsyncWaveData::GetFileName() |
|
{ |
|
static char sz[MAX_PATH]; |
|
|
|
if ( m_hFileNameHandle ) |
|
{ |
|
if ( g_pFileSystem->String( m_hFileNameHandle, sz, sizeof( sz ) ) ) |
|
{ |
|
return sz; |
|
} |
|
} |
|
|
|
Assert( 0 ); |
|
return ""; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CAsyncWaveData |
|
//----------------------------------------------------------------------------- |
|
CAsyncWaveData *CAsyncWaveData::GetData() |
|
{ |
|
return this; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CAsyncWaveData::Size() |
|
{ |
|
int size = sizeof( *this ); |
|
|
|
if ( IsPC() ) |
|
{ |
|
size += m_nDataSize; |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
// the data size can shrink during streaming near end of file |
|
// need the real contant size of this object's allocations |
|
size += m_nBufferBytes; |
|
} |
|
|
|
return size; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Static method for CDataLRU |
|
// Input : ¶ms - |
|
// Output : CAsyncWaveData |
|
//----------------------------------------------------------------------------- |
|
CAsyncWaveData *CAsyncWaveData::CreateResource( const asyncwaveparams_t ¶ms ) |
|
{ |
|
CAsyncWaveData *pData = new CAsyncWaveData; |
|
Assert( pData ); |
|
if ( pData ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
// create buffer now for re-use during streaming process |
|
pData->m_nBufferBytes = AlignValue( params.datasize, params.alignment ); |
|
pData->m_pAlloc = new byte[pData->m_nBufferBytes]; |
|
pData->m_pvData = pData->m_pAlloc; |
|
} |
|
pData->StartAsyncLoading( params ); |
|
} |
|
|
|
return pData; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Static method |
|
// Input : ¶ms - |
|
// Output : static unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CAsyncWaveData::EstimatedSize( const asyncwaveparams_t ¶ms ) |
|
{ |
|
int size = sizeof( CAsyncWaveData ); |
|
|
|
if ( IsPC() ) |
|
{ |
|
size += params.datasize; |
|
} |
|
if ( IsX360() ) |
|
{ |
|
// the expected size of this object's allocations |
|
size += AlignValue( params.datasize, params.alignment ); |
|
} |
|
|
|
return size; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!! |
|
// Input : asyncFilePtr - |
|
// numReadBytes - |
|
// err - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWaveData::AsyncCallback(const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err ) |
|
{ |
|
CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( asyncRequest.pContext ); |
|
Assert( pObject ); |
|
if ( pObject ) |
|
{ |
|
pObject->OnAsyncCompleted( &asyncRequest, numReadBytes, err ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!! |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWaveData::QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) |
|
{ |
|
CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( pContext ); |
|
Assert( pObject ); |
|
|
|
pObject->OnAsyncCompleted( NULL, nSize, loaderError == LOADERERROR_NONE ? FSASYNC_OK : FSASYNC_ERR_FILEOPEN ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: NOTE: THIS IS CALLED FROM A THREAD SO YOU CAN'T CALL INTO ANYTHING NON-THREADSAFE |
|
// such as CUtlSymbolTable/CUtlDict (many of the CUtl* are non-thread safe)!!! |
|
// Input : asyncFilePtr - |
|
// numReadBytes - |
|
// err - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWaveData::OnAsyncCompleted( const FileAsyncRequest_t *asyncFilePtr, int numReadBytes, FSAsyncStatus_t err ) |
|
{ |
|
if ( IsPC() ) |
|
{ |
|
// Take hold of pointer (we can just use delete[] across .dlls because we are using a shared memory allocator...) |
|
if ( err == FSASYNC_OK || err == FSASYNC_ERR_READING ) |
|
{ |
|
m_arrival = ( float )Plat_FloatTime(); |
|
|
|
// Take over ptr |
|
m_pAlloc = ( byte * )asyncFilePtr->pData; |
|
if ( SndAlignReads() ) |
|
{ |
|
m_async.nOffset = ( m_async.nBytes - m_nDataSize ); |
|
m_async.nBytes -= m_async.nOffset; |
|
m_pvData = ((byte *)m_pAlloc) + m_async.nOffset; |
|
m_nReadSize = numReadBytes - m_async.nOffset; |
|
} |
|
else |
|
{ |
|
m_pvData = m_pAlloc; |
|
m_nReadSize = numReadBytes; |
|
} |
|
|
|
// Needs to be post-processed |
|
m_bPostProcessed = false; |
|
|
|
// Finished loading |
|
m_bLoaded = true; |
|
} |
|
else if ( err == FSASYNC_ERR_FILEOPEN ) |
|
{ |
|
// SEE NOTE IN FUNCTION COMMENT ABOVE!!! |
|
// Tracker 22905, et al. |
|
// Because this api gets called from the other thread, don't spew warning here as it can |
|
// cause a crash in searching CUtlSymbolTables since they use a global var for a LessFunc context!!! |
|
m_bMissing = true; |
|
} |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
m_arrival = (float)Plat_FloatTime(); |
|
|
|
// possibly reading more than intended due to alignment restriction |
|
m_nReadSize = numReadBytes; |
|
if ( m_nReadSize > m_nDataSize ) |
|
{ |
|
// clamp to expected, extra data is unreliable |
|
m_nReadSize = m_nDataSize; |
|
} |
|
|
|
if ( err != FSASYNC_OK ) |
|
{ |
|
// track as any error |
|
m_bMissing = true; |
|
} |
|
|
|
if ( err != FSASYNC_ERR_FILEOPEN ) |
|
{ |
|
// some data got loaded |
|
m_bLoaded = true; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *destbuffer - |
|
// destbufsize - |
|
// startoffset - |
|
// count - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWaveData::BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count ) |
|
{ |
|
if ( !m_bLoaded ) |
|
{ |
|
Assert( m_hAsyncControl ); |
|
// Force it to finish |
|
// It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal |
|
if ( SndAsyncSpewBlocking() ) |
|
{ |
|
// Force it to finish |
|
float st = ( float )Plat_FloatTime(); |
|
g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); |
|
float ed = ( float )Plat_FloatTime(); |
|
Warning( "%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 ) ); |
|
} |
|
else |
|
{ |
|
g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); |
|
} |
|
} |
|
|
|
// notify on any error |
|
if ( m_bMissing ) |
|
{ |
|
// Only warn once |
|
m_bMissing = false; |
|
|
|
char fn[MAX_PATH]; |
|
if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) ) |
|
{ |
|
MaybeReportMissingWav( fn ); |
|
} |
|
} |
|
|
|
if ( !m_bLoaded ) |
|
{ |
|
return false; |
|
} |
|
else if ( m_arrival != 0 && snd_async_spew.GetBool() ) |
|
{ |
|
DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) ); |
|
m_arrival = 0; |
|
} |
|
|
|
// clamp requested to available |
|
if ( count > m_nReadSize ) |
|
{ |
|
count = m_nReadSize - startoffset; |
|
} |
|
|
|
if ( count < 0 ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Copy data from stream buffer |
|
Q_memcpy( destbuffer, (char *)m_pvData + ( startoffset - m_async.nOffset ), count ); |
|
|
|
g_pFileSystem->AsyncRelease( m_hAsyncControl ); |
|
m_hAsyncControl = NULL; |
|
return true; |
|
} |
|
|
|
|
|
bool CAsyncWaveData::IsCurrentlyLoading() |
|
{ |
|
if ( m_bLoaded ) |
|
return true; |
|
FSAsyncStatus_t status = g_pFileSystem->AsyncStatus( m_hAsyncControl ); |
|
if ( status == FSASYNC_STATUS_INPROGRESS || status == FSASYNC_OK ) |
|
return true; |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : **ppData - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWaveData::BlockingGetDataPointer( void **ppData ) |
|
{ |
|
Assert( ppData ); |
|
if ( !m_bLoaded ) |
|
{ |
|
// Force it to finish |
|
// It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal |
|
if ( SndAsyncSpewBlocking() ) |
|
{ |
|
float st = ( float )Plat_FloatTime(); |
|
g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); |
|
float ed = ( float )Plat_FloatTime(); |
|
Warning( "%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 ) ); |
|
} |
|
else |
|
{ |
|
g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); |
|
} |
|
} |
|
|
|
// notify on any error |
|
if ( m_bMissing ) |
|
{ |
|
// Only warn once |
|
m_bMissing = false; |
|
|
|
char fn[MAX_PATH]; |
|
if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) ) |
|
{ |
|
MaybeReportMissingWav( fn ); |
|
} |
|
} |
|
|
|
if ( !m_bLoaded ) |
|
{ |
|
return false; |
|
} |
|
else if ( m_arrival != 0 && snd_async_spew.GetBool() ) |
|
{ |
|
DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) ); |
|
m_arrival = 0; |
|
} |
|
|
|
*ppData = m_pvData; |
|
|
|
g_pFileSystem->AsyncRelease( m_hAsyncControl ); |
|
m_hAsyncControl = NULL; |
|
|
|
return true; |
|
} |
|
|
|
void CAsyncWaveData::SetAsyncPriority( int priority ) |
|
{ |
|
if ( m_async.priority != priority ) |
|
{ |
|
m_async.priority = priority; |
|
g_pFileSystem->AsyncSetPriority( m_hAsyncControl, m_async.priority ); |
|
if ( snd_async_spew.GetBool() ) |
|
{ |
|
DevMsg( "%f Async I/O Bumped priority for %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( Plat_FloatTime() - m_start ) ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : params - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWaveData::StartAsyncLoading( const asyncwaveparams_t& params ) |
|
{ |
|
Assert( IsX360() || ( IsPC() && !m_bLoaded ) ); |
|
|
|
// expected to be relative to the sound\ dir |
|
m_hFileNameHandle = params.hFilename; |
|
|
|
// build the real filename |
|
char szFilename[MAX_PATH]; |
|
Q_snprintf( szFilename, sizeof( szFilename ), "sound\\%s", GetFileName() ); |
|
|
|
int nPriority = 1; |
|
if ( params.bPrefetch ) |
|
{ |
|
// lower the priority of prefetched sounds, so they don't block immediate sounds from being loaded |
|
nPriority = 0; |
|
} |
|
|
|
if ( !IsX360() ) |
|
{ |
|
m_async.pData = NULL; |
|
if ( SndAlignReads() ) |
|
{ |
|
m_async.nOffset = 0; |
|
m_async.nBytes = params.seekpos + params.datasize; |
|
} |
|
else |
|
{ |
|
m_async.nOffset = params.seekpos; |
|
m_async.nBytes = params.datasize; |
|
} |
|
} |
|
else |
|
{ |
|
Assert( params.datasize > 0 ); |
|
|
|
// using explicit allocated buffer on xbox |
|
m_async.pData = m_pvData; |
|
m_async.nOffset = params.seekpos; |
|
m_async.nBytes = AlignValue( params.datasize, params.alignment ); |
|
} |
|
|
|
m_async.pfnCallback = AsyncCallback; // optional completion callback |
|
m_async.pContext = (void *)this; // caller's unique context |
|
m_async.priority = nPriority; // inter list priority, 0=lowest |
|
m_async.flags = IsX360() ? 0 : FSASYNC_FLAGS_ALLOCNOFREE; |
|
m_async.pszPathID = "GAME"; |
|
|
|
m_bLoaded = false; |
|
m_bMissing = false; |
|
m_nDataSize = params.datasize; |
|
m_start = (float)Plat_FloatTime(); |
|
m_arrival = 0; |
|
m_nReadSize = 0; |
|
m_bPostProcessed = false; |
|
|
|
// The async layer creates a copy of this string, ok to send a local reference |
|
m_async.pszFilename = szFilename; |
|
|
|
char szFullName[MAX_PATH]; |
|
if ( IsX360() && ( g_pFileSystem->GetDVDMode() == DVDMODE_STRICT ) ) |
|
{ |
|
// all audio is expected be in zips |
|
// resolve to absolute name now, where path can be filtered to just the zips (fast find, no real i/o) |
|
// otherwise the dvd will do a costly seek for each zip miss due to search path fall through |
|
PathTypeQuery_t pathType; |
|
if ( !g_pFileSystem->RelativePathToFullPath( m_async.pszFilename, m_async.pszPathID, szFullName, sizeof( szFullName ), FILTER_CULLNONPACK, &pathType ) ) |
|
{ |
|
// not found, do callback now to handle error |
|
m_async.pfnCallback( m_async, 0, FSASYNC_ERR_FILEOPEN ); |
|
return; |
|
} |
|
m_async.pszFilename = szFullName; |
|
} |
|
|
|
if ( IsX360() && params.bCanBeQueued ) |
|
{ |
|
// queued loader takes over |
|
LoaderJob_t loaderJob; |
|
loaderJob.m_pFilename = m_async.pszFilename; |
|
loaderJob.m_pPathID = m_async.pszPathID; |
|
loaderJob.m_pCallback = QueuedLoaderCallback; |
|
loaderJob.m_pContext = (void *)this; |
|
loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD; |
|
loaderJob.m_pTargetData = m_async.pData; |
|
loaderJob.m_nBytesToRead = m_async.nBytes; |
|
loaderJob.m_nStartOffset = m_async.nOffset; |
|
g_pQueuedLoader->AddJob( &loaderJob ); |
|
return; |
|
} |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
// Commence async I/O |
|
Assert( !m_hAsyncControl ); |
|
g_pFileSystem->AsyncRead( m_async, &m_hAsyncControl ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWaveData::GetPostProcessed() |
|
{ |
|
return m_bPostProcessed; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : proc - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWaveData::SetPostProcessed( bool proc ) |
|
{ |
|
m_bPostProcessed = proc; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Implements a cache of .wav / .mp3 data based on filename |
|
//----------------------------------------------------------------------------- |
|
class CAsyncWavDataCache : public IAsyncWavDataCache, |
|
public CManagedDataCacheClient<CAsyncWaveData, asyncwaveparams_t> |
|
{ |
|
public: |
|
CAsyncWavDataCache(); |
|
~CAsyncWavDataCache() {} |
|
|
|
virtual bool Init( unsigned int memSize ); |
|
virtual void Shutdown(); |
|
|
|
// implementation that treats file as monolithic |
|
virtual memhandle_t AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch = false ); |
|
virtual void PrefetchCache( char const *filename, int datasize, int startpos ); |
|
virtual bool CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ); |
|
virtual bool CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ); |
|
virtual void SetPostProcessed( memhandle_t handle, bool proc ); |
|
virtual void Unload( memhandle_t handle ); |
|
virtual bool GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed ); |
|
virtual bool IsDataLoadCompleted( memhandle_t handle, bool *pIsValid ); |
|
virtual void RestartDataLoad( memhandle_t* handle, char const *filename, int datasize, int startpos ); |
|
virtual bool IsDataLoadInProgress( memhandle_t handle ); |
|
|
|
// Xbox: alternate multi-buffer streaming implementation |
|
virtual StreamHandle_t OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags ); |
|
virtual void CloseStreamedLoad( StreamHandle_t hStream ); |
|
virtual int CopyStreamedDataIntoMemory( StreamHandle_t hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy ); |
|
virtual bool IsStreamedDataReady( StreamHandle_t hStream ); |
|
virtual void MarkBufferDiscarded( BufferHandle_t hBuffer ); |
|
virtual void *GetStreamedDataPointer( StreamHandle_t hStream, bool bSync ); |
|
|
|
virtual void Flush(); |
|
virtual void OnMixBegin(); |
|
virtual void OnMixEnd(); |
|
|
|
void QueueUnlock( const memhandle_t &handle ); |
|
void SpewMemoryUsage( int level ); |
|
|
|
// Cache helpers |
|
bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ); |
|
|
|
private: |
|
void Clear(); |
|
|
|
struct CacheEntry_t |
|
{ |
|
CacheEntry_t() : |
|
name( 0 ), |
|
handle( 0 ) |
|
{ |
|
} |
|
FileNameHandle_t name; |
|
memhandle_t handle; |
|
}; |
|
|
|
// tags the signature of a buffer inside a rb tree for faster than linear find |
|
struct BufferEntry_t |
|
{ |
|
FileNameHandle_t m_hName; |
|
memhandle_t m_hWaveData; |
|
int m_StartPos; |
|
bool m_bCanBeShared; |
|
}; |
|
|
|
static bool BufferHandleLessFunc( const BufferEntry_t& lhs, const BufferEntry_t& rhs ) |
|
{ |
|
if ( lhs.m_hName != rhs.m_hName ) |
|
{ |
|
return lhs.m_hName < rhs.m_hName; |
|
} |
|
|
|
if ( lhs.m_StartPos != rhs.m_StartPos ) |
|
{ |
|
return lhs.m_StartPos < rhs.m_StartPos; |
|
} |
|
|
|
return lhs.m_bCanBeShared < rhs.m_bCanBeShared; |
|
} |
|
|
|
CUtlRBTree< BufferEntry_t, BufferHandle_t > m_BufferList; |
|
|
|
// encapsulates (n) buffers for a streamed wave object |
|
struct StreamedEntry_t |
|
{ |
|
FileNameHandle_t m_hName; |
|
memhandle_t m_hWaveData[STREAM_BUFFER_COUNT]; |
|
int m_Front; // buffer index, forever incrementing |
|
int m_NextStartPos; // predicted offset if mixing linearly |
|
int m_DataSize; // length of the data set in bytes |
|
int m_DataStart; // file offset where data set starts |
|
int m_LoopStart; // offset in data set where loop starts |
|
int m_BufferSize; // size of the buffer in bytes |
|
int m_numBuffers; // number of buffers (1 or 2) to march through |
|
int m_SectorSize; // size of sector on stream device |
|
bool m_bSinglePlay; // hint to keep same buffers |
|
}; |
|
CUtlLinkedList< StreamedEntry_t, StreamHandle_t > m_StreamedHandles; |
|
|
|
static bool CacheHandleLessFunc( const CacheEntry_t& lhs, const CacheEntry_t& rhs ) |
|
{ |
|
return lhs.name < rhs.name; |
|
} |
|
CUtlRBTree< CacheEntry_t, int > m_CacheHandles; |
|
|
|
memhandle_t FindOrCreateBuffer( asyncwaveparams_t ¶ms, bool bFind ); |
|
bool m_bInitialized; |
|
bool m_bQueueCacheUnlocks; |
|
CUtlVector<memhandle_t> m_unlockQueue; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CAsyncWavDataCache::CAsyncWavDataCache() : |
|
m_CacheHandles( 0, 0, CacheHandleLessFunc ), |
|
m_BufferList( 0, 0, BufferHandleLessFunc ), |
|
m_bInitialized( false ), |
|
m_bQueueCacheUnlocks( false ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::Init( unsigned int memSize ) |
|
{ |
|
if ( m_bInitialized ) |
|
return true; |
|
|
|
if ( IsX360() ) |
|
{ |
|
const char *pGame = engineClient->GetGameDirectory(); |
|
if ( !Q_stricmp( Q_UnqualifiedFileName( pGame ), "tf" ) ) |
|
{ |
|
memSize = TF_XBOX_WAV_MEMORY_CACHE; |
|
} |
|
else |
|
{ |
|
memSize = DEFAULT_XBOX_WAV_MEMORY_CACHE; |
|
} |
|
} |
|
else |
|
{ |
|
if ( memSize < DEFAULT_WAV_MEMORY_CACHE ) |
|
{ |
|
memSize = DEFAULT_WAV_MEMORY_CACHE; |
|
} |
|
} |
|
|
|
#if FORCE_SMALL_MEMORY_CACHE_SIZE |
|
memSize = FORCE_SMALL_MEMORY_CACHE_SIZE; |
|
Msg( "WARNING CAsyncWavDataCache::Init() forcing small memory cache size: %u\n", memSize ); |
|
#endif |
|
|
|
CCacheClientBaseClass::Init( g_pDataCache, "WaveData", memSize ); |
|
|
|
m_bInitialized = true; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::Shutdown() |
|
{ |
|
if ( !m_bInitialized ) |
|
{ |
|
return; |
|
} |
|
|
|
Clear(); |
|
|
|
CCacheClientBaseClass::Shutdown(); |
|
|
|
m_bInitialized = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates initial cache object if it doesn't already exist, starts async loading the actual data |
|
// in any case. |
|
// Input : *filename - |
|
// datasize - |
|
// startpos - |
|
// Output : memhandle_t |
|
//----------------------------------------------------------------------------- |
|
memhandle_t CAsyncWavDataCache::AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::AsyncLoadCache" ); |
|
|
|
FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); |
|
|
|
CacheEntry_t search; |
|
search.name = fnh; |
|
search.handle = 0; |
|
|
|
// find or create the handle |
|
int idx = m_CacheHandles.Find( search ); |
|
if ( idx == m_CacheHandles.InvalidIndex() ) |
|
{ |
|
idx = m_CacheHandles.Insert( search ); |
|
Assert( idx != m_CacheHandles.InvalidIndex() ); |
|
} |
|
|
|
CacheEntry_t &entry = m_CacheHandles[idx]; |
|
|
|
// Try and pull it into cache |
|
CAsyncWaveData *data = CacheGet( entry.handle ); |
|
if ( !data ) |
|
{ |
|
// Try and reload it |
|
asyncwaveparams_t params; |
|
params.hFilename = fnh; |
|
params.datasize = datasize; |
|
params.seekpos = startpos; |
|
params.bPrefetch = bIsPrefetch; |
|
entry.handle = CacheCreate( params ); |
|
} |
|
|
|
return entry.handle; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Reclaim a buffer. A reclaimed resident buffer is ready for play. |
|
//----------------------------------------------------------------------------- |
|
memhandle_t CAsyncWavDataCache::FindOrCreateBuffer( asyncwaveparams_t ¶ms, bool bFind ) |
|
{ |
|
CAsyncWaveData *pWaveData; |
|
BufferEntry_t search; |
|
BufferHandle_t hBuffer; |
|
|
|
search.m_hName = params.hFilename; |
|
search.m_StartPos = params.seekpos; |
|
search.m_bCanBeShared = bFind; |
|
search.m_hWaveData = 0; |
|
|
|
if ( bFind ) |
|
{ |
|
// look for an existing buffer that matches exactly (same file, offset, and share) |
|
int iBuffer = m_BufferList.Find( search ); |
|
if ( iBuffer != m_BufferList.InvalidIndex() ) |
|
{ |
|
// found |
|
search.m_hWaveData = m_BufferList[iBuffer].m_hWaveData; |
|
if ( snd_async_stream_spew.GetInt() >= 2 ) |
|
{ |
|
char tempBuff[MAX_PATH]; |
|
g_pFileSystem->String( params.hFilename, tempBuff, sizeof( tempBuff ) ); |
|
Msg( "Found Buffer: %s, offset: %d\n", tempBuff, params.seekpos ); |
|
} |
|
} |
|
} |
|
|
|
// each resource buffer stays locked (valid) while in use |
|
// a buffering stream is not subject to lru and can rely on it's buffers |
|
// a buffering stream may obsolete it's buffers by reducing the lock count, allowing for lru |
|
pWaveData = CacheLock( search.m_hWaveData ); |
|
if ( !pWaveData ) |
|
{ |
|
// not in cache, create and lock |
|
// not found, create buffer and fill with data |
|
search.m_hWaveData = CacheCreate( params, DCAF_LOCK ); |
|
|
|
// add the buffer to our managed list |
|
hBuffer = m_BufferList.Insert( search ); |
|
Assert( hBuffer != m_BufferList.InvalidIndex() ); |
|
|
|
// store the handle into our managed list |
|
// used during a lru discard as a means to keep the list in-sync |
|
pWaveData = CacheGet( search.m_hWaveData ); |
|
pWaveData->m_hBuffer = hBuffer; |
|
} |
|
else |
|
{ |
|
// still in cache |
|
// same as requesting it and having it arrive instantly |
|
pWaveData->m_start = pWaveData->m_arrival = (float)Plat_FloatTime(); |
|
} |
|
|
|
return search.m_hWaveData; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Load data asynchronously via multi-buffers, returns specialized handle |
|
//----------------------------------------------------------------------------- |
|
StreamHandle_t CAsyncWavDataCache::OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::OpenStreamedLoad" ); |
|
|
|
StreamedEntry_t streamedEntry; |
|
StreamHandle_t hStream; |
|
asyncwaveparams_t params; |
|
int i; |
|
|
|
Assert( numBuffers > 0 && numBuffers <= STREAM_BUFFER_COUNT ); |
|
|
|
// queued load mandates one buffer |
|
Assert( !( flags & STREAMED_QUEUEDLOAD ) || numBuffers == 1 ); |
|
|
|
streamedEntry.m_hName = g_pFileSystem->FindOrAddFileName( pFileName ); |
|
streamedEntry.m_Front = 0; |
|
streamedEntry.m_DataSize = dataSize; |
|
streamedEntry.m_DataStart = dataStart; |
|
streamedEntry.m_NextStartPos = startPos + numBuffers * bufferSize; |
|
streamedEntry.m_LoopStart = loopPos; |
|
streamedEntry.m_BufferSize = bufferSize; |
|
streamedEntry.m_numBuffers = numBuffers; |
|
streamedEntry.m_bSinglePlay = ( flags & STREAMED_SINGLEPLAY ) != 0; |
|
streamedEntry.m_SectorSize = ( IsX360() && ( flags & STREAMED_FROMDVD ) ) ? XBOX_DVD_SECTORSIZE : 1; |
|
|
|
// single play streams expect to uniquely own and thus recycle their buffers though the data |
|
// single play streams are guaranteed that their buffers are private and cannot be shared |
|
// a non-single play stream wants persisting buffers and attempts to reclaim a matching buffer |
|
bool bFindBuffer = ( streamedEntry.m_bSinglePlay == false ); |
|
|
|
// initial load populates buffers |
|
// mixing starts after front buffer viable |
|
// buffer rotation occurs after front buffer consumed |
|
// there should be no blocking |
|
params.hFilename = streamedEntry.m_hName; |
|
params.datasize = bufferSize; |
|
params.alignment = streamedEntry.m_SectorSize; |
|
params.bCanBeQueued = ( flags & STREAMED_QUEUEDLOAD ) != 0; |
|
for ( i=0; i<numBuffers; ++i ) |
|
{ |
|
params.seekpos = dataStart + startPos + i * bufferSize; |
|
streamedEntry.m_hWaveData[i] = FindOrCreateBuffer( params, bFindBuffer ); |
|
} |
|
|
|
// get a unique handle for each stream request |
|
hStream = m_StreamedHandles.AddToTail( streamedEntry ); |
|
Assert( hStream != m_StreamedHandles.InvalidIndex() ); |
|
|
|
return hStream; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Cleanup a streamed load's resources. |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::CloseStreamedLoad( StreamHandle_t hStream ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::CloseStreamedLoad" ); |
|
|
|
if ( hStream == INVALID_STREAM_HANDLE ) |
|
{ |
|
return; |
|
} |
|
|
|
int lockCount; |
|
StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; |
|
for ( int i=0; i<streamedEntry.m_numBuffers; ++i ) |
|
{ |
|
// multiple streams could be using the same buffer, keeping the lock count nonzero |
|
lockCount = GetCacheSection()->GetLockCount( streamedEntry.m_hWaveData[i] ); |
|
Assert( lockCount >= 1 ); |
|
if ( lockCount > 0 ) |
|
{ |
|
lockCount = CacheUnlock( streamedEntry.m_hWaveData[i] ); |
|
} |
|
|
|
if ( streamedEntry.m_bSinglePlay ) |
|
{ |
|
// a buffering single play stream has no reason to reuse its own buffers and destroys them |
|
Assert( lockCount == 0 ); |
|
CacheRemove( streamedEntry.m_hWaveData[i] ); |
|
} |
|
} |
|
|
|
m_StreamedHandles.Remove( hStream ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename - |
|
// datasize - |
|
// startpos - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::PrefetchCache( char const *filename, int datasize, int startpos ) |
|
{ |
|
// Just do an async load, but don't get cache handle |
|
AsyncLoadCache( filename, datasize, startpos, true ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename - |
|
// datasize - |
|
// startpos - |
|
// *buffer - |
|
// bufsize - |
|
// copystartpos - |
|
// bytestocopy - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" ); |
|
|
|
bool bret = false; |
|
|
|
// Add to caching system |
|
AsyncLoadCache( filename, datasize, startpos ); |
|
|
|
FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); |
|
|
|
CacheEntry_t search; |
|
search.name = fnh; |
|
search.handle = 0; |
|
|
|
// Now look it up, it should be in the system |
|
int idx = m_CacheHandles.Find( search ); |
|
if ( idx == m_CacheHandles.InvalidIndex() ) |
|
{ |
|
Assert( 0 ); |
|
return bret; |
|
} |
|
|
|
// Now see if the handle has been paged out... |
|
return CopyDataIntoMemory( m_CacheHandles[ idx ].handle, filename, datasize, startpos, buffer, bufsize, copystartpos, bytestocopy, pbPostProcessed ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// *filename - |
|
// datasize - |
|
// startpos - |
|
// *buffer - |
|
// bufsize - |
|
// copystartpos - |
|
// bytestocopy - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" ); |
|
|
|
*pbPostProcessed = false; |
|
|
|
bool bret = false; |
|
|
|
CAsyncWaveData *data = CacheLock( handle ); |
|
if ( !data ) |
|
{ |
|
FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); |
|
|
|
CacheEntry_t search; |
|
search.name = fnh; |
|
search.handle = 0; |
|
|
|
// Now look it up, it should be in the system |
|
int idx = m_CacheHandles.Find( search ); |
|
if ( idx == m_CacheHandles.InvalidIndex() ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
// Try and reload it |
|
asyncwaveparams_t params; |
|
params.hFilename = fnh; |
|
params.datasize = datasize; |
|
params.seekpos = startpos; |
|
|
|
handle = m_CacheHandles[ idx ].handle = CacheCreate( params ); |
|
data = CacheLock( handle ); |
|
if ( !data ) |
|
{ |
|
return bret; |
|
} |
|
} |
|
|
|
// Cache entry exists, but if filesize == 0 then the file itself wasn't on disk... |
|
if ( data->m_nDataSize != 0 ) |
|
{ |
|
bret = data->BlockingCopyData( buffer, bufsize, copystartpos, bytestocopy ); |
|
} |
|
|
|
*pbPostProcessed = data->GetPostProcessed(); |
|
|
|
// Release lock |
|
CacheUnlock( handle ); |
|
return bret; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copy from streaming buffers into target memory, never blocks. |
|
//----------------------------------------------------------------------------- |
|
int CAsyncWavDataCache::CopyStreamedDataIntoMemory( int hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::CopyStreamedDataIntoMemory" ); |
|
|
|
int actualCopied; |
|
int count; |
|
int i; |
|
int which; |
|
CAsyncWaveData *pWaveData[STREAM_BUFFER_COUNT]; |
|
CAsyncWaveData *pFront; |
|
asyncwaveparams_t params; |
|
int nextStartPos; |
|
int bufferPos; |
|
bool bEndOfFile; |
|
int index; |
|
bool bWaiting; |
|
bool bCompleted; |
|
StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; |
|
|
|
if ( copyStartPos >= streamedEntry.m_DataStart + streamedEntry.m_DataSize ) |
|
{ |
|
// at or past end of file |
|
return 0; |
|
} |
|
|
|
for ( i=0; i<streamedEntry.m_numBuffers; ++i ) |
|
{ |
|
pWaveData[i] = CacheGetNoTouch( streamedEntry.m_hWaveData[i] ); |
|
Assert( pWaveData[i] ); |
|
} |
|
|
|
// drive the buffering |
|
index = streamedEntry.m_Front; |
|
bEndOfFile = 0; |
|
actualCopied = 0; |
|
bWaiting = false; |
|
while ( 1 ) |
|
{ |
|
// try to satisfy from the front |
|
pFront = pWaveData[index % streamedEntry.m_numBuffers]; |
|
bufferPos = copyStartPos - pFront->m_async.nOffset; |
|
|
|
// cache atomic async completion signal off to avoid coherency issues |
|
bCompleted = pFront->m_bLoaded || pFront->m_bMissing; |
|
|
|
if ( snd_async_stream_spew.GetInt() >= 1 ) |
|
{ |
|
// interval is the audio block clock rate, the block must be available within this interval |
|
// a faster audio rate or smaller block size implies a smaller interval |
|
// latency is the actual block delivery time |
|
// latency must not exceed the delivery interval or stariving occurs and audio pops |
|
float nowTime = Plat_FloatTime(); |
|
int interval = (int)(1000.0f*(nowTime-pFront->m_start)); |
|
int latency; |
|
if ( bCompleted && pFront->m_bLoaded ) |
|
{ |
|
latency = (int)(1000.0f*(pFront->m_arrival-pFront->m_start)); |
|
} |
|
else |
|
{ |
|
// buffer has not arrived yet |
|
latency = -1; |
|
} |
|
DevMsg( "Stream:%2d interval:%5dms latency:%5dms offset:%d length:%d (%s)\n", hStream, interval, latency, pFront->m_async.nOffset, pFront->m_nReadSize, pFront->GetFileName() ); |
|
} |
|
|
|
if ( bCompleted && pFront->m_hAsyncControl && ( pFront->m_bLoaded || pFront->m_bMissing) ) |
|
{ |
|
g_pFileSystem->AsyncRelease( pFront->m_hAsyncControl ); |
|
pFront->m_hAsyncControl = NULL; |
|
} |
|
|
|
if ( bCompleted && pFront->m_bLoaded ) |
|
{ |
|
if ( bufferPos >= 0 && bufferPos < pFront->m_nReadSize ) |
|
{ |
|
count = bytesToCopy; |
|
if ( bufferPos + bytesToCopy > pFront->m_nReadSize ) |
|
{ |
|
// clamp requested to actual available |
|
count = pFront->m_nReadSize - bufferPos; |
|
} |
|
if ( bufferPos + count > bufferSize ) |
|
{ |
|
// clamp requested to caller's buffer dimension |
|
count = bufferSize - bufferPos; |
|
} |
|
|
|
Q_memcpy( pBuffer, (char *)pFront->m_pvData + bufferPos, count ); |
|
|
|
// advance past consumed bytes |
|
actualCopied += count; |
|
copyStartPos += count; |
|
bufferPos += count; |
|
} |
|
} |
|
else if ( bCompleted && pFront->m_bMissing ) |
|
{ |
|
// notify on any error |
|
MaybeReportMissingWav( pFront->GetFileName() ); |
|
break; |
|
} |
|
else |
|
{ |
|
// data not available |
|
bWaiting = true; |
|
break; |
|
} |
|
|
|
// cycle past obsolete or consumed buffers |
|
if ( bufferPos < 0 || bufferPos >= pFront->m_nReadSize ) |
|
{ |
|
// move to next buffer |
|
index++; |
|
if ( index - streamedEntry.m_Front >= streamedEntry.m_numBuffers ) |
|
{ |
|
// out of buffers |
|
break; |
|
} |
|
} |
|
|
|
if ( actualCopied == bytesToCopy ) |
|
{ |
|
// satisfied request |
|
break; |
|
} |
|
} |
|
|
|
if ( streamedEntry.m_numBuffers > 1 ) |
|
{ |
|
// restart consumed buffers |
|
while ( streamedEntry.m_Front < index ) |
|
{ |
|
if ( !actualCopied && !bWaiting ) |
|
{ |
|
// couldn't return any data because the buffers aren't in the right location |
|
// oh no! caller must be skipping |
|
// due to latency the next buffer position has to start one full buffer ahead of the caller's desired read location |
|
// hopefully only 1 buffer will stutter |
|
nextStartPos = copyStartPos - streamedEntry.m_DataStart + streamedEntry.m_BufferSize; |
|
|
|
// advance past, ready for next possible iteration |
|
copyStartPos += streamedEntry.m_BufferSize; |
|
} |
|
else |
|
{ |
|
// get the next forecasted read location |
|
nextStartPos = streamedEntry.m_NextStartPos; |
|
} |
|
|
|
if ( nextStartPos >= streamedEntry.m_DataSize ) |
|
{ |
|
// next buffer is at or past end of file |
|
if ( streamedEntry.m_LoopStart >= 0 ) |
|
{ |
|
// wrap back around to loop position |
|
nextStartPos = streamedEntry.m_LoopStart; |
|
} |
|
else |
|
{ |
|
// advance past consumed buffer |
|
streamedEntry.m_Front++; |
|
|
|
// start no further buffers |
|
break; |
|
} |
|
} |
|
|
|
// still valid data left to read |
|
// snap the buffer position to required alignment |
|
nextStartPos = streamedEntry.m_SectorSize * (nextStartPos/streamedEntry.m_SectorSize); |
|
|
|
// start loading back buffer at future location |
|
params.hFilename = streamedEntry.m_hName; |
|
params.seekpos = streamedEntry.m_DataStart + nextStartPos; |
|
params.datasize = streamedEntry.m_DataSize - nextStartPos; |
|
params.alignment = streamedEntry.m_SectorSize; |
|
if ( params.datasize > streamedEntry.m_BufferSize ) |
|
{ |
|
// clamp to buffer size |
|
params.datasize = streamedEntry.m_BufferSize; |
|
} |
|
|
|
// save next start position |
|
streamedEntry.m_NextStartPos = nextStartPos + params.datasize; |
|
|
|
which = streamedEntry.m_Front % streamedEntry.m_numBuffers; |
|
if ( streamedEntry.m_bSinglePlay ) |
|
{ |
|
// a single play wave has no reason to persist its buffers into the lru |
|
// reuse buffer and restart until finished |
|
pWaveData[which]->StartAsyncLoading( params ); |
|
} |
|
else |
|
{ |
|
// release obsolete buffer to lru management |
|
CacheUnlock( streamedEntry.m_hWaveData[which] ); |
|
// reclaim or create/load the desired buffer |
|
streamedEntry.m_hWaveData[which] = FindOrCreateBuffer( params, true ); |
|
} |
|
|
|
streamedEntry.m_Front++; |
|
} |
|
|
|
if ( bWaiting ) |
|
{ |
|
// oh no! data needed is not yet available in front buffer |
|
// caller requesting data faster than can be provided or caller skipped |
|
// can only return what has been copied thus far (could be 0) |
|
return actualCopied; |
|
} |
|
} |
|
|
|
return actualCopied; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the front buffer, optionally block. |
|
// Intended for user of a single buffer stream. |
|
//----------------------------------------------------------------------------- |
|
void *CAsyncWavDataCache::GetStreamedDataPointer( StreamHandle_t hStream, bool bSync ) |
|
{ |
|
void *pData; |
|
CAsyncWaveData *pFront; |
|
int index; |
|
StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; |
|
|
|
index = streamedEntry.m_Front % streamedEntry.m_numBuffers; |
|
pFront = CacheGetNoTouch( streamedEntry.m_hWaveData[index] ); |
|
Assert( pFront ); |
|
if ( !pFront ) |
|
{ |
|
// shouldn't happen |
|
return NULL; |
|
} |
|
|
|
if ( !pFront->m_bMissing && pFront->m_bLoaded ) |
|
{ |
|
return pFront->m_pvData; |
|
} |
|
|
|
if ( bSync && pFront->BlockingGetDataPointer( &pData ) ) |
|
{ |
|
return pData; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: The front buffer must be valid |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::IsStreamedDataReady( int hStream ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::IsStreamedDataReady" ); |
|
|
|
if ( hStream == INVALID_STREAM_HANDLE ) |
|
{ |
|
return false; |
|
} |
|
|
|
StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; |
|
|
|
if ( streamedEntry.m_Front ) |
|
{ |
|
// already streaming, the buffers better be arriving as expected |
|
return true; |
|
} |
|
|
|
// only the first front buffer must be present |
|
CAsyncWaveData *pFront = CacheGetNoTouch( streamedEntry.m_hWaveData[0] ); |
|
Assert( pFront ); |
|
if ( !pFront ) |
|
{ |
|
// shouldn't happen |
|
// let the caller think data is ready, so stream can shutdown |
|
return true; |
|
} |
|
|
|
// regardless of any errors |
|
// errors handled during data fetch |
|
return pFront->m_bLoaded || pFront->m_bMissing; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Dequeue the buffer entry (backdoor for list management) |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::MarkBufferDiscarded( BufferHandle_t hBuffer ) |
|
{ |
|
m_BufferList.RemoveAt( hBuffer ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// proc - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::SetPostProcessed( memhandle_t handle, bool proc ) |
|
{ |
|
CAsyncWaveData *data = CacheGet( handle ); |
|
if ( data ) |
|
{ |
|
data->SetPostProcessed( proc ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::Unload( memhandle_t handle ) |
|
{ |
|
// Don't actually unload, just mark it as stale |
|
if ( GetCacheSection() ) |
|
{ |
|
GetCacheSection()->Age( handle ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// *filename - |
|
// datasize - |
|
// startpos - |
|
// **pData - |
|
// copystartpos - |
|
// *pbPostProcessed - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::GetDataPointer" ); |
|
|
|
Assert( pbPostProcessed ); |
|
Assert( pData ); |
|
|
|
*pbPostProcessed = false; |
|
|
|
bool bret = false; |
|
*pData = NULL; |
|
|
|
CAsyncWaveData *data = CacheLock( handle ); |
|
if ( !data ) |
|
{ |
|
FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); |
|
|
|
CacheEntry_t search; |
|
search.name = fnh; |
|
search.handle = 0; |
|
|
|
int idx = m_CacheHandles.Find( search ); |
|
if ( idx == m_CacheHandles.InvalidIndex() ) |
|
{ |
|
Assert( 0 ); |
|
return bret; |
|
} |
|
|
|
// Try and reload it |
|
asyncwaveparams_t params; |
|
params.hFilename = fnh; |
|
params.datasize = datasize; |
|
params.seekpos = startpos; |
|
|
|
handle = m_CacheHandles[ idx ].handle = CacheCreate( params ); |
|
data = CacheLock( handle ); |
|
if ( !data ) |
|
{ |
|
return bret; |
|
} |
|
} |
|
|
|
// Cache entry exists, but if filesize == 0 then the file itself wasn't on disk... |
|
if ( data->m_nDataSize != 0 ) |
|
{ |
|
if ( datasize != data->m_nDataSize ) |
|
{ |
|
// We've had issues where we are called with datasize larger than what we read on disk. |
|
// Ie: datasize is 277,180, data->m_nDataSize is 263,168 |
|
// This can happen due to a corrupted audio cache, but it's more likely that somehow |
|
// we wound up reading the cache data from one language and the file from another. |
|
DevMsg( "Cached datasize != sound datasize %d - %d.\n", datasize, data->m_nDataSize ); |
|
#ifdef STAGING_ONLY |
|
// Adding a STAGING_ONLY debugger break to try and help track this down. Hopefully we'll |
|
// get this crash internally with full debug information instead of just minidump files. |
|
DebuggerBreak(); |
|
#endif |
|
} |
|
else if ( copystartpos < data->m_nDataSize ) |
|
{ |
|
if ( data->BlockingGetDataPointer( pData ) ) |
|
{ |
|
*pData = (char *)*pData + copystartpos; |
|
bret = true; |
|
} |
|
} |
|
} |
|
|
|
*pbPostProcessed = data->GetPostProcessed(); |
|
|
|
// Release lock at the end of mixing |
|
QueueUnlock( handle ); |
|
return bret; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// *filename - |
|
// datasize - |
|
// startpos - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::IsDataLoadCompleted( memhandle_t handle, bool *pIsValid ) |
|
{ |
|
VPROF( "CAsyncWavDataCache::IsDataLoadCompleted" ); |
|
|
|
CAsyncWaveData *data = CacheGet( handle ); |
|
if ( !data ) |
|
{ |
|
*pIsValid = false; |
|
return false; |
|
} |
|
*pIsValid = true; |
|
// bump the priority |
|
data->SetAsyncPriority( 1 ); |
|
|
|
return data->m_bLoaded; |
|
} |
|
|
|
|
|
void CAsyncWavDataCache::RestartDataLoad( memhandle_t *pHandle, const char *pFilename, int dataSize, int startpos ) |
|
{ |
|
CAsyncWaveData *data = CacheGet( *pHandle ); |
|
if ( !data ) |
|
{ |
|
*pHandle = AsyncLoadCache( pFilename, dataSize, startpos ); |
|
} |
|
} |
|
|
|
bool CAsyncWavDataCache::IsDataLoadInProgress( memhandle_t handle ) |
|
{ |
|
CAsyncWaveData *data = CacheGet( handle ); |
|
if ( data ) |
|
{ |
|
return data->IsCurrentlyLoading(); |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::Flush() |
|
{ |
|
GetCacheSection()->Flush(); |
|
SpewMemoryUsage( 0 ); |
|
} |
|
|
|
void CAsyncWavDataCache::QueueUnlock( const memhandle_t &handle ) |
|
{ |
|
// not queuing right now, just unlock |
|
if ( !m_bQueueCacheUnlocks ) |
|
{ |
|
CacheUnlock( handle ); |
|
return; |
|
} |
|
// queue to unlock at the end of mixing |
|
m_unlockQueue.AddToTail( handle ); |
|
} |
|
|
|
void CAsyncWavDataCache::OnMixBegin() |
|
{ |
|
Assert( !m_bQueueCacheUnlocks ); |
|
m_bQueueCacheUnlocks = true; |
|
Assert( m_unlockQueue.Count() == 0 ); |
|
} |
|
|
|
void CAsyncWavDataCache::OnMixEnd() |
|
{ |
|
m_bQueueCacheUnlocks = false; |
|
// flush the unlock queue |
|
for ( int i = 0; i < m_unlockQueue.Count(); i++ ) |
|
{ |
|
CacheUnlock( m_unlockQueue[i] ); |
|
} |
|
m_unlockQueue.RemoveAll(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CAsyncWavDataCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ) |
|
{ |
|
CAsyncWaveData *pWaveData = (CAsyncWaveData *)pItem; |
|
Q_strncpy( pDest, pWaveData->GetFileName(), nMaxLen ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Spew a cache summary to the console |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::SpewMemoryUsage( int level ) |
|
{ |
|
DataCacheStatus_t status; |
|
DataCacheLimits_t limits; |
|
GetCacheSection()->GetStatus( &status, &limits ); |
|
int bytesUsed = status.nBytes; |
|
int bytesTotal = limits.nMaxBytes; |
|
|
|
if ( IsPC() ) |
|
{ |
|
float percent = 100.0f * (float)bytesUsed / (float)bytesTotal; |
|
|
|
Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent ); |
|
|
|
if ( level >= 1 ) |
|
{ |
|
for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) ) |
|
{ |
|
char name[MAX_PATH]; |
|
if ( !g_pFileSystem->String( m_CacheHandles[ i ].name, name, sizeof( name ) ) ) |
|
{ |
|
Assert( 0 ); |
|
continue; |
|
} |
|
memhandle_t &handle = m_CacheHandles[ i ].handle; |
|
CAsyncWaveData *data = CacheGetNoTouch( handle ); |
|
if ( data ) |
|
{ |
|
Msg( "\t%16.16s : %s\n", Q_pretifymem(data->Size()),name); |
|
} |
|
else |
|
{ |
|
Msg( "\t%16.16s : %s\n", "not resident",name); |
|
} |
|
} |
|
Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent ); |
|
} |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
CAsyncWaveData *pData; |
|
BufferEntry_t *pBuffer; |
|
BufferHandle_t h; |
|
float percent; |
|
int lockCount; |
|
|
|
if ( bytesTotal <= 0 ) |
|
{ |
|
// unbounded, indeterminate |
|
percent = 0; |
|
bytesTotal = 0; |
|
} |
|
else |
|
{ |
|
percent = 100.0f*(float)bytesUsed/(float)bytesTotal; |
|
} |
|
|
|
if ( level >= 1 ) |
|
{ |
|
// detail buffers |
|
ConMsg( "Streaming Buffer List:\n" ); |
|
for ( h = m_BufferList.FirstInorder(); h != m_BufferList.InvalidIndex(); h = m_BufferList.NextInorder( h ) ) |
|
{ |
|
pBuffer = &m_BufferList[h]; |
|
pData = CacheGetNoTouch( pBuffer->m_hWaveData ); |
|
lockCount = GetCacheSection()->GetLockCount( pBuffer->m_hWaveData ); |
|
|
|
CacheLockMutex(); |
|
if ( pData ) |
|
{ |
|
ConMsg( "Start:%7d Length:%7d Lock:%3d %s\n", pData->m_async.nOffset, pData->m_nDataSize, lockCount, pData->GetFileName() ); |
|
} |
|
CacheUnlockMutex(); |
|
} |
|
} |
|
|
|
ConMsg( "CAsyncWavDataCache: %.2f MB used of %.2f MB, %.2f%% of capacity", (float)bytesUsed/(1024.0f*1024.0f), (float)bytesTotal/(1024.0f*1024.0f), percent ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAsyncWavDataCache::Clear() |
|
{ |
|
for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) ) |
|
{ |
|
CacheEntry_t& dat = m_CacheHandles[i]; |
|
CacheRemove( dat.handle ); |
|
} |
|
m_CacheHandles.RemoveAll(); |
|
|
|
FOR_EACH_LL( m_StreamedHandles, i ) |
|
{ |
|
StreamedEntry_t &dat = m_StreamedHandles[i]; |
|
for ( int j=0; j<dat.m_numBuffers; ++j ) |
|
{ |
|
GetCacheSection()->BreakLock( dat.m_hWaveData[j] ); |
|
CacheRemove( dat.m_hWaveData[j] ); |
|
} |
|
} |
|
m_StreamedHandles.RemoveAll(); |
|
m_BufferList.RemoveAll(); |
|
} |
|
|
|
|
|
static CAsyncWavDataCache g_AsyncWaveDataCache; |
|
IAsyncWavDataCache *wavedatacache = &g_AsyncWaveDataCache; |
|
|
|
CON_COMMAND( snd_async_flush, "Flush all unlocked async audio data" ) |
|
{ |
|
g_AsyncWaveDataCache.Flush(); |
|
} |
|
|
|
CON_COMMAND( snd_async_showmem, "Show async memory stats" ) |
|
{ |
|
g_AsyncWaveDataCache.SpewMemoryUsage( 1 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFileName - |
|
// dataOffset - |
|
// dataSize - |
|
//----------------------------------------------------------------------------- |
|
void PrefetchDataStream( const char *pFileName, int dataOffset, int dataSize ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
// Xbox streaming buffer implementation does not support this "hinting" |
|
return; |
|
} |
|
|
|
wavedatacache->PrefetchCache( pFileName, dataSize, dataOffset ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is an instance of a stream. |
|
// This contains the file handle and streaming buffer |
|
// The mixer doesn't know the file is streaming. The IWaveData |
|
// abstracts the data access. The mixer abstracts data encoding/format |
|
//----------------------------------------------------------------------------- |
|
class CWaveDataStreamAsync : public IWaveData |
|
{ |
|
public: |
|
CWaveDataStreamAsync( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int fileStart, int fileSize, CSfxTable *sfx, int startOffset ); |
|
~CWaveDataStreamAsync( void ); |
|
|
|
// return the source pointer (mixer needs this to determine some things like sampling rate) |
|
CAudioSource &Source( void ) { return m_source; } |
|
|
|
// Read data from the source - this is the primary function of a IWaveData subclass |
|
// Get the data from the buffer (or reload from disk) |
|
virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ); |
|
bool IsValid() { return m_bValid; } |
|
virtual bool IsReadyToMix(); |
|
|
|
private: |
|
CWaveDataStreamAsync( const CWaveDataStreamAsync & ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : byte |
|
//----------------------------------------------------------------------------- |
|
inline byte *GetCachedDataPointer() |
|
{ |
|
VPROF( "CWaveDataStreamAsync::GetCachedDataPointer" ); |
|
|
|
CAudioSourceCachedInfo *info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); |
|
if ( !info ) |
|
{ |
|
Assert( !"CAudioSourceWave::GetCachedDataPointer info == NULL" ); |
|
return NULL; |
|
} |
|
|
|
return (byte *)info->CachedData(); |
|
} |
|
|
|
char const *GetFileName(); |
|
CAudioSource &m_source; // wave source |
|
IWaveStreamSource *m_pStreamSource; // streaming |
|
int m_sampleSize; // size of a sample in bytes |
|
int m_waveSize; // total number of samples in the file |
|
|
|
int m_bufferSize; // size of buffer in samples |
|
char *m_buffer; |
|
int m_sampleIndex; |
|
int m_bufferCount; |
|
int m_dataStart; |
|
int m_dataSize; |
|
|
|
memhandle_t m_hCache; |
|
StreamHandle_t m_hStream; |
|
FileNameHandle_t m_hFileName; |
|
|
|
bool m_bValid; |
|
CAudioSourceCachedInfoHandle_t m_AudioCacheHandle; |
|
int m_nCachedDataSize; |
|
CSfxTable *m_pSfx; |
|
}; |
|
|
|
CWaveDataStreamAsync::CWaveDataStreamAsync |
|
( |
|
CAudioSource &source, |
|
IWaveStreamSource *pStreamSource, |
|
const char *pFileName, |
|
int fileStart, |
|
int fileSize, |
|
CSfxTable *sfx, |
|
int startOffset |
|
) : |
|
m_source( source ), |
|
m_dataStart( fileStart ), |
|
m_dataSize( fileSize ), |
|
m_pStreamSource( pStreamSource ), |
|
m_bValid( false ), |
|
m_hCache( 0 ), |
|
m_hStream( INVALID_STREAM_HANDLE ), |
|
m_hFileName( 0 ), |
|
m_pSfx( sfx ) |
|
{ |
|
m_hFileName = g_pFileSystem->FindOrAddFileName( pFileName ); |
|
|
|
// nothing in the buffer yet |
|
m_sampleIndex = 0; |
|
m_bufferCount = 0; |
|
|
|
if ( IsPC() ) |
|
{ |
|
m_buffer = new char[SINGLE_BUFFER_SIZE]; |
|
Q_memset( m_buffer, 0, SINGLE_BUFFER_SIZE ); |
|
} |
|
|
|
m_nCachedDataSize = 0; |
|
|
|
if ( m_dataSize <= 0 ) |
|
{ |
|
DevMsg(1, "Can't find streaming wav file: sound\\%s\n", GetFileName() ); |
|
return; |
|
} |
|
|
|
if ( IsPC() ) |
|
{ |
|
m_hCache = wavedatacache->AsyncLoadCache( GetFileName(), m_dataSize, m_dataStart ); |
|
|
|
// size of a sample |
|
m_sampleSize = source.SampleSize(); |
|
// size in samples of the buffer |
|
m_bufferSize = SINGLE_BUFFER_SIZE / m_sampleSize; |
|
// size in samples (not bytes) of the wave itself |
|
m_waveSize = fileSize / m_sampleSize; |
|
|
|
m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
// size of a sample |
|
m_sampleSize = source.SampleSize(); |
|
// size in samples (not bytes) of the wave itself |
|
m_waveSize = fileSize / m_sampleSize; |
|
|
|
streamFlags_t flags = STREAMED_FROMDVD; |
|
|
|
if ( !Q_strnicmp( pFileName, "music", 5 ) && ( pFileName[5] == '\\' || pFileName[5] == '/') ) |
|
{ |
|
// music discards and cycles its buffers |
|
flags |= STREAMED_SINGLEPLAY; |
|
} |
|
else if ( !Q_strnicmp( pFileName, "vo", 2 ) && ( pFileName[2] == '\\' || pFileName[2] == '/' ) && !source.IsSentenceWord() ) |
|
{ |
|
// vo discards and cycles its buffers, except for sentence sources, which do recur |
|
flags |= STREAMED_SINGLEPLAY; |
|
} |
|
|
|
int bufferSize; |
|
if ( source.Format() == WAVE_FORMAT_XMA ) |
|
{ |
|
// each xma block has its own compression rate |
|
// the buffer must be large enough to cover worst case delivery i/o latency |
|
// the xma mixer expects quantum xma blocks |
|
COMPILE_TIME_ASSERT( ( STREAM_BUFFER_DATASIZE % XMA_BLOCK_SIZE ) == 0 ); |
|
bufferSize = STREAM_BUFFER_DATASIZE; |
|
} |
|
else |
|
{ |
|
// calculate a worst case buffer size based on rate |
|
bufferSize = STREAM_BUFFER_TIME*source.SampleRate()*m_sampleSize; |
|
if ( source.Format() == WAVE_FORMAT_ADPCM ) |
|
{ |
|
// consider adpcm as 4 bit samples |
|
bufferSize /= 2; |
|
} |
|
|
|
if ( source.IsLooped() ) |
|
{ |
|
// lighten the streaming load for looping samples |
|
// doubling the buffer halves the buffer search/load requests |
|
bufferSize *= 2; |
|
} |
|
} |
|
|
|
// streaming buffers obey alignments |
|
bufferSize = AlignValue( bufferSize, XBOX_DVD_SECTORSIZE ); |
|
|
|
// use double buffering |
|
int numBuffers = 2; |
|
|
|
if ( m_dataSize <= STREAM_BUFFER_DATASIZE || m_dataSize <= numBuffers*bufferSize ) |
|
{ |
|
// no gain for buffering a small file or multiple buffering |
|
// match the expected transfer with a single buffer |
|
bufferSize = m_dataSize; |
|
numBuffers = 1; |
|
} |
|
|
|
// size in samples of the transfer buffer |
|
m_bufferSize = bufferSize / m_sampleSize; |
|
|
|
// allocate a transfer buffer |
|
// matches the size of the streaming buffer exactly |
|
// ensures that buffers can be filled and then consumed/requeued at the same time |
|
m_buffer = new char[bufferSize]; |
|
|
|
int loopStart; |
|
if ( source.IsLooped() ) |
|
{ |
|
int loopBlock; |
|
loopStart = m_pStreamSource->GetLoopingInfo( &loopBlock, NULL, NULL ) * m_sampleSize; |
|
if ( source.Format() == WAVE_FORMAT_XMA ) |
|
{ |
|
// xma works in blocks, mixer handles inter-block accurate loop positioning |
|
// block streaming will cycle from the block where the loop occurs |
|
loopStart = loopBlock * XMA_BLOCK_SIZE; |
|
} |
|
} |
|
else |
|
{ |
|
// sample not looped |
|
loopStart = -1; |
|
} |
|
|
|
// load the file piecewise through a buffering implementation |
|
m_hStream = wavedatacache->OpenStreamedLoad( pFileName, m_dataSize, m_dataStart, startOffset, loopStart, bufferSize, numBuffers, flags ); |
|
} |
|
|
|
m_bValid = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CWaveDataStreamAsync::~CWaveDataStreamAsync( void ) |
|
{ |
|
if ( IsPC() && m_source.IsPlayOnce() && m_source.CanDelete() ) |
|
{ |
|
m_source.SetPlayOnce( false ); // in case it gets used again |
|
wavedatacache->Unload( m_hCache ); |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
wavedatacache->CloseStreamedLoad( m_hStream ); |
|
} |
|
|
|
delete [] m_buffer; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : char const |
|
//----------------------------------------------------------------------------- |
|
char const *CWaveDataStreamAsync::GetFileName() |
|
{ |
|
static char fn[MAX_PATH]; |
|
|
|
if ( m_hFileName ) |
|
{ |
|
if ( g_pFileSystem->String( m_hFileName, fn, sizeof( fn ) ) ) |
|
{ |
|
return fn; |
|
} |
|
} |
|
|
|
Assert( 0 ); |
|
return ""; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CWaveDataStreamAsync::IsReadyToMix() |
|
{ |
|
if ( IsPC() ) |
|
{ |
|
// If not async loaded, start mixing right away |
|
if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() ) |
|
{ |
|
return true; |
|
} |
|
|
|
bool bCacheValid; |
|
bool bLoaded = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid ); |
|
if ( !bCacheValid ) |
|
{ |
|
wavedatacache->RestartDataLoad( &m_hCache, GetFileName(), m_dataSize, m_dataStart ); |
|
} |
|
return bLoaded; |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
return wavedatacache->IsStreamedDataReady( m_hStream ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Read data from the source - this is the primary function of a IWaveData subclass |
|
// Get the data from the buffer (or reload from disk) |
|
// Input : **pData - |
|
// sampleIndex - |
|
// sampleCount - |
|
// copyBuf[AUDIOSOURCE_COPYBUF_SIZE] - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CWaveDataStreamAsync::ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) |
|
{ |
|
// Current file position |
|
int seekpos = m_dataStart + m_sampleIndex * m_sampleSize; |
|
|
|
// wrap position if looping |
|
if ( m_source.IsLooped() ) |
|
{ |
|
sampleIndex = m_pStreamSource->UpdateLoopingSamplePosition( sampleIndex ); |
|
if ( sampleIndex < m_sampleIndex ) |
|
{ |
|
// looped back, buffer has no samples yet |
|
m_sampleIndex = sampleIndex; |
|
m_bufferCount = 0; |
|
|
|
// update file position |
|
seekpos = m_dataStart + sampleIndex * m_sampleSize; |
|
} |
|
} |
|
|
|
// UNDONE: This is an error!! |
|
// The mixer playing back the stream tried to go backwards!?!?! |
|
// BUGBUG: Just play the beginning of the buffer until we get to a valid linear position |
|
if ( sampleIndex < m_sampleIndex ) |
|
sampleIndex = m_sampleIndex; |
|
|
|
// calc sample position relative to the current buffer |
|
// m_sampleIndex is the sample position of the first byte of the buffer |
|
sampleIndex -= m_sampleIndex; |
|
|
|
// out of range? refresh buffer |
|
if ( sampleIndex >= m_bufferCount ) |
|
{ |
|
// advance one buffer (the file is positioned here) |
|
m_sampleIndex += m_bufferCount; |
|
// next sample to load |
|
sampleIndex -= m_bufferCount; |
|
|
|
// if the remainder is greated than one buffer size, seek over it. Otherwise, read the next chunk |
|
// and leave the remainder as an offset. |
|
|
|
// number of buffers to "skip" (as in the case where we are starting a streaming sound not at the beginning) |
|
int skips = sampleIndex / m_bufferSize; |
|
|
|
// If we are skipping over a buffer, do it with a seek instead of a read. |
|
if ( skips ) |
|
{ |
|
// skip directly to next position |
|
m_sampleIndex += sampleIndex; |
|
sampleIndex = 0; |
|
} |
|
|
|
// move the file to the new position |
|
seekpos = m_dataStart + (m_sampleIndex * m_sampleSize); |
|
|
|
// This is the maximum number of samples we could read from the file |
|
m_bufferCount = m_waveSize - m_sampleIndex; |
|
|
|
// past the end of the file? stop the wave. |
|
if ( m_bufferCount <= 0 ) |
|
return 0; |
|
|
|
// clamp available samples to buffer size |
|
if ( m_bufferCount > m_bufferSize ) |
|
m_bufferCount = m_bufferSize; |
|
|
|
if ( IsPC() ) |
|
{ |
|
// See if we can load in the intial data right out of the cached data lump instead. |
|
int cacheddatastartpos = ( seekpos - m_dataStart ); |
|
|
|
// FastGet doesn't call into IsPrecachedSound if the handle appears valid... |
|
CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet(); |
|
if ( !info ) |
|
{ |
|
// Full recache |
|
info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); |
|
} |
|
|
|
bool startupCacheUsed = false; |
|
|
|
if ( info && |
|
( m_nCachedDataSize > 0 ) && |
|
( cacheddatastartpos < m_nCachedDataSize ) ) |
|
{ |
|
// Get a ptr to the cached data |
|
const byte *cacheddata = info->CachedData(); |
|
if ( cacheddata ) |
|
{ |
|
// See how many samples of cached data are available (cacheddatastartpos is zero on the first read) |
|
int availSamples = ( m_nCachedDataSize - cacheddatastartpos ) / m_sampleSize; |
|
|
|
// Clamp to size of our internal buffer |
|
if ( availSamples > m_bufferSize ) |
|
{ |
|
availSamples = m_bufferSize; |
|
} |
|
|
|
// Mark how many we are returning |
|
m_bufferCount = availSamples; |
|
// Copy raw sample data directly out of cache |
|
Q_memcpy( m_buffer, ( char * )cacheddata + cacheddatastartpos, availSamples * m_sampleSize ); |
|
|
|
startupCacheUsed = true; |
|
} |
|
} |
|
|
|
// Not in startup cache, grab data from async cache loader (will block if data hasn't arrived yet) |
|
if ( !startupCacheUsed ) |
|
{ |
|
bool postprocessed = false; |
|
|
|
// read in the max bufferable, available samples |
|
if ( !wavedatacache->CopyDataIntoMemory( |
|
m_hCache, |
|
GetFileName(), |
|
m_dataSize, |
|
m_dataStart, |
|
m_buffer, |
|
sizeof( m_buffer ), |
|
seekpos, |
|
m_bufferCount * m_sampleSize, |
|
&postprocessed ) ) |
|
{ |
|
return 0; |
|
} |
|
|
|
// do any conversion the source needs (mixer will decode/decompress) |
|
if ( !postprocessed ) |
|
{ |
|
// Note that we don't set the postprocessed flag on the underlying data, since for streaming we're copying the |
|
// original data into this buffer instead. |
|
m_pStreamSource->UpdateSamples( m_buffer, m_bufferCount ); |
|
} |
|
} |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
if ( m_hStream != INVALID_STREAM_HANDLE ) |
|
{ |
|
// request available data, may get less |
|
// drives the buffering |
|
m_bufferCount = wavedatacache->CopyStreamedDataIntoMemory( |
|
m_hStream, |
|
m_buffer, |
|
m_bufferSize * m_sampleSize, |
|
seekpos, |
|
m_bufferCount * m_sampleSize ); |
|
// convert to number of samples in the buffer |
|
m_bufferCount /= m_sampleSize; |
|
} |
|
else |
|
{ |
|
return 0; |
|
} |
|
|
|
// do any conversion now the source needs (mixer will decode/decompress) on this buffer |
|
m_pStreamSource->UpdateSamples( m_buffer, m_bufferCount ); |
|
} |
|
} |
|
|
|
// If we have some samples in the buffer that are within range of the request |
|
// Use unsigned comparisons so that if sampleIndex is somehow negative that |
|
// will be treated as out of range. |
|
if ( (unsigned)sampleIndex < (unsigned)m_bufferCount ) |
|
{ |
|
// Get the desired starting sample |
|
*pData = (void *)&m_buffer[sampleIndex * m_sampleSize]; |
|
|
|
// max available |
|
int available = m_bufferCount - sampleIndex; |
|
// clamp available to max requested |
|
if ( available > sampleCount ) |
|
available = sampleCount; |
|
|
|
return available; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Iterator for wave data (this is to abstract streaming/buffering) |
|
//----------------------------------------------------------------------------- |
|
class CWaveDataMemoryAsync : public IWaveData |
|
{ |
|
public: |
|
CWaveDataMemoryAsync( CAudioSource &source ); |
|
~CWaveDataMemoryAsync( void ) {} |
|
CAudioSource &Source( void ) { return m_source; } |
|
|
|
virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ); |
|
virtual bool IsReadyToMix(); |
|
|
|
private: |
|
CAudioSource &m_source; // pointer to source |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &source - |
|
//----------------------------------------------------------------------------- |
|
CWaveDataMemoryAsync::CWaveDataMemoryAsync( CAudioSource &source ) : |
|
m_source(source) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : **pData - |
|
// sampleIndex - |
|
// sampleCount - |
|
// copyBuf[AUDIOSOURCE_COPYBUF_SIZE] - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CWaveDataMemoryAsync::ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) |
|
{ |
|
return m_source.GetOutputData( pData, sampleIndex, sampleCount, copyBuf ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CWaveDataMemoryAsync::IsReadyToMix() |
|
{ |
|
if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() ) |
|
{ |
|
// Wait until we're pending at least |
|
if ( m_source.GetCacheStatus() == CAudioSource::AUDIO_NOT_LOADED ) |
|
{ |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
if ( m_source.IsCached() ) |
|
{ |
|
return true; |
|
} |
|
|
|
if ( IsPC() ) |
|
{ |
|
// Msg( "Waiting for data '%s'\n", m_source.GetFileName() ); |
|
m_source.CacheLoad(); |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
// expected to be resident and valid, otherwise being called prior to load |
|
Assert( 0 ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &source - |
|
// *pStreamSource - |
|
// &io - |
|
// *pFileName - |
|
// dataOffset - |
|
// dataSize - |
|
// Output : IWaveData |
|
//----------------------------------------------------------------------------- |
|
IWaveData *CreateWaveDataStream( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int dataStart, int dataSize, CSfxTable *pSfx, int startOffset ) |
|
{ |
|
CWaveDataStreamAsync *pStream = new CWaveDataStreamAsync( source, pStreamSource, pFileName, dataStart, dataSize, pSfx, startOffset ); |
|
if ( !pStream || !pStream->IsValid() ) |
|
{ |
|
delete pStream; |
|
pStream = NULL; |
|
} |
|
return pStream; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &source - |
|
// Output : IWaveData |
|
//----------------------------------------------------------------------------- |
|
IWaveData *CreateWaveDataMemory( CAudioSource &source ) |
|
{ |
|
CWaveDataMemoryAsync *mem = new CWaveDataMemoryAsync( source ); |
|
return mem; |
|
}
|
|
|