source-engine/engine/audio/MPAFile.cpp

415 lines
11 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 <dwNewBufferSize-m_dwBufferSize> bytes from offset <m_dwBufferSize>
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;
}