mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-08 09:49:36 +00:00
1
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//=============================================================================//
|
||||
#include "audio_pch.h"
|
||||
#include "snd_mp3_source.h"
|
||||
#include "snd_dma.h"
|
||||
#include "snd_wave_mixer_mp3.h"
|
||||
#include "filesystem_engine.h"
|
||||
#include "utldict.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
#ifndef DEDICATED // have to test this because VPC is forcing us to compile this file.
|
||||
|
||||
// How many bytes initial data bytes of the mp3 should be saved in the soundcache, in addition to the small amount of
|
||||
// metadata (playbackrate, etc). This will increase memory usage by
|
||||
// ( N * number-of-precached-mp3-sounds-in-the-whole-game ) at all times, as the soundcache is held in memory.
|
||||
//
|
||||
// Right now we're setting this to zero. The IsReadyToMix() logic at the data layer will delay mixing of the sound until
|
||||
// it arrives. Setting this to anything above zero, however, will allow the sound to start, so it needs to either be
|
||||
// enough to cover SND_ASYNC_LOOKAHEAD_SECONDS or none at all.
|
||||
#define MP3_STARTUP_DATA_SIZE_BYTES 0
|
||||
|
||||
CUtlDict< CSentence *, int> g_PhonemeFileSentences;
|
||||
bool g_bAllPhonemesLoaded;
|
||||
|
||||
void PhonemeMP3Shutdown( void )
|
||||
{
|
||||
g_PhonemeFileSentences.PurgeAndDeleteElements();
|
||||
g_bAllPhonemesLoaded = false;
|
||||
}
|
||||
|
||||
void AddPhonemesFromFile( const char *pszFileName )
|
||||
{
|
||||
// If all Phonemes are loaded, do not load anymore
|
||||
if ( g_bAllPhonemesLoaded && g_PhonemeFileSentences.Count() != 0 )
|
||||
return;
|
||||
|
||||
// Empty file name implies stop loading more phonemes
|
||||
if ( pszFileName == NULL )
|
||||
{
|
||||
g_bAllPhonemesLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load this file
|
||||
g_bAllPhonemesLoaded = false;
|
||||
|
||||
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
||||
if ( g_pFileSystem->ReadFile( pszFileName, "MOD", buf ) )
|
||||
{
|
||||
while ( 1 )
|
||||
{
|
||||
char token[4096];
|
||||
buf.GetString( token );
|
||||
|
||||
V_FixSlashes( token );
|
||||
|
||||
int iIndex = g_PhonemeFileSentences.Find( token );
|
||||
|
||||
if ( iIndex != g_PhonemeFileSentences.InvalidIndex() )
|
||||
{
|
||||
delete g_PhonemeFileSentences.Element( iIndex );
|
||||
g_PhonemeFileSentences.Remove( token );
|
||||
}
|
||||
|
||||
CSentence *pSentence = new CSentence;
|
||||
g_PhonemeFileSentences.Insert( token, pSentence );
|
||||
|
||||
buf.GetString( token );
|
||||
|
||||
if ( strlen( token ) <= 0 )
|
||||
break;
|
||||
|
||||
if ( !stricmp( token, "{" ) )
|
||||
{
|
||||
pSentence->InitFromBuffer( buf );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CAudioSourceMP3::CAudioSourceMP3( CSfxTable *pSfx )
|
||||
{
|
||||
m_sampleRate = 0;
|
||||
m_pSfx = pSfx;
|
||||
m_refCount = 0;
|
||||
|
||||
m_dataStart = 0;
|
||||
|
||||
int file = g_pSndIO->open( pSfx->GetFileName() );
|
||||
if ( file != -1 )
|
||||
{
|
||||
m_dataSize = g_pSndIO->size( file );
|
||||
g_pSndIO->close( file );
|
||||
}
|
||||
else
|
||||
{
|
||||
// No sound cache, the file isn't here, print this so that the relatively deep failure points that are about to
|
||||
// spew make a little more sense
|
||||
Warning( "MP3 is completely missing, sound system will be upset to learn of this [ %s ]\n", pSfx->GetFileName() );
|
||||
m_dataSize = 0;
|
||||
}
|
||||
|
||||
|
||||
m_nCachedDataSize = 0;
|
||||
m_bIsPlayOnce = false;
|
||||
m_bIsSentenceWord = false;
|
||||
m_bCheckedForPendingSentence = false;
|
||||
}
|
||||
|
||||
CAudioSourceMP3::CAudioSourceMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info )
|
||||
{
|
||||
m_pSfx = pSfx;
|
||||
m_refCount = 0;
|
||||
|
||||
m_sampleRate = info->SampleRate();
|
||||
m_dataSize = info->DataSize();
|
||||
m_dataStart = info->DataStart();
|
||||
|
||||
m_nCachedDataSize = 0;
|
||||
m_bIsPlayOnce = false;
|
||||
m_bCheckedForPendingSentence = false;
|
||||
|
||||
CheckAudioSourceCache();
|
||||
}
|
||||
|
||||
CAudioSourceMP3::~CAudioSourceMP3()
|
||||
{
|
||||
}
|
||||
|
||||
// mixer's references
|
||||
void CAudioSourceMP3::ReferenceAdd( CAudioMixer * )
|
||||
{
|
||||
m_refCount++;
|
||||
}
|
||||
|
||||
void CAudioSourceMP3::ReferenceRemove( CAudioMixer * )
|
||||
{
|
||||
m_refCount--;
|
||||
if ( m_refCount == 0 && IsPlayOnce() )
|
||||
{
|
||||
SetPlayOnce( false ); // in case it gets used again
|
||||
CacheUnload();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAudioSourceMP3::IsAsyncLoad()
|
||||
{
|
||||
// If there's a bit of "cached data" then we don't have to lazy/async load (we still async load the remaining data,
|
||||
// but we run from the cache initially)
|
||||
return ( m_nCachedDataSize > 0 ) ? false : true;
|
||||
}
|
||||
|
||||
// check reference count, return true if nothing is referencing this
|
||||
bool CAudioSourceMP3::CanDelete( void )
|
||||
{
|
||||
return m_refCount > 0 ? false : true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : int
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAudioSourceMP3::GetType()
|
||||
{
|
||||
return AUDIO_SOURCE_MP3;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAudioSourceMP3::SetSentence( CSentence *pSentence )
|
||||
{
|
||||
CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
|
||||
|
||||
if ( !info )
|
||||
return;
|
||||
|
||||
if ( info && info->Sentence() )
|
||||
return;
|
||||
|
||||
CSentence *pNewSentence = new CSentence;
|
||||
|
||||
pNewSentence->Append( 0.0f, *pSentence );
|
||||
pNewSentence->MakeRuntimeOnly();
|
||||
|
||||
info->SetSentence( pNewSentence );
|
||||
}
|
||||
|
||||
int CAudioSourceMP3::SampleRate()
|
||||
{
|
||||
if ( !m_sampleRate )
|
||||
{
|
||||
// This should've come from the sound cache. We can avoid sync I/O jank if and only if we've started streaming
|
||||
// data already for some other reason. (Despite the name, CreateWaveDataMemory is just creating a wrapper class
|
||||
// that manages access to the wave data cache)
|
||||
IWaveData *pData = CreateWaveDataMemory( *this );
|
||||
if ( !pData->IsReadyToMix() && SND_IsInGame() )
|
||||
{
|
||||
// If you hit this, you're creating a sound source that isn't in the sound cache, and asking for its sample
|
||||
// rate before it has streamed enough data in to read it from the underlying file. Your options are:
|
||||
// - Rebuild sound cache or figure out why this sound wasn't included.
|
||||
// - Precache this sound at level load so this doesn't happen during gameplay.
|
||||
// - Somehow call CacheLoad() on this source earlier so it has time to get data into memory so the data
|
||||
// shows up as IsReadyToMix here, and this crutch won't jank.
|
||||
Warning( "MP3 initialized with no sound cache, this may cause janking. [ %s ]\n", GetFileName() );
|
||||
// The code below will still go fine, but the mixer will emit a jank warning that the data wasn't ready and
|
||||
// do sync I/O
|
||||
}
|
||||
CAudioMixerWaveMP3 *pMixer = new CAudioMixerWaveMP3( pData );
|
||||
m_sampleRate = pMixer->GetStreamOutputRate();
|
||||
// pData ownership is passed to, and free'd by, pMixer
|
||||
delete pMixer;
|
||||
}
|
||||
return m_sampleRate;
|
||||
}
|
||||
|
||||
void CAudioSourceMP3::GetCacheData( CAudioSourceCachedInfo *info )
|
||||
{
|
||||
// Don't want to replicate our cached sample rate back into the new cache, ensure we recompute it.
|
||||
CAudioMixerWaveMP3 *pTempMixer = new CAudioMixerWaveMP3( CreateWaveDataMemory(*this) );
|
||||
m_sampleRate = pTempMixer->GetStreamOutputRate();
|
||||
delete pTempMixer;
|
||||
|
||||
AssertMsg( m_sampleRate, "Creating cache with invalid sample rate data" );
|
||||
if ( !m_sampleRate )
|
||||
{
|
||||
Warning( "Failed to find sample rate creating cache data for MP3, cache will be invalid [ %s ]\n", GetFileName() );
|
||||
}
|
||||
|
||||
info->SetSampleRate( m_sampleRate );
|
||||
info->SetDataStart( 0 );
|
||||
|
||||
int file = g_pSndIO->open( m_pSfx->GetFileName() );
|
||||
if ( !file )
|
||||
{
|
||||
Warning( "Failed to find file for building soundcache [ %s ]\n", m_pSfx->GetFileName() );
|
||||
// Don't re-use old cached value
|
||||
m_dataSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dataSize = (int)g_pSndIO->size( file );
|
||||
}
|
||||
|
||||
Assert( m_dataSize > 0 );
|
||||
|
||||
// Do we need to actually load any audio data?
|
||||
#if MP3_STARTUP_DATA_SIZE_BYTES > 0 // We may have defined the startup data to nothingness
|
||||
if ( info->s_bIsPrecacheSound && m_dataSize > 0 )
|
||||
{
|
||||
// Ideally this would mimic the wave startup data code and figure out this calculation:
|
||||
// int bytesNeeded = m_channels * ( m_bits >> 3 ) * m_rate * SND_ASYNC_LOOKAHEAD_SECONDS;
|
||||
// (plus header)
|
||||
int dataSize = min( MP3_STARTUP_DATA_SIZE_BYTES, m_dataSize );
|
||||
byte *data = new byte[ dataSize ]();
|
||||
int readSize = g_pSndIO->read( data, dataSize, file );
|
||||
if ( readSize != dataSize )
|
||||
{
|
||||
Warning( "Building soundcache, expected %i bytes of data but got %i [ %s ]\n", dataSize, readSize, m_pSfx->GetFileName() );
|
||||
dataSize = readSize;
|
||||
}
|
||||
info->SetCachedDataSize( dataSize );
|
||||
info->SetCachedData( data );
|
||||
}
|
||||
#endif // MP3_STARTUP_DATA_SIZE_BYTES > 0
|
||||
|
||||
g_pSndIO->close( file );
|
||||
|
||||
// Data size gets computed in GetStartupData!!!
|
||||
info->SetDataSize( m_dataSize );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : char const
|
||||
//-----------------------------------------------------------------------------
|
||||
char const *CAudioSourceMP3::GetFileName()
|
||||
{
|
||||
return m_pSfx ? m_pSfx->GetFileName() : "NULL m_pSfx";
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAudioSourceMP3::CheckAudioSourceCache()
|
||||
{
|
||||
Assert( m_pSfx );
|
||||
|
||||
if ( !m_pSfx->IsPrecachedSound() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This will "re-cache" this if it's not in this level's cache already
|
||||
m_AudioCacheHandle.Get( GetType(), true, m_pSfx, &m_nCachedDataSize );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: NULL the wave data pointer (we haven't loaded yet)
|
||||
//-----------------------------------------------------------------------------
|
||||
CAudioSourceMP3Cache::CAudioSourceMP3Cache( CSfxTable *pSfx ) :
|
||||
CAudioSourceMP3( pSfx )
|
||||
{
|
||||
m_hCache = 0;
|
||||
}
|
||||
|
||||
CAudioSourceMP3Cache::CAudioSourceMP3Cache( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
|
||||
CAudioSourceMP3( pSfx, info )
|
||||
{
|
||||
m_hCache = 0;
|
||||
|
||||
m_dataSize = info->DataSize();
|
||||
m_dataStart = info->DataStart();
|
||||
|
||||
m_bNoSentence = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Free any wave data we've allocated
|
||||
//-----------------------------------------------------------------------------
|
||||
CAudioSourceMP3Cache::~CAudioSourceMP3Cache( void )
|
||||
{
|
||||
CacheUnload();
|
||||
}
|
||||
|
||||
int CAudioSourceMP3Cache::GetCacheStatus( void )
|
||||
{
|
||||
bool bCacheValid;
|
||||
int loaded = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid ) ? AUDIO_IS_LOADED : AUDIO_NOT_LOADED;
|
||||
if ( !bCacheValid )
|
||||
{
|
||||
wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(), m_dataSize, m_dataStart );
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
|
||||
void CAudioSourceMP3Cache::CacheLoad( void )
|
||||
{
|
||||
// Commence lazy load?
|
||||
if ( m_hCache != 0 )
|
||||
{
|
||||
GetCacheStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
m_hCache = wavedatacache->AsyncLoadCache( m_pSfx->GetFileName(), m_dataSize, m_dataStart );
|
||||
}
|
||||
|
||||
void CAudioSourceMP3Cache::CacheUnload( void )
|
||||
{
|
||||
if ( m_hCache != 0 )
|
||||
{
|
||||
wavedatacache->Unload( m_hCache );
|
||||
}
|
||||
}
|
||||
|
||||
char *CAudioSourceMP3Cache::GetDataPointer( void )
|
||||
{
|
||||
char *pMP3Data = NULL;
|
||||
bool dummy = false;
|
||||
|
||||
if ( m_hCache == 0 )
|
||||
{
|
||||
CacheLoad();
|
||||
}
|
||||
|
||||
wavedatacache->GetDataPointer(
|
||||
m_hCache,
|
||||
m_pSfx->GetFileName(),
|
||||
m_dataSize,
|
||||
m_dataStart,
|
||||
(void **)&pMP3Data,
|
||||
0,
|
||||
&dummy );
|
||||
|
||||
return pMP3Data;
|
||||
}
|
||||
|
||||
int CAudioSourceMP3Cache::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
|
||||
{
|
||||
// how many bytes are available ?
|
||||
int totalSampleCount = m_dataSize - samplePosition;
|
||||
|
||||
// may be asking for a sample out of range, clip at zero
|
||||
if ( totalSampleCount < 0 )
|
||||
totalSampleCount = 0;
|
||||
|
||||
// clip max output samples to max available
|
||||
if ( sampleCount > totalSampleCount )
|
||||
sampleCount = totalSampleCount;
|
||||
|
||||
// if we are returning some samples, store the pointer
|
||||
if ( sampleCount )
|
||||
{
|
||||
// Starting past end of "preloaded" data, just use regular cache
|
||||
if ( samplePosition >= m_nCachedDataSize )
|
||||
{
|
||||
*pData = GetDataPointer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start async loader if we haven't already done so
|
||||
CacheLoad();
|
||||
|
||||
// Return less data if we are about to run out of uncached data
|
||||
if ( samplePosition + sampleCount >= m_nCachedDataSize )
|
||||
{
|
||||
sampleCount = m_nCachedDataSize - samplePosition;
|
||||
}
|
||||
|
||||
// Point at preloaded/cached data from .cache file for now
|
||||
*pData = GetCachedDataPointer();
|
||||
}
|
||||
|
||||
if ( *pData )
|
||||
{
|
||||
*pData = (char *)*pData + samplePosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Out of data or file i/o problem
|
||||
sampleCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
CAudioMixer *CAudioSourceMP3Cache::CreateMixer( int initialStreamPosition )
|
||||
{
|
||||
CAudioMixer *pMixer = new CAudioMixerWaveMP3( CreateWaveDataMemory(*this) );
|
||||
|
||||
return pMixer;
|
||||
}
|
||||
|
||||
CSentence *CAudioSourceMP3Cache::GetSentence( void )
|
||||
{
|
||||
// Already checked and this wav doesn't have sentence data...
|
||||
if ( m_bNoSentence == true )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Look up sentence from cache
|
||||
CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
|
||||
if ( !info )
|
||||
{
|
||||
info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
|
||||
}
|
||||
Assert( info );
|
||||
if ( !info )
|
||||
{
|
||||
m_bNoSentence = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CSentence *sentence = info->Sentence();
|
||||
if ( !sentence )
|
||||
{
|
||||
if ( !m_bCheckedForPendingSentence )
|
||||
{
|
||||
int iSentence = g_PhonemeFileSentences.Find( m_pSfx->GetFileName() );
|
||||
if ( iSentence != g_PhonemeFileSentences.InvalidIndex() )
|
||||
{
|
||||
sentence = g_PhonemeFileSentences.Element( iSentence );
|
||||
SetSentence( sentence );
|
||||
}
|
||||
m_bCheckedForPendingSentence = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !sentence )
|
||||
{
|
||||
m_bNoSentence = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( sentence->m_bIsValid )
|
||||
{
|
||||
return sentence;
|
||||
}
|
||||
|
||||
m_bNoSentence = true;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CAudioSourceStreamMP3
|
||||
//-----------------------------------------------------------------------------
|
||||
CAudioSourceStreamMP3::CAudioSourceStreamMP3( CSfxTable *pSfx ) :
|
||||
CAudioSourceMP3( pSfx )
|
||||
{
|
||||
}
|
||||
|
||||
CAudioSourceStreamMP3::CAudioSourceStreamMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
|
||||
CAudioSourceMP3( pSfx, info )
|
||||
{
|
||||
m_dataSize = info->DataSize();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAudioSourceStreamMP3::Prefetch()
|
||||
{
|
||||
PrefetchDataStream( m_pSfx->GetFileName(), 0, m_dataSize );
|
||||
}
|
||||
|
||||
CAudioMixer *CAudioSourceStreamMP3::CreateMixer( int intialStreamPosition )
|
||||
{
|
||||
// BUGBUG: Source constructs the IWaveData, mixer frees it, fix this?
|
||||
IWaveData *pWaveData = CreateWaveDataStream( *this, static_cast<IWaveStreamSource *>( this ), m_pSfx->GetFileName(), 0, m_dataSize, m_pSfx, 0 );
|
||||
if ( pWaveData )
|
||||
{
|
||||
CAudioMixer *pMixer = new CAudioMixerWaveMP3( pWaveData );
|
||||
if ( pMixer )
|
||||
{
|
||||
if ( !m_bCheckedForPendingSentence )
|
||||
{
|
||||
int iSentence = g_PhonemeFileSentences.Find( m_pSfx->GetFileName() );
|
||||
|
||||
if ( iSentence != g_PhonemeFileSentences.InvalidIndex() )
|
||||
{
|
||||
SetSentence( g_PhonemeFileSentences.Element( iSentence ) );
|
||||
}
|
||||
|
||||
m_bCheckedForPendingSentence = true;
|
||||
}
|
||||
|
||||
return pMixer;
|
||||
}
|
||||
|
||||
// no mixer but pWaveData was deleted in mixer's destructor
|
||||
// so no need to delete
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int CAudioSourceStreamMP3::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Audio_IsMP3( const char *pName )
|
||||
{
|
||||
int len = strlen(pName);
|
||||
if ( len > 4 )
|
||||
{
|
||||
if ( !Q_strnicmp( &pName[len - 4], ".mp3", 4 ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
CAudioSource *Audio_CreateStreamedMP3( CSfxTable *pSfx )
|
||||
{
|
||||
CAudioSourceStreamMP3 *pMP3 = NULL;
|
||||
CAudioSourceCachedInfo *info = audiosourcecache->GetInfo( CAudioSource::AUDIO_SOURCE_MP3, pSfx->IsPrecachedSound(), pSfx );
|
||||
if ( info )
|
||||
{
|
||||
pMP3 = new CAudioSourceStreamMP3( pSfx, info );
|
||||
}
|
||||
else
|
||||
{
|
||||
pMP3 = new CAudioSourceStreamMP3( pSfx );
|
||||
}
|
||||
return pMP3;
|
||||
}
|
||||
|
||||
|
||||
CAudioSource *Audio_CreateMemoryMP3( CSfxTable *pSfx )
|
||||
{
|
||||
CAudioSourceMP3Cache *pMP3 = NULL;
|
||||
CAudioSourceCachedInfo *info = audiosourcecache->GetInfo( CAudioSource::AUDIO_SOURCE_MP3, pSfx->IsPrecachedSound(), pSfx );
|
||||
if ( info )
|
||||
{
|
||||
pMP3 = new CAudioSourceMP3Cache( pSfx, info );
|
||||
}
|
||||
else
|
||||
{
|
||||
pMP3 = new CAudioSourceMP3Cache( pSfx );
|
||||
}
|
||||
return pMP3;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user