cog/Libraries/DecMPA/Files/src/MPAFrameFinder.cpp

551 lines
12 KiB
C++

/* DecMPA - simple MPEG Audio decoding library.
Copyright (C) 2002 Hauke Duden
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For more information look at the file License.txt in this package.
email: hazard_hd@users.sourceforge.net
*/
#include "MPAFrameFinder.h"
#include <memory.h>
CMPAFrameFinder::CMPAFrameFinder()
{
// max size of buffer is:
// header: 4
// max bitrate: 448
// min freq: 22050
// padding: 1
// ------------------
// maxsize: 4+144000*max(bitrate)/min(freq)+1 ca: 2931 byte
// then we add a "sentinel" at the end these are 4 byte.
// so we should be ok, with a 4KB buffer.
m_OutBuffer.Alloc(4096);
m_OutBuffer.SetSize(4096);
m_MyInBuffer.Alloc(65536);
m_pFirstHeader=(HeaderRecord*)0;
m_pLastHeader=(HeaderRecord*)0;
m_pNewHeaderRecord=(HeaderRecord*)0;
m_bStreamInvalid=false;
m_nLayer=-1;
m_bAllowFrequencyChanges=false;
m_nLookAheadFrames=3; //try to find 4 consistent frames by default
m_nReadStartPosition=0;
Restart();
}
CMPAFrameFinder::~CMPAFrameFinder()
{
DeleteHeaderRecords();
}
bool CMPAFrameFinder::ReadNextFrame()
{
bool bNeedMoreThanAvailable;
if(m_State==STATE_HAVEFRAME)
ResetState();
while(true)
{
bNeedMoreThanAvailable=false;
while(m_State!=STATE_HAVEFRAME && m_pInBuffer->HasMoreData() && !bNeedMoreThanAvailable)
{
switch(m_State)
{
case STATE_FINDCHARACTERISTICS: if(!FindCharacteristics())
{
//need more data, but we may still have
//up to 3 bytes buffered
//so, break manually here
bNeedMoreThanAvailable=true;
}
break;
case STATE_FINDSYNC: FindNextSync();
break;
case STATE_READHEADER: ReadHeader();
break;
case STATE_CHECKHEADER: CheckHeader();
break;
case STATE_READFRAME: ReadFrame();
break;
}
}
if(m_State!=STATE_HAVEFRAME && m_State>=STATE_FINDSYNC && m_pInBuffer==&m_MyInBuffer)
{
//we need more data, but were still reading
//from our own preread data that was used to find out the
//stream characteristics
//switch to user buffer (it may already contain some data)
m_pInBuffer=&m_UserInBuffer;
}
else
break;
}
if(m_State==STATE_FINDCHARACTERISTICS && m_MyInBuffer.GetSize()==m_MyInBuffer.GetCapacity())
{
//we have read the maximum of data and not found m_nLookAheadFrames+1 consistent
//frames
//the stream does probably not contain valid MPEG Audio data
m_bStreamInvalid=true;
}
return (m_State==STATE_HAVEFRAME); //otherwise we need more data
}
bool CMPAFrameFinder::FindCharacteristics()
{
unsigned char* pData;
//we read up to 64k data and try to find at least
//m_nLookAheadFrames+1 frame headers that are consistent with one
//another
while(m_MyInBuffer.GetBytesLeft()>=4)
{
pData=m_MyInBuffer.GetPosPtr();
if(pData[0]==0xff && (pData[1] & 0xe0)==0xe0)
{
//found sync
//parse the header
if(m_pNewHeaderRecord==(HeaderRecord*)0)
m_pNewHeaderRecord=new HeaderRecord;
if(m_pNewHeaderRecord->Header.parseHeader(pData))
{
if(IsValidHeader(&m_pNewHeaderRecord->Header))
{
//seems to be a valid header.
//add the a header record to the list
m_pNewHeaderRecord->nPosition=m_MyInBuffer.GetPos();
m_pNewHeaderRecord->pNext=(HeaderRecord*)0;
if(m_pLastHeader==(HeaderRecord*)0)
m_pFirstHeader=m_pLastHeader=m_pNewHeaderRecord;
else
{
m_pLastHeader->pNext=m_pNewHeaderRecord;
m_pLastHeader=m_pNewHeaderRecord;
}
m_pNewHeaderRecord=(HeaderRecord*)0;
//check wether we have found m_nLookAheadFrames+1 consistent headers yet
if(FindConsistentHeaders())
break;
}
}
}
m_MyInBuffer.AddToPos(1);
}
return (m_State>=STATE_FINDSYNC);
}
bool CMPAFrameFinder::FindConsistentHeaders()
{
HeaderRecord* pRecord;
//ok, we have found some headers in the data stream
//now we try to find m_nLookAheadFrames+1 that are consistent
//and assume that they correspond to valid frames in the stream
pRecord=m_pFirstHeader;
while(pRecord!=(HeaderRecord*)0)
{
//be prejudiced for early headers
if(!CanHaveConsistentHeaders(pRecord,m_nLookAheadFrames))
{
//if we haven't yet read enough data to find
//consistent headers for the first headers, stop
//here
pRecord=(HeaderRecord*)0;
break;
}
//find two headers that are consistent with this one
if(HasConsistentHeaders(pRecord,pRecord->pNext,m_nLookAheadFrames))
break;
pRecord=pRecord->pNext;
}
if(pRecord!=(HeaderRecord*)0)
{
//yay! we found m_nLookAheadFrames+1 consistent headers.
//The first of them is pRecord
//store stream characteristics
m_nLayer=pRecord->Header.getLayer();
m_nInputStereo=pRecord->Header.getInputstereo();
m_nFrequency=pRecord->Header.getFrequencyHz();
m_nLayer25=pRecord->Header.getLayer25();
m_nVersion=pRecord->Header.getVersion();
//set input buffer to start of first frame
m_MyInBuffer.SetPos(pRecord->nPosition);
m_nFirstFramePosition=pRecord->nPosition+m_nReadStartPosition;
//throw away the header records
DeleteHeaderRecords();
//got to next state (start extracting frame data)
m_State++;
return true;
}
return false;
}
bool CMPAFrameFinder::CanHaveConsistentHeaders(CMPAFrameFinder::HeaderRecord* pRecord,int nHeaderCount)
{
long nFirstFrameBegin;
int nFrameSize;
long nLastFrameHeaderEnd;
nFirstFrameBegin=pRecord->nPosition;
nFrameSize=pRecord->Header.getFramesize();
//assume that the frames have a size difference of at most 512 bytes
//(or at most 512 bytes of data in between)
nLastFrameHeaderEnd=nFirstFrameBegin+((nFrameSize+512)*nHeaderCount)+4;
if(m_MyInBuffer.GetPos()<nLastFrameHeaderEnd)
{
//abort because we have not yet read parsed enough data
return false;
}
return true;
}
bool CMPAFrameFinder::HasConsistentHeaders(CMPAFrameFinder::HeaderRecord* pCheckRecord,HeaderRecord* pRecord,int nHeadersNeeded)
{
while(pRecord!=(HeaderRecord*)0)
{
if(AreHeadersConsistent(pCheckRecord,pRecord))
{
nHeadersNeeded--;
if(nHeadersNeeded==0)
return true;
pCheckRecord=pRecord;
}
pRecord=pRecord->pNext;
}
return false;
}
bool CMPAFrameFinder::AreHeadersConsistent(HeaderRecord* pFirst,HeaderRecord* pSecond)
{
//do the corresponding frames overlap?
if(pFirst->nPosition+pFirst->Header.getFramesize()>pSecond->nPosition)
return false;
//different layers?
if(pFirst->Header.getLayer()!=pSecond->Header.getLayer())
return false;
//one stereo, the other not?
if(pFirst->Header.getInputstereo()!=pSecond->Header.getInputstereo())
return false;
//mpeg 2.5?
if(pFirst->Header.getLayer25()!=pSecond->Header.getLayer25())
return false;
//version?
if(pFirst->Header.getVersion()!=pSecond->Header.getVersion())
return false;
if(!m_bAllowFrequencyChanges)
{
if(pFirst->Header.getFrequencyHz()!=pSecond->Header.getFrequencyHz())
return false;
}
return true;
}
bool CMPAFrameFinder::IsConformingHeader(MpegAudioHeader* pHeader)
{
//check wether the header conforms with the detected stream characteristics
if(pHeader->getInputstereo()==m_nInputStereo
&& pHeader->getLayer()==m_nLayer
&& pHeader->getLayer25()==m_nLayer25
&& pHeader->getVersion()==m_nVersion
&& (pHeader->getFrequencyHz()==m_nFrequency || m_bAllowFrequencyChanges))
{
return true;
}
return false;
}
void CMPAFrameFinder::FindNextSync()
{
unsigned char* pOut=m_OutBuffer.GetPosPtr();
while(m_pInBuffer->HasMoreData())
{
// shift
pOut[0]=pOut[1];
pOut[1]=m_pInBuffer->ReadByte();
if (pOut[0] == 0xff)
{
// upper 4 bit are syncword, except bit one
// which is layer 2.5 indicator.
if ( (pOut[1] & 0xe0) == 0xe0)
{
m_OutBuffer.SetPos(2);
m_State++;
break;
}
}
}
}
void CMPAFrameFinder::ReadHeader()
{
while(m_pInBuffer->HasMoreData())
{
if(m_OutBuffer.GetPos()>=4)
{
m_State++;
break;
}
m_OutBuffer.WriteByte(m_pInBuffer->ReadByte());
}
}
void CMPAFrameFinder::CheckHeader()
{
bool bHeaderOK=false;
if(m_Header.parseHeader(m_OutBuffer.GetPtr()))
{
if(IsValidHeader(&m_Header))
{
//make sure the header conforms to the established stream
//characteristics
if(IsConformingHeader(&m_Header))
{
m_nFrameSize=m_Header.getFramesize();
bHeaderOK=true;
}
}
}
if(bHeaderOK)
m_State++;
else
ResetState();
}
void CMPAFrameFinder::ReadFrame()
{
int nBytesNeeded;
int nBytesAvailable;
int nCopyBytes;
do
{
nBytesNeeded=m_nFrameSize-m_OutBuffer.GetPos();
nBytesAvailable=m_pInBuffer->GetSize()-m_pInBuffer->GetPos();
nCopyBytes=(nBytesAvailable<nBytesNeeded) ? nBytesAvailable : nBytesNeeded;
memcpy(m_OutBuffer.GetPosPtr(),m_pInBuffer->GetPosPtr(),nCopyBytes);
m_OutBuffer.AddToPos(nCopyBytes);
m_pInBuffer->AddToPos(nCopyBytes);
if(nBytesNeeded==nCopyBytes)
{
m_State++;
break;
}
}
while(m_pInBuffer->HasMoreData());
}
void CMPAFrameFinder::SetInput(void* pBuffer,int nBufferBytes,long nStreamPosition)
{
if(m_State==STATE_FINDCHARACTERISTICS)
{
int nCopyBytes=m_MyInBuffer.GetCapacity()-m_MyInBuffer.GetSize();
//copy it in into our own buffer
if(nCopyBytes>nBufferBytes)
nCopyBytes=nBufferBytes;
if(m_MyInBuffer.GetSize()==0)
m_nReadStartPosition=nStreamPosition;
memcpy(m_MyInBuffer.GetPtr()+m_MyInBuffer.GetSize(),pBuffer,nCopyBytes);
m_MyInBuffer.SetSize(m_MyInBuffer.GetSize()+nCopyBytes);
pBuffer=((unsigned char*)pBuffer)+nCopyBytes;
nBufferBytes-=nCopyBytes;
//attach the rest to the user buffer
}
m_UserInBuffer.Attach(pBuffer,nBufferBytes);
}
bool CMPAFrameFinder::ReadInput(IFileAccess* pFileAccess)
{
int nResult;
if(m_State==STATE_FINDCHARACTERISTICS)
{
int nFree=m_MyInBuffer.GetCapacity()-m_MyInBuffer.GetSize();
//read data into our own buffer
if(nFree>0)
{
//read in small chunks
if(nFree>4096)
nFree=4096;
if(m_MyInBuffer.GetSize()==0) //first input
m_nReadStartPosition=pFileAccess->GetPosition();
nResult=pFileAccess->Read((char*)m_MyInBuffer.GetPtr()+m_MyInBuffer.GetSize(),nFree);
if(nResult<=0)
return false;
m_MyInBuffer.SetSize(m_MyInBuffer.GetSize()+nResult);
}
}
else
{
if(m_UserInBuffer.IsAttached())
m_UserInBuffer.Alloc(4096);
nResult=pFileAccess->Read((char*)m_UserInBuffer.GetPtr(),m_UserInBuffer.GetCapacity());
if(nResult<=0)
return false;
m_UserInBuffer.SetSize(nResult);
m_UserInBuffer.SetPos(0);
}
return true;
}
void CMPAFrameFinder::Flush()
{
ResetState();
m_UserInBuffer.SetSize(0);
m_MyInBuffer.SetSize(0);
DeleteHeaderRecords();
}
void CMPAFrameFinder::ResetState()
{
m_OutBuffer.SetPos(0);
//make sure that we do not accidentally treat
//"leftover" data in the buffer as a sync mark
m_OutBuffer.GetPtr()[0]=0;
m_OutBuffer.GetPtr()[1]=0;
if(m_State>=STATE_FINDSYNC)
m_State=STATE_FINDSYNC;
}
void CMPAFrameFinder::Restart()
{
Flush();
m_State=STATE_FINDCHARACTERISTICS;
m_pInBuffer=&m_MyInBuffer;
m_bStreamInvalid=false;
m_nFirstFramePosition=0;
}
void CMPAFrameFinder::DeleteHeaderRecords()
{
HeaderRecord* pRecord;
while(m_pFirstHeader!=(HeaderRecord*)0)
{
pRecord=m_pFirstHeader;
m_pFirstHeader=m_pFirstHeader->pNext;
delete pRecord;
}
m_pLastHeader=(HeaderRecord*)0;
if(m_pNewHeaderRecord!=(HeaderRecord*)0)
{
delete m_pNewHeaderRecord;
m_pNewHeaderRecord=(HeaderRecord*)0;
}
}
bool CMPAFrameFinder::IsValidHeader(MpegAudioHeader* pHeader)
{
int nFrameSize;
nFrameSize=pHeader->getFramesize();
// don't allow stupid framesizes:
// if framesize <4 or > max mepg framsize its an error
if(nFrameSize<4 || nFrameSize+4>4096)
return false;
if(pHeader->GetBitRateKbps()==0)
{
//skip frames with bitrate 0 (empty)
return false;
}
return true;
}