//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "audio_pch.h" #include "snd_mp3_source.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" CAudioSourceMP3::CAudioSourceMP3( CSfxTable *pSfx ) { m_sampleRate = 44100; m_pSfx = pSfx; m_refCount = 0; m_dataStart = 0; char nameBuf[MAX_PATH]; FileHandle_t file = g_pSndIO->open( pSfx->GetFileName( nameBuf, sizeof(nameBuf) ) ); if ( (intp)file != -1 ) { m_dataSize = g_pSndIO->size( file ); g_pSndIO->close( file ); } else { m_dataSize = 0; } m_nCachedDataSize = 0; m_bIsPlayOnce = false; m_bIsSentenceWord = false; } CAudioSourceMP3::CAudioSourceMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) { m_sampleRate = 44100; m_pSfx = pSfx; m_refCount = 0; m_dataSize = info->DataSize(); m_dataStart = info->DataStart(); m_nCachedDataSize = 0; m_bIsPlayOnce = false; } 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 ( !m_AudioCacheHandle.IsValid() ) { m_AudioCacheHandle.Get( GetType(), m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); } // 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; } bool CAudioSourceMP3::GetStartupData() { char nameBuf[MAX_PATH]; FileHandle_t file = g_pSndIO->open( m_pSfx->GetFileName( nameBuf, sizeof(nameBuf) ) ); if ( !file ) { return false; } m_dataSize = (int)g_pSndIO->size( file ); g_pSndIO->close( file ); return true; } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CAudioSourceMP3::GetType() { return AUDIO_SOURCE_MP3; } void CAudioSourceMP3::GetCacheData( CAudioSourceCachedInfo *info ) { info->SetSampleRate( m_sampleRate ); info->SetDataStart( 0 ); GetStartupData(); // Data size gets computed in GetStartupData!!! info->SetDataSize( m_dataSize ); } //----------------------------------------------------------------------------- // Purpose: // Output : char const //----------------------------------------------------------------------------- char const *CAudioSourceMP3::GetFileName( char *pOutBuf, size_t bufLen ) { return m_pSfx ? m_pSfx->GetFileName(pOutBuf, bufLen) : "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(); } //----------------------------------------------------------------------------- // 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 ) { char nameBuf[MAX_PATH]; wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(nameBuf, sizeof(nameBuf)), m_dataSize, m_dataStart ); } return loaded; } void CAudioSourceMP3Cache::CacheLoad( void ) { // Commence lazy load? if ( m_hCache != 0 ) { GetCacheStatus(); return; } char nameBuf[MAX_PATH]; m_hCache = wavedatacache->AsyncLoadCache( m_pSfx->GetFileName(nameBuf, sizeof(nameBuf)), 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(); } char nameBuf[MAX_PATH]; wavedatacache->GetDataPointer( m_hCache, m_pSfx->GetFileName(nameBuf, sizeof(nameBuf)), m_dataSize, m_dataStart, (void **)&pMP3Data, 0, &dummy ); return pMP3Data; } int CAudioSourceMP3Cache::GetOutputData( void **pData, int64 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, int skipInitialSamples, bool bUpdateDelayForChoreo, SoundError &soundError, hrtf_info_t *pHRTFVector ) { CAudioMixer *pMixer = CreateMP3Mixer( CreateWaveDataMemory(*this), &m_sampleRate ); if ( pMixer ) { ReferenceAdd( pMixer ); soundError = SE_OK; } else { soundError = SE_CANT_CREATE_MIXER; } return pMixer; } CAudioSourceStreamMP3::CAudioSourceStreamMP3( CSfxTable *pSfx ) : CAudioSourceMP3( pSfx ) { } CAudioSourceStreamMP3::CAudioSourceStreamMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) : CAudioSourceMP3( pSfx, info ) { m_dataSize = info->DataSize(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAudioSourceStreamMP3::Prefetch() { char nameBuf[MAX_PATH]; PrefetchDataStream( m_pSfx->GetFileName(nameBuf, sizeof(nameBuf)), 0, m_dataSize ); } CAudioMixer *CAudioSourceStreamMP3::CreateMixer(int intialStreamPosition, int initialSkipSamples, bool bUpdateDelayForChoreo, SoundError &soundError, hrtf_info_t *pHRTFVector ) { char nameBuf[MAX_PATH]; // BUGBUG: Source constructs the IWaveData, mixer frees it, fix this? IWaveData *pWaveData = CreateWaveDataStream(*this, static_cast(this), m_pSfx->GetFileName(nameBuf, sizeof(nameBuf)), 0, m_dataSize, m_pSfx, 0, 0, soundError ); if ( pWaveData ) { CAudioMixer *pMixer = CreateMP3Mixer( pWaveData, &m_sampleRate ); if ( pMixer ) { ReferenceAdd( pMixer ); soundError = SE_OK; return pMixer; } // no mixer but pWaveData was deleted in mixer's destructor // so no need to delete } soundError = SE_CANT_CREATE_MIXER; return NULL; } int CAudioSourceStreamMP3::GetOutputData( void **pData, int64 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 && info->DataSize() != 0 ) { 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; }