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.
304 lines
7.8 KiB
304 lines
7.8 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "audio_pch.h" |
|
#include "tier0/platform.h" |
|
#include "MPAFile.h" // also includes vbrheader.h |
|
#include "tier0/dbg.h" |
|
|
|
#ifndef MAKEFOURCC |
|
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ |
|
((uint32)(BYTE)(ch0) | ((uint32)(BYTE)(ch1) << 8) | \ |
|
((uint32)(BYTE)(ch2) << 16) | ((uint32)(BYTE)(ch3) << 24 )) |
|
#endif //defined(MAKEFOURCC) |
|
|
|
// XING Header offset: 1. index = lsf, 2. index = mono |
|
uint32 CVBRHeader::m_dwXINGOffsets[2][2] = |
|
{ |
|
// MPEG 1 (not mono, mono) |
|
{ 32 + MPA_HEADER_SIZE, 17 + MPA_HEADER_SIZE }, |
|
// MPEG 2/2.5 |
|
{ 17 + MPA_HEADER_SIZE, 9 + MPA_HEADER_SIZE } |
|
}; |
|
|
|
// first test with this static method, if it does exist |
|
bool CVBRHeader::IsVBRHeaderAvailable( CMPAFile* pMPAFile, VBRHeaderType& HeaderType, uint32& dwOffset ) |
|
{ |
|
Assert(pMPAFile); |
|
|
|
// where does VBR header begin (XING) |
|
uint32 dwNewOffset = dwOffset + m_dwXINGOffsets[pMPAFile->m_pMPAHeader->IsLSF()][pMPAFile->m_pMPAHeader->IsMono()]; |
|
|
|
// check for XING header first |
|
if( CheckXING( pMPAFile, dwNewOffset ) ) |
|
{ |
|
HeaderType = XINGHeader; |
|
// seek offset back to header begin |
|
dwOffset = dwNewOffset - 4; |
|
return true; |
|
} |
|
|
|
// VBRI header always at fixed offset |
|
dwNewOffset = dwOffset + 32 + MPA_HEADER_SIZE; |
|
if( CheckVBRI( pMPAFile, dwNewOffset ) ) |
|
{ |
|
HeaderType = VBRIHeader; |
|
// seek offset back to header begin |
|
dwOffset = dwNewOffset - 4; |
|
return true; |
|
} |
|
HeaderType = NoHeader; |
|
return false; |
|
} |
|
|
|
CVBRHeader::CVBRHeader( CMPAFile* pMPAFile, VBRHeaderType HeaderType, uint32 dwOffset ) : |
|
m_pMPAFile( pMPAFile ), m_pnToc(NULL), m_HeaderType( HeaderType ), m_dwOffset(dwOffset), m_dwFrames(0), m_dwBytes(0) |
|
{ |
|
switch( m_HeaderType ) |
|
{ |
|
case NoHeader: |
|
// no Header found |
|
throw CMPAException( CMPAException::NoVBRHeader, pMPAFile->GetFilename(), NULL, false ); |
|
break; |
|
case XINGHeader: |
|
if( !ExtractXINGHeader( m_dwOffset ) ) |
|
throw CMPAException( CMPAException::NoVBRHeader, pMPAFile->GetFilename(), NULL, false ); |
|
break; |
|
case VBRIHeader: |
|
if( !ExtractVBRIHeader( m_dwOffset ) ) |
|
throw CMPAException( CMPAException::NoVBRHeader, pMPAFile->GetFilename(), NULL, false ); |
|
break; |
|
} |
|
// calc bitrate |
|
if( m_dwBytes > 0 && m_dwFrames > 0 ) |
|
{ |
|
// calc number of seconds |
|
m_dwBytesPerSec = m_pMPAFile->m_pMPAHeader->GetBytesPerSecond( m_dwFrames, m_dwBytes ); |
|
} |
|
else // incomplete header found |
|
{ |
|
throw CMPAException( CMPAException::IncompleteVBRHeader, pMPAFile->GetFilename(), NULL, false ); |
|
} |
|
} |
|
|
|
bool CVBRHeader::CheckID( CMPAFile* pMPAFile, char ch0, char ch1, char ch2, char ch3, uint32& dwOffset ) |
|
{ |
|
return ( pMPAFile->ExtractBytes( dwOffset, 4 ) == MAKEFOURCC( ch3, ch2, ch1, ch0 ) ); |
|
} |
|
|
|
bool CVBRHeader::CheckXING( CMPAFile* pMPAFile, uint32& dwOffset ) |
|
{ |
|
// XING ID found? |
|
if( !CheckID( pMPAFile, 'X', 'i', 'n', 'g', dwOffset) && !CheckID( pMPAFile, 'I', 'n', 'f', 'o', dwOffset) ) |
|
return false; |
|
return true; |
|
} |
|
|
|
bool CVBRHeader::CheckVBRI( CMPAFile* pMPAFile, uint32& dwOffset ) |
|
{ |
|
// VBRI ID found? |
|
if( !CheckID( pMPAFile, 'V', 'B', 'R', 'I', dwOffset ) ) |
|
return false; |
|
return true; |
|
} |
|
|
|
|
|
// currently not used |
|
bool CVBRHeader::ExtractLAMETag( uint32 dwOffset ) |
|
{ |
|
// LAME ID found? |
|
if( !CheckID( m_pMPAFile, 'L', 'A', 'M', 'E', dwOffset ) && !CheckID( m_pMPAFile, 'G', 'O', 'G', 'O', dwOffset ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
bool CVBRHeader::ExtractXINGHeader( uint32 dwOffset ) |
|
{ |
|
/* XING VBR-Header |
|
|
|
size description |
|
4 'Xing' or 'Info' |
|
4 flags (indicates which fields are used) |
|
4 frames (optional) |
|
4 bytes (optional) |
|
100 toc (optional) |
|
4 a VBR quality indicator: 0=best 100=worst (optional) |
|
|
|
*/ |
|
if( !CheckXING( m_pMPAFile, dwOffset ) ) |
|
return false; |
|
|
|
uint32 dwFlags; |
|
|
|
// get flags (mandatory in XING header) |
|
dwFlags = m_pMPAFile->ExtractBytes( dwOffset, 4 ); |
|
|
|
// extract total number of frames in file |
|
if(dwFlags & FRAMES_FLAG) |
|
m_dwFrames = m_pMPAFile->ExtractBytes(dwOffset,4); |
|
|
|
// extract total number of bytes in file |
|
if(dwFlags & BYTES_FLAG) |
|
m_dwBytes = m_pMPAFile->ExtractBytes(dwOffset,4); |
|
|
|
// extract TOC (for more accurate seeking) |
|
if (dwFlags & TOC_FLAG) |
|
{ |
|
m_dwTableSize = 100; |
|
m_pnToc = new int[m_dwTableSize]; |
|
|
|
if( m_pnToc ) |
|
{ |
|
for(uint32 i=0;i<m_dwTableSize;i++) |
|
m_pnToc[i] = m_pMPAFile->ExtractBytes( dwOffset, 1 ); |
|
} |
|
} |
|
|
|
m_dwQuality = (uint32)-1; |
|
if(dwFlags & VBR_SCALE_FLAG ) |
|
m_dwQuality = m_pMPAFile->ExtractBytes(dwOffset, 4); |
|
|
|
return true; |
|
} |
|
|
|
bool CVBRHeader::ExtractVBRIHeader( uint32 dwOffset ) |
|
{ |
|
/* FhG VBRI Header |
|
|
|
size description |
|
4 'VBRI' (ID) |
|
2 version |
|
2 delay |
|
2 quality |
|
4 # bytes |
|
4 # frames |
|
2 table size (for TOC) |
|
2 table scale (for TOC) |
|
2 size of table entry (max. size = 4 byte (must be stored in an integer)) |
|
2 frames per table entry |
|
|
|
?? dynamic table consisting out of frames with size 1-4 |
|
whole length in table size! (for TOC) |
|
|
|
*/ |
|
|
|
if( !CheckVBRI( m_pMPAFile, dwOffset ) ) |
|
return false; |
|
|
|
// extract all fields from header (all mandatory) |
|
m_dwVersion = m_pMPAFile->ExtractBytes(dwOffset, 2 ); |
|
m_fDelay = (float)m_pMPAFile->ExtractBytes(dwOffset, 2 ); |
|
m_dwQuality = m_pMPAFile->ExtractBytes(dwOffset, 2 ); |
|
m_dwBytes = m_pMPAFile->ExtractBytes(dwOffset, 4 ); |
|
m_dwFrames = m_pMPAFile->ExtractBytes(dwOffset, 4 ); |
|
m_dwTableSize = m_pMPAFile->ExtractBytes(dwOffset, 2 ) + 1; //!!! |
|
m_dwTableScale = m_pMPAFile->ExtractBytes(dwOffset, 2 ); |
|
m_dwBytesPerEntry = m_pMPAFile->ExtractBytes(dwOffset, 2 ); |
|
m_dwFramesPerEntry = m_pMPAFile->ExtractBytes(dwOffset, 2 ); |
|
|
|
// extract TOC (for more accurate seeking) |
|
m_pnToc = new int[m_dwTableSize]; |
|
if( m_pnToc ) |
|
{ |
|
for ( unsigned int i = 0 ; i < m_dwTableSize ; i++) |
|
{ |
|
m_pnToc[i] = m_pMPAFile->ExtractBytes(dwOffset, m_dwBytesPerEntry ); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
CVBRHeader::~CVBRHeader(void) |
|
{ |
|
if( m_pnToc ) |
|
delete[] m_pnToc; |
|
} |
|
|
|
// get byte position for percentage value (fPercent) of file |
|
bool CVBRHeader::SeekPoint(float fPercent, uint32& dwSeekPoint) |
|
{ |
|
if( !m_pnToc || m_dwBytes == 0 ) |
|
return false; |
|
|
|
if( fPercent < 0.0f ) |
|
fPercent = 0.0f; |
|
if( fPercent > 100.0f ) |
|
fPercent = 100.0f; |
|
|
|
switch( m_HeaderType ) |
|
{ |
|
case XINGHeader: |
|
dwSeekPoint = SeekPointXING( fPercent ); |
|
break; |
|
case VBRIHeader: |
|
dwSeekPoint = SeekPointVBRI( fPercent ); |
|
break; |
|
} |
|
return true; |
|
} |
|
|
|
uint32 CVBRHeader::SeekPointXING(float fPercent) const |
|
{ |
|
// interpolate in TOC to get file seek point in bytes |
|
int a; |
|
float fa, fb, fx; |
|
|
|
a = (int)fPercent; |
|
if( a > 99 ) a = 99; |
|
fa = (float)m_pnToc[a]; |
|
|
|
if( a < 99 ) |
|
{ |
|
fb = (float)m_pnToc[a+1]; |
|
} |
|
else |
|
{ |
|
fb = 256.0f; |
|
} |
|
|
|
fx = fa + (fb-fa)*(fPercent-a); |
|
|
|
uint32 dwSeekpoint = (int)((1.0f/256.0f)*fx*m_dwBytes); |
|
return dwSeekpoint; |
|
} |
|
|
|
uint32 CVBRHeader::SeekPointVBRI(float fPercent) const |
|
{ |
|
return SeekPointByTimeVBRI( (fPercent/100.0f) * m_pMPAFile->m_pMPAHeader->GetLengthSecond( m_dwFrames ) * 1000.0f ); |
|
} |
|
|
|
uint32 CVBRHeader::SeekPointByTimeVBRI(float fEntryTimeMS) const |
|
{ |
|
unsigned int i=0, fraction = 0; |
|
uint32 dwSeekPoint = 0; |
|
|
|
float fLengthMS; |
|
float fLengthMSPerTOCEntry; |
|
float fAccumulatedTimeMS = 0.0f ; |
|
|
|
fLengthMS = (float)m_pMPAFile->m_pMPAHeader->GetLengthSecond( m_dwFrames ) * 1000.0f ; |
|
fLengthMSPerTOCEntry = fLengthMS / (float)m_dwTableSize; |
|
|
|
if ( fEntryTimeMS > fLengthMS ) |
|
fEntryTimeMS = fLengthMS; |
|
|
|
while ( fAccumulatedTimeMS <= fEntryTimeMS ) |
|
{ |
|
dwSeekPoint += m_pnToc[i++]; |
|
fAccumulatedTimeMS += fLengthMSPerTOCEntry; |
|
} |
|
|
|
// Searched too far; correct result |
|
fraction = ( (int)(((( fAccumulatedTimeMS - fEntryTimeMS ) / fLengthMSPerTOCEntry ) |
|
+ (1.0f/(2.0f*(float)m_dwFramesPerEntry))) * (float)m_dwFramesPerEntry)); |
|
|
|
dwSeekPoint -= (uint32)((float)m_pnToc[i-1] * (float)(fraction) |
|
/ (float)m_dwFramesPerEntry); |
|
|
|
return dwSeekPoint; |
|
} |
|
|
|
|