639 lines
14 KiB
C++
639 lines
14 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
|
||
|
*/
|
||
|
|
||
|
//This file is a heavily modified version of SPlayPlugin.cpp from the original
|
||
|
//mpeglib. See Readme.txt for details.
|
||
|
|
||
|
#include "DefInc.h"
|
||
|
#include "MPADecoder.h"
|
||
|
|
||
|
#include "DecodeEngine.h"
|
||
|
|
||
|
#include <exception>
|
||
|
#include <memory>
|
||
|
|
||
|
#include <math.h>
|
||
|
|
||
|
|
||
|
CMPADecoder::CMPADecoder(const DecMPA_Callbacks& Callbacks,void* pCallbackContext)
|
||
|
{
|
||
|
pow(6.0,3.0); // fixes bug in __math.h
|
||
|
|
||
|
m_Callbacks=Callbacks;
|
||
|
m_pCallbackContext=pCallbackContext;
|
||
|
|
||
|
m_pFileAccess=new CDecMPAFileAccess(Callbacks,pCallbackContext);
|
||
|
|
||
|
m_nResyncCounter=0;
|
||
|
|
||
|
m_nBufferBytes=0;
|
||
|
m_nUsedBufferBytes=0;
|
||
|
m_pBuffer=NULL;
|
||
|
|
||
|
m_OutputFormat.nType=0;
|
||
|
m_OutputFormat.nFrequency=0;
|
||
|
m_OutputFormat.nChannels=0;
|
||
|
m_bOutputFormatChanged=false;
|
||
|
|
||
|
memset(&m_MPEGHeader,0,sizeof(m_MPEGHeader));
|
||
|
|
||
|
m_bDoFloat=false;
|
||
|
m_pPCMFrame=new PCMFrame(MP3FRAMESIZE);
|
||
|
m_pFloatFrame=new FloatFrame(MP3FRAMESIZE);
|
||
|
m_pAudioFrame=NULL;
|
||
|
|
||
|
m_pFormatFrame=new AudioFrame;
|
||
|
|
||
|
m_nReadTime=0;
|
||
|
m_nBufferStartTime=0;
|
||
|
m_nDecodeTime=0;
|
||
|
|
||
|
m_bPrepared=false;
|
||
|
m_bHasFrame=false;
|
||
|
|
||
|
m_bFirstDecode=true;
|
||
|
|
||
|
m_pDecodeEngine=DecodeEngine_Create();
|
||
|
|
||
|
m_bStoreID3v2=false;
|
||
|
m_pID3v2Data=(unsigned char*)0;
|
||
|
m_nID3v2DataSize=0;
|
||
|
|
||
|
m_pDestroyNotify=(void (*)(void*))0;
|
||
|
m_pDestroyNotifyContext=(void*)0;
|
||
|
|
||
|
m_aParams[DECMPA_PARAM_OUTPUT]=DECMPA_OUTPUT_INT16;
|
||
|
m_aParams[DECMPA_PARAM_PROVIDEID3V2]=DECMPA_NO;
|
||
|
|
||
|
m_bNotMPEG=false;
|
||
|
|
||
|
m_bDecoderNeedsFlush=false;
|
||
|
}
|
||
|
|
||
|
CMPADecoder::~CMPADecoder()
|
||
|
{
|
||
|
if(m_pDestroyNotify!=(void (*)(void*))0)
|
||
|
m_pDestroyNotify(m_pDestroyNotifyContext);
|
||
|
|
||
|
delete m_pPCMFrame;
|
||
|
delete m_pFloatFrame;
|
||
|
|
||
|
delete m_pFileAccess;
|
||
|
delete m_pFormatFrame;
|
||
|
|
||
|
DecodeEngine_Destroy(m_pDecodeEngine);
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::DecodeNoData(long& nDecodedBytes)
|
||
|
{
|
||
|
return Decode(NULL,-1,nDecodedBytes);
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::Decode(void* pBuffer,long nBufferBytes,long& nBytesDecoded)
|
||
|
{
|
||
|
int Result=DECMPA_OK;
|
||
|
|
||
|
if(pBuffer==NULL)
|
||
|
nBufferBytes=-1;
|
||
|
|
||
|
nBytesDecoded=0;
|
||
|
m_bOutputFormatChanged=false;
|
||
|
|
||
|
if((Result=EnsurePrepared())!=DECMPA_OK)
|
||
|
return Result;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
while(!ReadDecodedData(pBuffer,nBufferBytes,nBytesDecoded))
|
||
|
{
|
||
|
Result=DecodeNextFrame(pBuffer==NULL);
|
||
|
if(Result!=DECMPA_OK)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
catch(std::bad_alloc)
|
||
|
{
|
||
|
return DECMPA_ERROR_MEMORY;
|
||
|
}
|
||
|
catch(std::exception)
|
||
|
{
|
||
|
return DECMPA_ERROR_INTERNAL;
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::DecodeNextFrame(bool bNoData)
|
||
|
{
|
||
|
int Result=DECMPA_OK;
|
||
|
|
||
|
//find and read the next frame
|
||
|
//we may still have one frame buffered that was read to find out
|
||
|
//the file information. In that case don't read another one
|
||
|
if(!m_bHasFrame)
|
||
|
m_bHasFrame=ReadNextFrame();
|
||
|
if(m_bHasFrame)
|
||
|
{
|
||
|
bool bDecodeOK;
|
||
|
|
||
|
//don't use this frame again
|
||
|
m_bHasFrame=false;
|
||
|
|
||
|
//if bNoData==true we don't decode any audio data but only
|
||
|
//parse the frame headers
|
||
|
//this allows the user to do a quick pass over the file and
|
||
|
//get an accurate estimate of the files properties
|
||
|
//(actual duration, decoded size, etc...).
|
||
|
if(!bNoData)
|
||
|
{
|
||
|
if(m_bDecoderNeedsFlush)
|
||
|
{
|
||
|
DecodeEngine_Flush(m_pDecodeEngine);
|
||
|
m_bDecoderNeedsFlush=false;
|
||
|
}
|
||
|
|
||
|
bDecodeOK=DecodeEngine_Decode(m_pDecodeEngine,m_pAudioFrame,m_FrameFinder.GetFrameData(),m_FrameFinder.GetFrameSize());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MpegAudioHeader* pHeader=m_FrameFinder.GetFrameHeader();
|
||
|
long nDecodedFrameSize;
|
||
|
|
||
|
m_bDecoderNeedsFlush=true;
|
||
|
|
||
|
nDecodedFrameSize=pHeader->getpcmperframe();
|
||
|
if(pHeader->getInputstereo()==1)
|
||
|
nDecodedFrameSize*=2;
|
||
|
|
||
|
//pretend that we have decoded something
|
||
|
m_pAudioFrame->setLen(nDecodedFrameSize);
|
||
|
m_pAudioFrame->setFrameFormat(pHeader->getInputstereo(),pHeader->getFrequencyHz());
|
||
|
|
||
|
//note that this "ghost data" will never be retrieved because the call
|
||
|
//to ReadDecodedData that follows will always remove all of it
|
||
|
//(see Decode)
|
||
|
|
||
|
bDecodeOK=true;
|
||
|
}
|
||
|
|
||
|
if(bDecodeOK)
|
||
|
{
|
||
|
//make the decoded data available to the "user"
|
||
|
HandleDecodedData(m_pAudioFrame);
|
||
|
}
|
||
|
//ignore if the decoding of the frame failed. Just continue with
|
||
|
//the next frame
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(m_FrameFinder.IsStreamInvalid())
|
||
|
Result=DECMPA_ERROR_DECODE;
|
||
|
else
|
||
|
{
|
||
|
if(m_pFileAccess->IsEndOfFile())
|
||
|
{
|
||
|
//end of file reached
|
||
|
if(!m_FrameFinder.KnowsCharacteristics())
|
||
|
{
|
||
|
//the end was reached without being able to find out
|
||
|
//essential stream characteristics
|
||
|
//=> not an MPEG Audio file (or one that has less than 4 frames)
|
||
|
Result=DECMPA_ERROR_DECODE;
|
||
|
}
|
||
|
else
|
||
|
Result=DECMPA_END;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(m_pFileAccess->GetLastError()!=DECMPA_OK)
|
||
|
Result=m_pFileAccess->GetLastError();
|
||
|
else
|
||
|
{
|
||
|
//should never happen - an invalid stream, end of file
|
||
|
//or a read error are the only things that could cause
|
||
|
//ReadNextFrame to fail
|
||
|
Result=DECMPA_ERROR_INTERNAL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
void CMPADecoder::Flush()
|
||
|
{
|
||
|
m_FrameFinder.Flush();
|
||
|
DecodeEngine_Flush(m_pDecodeEngine);
|
||
|
|
||
|
m_nBufferBytes=0;
|
||
|
m_nUsedBufferBytes=0;
|
||
|
}
|
||
|
|
||
|
bool CMPADecoder::ReadDecodedData(void* pDest,long nBytes,long& nRead)
|
||
|
{
|
||
|
long nAvailBytes=m_nBufferBytes-m_nUsedBufferBytes;
|
||
|
|
||
|
if(nAvailBytes>0)
|
||
|
{
|
||
|
if(nBytes>nAvailBytes || nBytes==-1)
|
||
|
nBytes=nAvailBytes;
|
||
|
nBytes&=~m_nOutputBlockSize; //only full samples
|
||
|
|
||
|
if(pDest!=NULL)
|
||
|
memcpy(pDest,m_pBuffer+m_nUsedBufferBytes,nBytes);
|
||
|
m_nUsedBufferBytes+=nBytes;
|
||
|
|
||
|
nRead=nBytes;
|
||
|
|
||
|
m_nReadTime=m_nBufferStartTime+(((double)(m_nUsedBufferBytes/m_nOutputBlockSize))*1000)/m_pFormatFrame->getFrequenceHZ();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
long CMPADecoder::GetFilePositionFromTime(long Millis)
|
||
|
{
|
||
|
int nResult;
|
||
|
|
||
|
if((nResult=EnsurePrepared())!=DECMPA_OK)
|
||
|
return nResult;
|
||
|
|
||
|
return m_Info.GetFilePositionFromTime(Millis);
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::SeekToTime(long Millis)
|
||
|
{
|
||
|
int Result=DECMPA_OK;
|
||
|
|
||
|
if(!m_pFileAccess->CanSeek())
|
||
|
Result=DECMPA_ERROR_UNSUPPORTED;
|
||
|
else
|
||
|
{
|
||
|
long nFilePos;
|
||
|
|
||
|
if((Result=EnsurePrepared())==DECMPA_OK)
|
||
|
{
|
||
|
nFilePos=m_Info.GetFilePositionFromTime(Millis);
|
||
|
if(nFilePos==-1)
|
||
|
Result=DECMPA_ERROR_UNSUPPORTED;
|
||
|
else
|
||
|
{
|
||
|
if(!m_pFileAccess->Seek(nFilePos))
|
||
|
Result=DECMPA_ERROR_SEEK;
|
||
|
else
|
||
|
{
|
||
|
//throw away buffered data
|
||
|
Flush();
|
||
|
|
||
|
if(Millis!=0)
|
||
|
{
|
||
|
//initiate resync (skip next 5 frames until back
|
||
|
//references are ok again)
|
||
|
m_nResyncCounter=5;
|
||
|
}
|
||
|
|
||
|
m_nDecodeTime=Millis;
|
||
|
m_nBufferStartTime=Millis;
|
||
|
m_nReadTime=Millis;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::GetTime(long& Time)
|
||
|
{
|
||
|
Time=(long)m_nReadTime;
|
||
|
|
||
|
return DECMPA_OK;
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::GetDuration(long& Duration)
|
||
|
{
|
||
|
int nResult;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if((nResult=EnsurePrepared())!=DECMPA_OK)
|
||
|
return nResult;
|
||
|
|
||
|
Duration=m_Info.GetDuration();
|
||
|
|
||
|
return DECMPA_OK;
|
||
|
}
|
||
|
catch(std::bad_alloc)
|
||
|
{
|
||
|
return DECMPA_ERROR_MEMORY;
|
||
|
}
|
||
|
catch(std::exception)
|
||
|
{
|
||
|
return DECMPA_ERROR_INTERNAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CMPADecoder::ReadNextFrame()
|
||
|
{
|
||
|
while(!m_FrameFinder.ReadNextFrame())
|
||
|
{
|
||
|
if(m_FrameFinder.IsStreamInvalid())
|
||
|
return false;
|
||
|
|
||
|
if(!m_FrameFinder.ReadInput(m_pFileAccess))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CMPADecoder::SetOutputFormat(AudioFrame* pNewFormatFrame)
|
||
|
{
|
||
|
pNewFormatFrame->copyFormat(m_pFormatFrame);
|
||
|
|
||
|
m_OutputFormat.nFrequency=m_pFormatFrame->getFrequenceHZ();
|
||
|
m_OutputFormat.nChannels=m_pFormatFrame->getStereo() ? 2 : 1;
|
||
|
|
||
|
m_bOutputFormatChanged=true;
|
||
|
|
||
|
m_nOutputBlockSize=m_pFormatFrame->getSampleSize()/8;
|
||
|
m_nOutputBlockSize*=m_OutputFormat.nChannels;
|
||
|
}
|
||
|
|
||
|
void CMPADecoder::UpdateMPEGHeader(MpegAudioHeader* pHeader)
|
||
|
{
|
||
|
memcpy(m_MPEGHeader.aRawData,pHeader->getHeader(),4);
|
||
|
m_MPEGHeader.bProtection=pHeader->getProtection()!=0;
|
||
|
|
||
|
m_MPEGHeader.nLayer=pHeader->getLayer();
|
||
|
m_MPEGHeader.nVersion=pHeader->getVersion();
|
||
|
m_MPEGHeader.bPadding=pHeader->getPadding()!=0;
|
||
|
m_MPEGHeader.nFrequencyIndex=pHeader->getFrequency();
|
||
|
m_MPEGHeader.nFrequency=pHeader->getFrequencyHz();
|
||
|
m_MPEGHeader.nBitRateIndex=pHeader->getBitrateindex();
|
||
|
m_MPEGHeader.nExtendedMode=pHeader->getExtendedmode();
|
||
|
m_MPEGHeader.nMode=pHeader->getMode();
|
||
|
m_MPEGHeader.bInputStereo=pHeader->getInputstereo()!=0;
|
||
|
m_MPEGHeader.bMPEG25=pHeader->getLayer25()!=0;
|
||
|
|
||
|
m_MPEGHeader.nFrameSize=pHeader->getFramesize();
|
||
|
m_MPEGHeader.nDecodedSamplesPerFrame=pHeader->getpcmperframe();
|
||
|
m_MPEGHeader.nBitRateKbps=pHeader->GetBitRateKbps();
|
||
|
}
|
||
|
|
||
|
bool CMPADecoder::OutputFormatChanged()
|
||
|
{
|
||
|
return m_bOutputFormatChanged;
|
||
|
}
|
||
|
|
||
|
void CMPADecoder::GetOutputFormat(DecMPA_OutputFormat& Format)
|
||
|
{
|
||
|
Format=m_OutputFormat;
|
||
|
}
|
||
|
|
||
|
void CMPADecoder::GetMPEGHeader(DecMPA_MPEGHeader& Header)
|
||
|
{
|
||
|
Header=m_MPEGHeader;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CMPADecoder::HandleDecodedData(AudioFrame* pDecodedFrame)
|
||
|
{
|
||
|
int nDecodedSamples;
|
||
|
|
||
|
if(m_bFirstDecode)
|
||
|
{
|
||
|
SetOutputFormat(pDecodedFrame);
|
||
|
m_bFirstDecode=false;
|
||
|
}
|
||
|
|
||
|
nDecodedSamples=pDecodedFrame->getLen();
|
||
|
if(pDecodedFrame->getStereo()==1)
|
||
|
nDecodedSamples/=2;
|
||
|
|
||
|
m_nBufferStartTime=m_nDecodeTime;
|
||
|
m_nDecodeTime+=(((double)nDecodedSamples)*1000)/pDecodedFrame->getFrequenceHZ();
|
||
|
|
||
|
if(m_nResyncCounter>0)
|
||
|
{
|
||
|
//we need to resync = read a few frames to make sure
|
||
|
//that the back references are ok again
|
||
|
//do not output any of the decoded data
|
||
|
m_nResyncCounter--;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!m_pFormatFrame->isFormatEqual(pDecodedFrame))
|
||
|
SetOutputFormat(pDecodedFrame);
|
||
|
|
||
|
UpdateMPEGHeader(m_FrameFinder.GetFrameHeader());
|
||
|
|
||
|
if(m_bDoFloat)
|
||
|
{
|
||
|
m_nBufferBytes=m_pFloatFrame->getLen()*sizeof(float);
|
||
|
m_pBuffer=(unsigned char*)m_pFloatFrame->getData();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_nBufferBytes=m_pPCMFrame->getLen()*sizeof(short);
|
||
|
m_pBuffer=(unsigned char*)m_pPCMFrame->getData();
|
||
|
}
|
||
|
m_nUsedBufferBytes=0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::EnsurePrepared()
|
||
|
{
|
||
|
int Result=DECMPA_OK;
|
||
|
|
||
|
if(!m_bPrepared)
|
||
|
{
|
||
|
if(m_bNotMPEG)
|
||
|
Result=DECMPA_ERROR_DECODE;
|
||
|
else
|
||
|
{
|
||
|
m_bStoreID3v2=(m_aParams[DECMPA_PARAM_PROVIDEID3V2]==DECMPA_YES);
|
||
|
|
||
|
m_bDoFloat=(m_aParams[DECMPA_PARAM_OUTPUT]==DECMPA_OUTPUT_FLOAT);
|
||
|
m_nOutputBlockSize=1;
|
||
|
if(m_bDoFloat)
|
||
|
m_pAudioFrame=m_pFloatFrame;
|
||
|
else
|
||
|
m_pAudioFrame=m_pPCMFrame;
|
||
|
|
||
|
m_OutputFormat.nType=m_aParams[DECMPA_PARAM_OUTPUT];
|
||
|
|
||
|
//Skip or store any leading ID3 tags - some tags are not
|
||
|
//correctly escaped and contain "sync" codes that can confuse
|
||
|
//the decoder
|
||
|
HandleID3Tag();
|
||
|
|
||
|
if(!m_Info.InitInfo(&m_FrameFinder,m_pFileAccess))
|
||
|
{
|
||
|
m_bNotMPEG=true;
|
||
|
Result=DECMPA_ERROR_DECODE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//info reads up to one frame.
|
||
|
//we can use this in decoding, we only have to remember not
|
||
|
//to read a fresh one in the first iteration of Decode
|
||
|
m_bHasFrame=m_FrameFinder.HasFrame();
|
||
|
|
||
|
m_bPrepared=true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
void CMPADecoder::HandleID3Tag()
|
||
|
{
|
||
|
unsigned char aHeaderData[10];
|
||
|
struct ID3v2Header
|
||
|
{
|
||
|
char sID[3];
|
||
|
unsigned char Version;
|
||
|
unsigned char Revision;
|
||
|
unsigned char Flags;
|
||
|
unsigned long Size;
|
||
|
} Header;
|
||
|
|
||
|
int nBytesRead;
|
||
|
long UndoID;
|
||
|
|
||
|
UndoID=m_pFileAccess->StartUndoRecording();
|
||
|
|
||
|
if((nBytesRead=m_pFileAccess->Read((char*)&aHeaderData,10))==10)
|
||
|
{
|
||
|
//we do not read directly into the header because the fields may
|
||
|
//have been padded by the compiler
|
||
|
memcpy(Header.sID,aHeaderData,3);
|
||
|
Header.Version=aHeaderData[3];
|
||
|
Header.Revision=aHeaderData[4];
|
||
|
Header.Flags=aHeaderData[5];
|
||
|
Header.Size=aHeaderData[6];
|
||
|
Header.Size<<=8;
|
||
|
Header.Size|=aHeaderData[7];
|
||
|
Header.Size<<=8;
|
||
|
Header.Size|=aHeaderData[8];
|
||
|
Header.Size<<=8;
|
||
|
Header.Size|=aHeaderData[9];
|
||
|
|
||
|
//make sure its an ID3 header
|
||
|
if(strncmp(Header.sID,"ID3",3)==0 && Header.Version!=0xff && Header.Revision!=0xff
|
||
|
&& (Header.Size & 0x80808080)==0)
|
||
|
{
|
||
|
unsigned long TagSize=Header.Size;
|
||
|
|
||
|
//ok, we have an ID3 tag - no need for undo
|
||
|
m_pFileAccess->EndUndoRecording(UndoID,false);
|
||
|
UndoID=-1;
|
||
|
|
||
|
//correct "unsyncing" of tag size
|
||
|
TagSize=(TagSize & 0x7f) | ((TagSize & 0x7f00)>>1)
|
||
|
| ((TagSize & 0x7f0000)>>2) | ((TagSize & 0x7f000000)>>3);
|
||
|
|
||
|
if(m_bStoreID3v2)
|
||
|
{
|
||
|
//read ID3 tag
|
||
|
|
||
|
m_pID3v2Data=new unsigned char[10+TagSize];
|
||
|
memcpy(m_pID3v2Data,aHeaderData,10);
|
||
|
|
||
|
if(m_pFileAccess->Read(m_pID3v2Data+10,TagSize)==(long)TagSize)
|
||
|
m_nID3v2DataSize=TagSize+10;
|
||
|
else
|
||
|
{
|
||
|
delete[] m_pID3v2Data;
|
||
|
m_pID3v2Data=(unsigned char*)0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//skip the ID3 tag
|
||
|
m_pFileAccess->Skip(TagSize);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(UndoID!=-1)
|
||
|
{
|
||
|
//no ID3 tag found
|
||
|
//=> undo read operations
|
||
|
m_pFileAccess->EndUndoRecording(UndoID,true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CMPADecoder::GetID3v2Data(unsigned char*& pData,long& nDataSize)
|
||
|
{
|
||
|
int nResult;
|
||
|
|
||
|
if((nResult=EnsurePrepared())!=DECMPA_OK)
|
||
|
return nResult;
|
||
|
|
||
|
if(m_pID3v2Data==(unsigned char*)0)
|
||
|
return DECMPA_ERROR_NOTAVAILABLE;
|
||
|
|
||
|
pData=m_pID3v2Data;
|
||
|
nDataSize=m_nID3v2DataSize;
|
||
|
|
||
|
return DECMPA_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
int CMPADecoder::SetParam(int ID,long Value)
|
||
|
{
|
||
|
bool bOK=false;
|
||
|
|
||
|
if(m_bPrepared)
|
||
|
return DECMPA_ERROR_WRONGSTATE;
|
||
|
|
||
|
switch(ID)
|
||
|
{
|
||
|
case DECMPA_PARAM_OUTPUT: bOK=(Value==DECMPA_OUTPUT_INT16 || Value==DECMPA_OUTPUT_FLOAT);
|
||
|
break;
|
||
|
case DECMPA_PARAM_PROVIDEID3V2: bOK=(Value==DECMPA_YES || Value==DECMPA_NO);
|
||
|
break;
|
||
|
}
|
||
|
if(!bOK)
|
||
|
return DECMPA_ERROR_PARAM;
|
||
|
|
||
|
m_aParams[ID]=Value;
|
||
|
|
||
|
return DECMPA_OK;
|
||
|
|
||
|
}
|
||
|
|
||
|
long CMPADecoder::GetParam(int ID)
|
||
|
{
|
||
|
if(ID<0 || ID>=DECMPA_PARAMCOUNT)
|
||
|
return 0;
|
||
|
|
||
|
return m_aParams[ID];
|
||
|
}
|