/* * 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.tools.ByteBuffer; import davaguine.jmac.tools.File; import davaguine.jmac.tools.JMACException; import java.io.IOException; import java.util.Arrays; /** * Author: Dmitry Vaguine * Date: 04.03.2004 * Time: 14:51:31 */ public class APEDecompressOld extends IAPEDecompress { public APEDecompressOld(APEInfo pAPEInfo) { this(pAPEInfo, -1, -1); } public APEDecompressOld(APEInfo pAPEInfo, int nStartBlock) { this(pAPEInfo, nStartBlock, -1); } public APEDecompressOld(APEInfo pAPEInfo, int nStartBlock, int nFinishBlock) { // open / analyze the file m_spAPEInfo = pAPEInfo; // version check (this implementation only works with 3.92 and earlier files) if (getApeInfoFileVersion() > 3920) throw new JMACException("Wrong Version"); // create the buffer m_nBlockAlign = getApeInfoBlockAlign(); // initialize other stuff m_nBufferTail = 0; m_bDecompressorInitialized = false; m_nCurrentFrame = 0; m_nCurrentBlock = 0; // set the "real" start and finish blocks m_nStartBlock = (nStartBlock < 0) ? 0 : Math.min(nStartBlock, getApeInfoTotalBlocks()); m_nFinishBlock = (nFinishBlock < 0) ? getApeInfoTotalBlocks() : Math.min(nFinishBlock, getApeInfoTotalBlocks()); m_bIsRanged = (m_nStartBlock != 0) || (m_nFinishBlock != getApeInfoTotalBlocks()); } public int GetData(byte[] pBuffer, int nBlocks) throws IOException { InitializeDecompressor(); // cap int nBlocksUntilFinish = m_nFinishBlock - m_nCurrentBlock; nBlocks = Math.min(nBlocks, nBlocksUntilFinish); int nBlocksRetrieved = 0; //fulfill as much of the request as possible int nTotalBytesNeeded = nBlocks * m_nBlockAlign; int nBytesLeft = nTotalBytesNeeded; int nBlocksDecoded = 1; while (nBytesLeft > 0 && nBlocksDecoded > 0) { //empty the buffer int nBytesAvailable = m_nBufferTail; int nIntialBytes = Math.min(nBytesLeft, nBytesAvailable); if (nIntialBytes > 0) { System.arraycopy(m_spBuffer, 0, pBuffer, nTotalBytesNeeded - nBytesLeft, nIntialBytes); if ((m_nBufferTail - nIntialBytes) > 0) System.arraycopy(m_spBuffer, nIntialBytes, m_spBuffer, 0, m_nBufferTail - nIntialBytes); nBytesLeft -= nIntialBytes; m_nBufferTail -= nIntialBytes; } //decode more if (nBytesLeft > 0) { output.reset(m_spBuffer, m_nBufferTail); nBlocksDecoded = m_UnMAC.DecompressFrame(output, m_nCurrentFrame++); m_nBufferTail += (nBlocksDecoded * m_nBlockAlign); } } nBlocksRetrieved = (nTotalBytesNeeded - nBytesLeft) / m_nBlockAlign; // update the 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; // flush the buffer m_nBufferTail = 0; // seek to the perfect location int nBaseFrame = nBlockOffset / getApeInfoBlocksPerFrame(); int nBlocksToSkip = nBlockOffset % getApeInfoBlocksPerFrame(); int nBytesToSkip = nBlocksToSkip * m_nBlockAlign; // skip necessary blocks int nMaximumDecompressedFrameBytes = m_nBlockAlign * getApeInfoBlocksPerFrame(); byte[] pTempBuffer = new byte[nMaximumDecompressedFrameBytes + 16]; Arrays.fill(pTempBuffer, (byte) 0); m_nCurrentFrame = nBaseFrame; output.reset(pTempBuffer); int nBlocksDecoded = m_UnMAC.DecompressFrame(output, m_nCurrentFrame++); if (nBlocksDecoded == -1) throw new JMACException("Error While Decoding"); int nBytesToKeep = (nBlocksDecoded * m_nBlockAlign) - nBytesToSkip; System.arraycopy(pTempBuffer, nBytesToSkip, m_spBuffer, m_nBufferTail, nBytesToKeep); m_nBufferTail += nBytesToKeep; m_nCurrentBlock = nBlockOffset; } // buffer protected byte[] m_spBuffer; protected int m_nBufferTail; protected ByteBuffer output = new ByteBuffer(); // file info protected int m_nBlockAlign; protected int m_nCurrentFrame; // start / finish information protected int m_nStartBlock; protected int m_nFinishBlock; protected int m_nCurrentBlock; protected boolean m_bIsRanged; // decoding tools protected UnMAC m_UnMAC = new UnMAC(); protected APEInfo m_spAPEInfo; protected boolean m_bDecompressorInitialized; protected void InitializeDecompressor() throws IOException { // check if we have anything to do if (m_bDecompressorInitialized) return; // initialize the core m_UnMAC.Initialize(this); int nMaximumDecompressedFrameBytes = m_nBlockAlign * getApeInfoBlocksPerFrame(); int nTotalBufferBytes = Math.max(65536, (nMaximumDecompressedFrameBytes + 16) * 2); m_spBuffer = new byte[nTotalBufferBytes]; // update the initialized flag m_bDecompressorInitialized = true; // seek to the beginning Seek(0); } 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) { // 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(); } }