/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*----------------------------------------------------------------------
*/
package davaguine.jmac.decoder;
import davaguine.jmac.info.*;
import davaguine.jmac.prediction.IPredictorDecompress;
import davaguine.jmac.prediction.PredictorDecompress3950toCurrent;
import davaguine.jmac.prediction.PredictorDecompressNormal3930to3950;
import davaguine.jmac.tools.*;
import java.io.IOException;
/**
* Author: Dmitry Vaguine
* Date: 04.03.2004
* Time: 14:51:31
*/
public class APEDecompress extends IAPEDecompress {
private final static int DECODE_BLOCK_SIZE = 4096;
public APEDecompress(APEInfo pAPEInfo) {
this(pAPEInfo, -1, -1);
}
public APEDecompress(APEInfo pAPEInfo, int nStartBlock) {
this(pAPEInfo, nStartBlock, -1);
}
public APEDecompress(APEInfo pAPEInfo, int nStartBlock, int nFinishBlock) {
// open / analyze the file
m_spAPEInfo = pAPEInfo;
// version check (this implementation only works with 3.93 and later files)
if (m_spAPEInfo.getApeInfoFileVersion() < 3930)
throw new JMACException("Unsupported Version");
// get format information
m_wfeInput = m_spAPEInfo.getApeInfoWaveFormatEx();
m_nBlockAlign = m_spAPEInfo.getApeInfoBlockAlign();
// initialize other stuff
m_bDecompressorInitialized = false;
m_nCurrentFrame = 0;
m_nRealFrame = 0;
m_nCurrentBlock = 0;
m_nCurrentFrameBufferBlock = 0;
m_nFrameBufferFinishedBlocks = 0;
m_bErrorDecodingCurrentFrame = false;
// set the "real" start and finish blocks
m_nStartBlock = (nStartBlock < 0) ? 0 : Math.min(nStartBlock, m_spAPEInfo.getApeInfoTotalBlocks());
m_nFinishBlock = (nFinishBlock < 0) ? m_spAPEInfo.getApeInfoTotalBlocks() : Math.min(nFinishBlock, m_spAPEInfo.getApeInfoTotalBlocks());
m_bIsRanged = (m_nStartBlock != 0) || (m_nFinishBlock != m_spAPEInfo.getApeInfoTotalBlocks());
}
public int GetData(byte[] pBuffer, int nBlocks) throws IOException {
InitializeDecompressor();
// cap
int nBlocksUntilFinish = m_nFinishBlock - m_nCurrentBlock;
int nBlocksToRetrieve = Math.min(nBlocks, nBlocksUntilFinish);
// get the data
int nBlocksLeft = nBlocksToRetrieve;
int nBlocksThisPass = 1;
int index = 0;
while ((nBlocksLeft > 0) && (nBlocksThisPass > 0)) {
// fill up the frame buffer
FillFrameBuffer();
// analyze how much to remove from the buffer
int nFrameBufferBlocks = m_nFrameBufferFinishedBlocks;
nBlocksThisPass = Math.min(nBlocksLeft, nFrameBufferBlocks);
// remove as much as possible
if (nBlocksThisPass > 0) {
m_cbFrameBuffer.Get(pBuffer, index, nBlocksThisPass * m_nBlockAlign);
index += nBlocksThisPass * m_nBlockAlign;
nBlocksLeft -= nBlocksThisPass;
m_nFrameBufferFinishedBlocks -= nBlocksThisPass;
}
}
// calculate the blocks retrieved
int nBlocksRetrieved = nBlocksToRetrieve - nBlocksLeft;
// update position
m_nCurrentBlock += nBlocksRetrieved;
return nBlocksRetrieved;
}
public void Seek(int nBlockOffset) throws IOException {
InitializeDecompressor();
// use the offset
nBlockOffset += m_nStartBlock;
// cap (to prevent seeking too far)
if (nBlockOffset >= m_nFinishBlock)
nBlockOffset = m_nFinishBlock - 1;
if (nBlockOffset < m_nStartBlock)
nBlockOffset = m_nStartBlock;
// seek to the perfect location
int nBaseFrame = nBlockOffset / m_spAPEInfo.getApeInfoBlocksPerFrame();
int nBlocksToSkip = nBlockOffset % m_spAPEInfo.getApeInfoBlocksPerFrame();
int nBytesToSkip = nBlocksToSkip * m_nBlockAlign;
m_nCurrentBlock = nBaseFrame * this.getApeInfoBlocksPerFrame();
m_nCurrentFrameBufferBlock = nBaseFrame * this.getApeInfoBlocksPerFrame();
m_nCurrentFrame = nBaseFrame;
m_nFrameBufferFinishedBlocks = 0;
m_cbFrameBuffer.Empty();
SeekToFrame(m_nCurrentFrame);
// skip necessary blocks
byte[] spTempBuffer = new byte[nBytesToSkip];
int nBlocksRetrieved = GetData(spTempBuffer, nBlocksToSkip);
if (nBlocksRetrieved != nBlocksToSkip)
throw new JMACException("Undefined Error");
}
public int getApeInfoDecompressCurrentBlock() {
return m_nCurrentBlock - m_nStartBlock;
}
public int getApeInfoDecompressCurrentMS() {
int nSampleRate = m_spAPEInfo.getApeInfoSampleRate();
if (nSampleRate > 0)
return (int) ((m_nCurrentBlock * 1000L) / nSampleRate);
return 0;
}
public int getApeInfoDecompressTotalBlocks() {
return m_nFinishBlock - m_nStartBlock;
}
public int getApeInfoDecompressLengthMS() {
int nSampleRate = m_spAPEInfo.getApeInfoSampleRate();
if (nSampleRate > 0)
return (int) (((m_nFinishBlock - m_nStartBlock) * 1000L) / nSampleRate);
return 0;
}
public int getApeInfoDecompressCurrentBitRate() throws IOException {
return m_spAPEInfo.getApeInfoFrameBitrate(m_nCurrentFrame);
}
public int getApeInfoDecompressAverageBitrate() throws IOException {
if (m_bIsRanged || !m_spAPEInfo.getApeInfoIoSource().isLocal()) {
// figure the frame range
int nBlocksPerFrame = m_spAPEInfo.getApeInfoBlocksPerFrame();
int nStartFrame = m_nStartBlock / nBlocksPerFrame;
int nFinishFrame = (m_nFinishBlock + nBlocksPerFrame - 1) / nBlocksPerFrame;
// get the number of bytes in the first and last frame
int nTotalBytes = (m_spAPEInfo.getApeInfoFrameBytes(nStartFrame) * (m_nStartBlock % nBlocksPerFrame)) / nBlocksPerFrame;
if (nFinishFrame != nStartFrame)
nTotalBytes += (m_spAPEInfo.getApeInfoFrameBytes(nFinishFrame) * (m_nFinishBlock % nBlocksPerFrame)) / nBlocksPerFrame;
// get the number of bytes in between
int nTotalFrames = m_spAPEInfo.getApeInfoTotalFrames();
for (int nFrame = nStartFrame + 1; (nFrame < nFinishFrame) && (nFrame < nTotalFrames); nFrame++)
nTotalBytes += m_spAPEInfo.getApeInfoFrameBytes(nFrame);
// figure the bitrate
int nTotalMS = (int) (((m_nFinishBlock - m_nStartBlock) * 1000L) / m_spAPEInfo.getApeInfoSampleRate());
if (nTotalMS != 0)
return (nTotalBytes * 8) / nTotalMS;
} else
return m_spAPEInfo.getApeInfoAverageBitrate();
return 0;
}
public int getApeInfoWavHeaderBytes() {
if (m_bIsRanged)
return WaveHeader.WAVE_HEADER_BYTES;
return m_spAPEInfo.getApeInfoWavHeaderBytes();
}
public byte[] getApeInfoWavHeaderData(int nMaxBytes) {
if (m_bIsRanged) {
if (WaveHeader.WAVE_HEADER_BYTES > nMaxBytes)
return null;
else {
WaveFormat wfeFormat = m_spAPEInfo.getApeInfoWaveFormatEx();
WaveHeader WAVHeader = new WaveHeader();
WaveHeader.FillWaveHeader(WAVHeader, (m_nFinishBlock - m_nStartBlock) * m_spAPEInfo.getApeInfoBlockAlign(), wfeFormat, 0);
return WAVHeader.write();
}
}
return m_spAPEInfo.getApeInfoWavHeaderData(nMaxBytes);
}
public int getApeInfoWavTerminatingBytes() {
if (m_bIsRanged)
return 0;
else
return m_spAPEInfo.getApeInfoWavTerminatingBytes();
}
public byte[] getApeInfoWavTerminatingData(int nMaxBytes) throws IOException {
if (m_bIsRanged)
return null;
else
return m_spAPEInfo.getApeInfoWavTerminatingData(nMaxBytes);
}
public WaveFormat getApeInfoWaveFormatEx() {
return m_spAPEInfo.getApeInfoWaveFormatEx();
}
public File getApeInfoIoSource() {
return m_spAPEInfo.getApeInfoIoSource();
}
public int getApeInfoBlocksPerFrame() {
return m_spAPEInfo.getApeInfoBlocksPerFrame();
}
public int getApeInfoFileVersion() {
return m_spAPEInfo.getApeInfoFileVersion();
}
public int getApeInfoCompressionLevel() {
return m_spAPEInfo.getApeInfoCompressionLevel();
}
public int getApeInfoFormatFlags() {
return m_spAPEInfo.getApeInfoFormatFlags();
}
public int getApeInfoSampleRate() {
return m_spAPEInfo.getApeInfoSampleRate();
}
public int getApeInfoBitsPerSample() {
return m_spAPEInfo.getApeInfoBitsPerSample();
}
public int getApeInfoBytesPerSample() {
return m_spAPEInfo.getApeInfoBytesPerSample();
}
public int getApeInfoChannels() {
return m_spAPEInfo.getApeInfoChannels();
}
public int getApeInfoBlockAlign() {
return m_spAPEInfo.getApeInfoBlockAlign();
}
public int getApeInfoFinalFrameBlocks() {
return m_spAPEInfo.getApeInfoFinalFrameBlocks();
}
public int getApeInfoTotalFrames() {
return m_spAPEInfo.getApeInfoTotalFrames();
}
public int getApeInfoWavDataBytes() {
return m_spAPEInfo.getApeInfoWavDataBytes();
}
public int getApeInfoWavTotalBytes() {
return m_spAPEInfo.getApeInfoWavTotalBytes();
}
public int getApeInfoApeTotalBytes() {
return m_spAPEInfo.getApeInfoApeTotalBytes();
}
public int getApeInfoTotalBlocks() {
return m_spAPEInfo.getApeInfoTotalBlocks();
}
public int getApeInfoLengthMs() {
return m_spAPEInfo.getApeInfoLengthMs();
}
public int getApeInfoAverageBitrate() {
return m_spAPEInfo.getApeInfoAverageBitrate();
}
public int getApeInfoSeekByte(int nFrame) {
return m_spAPEInfo.getApeInfoSeekByte(nFrame);
}
public int getApeInfoFrameBytes(int nFrame) throws IOException {
return m_spAPEInfo.getApeInfoFrameBytes(nFrame);
}
public int getApeInfoFrameBlocks(int nFrame) {
return m_spAPEInfo.getApeInfoFrameBlocks(nFrame);
}
public int getApeInfoFrameBitrate(int nFrame) throws IOException {
return m_spAPEInfo.getApeInfoFrameBitrate(nFrame);
}
public int getApeInfoDecompressedBitrate() {
return m_spAPEInfo.getApeInfoDecompressedBitrate();
}
public int getApeInfoPeakLevel() {
return m_spAPEInfo.getApeInfoPeakLevel();
}
public int getApeInfoSeekBit(int nFrame) {
return m_spAPEInfo.getApeInfoSeekBit(nFrame);
}
public APETag getApeInfoTag() {
return m_spAPEInfo.getApeInfoTag();
}
public APEFileInfo getApeInfoInternalInfo() {
return m_spAPEInfo.getApeInfoInternalInfo();
}
// file info
protected int m_nBlockAlign;
protected int m_nCurrentFrame;
protected int m_nRealFrame;
// start / finish information
protected int m_nStartBlock;
protected int m_nFinishBlock;
protected int m_nCurrentBlock;
protected boolean m_bIsRanged;
protected boolean m_bDecompressorInitialized;
// decoding tools
protected Prepare m_Prepare = new Prepare();
protected WaveFormat m_wfeInput;
protected Crc32 m_nCRC;
protected long m_nStoredCRC;
protected int m_nSpecialCodes;
public void SeekToFrame(int nFrameIndex) throws IOException {
int nSeekRemainder = (m_spAPEInfo.getApeInfoSeekByte(nFrameIndex) - m_spAPEInfo.getApeInfoSeekByte(0)) % 4;
m_spUnBitArray.FillAndResetBitArray(m_spAPEInfo.getApeInfoSeekByte(nFrameIndex) - nSeekRemainder, nSeekRemainder * 8);
m_nRealFrame = nFrameIndex;
}
protected void DecodeBlocksToFrameBuffer(int nBlocks) throws IOException {
// decode the samples
int nBlocksProcessed = 0;
try {
if (m_wfeInput.nChannels == 2) {
if ((m_nSpecialCodes & SpecialFrame.SPECIAL_FRAME_LEFT_SILENCE) > 0 &&
(m_nSpecialCodes & SpecialFrame.SPECIAL_FRAME_RIGHT_SILENCE) > 0) {
for (nBlocksProcessed = 0; nBlocksProcessed < nBlocks; nBlocksProcessed++) {
m_Prepare.unprepare(0, 0, m_wfeInput, m_cbFrameBuffer.GetDirectWritePointer(), m_nCRC);
m_cbFrameBuffer.UpdateAfterDirectWrite(m_nBlockAlign);
}
} else if ((m_nSpecialCodes & SpecialFrame.SPECIAL_FRAME_PSEUDO_STEREO) > 0) {
for (nBlocksProcessed = 0; nBlocksProcessed < nBlocks; nBlocksProcessed++) {
int X = m_spNewPredictorX.DecompressValue(m_spUnBitArray.DecodeValueRange(m_BitArrayStateX));
m_Prepare.unprepare(X, 0, m_wfeInput, m_cbFrameBuffer.GetDirectWritePointer(), m_nCRC);
m_cbFrameBuffer.UpdateAfterDirectWrite(m_nBlockAlign);
}
} else {
if (m_spAPEInfo.getApeInfoFileVersion() >= 3950) {
for (nBlocksProcessed = 0; nBlocksProcessed < nBlocks; nBlocksProcessed++) {
int nY = m_spUnBitArray.DecodeValueRange(m_BitArrayStateY);
int nX = m_spUnBitArray.DecodeValueRange(m_BitArrayStateX);
int Y = m_spNewPredictorY.DecompressValue(nY, m_nLastX);
int X = m_spNewPredictorX.DecompressValue(nX, Y);
m_nLastX = X;
m_Prepare.unprepare(X, Y, m_wfeInput, m_cbFrameBuffer.GetDirectWritePointer(), m_nCRC);
m_cbFrameBuffer.UpdateAfterDirectWrite(m_nBlockAlign);
}
} else {
for (nBlocksProcessed = 0; nBlocksProcessed < nBlocks; nBlocksProcessed++) {
int X = m_spNewPredictorX.DecompressValue(m_spUnBitArray.DecodeValueRange(m_BitArrayStateX));
int Y = m_spNewPredictorY.DecompressValue(m_spUnBitArray.DecodeValueRange(m_BitArrayStateY));
m_Prepare.unprepare(X, Y, m_wfeInput, m_cbFrameBuffer.GetDirectWritePointer(), m_nCRC);
m_cbFrameBuffer.UpdateAfterDirectWrite(m_nBlockAlign);
}
}
}
} else {
if ((m_nSpecialCodes & SpecialFrame.SPECIAL_FRAME_MONO_SILENCE) > 0) {
for (nBlocksProcessed = 0; nBlocksProcessed < nBlocks; nBlocksProcessed++) {
m_Prepare.unprepare(0, 0, m_wfeInput, m_cbFrameBuffer.GetDirectWritePointer(), m_nCRC);
m_cbFrameBuffer.UpdateAfterDirectWrite(m_nBlockAlign);
}
} else {
for (nBlocksProcessed = 0; nBlocksProcessed < nBlocks; nBlocksProcessed++) {
int X = m_spNewPredictorX.DecompressValue(m_spUnBitArray.DecodeValueRange(m_BitArrayStateX));
m_Prepare.unprepare(X, 0, m_wfeInput, m_cbFrameBuffer.GetDirectWritePointer(), m_nCRC);
m_cbFrameBuffer.UpdateAfterDirectWrite(m_nBlockAlign);
}
}
}
} catch (JMACException e) {
m_bErrorDecodingCurrentFrame = true;
}
m_nCurrentFrameBufferBlock += nBlocks;
}
protected void FillFrameBuffer() throws IOException {
// determine the maximum blocks we can decode
// note that we won't do end capping because we can't use data
// until EndFrame(...) successfully handles the frame
// that means we may decode a little extra in end capping cases
// but this allows robust error handling of bad frames
int nMaxBlocks = m_cbFrameBuffer.MaxAdd() / m_nBlockAlign;
boolean invalidChecksum = false;
// loop and decode data
int nBlocksLeft = nMaxBlocks;
while (nBlocksLeft > 0) {
int nFrameBlocks = this.getApeInfoFrameBlocks(m_nCurrentFrame);
if (nFrameBlocks < 0)
break;
int nFrameOffsetBlocks = m_nCurrentFrameBufferBlock % this.getApeInfoBlocksPerFrame();
int nFrameBlocksLeft = nFrameBlocks - nFrameOffsetBlocks;
int nBlocksThisPass = Math.min(nFrameBlocksLeft, nBlocksLeft);
// start the frame if we need to
if (nFrameOffsetBlocks == 0)
StartFrame();
// store the frame buffer bytes before we start
int nFrameBufferBytes = m_cbFrameBuffer.MaxGet();
// decode data
DecodeBlocksToFrameBuffer(nBlocksThisPass);
// end the frame if we need to
if ((nFrameOffsetBlocks + nBlocksThisPass) >= nFrameBlocks) {
EndFrame();
if (m_bErrorDecodingCurrentFrame) {
// remove any decoded data from the buffer
m_cbFrameBuffer.RemoveTail(m_cbFrameBuffer.MaxGet() - nFrameBufferBytes);
// add silence
byte cSilence = (this.getApeInfoBitsPerSample() == 8) ? (byte) 127 : (byte) 0;
for (int z = 0; z < nFrameBlocks * m_nBlockAlign; z++) {
m_cbFrameBuffer.GetDirectWritePointer().append(cSilence);
m_cbFrameBuffer.UpdateAfterDirectWrite(1);
}
// seek to try to synchronize after an error
SeekToFrame(m_nCurrentFrame);
// save the return value
invalidChecksum = true;
}
}
nBlocksLeft -= nBlocksThisPass;
}
if (invalidChecksum)
throw new JMACException("Invalid Checksum");
}
protected void StartFrame() throws IOException {
m_nCRC = new Crc32();
// get the frame header
m_nStoredCRC = m_spUnBitArray.DecodeValue(DecodeValueMethod.DECODE_VALUE_METHOD_UNSIGNED_INT);
m_bErrorDecodingCurrentFrame = false;
//get any 'special' codes if the file uses them (for silence, FALSE stereo, etc.)
m_nSpecialCodes = 0;
if (m_spAPEInfo.getApeInfoFileVersion() > 3820) {
if ((m_nStoredCRC & 0x80000000L) > 0) {
m_nSpecialCodes = (int) m_spUnBitArray.DecodeValue(DecodeValueMethod.DECODE_VALUE_METHOD_UNSIGNED_INT);
}
m_nStoredCRC &= 0x7fffffff;
}
m_spNewPredictorX.Flush();
m_spNewPredictorY.Flush();
m_spUnBitArray.FlushState(m_BitArrayStateX);
m_spUnBitArray.FlushState(m_BitArrayStateY);
m_spUnBitArray.FlushBitArray();
m_nLastX = 0;
}
protected void EndFrame() {
m_nFrameBufferFinishedBlocks += this.getApeInfoFrameBlocks(m_nCurrentFrame);
m_nCurrentFrame++;
// finalize
m_spUnBitArray.Finalize();
// check the CRC
if (m_nCRC.checksum() != m_nStoredCRC)
m_bErrorDecodingCurrentFrame = true;
}
protected void InitializeDecompressor() throws IOException {
// check if we have anything to do
if (m_bDecompressorInitialized)
return;
// update the initialized flag
m_bDecompressorInitialized = true;
// create a frame buffer
m_cbFrameBuffer.CreateBuffer((this.getApeInfoBlocksPerFrame() + DECODE_BLOCK_SIZE) * m_nBlockAlign, m_nBlockAlign * 64);
// create decoding components
m_spUnBitArray = UnBitArrayBase.CreateUnBitArray(this, m_spAPEInfo.getApeInfoFileVersion());
if (m_spAPEInfo.getApeInfoFileVersion() >= 3950) {
m_spNewPredictorX = new PredictorDecompress3950toCurrent(m_spAPEInfo.getApeInfoCompressionLevel(), m_spAPEInfo.getApeInfoFileVersion());
m_spNewPredictorY = new PredictorDecompress3950toCurrent(m_spAPEInfo.getApeInfoCompressionLevel(), m_spAPEInfo.getApeInfoFileVersion());
} else {
m_spNewPredictorX = new PredictorDecompressNormal3930to3950(m_spAPEInfo.getApeInfoCompressionLevel(), m_spAPEInfo.getApeInfoFileVersion());
m_spNewPredictorY = new PredictorDecompressNormal3930to3950(m_spAPEInfo.getApeInfoCompressionLevel(), m_spAPEInfo.getApeInfoFileVersion());
}
// seek to the beginning
Seek(-1);
}
// more decoding components
protected APEInfo m_spAPEInfo;
protected UnBitArrayBase m_spUnBitArray;
protected UnBitArrayState m_BitArrayStateX = new UnBitArrayState();
protected UnBitArrayState m_BitArrayStateY = new UnBitArrayState();
protected IPredictorDecompress m_spNewPredictorX;
protected IPredictorDecompress m_spNewPredictorY;
protected int m_nLastX;
// decoding buffer
protected boolean m_bErrorDecodingCurrentFrame;
protected int m_nCurrentFrameBufferBlock;
protected int m_nFrameBufferFinishedBlocks;
protected CircleBuffer m_cbFrameBuffer = new CircleBuffer();
}