Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #if defined( WIN32) && !defined( _X360 )
  7. #include "winlite.h"
  8. #endif
  9. #include "tier0/platform.h"
  10. #include "MPAFile.h"
  11. #include "soundchars.h"
  12. #include "tier1/utlrbtree.h"
  13. #include "memdbgon.h"
  14. extern IFileSystem *g_pFullFileSystem;
  15. // exception class
  16. CMPAException::CMPAException(ErrorIDs ErrorID, const char *szFile, const char *szFunction, bool bGetLastError ) :
  17. m_ErrorID( ErrorID ), m_bGetLastError( bGetLastError )
  18. {
  19. m_szFile = szFile ? strdup(szFile) : NULL;
  20. m_szFunction = szFunction ? strdup(szFunction) : NULL;
  21. }
  22. // copy constructor (necessary for exception throwing without pointers)
  23. CMPAException::CMPAException(const CMPAException& Source)
  24. {
  25. m_ErrorID = Source.m_ErrorID;
  26. m_bGetLastError = Source.m_bGetLastError;
  27. m_szFile = Source.m_szFile ? strdup(Source.m_szFile) : NULL;
  28. m_szFunction = Source.m_szFunction ? strdup(Source.m_szFunction) : NULL;
  29. }
  30. // destructor
  31. CMPAException::~CMPAException()
  32. {
  33. if( m_szFile )
  34. free( (void*)m_szFile );
  35. if( m_szFunction )
  36. free( (void*)m_szFunction );
  37. }
  38. // should be in resource file for multi language applications
  39. const char *m_szErrors[] =
  40. {
  41. "Can't open the file.",
  42. "Can't set file position.",
  43. "Can't read from file.",
  44. "Reached end of buffer.",
  45. "No VBR Header found.",
  46. "Incomplete VBR Header.",
  47. "No subsequent frame found within tolerance range.",
  48. "No frame found."
  49. };
  50. #define MAX_ERR_LENGTH 256
  51. void CMPAException::ShowError()
  52. {
  53. char szErrorMsg[MAX_ERR_LENGTH] = {0};
  54. char szHelp[MAX_ERR_LENGTH];
  55. // this is not buffer-overflow-proof!
  56. if( m_szFunction )
  57. {
  58. sprintf( szHelp, _T("%s: "), m_szFunction );
  59. strcat( szErrorMsg, szHelp );
  60. }
  61. if( m_szFile )
  62. {
  63. sprintf( szHelp, _T("'%s'\n"), m_szFile );
  64. strcat( szErrorMsg, szHelp );
  65. }
  66. strcat( szErrorMsg, m_szErrors[m_ErrorID] );
  67. #if defined(WIN32) && !defined(_X360)
  68. if( m_bGetLastError )
  69. {
  70. // get error message of last system error id
  71. LPVOID pMsgBuf;
  72. if ( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  73. NULL,
  74. GetLastError(),
  75. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  76. (LPTSTR) &pMsgBuf,
  77. 0,
  78. NULL ))
  79. {
  80. strcat( szErrorMsg, "\n" );
  81. strcat( szErrorMsg, (const char *)pMsgBuf );
  82. LocalFree( pMsgBuf );
  83. }
  84. }
  85. #endif
  86. // show error message
  87. Warning( "%s\n", szErrorMsg );
  88. }
  89. // 1KB is inital buffersize, each time the buffer needs to be increased it is doubled
  90. const uint32 CMPAFile::m_dwInitBufferSize = 1024;
  91. CMPAFile::CMPAFile( const char * szFile, uint32 dwFileOffset, FileHandle_t hFile ) :
  92. m_pBuffer(NULL), m_dwBufferSize(0), m_dwBegin( dwFileOffset ), m_dwEnd(0),
  93. m_dwNumTimesRead(0), m_bVBRFile( false ), m_pVBRHeader(NULL), m_bMustReleaseFile( false ),
  94. m_pMPAHeader(NULL), m_hFile( hFile ), m_szFile(NULL), m_dwFrameNo(1)
  95. {
  96. // open file, if not already done
  97. if( m_hFile == FILESYSTEM_INVALID_HANDLE )
  98. {
  99. Open( szFile );
  100. m_bMustReleaseFile = true;
  101. }
  102. // save filename
  103. m_szFile = strdup( szFile );
  104. // set end of MPEG data (assume file end)
  105. if( m_dwEnd <= 0 )
  106. {
  107. // get file size
  108. m_dwEnd = g_pFullFileSystem->Size( m_hFile );
  109. }
  110. // find first valid MPEG frame
  111. m_pMPAHeader = new CMPAHeader( this );
  112. // is VBR header available?
  113. CVBRHeader::VBRHeaderType HeaderType = CVBRHeader::NoHeader;
  114. uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset;
  115. if( CVBRHeader::IsVBRHeaderAvailable( this, HeaderType, dwOffset ) )
  116. {
  117. try
  118. {
  119. // read out VBR header
  120. m_pVBRHeader = new CVBRHeader( this, HeaderType, dwOffset );
  121. m_bVBRFile = true;
  122. m_dwBytesPerSec = m_pVBRHeader->m_dwBytesPerSec;
  123. if( m_pVBRHeader->m_dwBytes > 0 )
  124. m_dwEnd = m_dwBegin + m_pVBRHeader->m_dwBytes;
  125. }
  126. catch(CMPAException& Exc)
  127. {
  128. Exc.ShowError();
  129. }
  130. }
  131. if( !m_pVBRHeader )
  132. {
  133. // always skip empty (32kBit) frames
  134. m_bVBRFile = m_pMPAHeader->SkipEmptyFrames();
  135. m_dwBytesPerSec = m_pMPAHeader->GetBytesPerSecond();
  136. }
  137. }
  138. bool CMPAFile::GetNextFrame()
  139. {
  140. uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset + m_pMPAHeader->m_dwRealFrameSize;
  141. try
  142. {
  143. CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false );
  144. delete m_pMPAHeader;
  145. m_pMPAHeader = pFrame;
  146. if( m_dwFrameNo > 0 )
  147. m_dwFrameNo++;
  148. }
  149. catch(...)
  150. {
  151. return false;
  152. }
  153. return true;
  154. }
  155. bool CMPAFile::GetPrevFrame()
  156. {
  157. uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset-MPA_HEADER_SIZE;
  158. try
  159. {
  160. // look backward from dwOffset on
  161. CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false, true );
  162. delete m_pMPAHeader;
  163. m_pMPAHeader = pFrame;
  164. if( m_dwFrameNo > 0 )
  165. m_dwFrameNo --;
  166. }
  167. catch(...)
  168. {
  169. return false;
  170. }
  171. return true;
  172. }
  173. bool CMPAFile::GetFirstFrame()
  174. {
  175. uint32 dwOffset = 0;
  176. try
  177. {
  178. CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false );
  179. delete m_pMPAHeader;
  180. m_pMPAHeader = pFrame;
  181. m_dwFrameNo = 1;
  182. }
  183. catch(...)
  184. {
  185. return false;
  186. }
  187. return true;
  188. }
  189. bool CMPAFile::GetLastFrame()
  190. {
  191. uint32 dwOffset = m_dwEnd - m_dwBegin - MPA_HEADER_SIZE;
  192. try
  193. {
  194. // look backward from dwOffset on
  195. CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false, true );
  196. delete m_pMPAHeader;
  197. m_pMPAHeader = pFrame;
  198. m_dwFrameNo = 0;
  199. }
  200. catch(...)
  201. {
  202. return false;
  203. }
  204. return true;
  205. }
  206. // destructor
  207. CMPAFile::~CMPAFile(void)
  208. {
  209. delete m_pMPAHeader;
  210. if( m_pVBRHeader )
  211. delete m_pVBRHeader;
  212. if( m_pBuffer )
  213. delete[] m_pBuffer;
  214. // close file
  215. if( m_bMustReleaseFile )
  216. g_pFullFileSystem->Close( m_hFile );
  217. if( m_szFile )
  218. free( (void*)m_szFile );
  219. }
  220. // open file
  221. void CMPAFile::Open( const char * szFilename )
  222. {
  223. // open with CreateFile (no limitation of 128byte filename length, like in mmioOpen)
  224. m_hFile = g_pFullFileSystem->Open( szFilename, "rb", "GAME" );//::CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
  225. if( m_hFile == FILESYSTEM_INVALID_HANDLE )
  226. {
  227. // throw error
  228. throw CMPAException( CMPAException::ErrOpenFile, szFilename, _T("CreateFile"), true );
  229. }
  230. }
  231. // set file position
  232. void CMPAFile::SetPosition( int offset )
  233. {
  234. /*
  235. LARGE_INTEGER liOff;
  236. liOff.QuadPart = lOffset;
  237. liOff.LowPart = ::SetFilePointer(m_hFile, liOff.LowPart, &liOff.HighPart, dwMoveMethod );
  238. if (liOff.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR )
  239. {
  240. // throw error
  241. throw CMPAException( CMPAException::ErrSetPosition, m_szFile, _T("SetFilePointer"), true );
  242. }
  243. */
  244. g_pFullFileSystem->Seek( m_hFile, offset, FILESYSTEM_SEEK_HEAD );
  245. }
  246. // read from file, return number of bytes read
  247. uint32 CMPAFile::Read( void *pData, uint32 dwSize, uint32 dwOffset )
  248. {
  249. uint32 dwBytesRead = 0;
  250. // set position first
  251. SetPosition( m_dwBegin+dwOffset );
  252. //if( !::ReadFile( m_hFile, pData, dwSize, &dwBytesRead, NULL ) )
  253. // throw CMPAException( CMPAException::ErrReadFile, m_szFile, _T("ReadFile"), true );
  254. dwBytesRead = g_pFullFileSystem->Read( pData, dwSize, m_hFile );
  255. return dwBytesRead;
  256. }
  257. // convert from big endian to native format (Intel=little endian) and return as uint32 (32bit)
  258. uint32 CMPAFile::ExtractBytes( uint32& dwOffset, uint32 dwNumBytes, bool bMoveOffset )
  259. {
  260. Assert( dwNumBytes > 0 );
  261. Assert( dwNumBytes <= 4 ); // max 4 byte
  262. // enough bytes in buffer, otherwise read from file
  263. if( !m_pBuffer || ( ((int)(m_dwBufferSize - dwOffset)) < (int)dwNumBytes) )
  264. FillBuffer( dwOffset + dwNumBytes );
  265. uint32 dwResult = 0;
  266. // big endian extract (most significant byte first) (will work on little and big-endian computers)
  267. uint32 dwNumByteShifts = dwNumBytes - 1;
  268. for( uint32 n=dwOffset; n < dwOffset+dwNumBytes; n++ )
  269. {
  270. dwResult |= ((byte)m_pBuffer[n]) << (8*dwNumByteShifts); // the bit shift will do the correct byte order for you
  271. dwNumByteShifts--;
  272. }
  273. if( bMoveOffset )
  274. dwOffset += dwNumBytes;
  275. return dwResult;
  276. }
  277. // throws exception if not possible
  278. void CMPAFile::FillBuffer( uint32 dwOffsetToRead )
  279. {
  280. uint32 dwNewBufferSize;
  281. // calc new buffer size
  282. if( m_dwBufferSize == 0 )
  283. dwNewBufferSize = m_dwInitBufferSize;
  284. else
  285. dwNewBufferSize = m_dwBufferSize*2;
  286. // is it big enough?
  287. if( dwNewBufferSize < dwOffsetToRead )
  288. dwNewBufferSize = dwOffsetToRead;
  289. // reserve new buffer
  290. BYTE* pNewBuffer = new BYTE[dwNewBufferSize];
  291. // take over data from old buffer
  292. if( m_pBuffer )
  293. {
  294. memcpy( pNewBuffer, m_pBuffer, m_dwBufferSize );
  295. // release old buffer
  296. delete[] m_pBuffer;
  297. }
  298. m_pBuffer = (char*)pNewBuffer;
  299. // read <dwNewBufferSize-m_dwBufferSize> bytes from offset <m_dwBufferSize>
  300. uint32 dwBytesRead = Read( m_pBuffer+m_dwBufferSize, dwNewBufferSize-m_dwBufferSize, m_dwBufferSize );
  301. // no more bytes in buffer than read out from file
  302. m_dwBufferSize += dwBytesRead;
  303. }
  304. // Uses mp3 code from: http://www.codeproject.com/audio/MPEGAudioInfo.asp
  305. struct MP3Duration_t
  306. {
  307. FileNameHandle_t h;
  308. float duration;
  309. static bool LessFunc( const MP3Duration_t& lhs, const MP3Duration_t& rhs )
  310. {
  311. return lhs.h < rhs.h;
  312. }
  313. };
  314. CUtlRBTree< MP3Duration_t, int > g_MP3Durations( 0, 0, MP3Duration_t::LessFunc );
  315. float GetMP3Duration_Helper( char const *filename )
  316. {
  317. float duration = 60.0f;
  318. // See if it's in the RB tree already...
  319. char fn[ 512 ];
  320. Q_snprintf( fn, sizeof( fn ), "sound/%s", PSkipSoundChars( filename ) );
  321. FileNameHandle_t h = g_pFullFileSystem->FindOrAddFileName( fn );
  322. MP3Duration_t search;
  323. search.h = h;
  324. int idx = g_MP3Durations.Find( search );
  325. if ( idx != g_MP3Durations.InvalidIndex() )
  326. {
  327. return g_MP3Durations[ idx ].duration;
  328. }
  329. try
  330. {
  331. CMPAFile MPAFile( fn, 0 );
  332. if ( MPAFile.m_dwBytesPerSec != 0 )
  333. {
  334. duration = (float)(MPAFile.m_dwEnd - MPAFile.m_dwBegin) / (float)MPAFile.m_dwBytesPerSec;
  335. }
  336. }
  337. catch ( ... )
  338. {
  339. }
  340. search.duration = duration;
  341. g_MP3Durations.Insert( search );
  342. return duration;
  343. }