//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #if defined( WIN32) && !defined( _X360 ) #include "winlite.h" #endif #include "tier0/platform.h" #include "MPAFile.h" #include "soundchars.h" #include "tier1/utlrbtree.h" #include "memdbgon.h" extern IFileSystem *g_pFullFileSystem; // exception class CMPAException::CMPAException(ErrorIDs ErrorID, const char *szFile, const char *szFunction, bool bGetLastError ) : m_ErrorID( ErrorID ), m_bGetLastError( bGetLastError ) { m_szFile = szFile ? strdup(szFile) : NULL; m_szFunction = szFunction ? strdup(szFunction) : NULL; } // copy constructor (necessary for exception throwing without pointers) CMPAException::CMPAException(const CMPAException& Source) { m_ErrorID = Source.m_ErrorID; m_bGetLastError = Source.m_bGetLastError; m_szFile = Source.m_szFile ? strdup(Source.m_szFile) : NULL; m_szFunction = Source.m_szFunction ? strdup(Source.m_szFunction) : NULL; } // destructor CMPAException::~CMPAException() { if( m_szFile ) free( (void*)m_szFile ); if( m_szFunction ) free( (void*)m_szFunction ); } // should be in resource file for multi language applications const char *m_szErrors[] = { "Can't open the file.", "Can't set file position.", "Can't read from file.", "Reached end of buffer.", "No VBR Header found.", "Incomplete VBR Header.", "No subsequent frame found within tolerance range.", "No frame found." }; #define MAX_ERR_LENGTH 256 void CMPAException::ShowError() { char szErrorMsg[MAX_ERR_LENGTH] = {0}; char szHelp[MAX_ERR_LENGTH]; // this is not buffer-overflow-proof! if( m_szFunction ) { sprintf( szHelp, _T("%s: "), m_szFunction ); strcat( szErrorMsg, szHelp ); } if( m_szFile ) { sprintf( szHelp, _T("'%s'\n"), m_szFile ); strcat( szErrorMsg, szHelp ); } strcat( szErrorMsg, m_szErrors[m_ErrorID] ); #if defined(WIN32) && !defined(_X360) if( m_bGetLastError ) { // get error message of last system error id LPVOID pMsgBuf; if ( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &pMsgBuf, 0, NULL )) { strcat( szErrorMsg, "\n" ); strcat( szErrorMsg, (const char *)pMsgBuf ); LocalFree( pMsgBuf ); } } #endif // show error message Warning( "%s\n", szErrorMsg ); } // 1KB is inital buffersize, each time the buffer needs to be increased it is doubled const uint32 CMPAFile::m_dwInitBufferSize = 1024; CMPAFile::CMPAFile( const char * szFile, uint32 dwFileOffset, FileHandle_t hFile ) : m_pBuffer(NULL), m_dwBufferSize(0), m_dwBegin( dwFileOffset ), m_dwEnd(0), m_dwNumTimesRead(0), m_bVBRFile( false ), m_pVBRHeader(NULL), m_bMustReleaseFile( false ), m_pMPAHeader(NULL), m_hFile( hFile ), m_szFile(NULL), m_dwFrameNo(1) { // open file, if not already done if( m_hFile == FILESYSTEM_INVALID_HANDLE ) { Open( szFile ); m_bMustReleaseFile = true; } // save filename m_szFile = strdup( szFile ); // set end of MPEG data (assume file end) if( m_dwEnd <= 0 ) { // get file size m_dwEnd = g_pFullFileSystem->Size( m_hFile ); } // find first valid MPEG frame m_pMPAHeader = new CMPAHeader( this ); // is VBR header available? CVBRHeader::VBRHeaderType HeaderType = CVBRHeader::NoHeader; uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset; if( CVBRHeader::IsVBRHeaderAvailable( this, HeaderType, dwOffset ) ) { try { // read out VBR header m_pVBRHeader = new CVBRHeader( this, HeaderType, dwOffset ); m_bVBRFile = true; m_dwBytesPerSec = m_pVBRHeader->m_dwBytesPerSec; if( m_pVBRHeader->m_dwBytes > 0 ) m_dwEnd = m_dwBegin + m_pVBRHeader->m_dwBytes; } catch(CMPAException& Exc) { Exc.ShowError(); } } if( !m_pVBRHeader ) { // always skip empty (32kBit) frames m_bVBRFile = m_pMPAHeader->SkipEmptyFrames(); m_dwBytesPerSec = m_pMPAHeader->GetBytesPerSecond(); } } bool CMPAFile::GetNextFrame() { uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset + m_pMPAHeader->m_dwRealFrameSize; try { CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false ); delete m_pMPAHeader; m_pMPAHeader = pFrame; if( m_dwFrameNo > 0 ) m_dwFrameNo++; } catch(...) { return false; } return true; } bool CMPAFile::GetPrevFrame() { uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset-MPA_HEADER_SIZE; try { // look backward from dwOffset on CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false, true ); delete m_pMPAHeader; m_pMPAHeader = pFrame; if( m_dwFrameNo > 0 ) m_dwFrameNo --; } catch(...) { return false; } return true; } bool CMPAFile::GetFirstFrame() { uint32 dwOffset = 0; try { CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false ); delete m_pMPAHeader; m_pMPAHeader = pFrame; m_dwFrameNo = 1; } catch(...) { return false; } return true; } bool CMPAFile::GetLastFrame() { uint32 dwOffset = m_dwEnd - m_dwBegin - MPA_HEADER_SIZE; try { // look backward from dwOffset on CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false, true ); delete m_pMPAHeader; m_pMPAHeader = pFrame; m_dwFrameNo = 0; } catch(...) { return false; } return true; } // destructor CMPAFile::~CMPAFile(void) { delete m_pMPAHeader; if( m_pVBRHeader ) delete m_pVBRHeader; if( m_pBuffer ) delete[] m_pBuffer; // close file if( m_bMustReleaseFile ) g_pFullFileSystem->Close( m_hFile ); if( m_szFile ) free( (void*)m_szFile ); } // open file void CMPAFile::Open( const char * szFilename ) { // open with CreateFile (no limitation of 128byte filename length, like in mmioOpen) m_hFile = g_pFullFileSystem->Open( szFilename, "rb", "GAME" );//::CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( m_hFile == FILESYSTEM_INVALID_HANDLE ) { // throw error throw CMPAException( CMPAException::ErrOpenFile, szFilename, _T("CreateFile"), true ); } } // set file position void CMPAFile::SetPosition( int offset ) { /* LARGE_INTEGER liOff; liOff.QuadPart = lOffset; liOff.LowPart = ::SetFilePointer(m_hFile, liOff.LowPart, &liOff.HighPart, dwMoveMethod ); if (liOff.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR ) { // throw error throw CMPAException( CMPAException::ErrSetPosition, m_szFile, _T("SetFilePointer"), true ); } */ g_pFullFileSystem->Seek( m_hFile, offset, FILESYSTEM_SEEK_HEAD ); } // read from file, return number of bytes read uint32 CMPAFile::Read( void *pData, uint32 dwSize, uint32 dwOffset ) { uint32 dwBytesRead = 0; // set position first SetPosition( m_dwBegin+dwOffset ); //if( !::ReadFile( m_hFile, pData, dwSize, &dwBytesRead, NULL ) ) // throw CMPAException( CMPAException::ErrReadFile, m_szFile, _T("ReadFile"), true ); dwBytesRead = g_pFullFileSystem->Read( pData, dwSize, m_hFile ); return dwBytesRead; } // convert from big endian to native format (Intel=little endian) and return as uint32 (32bit) uint32 CMPAFile::ExtractBytes( uint32& dwOffset, uint32 dwNumBytes, bool bMoveOffset ) { Assert( dwNumBytes > 0 ); Assert( dwNumBytes <= 4 ); // max 4 byte // enough bytes in buffer, otherwise read from file if( !m_pBuffer || ( ((int)(m_dwBufferSize - dwOffset)) < (int)dwNumBytes) ) FillBuffer( dwOffset + dwNumBytes ); uint32 dwResult = 0; // big endian extract (most significant byte first) (will work on little and big-endian computers) uint32 dwNumByteShifts = dwNumBytes - 1; for( uint32 n=dwOffset; n < dwOffset+dwNumBytes; n++ ) { dwResult |= ((byte)m_pBuffer[n]) << (8*dwNumByteShifts); // the bit shift will do the correct byte order for you dwNumByteShifts--; } if( bMoveOffset ) dwOffset += dwNumBytes; return dwResult; } // throws exception if not possible void CMPAFile::FillBuffer( uint32 dwOffsetToRead ) { uint32 dwNewBufferSize; // calc new buffer size if( m_dwBufferSize == 0 ) dwNewBufferSize = m_dwInitBufferSize; else dwNewBufferSize = m_dwBufferSize*2; // is it big enough? if( dwNewBufferSize < dwOffsetToRead ) dwNewBufferSize = dwOffsetToRead; // reserve new buffer BYTE* pNewBuffer = new BYTE[dwNewBufferSize]; // take over data from old buffer if( m_pBuffer ) { memcpy( pNewBuffer, m_pBuffer, m_dwBufferSize ); // release old buffer delete[] m_pBuffer; } m_pBuffer = (char*)pNewBuffer; // read bytes from offset uint32 dwBytesRead = Read( m_pBuffer+m_dwBufferSize, dwNewBufferSize-m_dwBufferSize, m_dwBufferSize ); // no more bytes in buffer than read out from file m_dwBufferSize += dwBytesRead; } // Uses mp3 code from: http://www.codeproject.com/audio/MPEGAudioInfo.asp struct MP3Duration_t { FileNameHandle_t h; float duration; static bool LessFunc( const MP3Duration_t& lhs, const MP3Duration_t& rhs ) { return lhs.h < rhs.h; } }; CUtlRBTree< MP3Duration_t, int > g_MP3Durations( 0, 0, MP3Duration_t::LessFunc ); float GetMP3Duration_Helper( char const *filename ) { float duration = 60.0f; // See if it's in the RB tree already... char fn[ 512 ]; Q_snprintf( fn, sizeof( fn ), "sound/%s", PSkipSoundChars( filename ) ); FileNameHandle_t h = g_pFullFileSystem->FindOrAddFileName( fn ); MP3Duration_t search; search.h = h; int idx = g_MP3Durations.Find( search ); if ( idx != g_MP3Durations.InvalidIndex() ) { return g_MP3Durations[ idx ].duration; } try { CMPAFile MPAFile( fn, 0 ); if ( MPAFile.m_dwBytesPerSec != 0 ) { duration = (float)(MPAFile.m_dwEnd - MPAFile.m_dwBegin) / (float)MPAFile.m_dwBytesPerSec; } } catch ( ... ) { } search.duration = duration; g_MP3Durations.Insert( search ); return duration; }