mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-22 14:16:50 +00:00
2395 lines
70 KiB
C++
2395 lines
70 KiB
C++
//========= 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;
|
|
}
|