/* * 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.encoder; import davaguine.jmac.info.*; import davaguine.jmac.tools.*; import java.io.IOException; import java.util.Arrays; /** * Author: Dmitry Vaguine * Date: 08.05.2004 * Time: 12:40:36 */ public class APECompressCreate { APECompressCreate() { m_nMaxFrames = 0; } public void InitializeFile(File pIO, WaveFormat pwfeInput, int nMaxFrames, int nCompressionLevel, byte[] pHeaderData, int nHeaderBytes) throws IOException { // error check the parameters if (pIO == null || pwfeInput == null || nMaxFrames <= 0) throw new JMACException("Bad Parameters"); APEDescriptor APEDescriptor = new APEDescriptor(); APEHeaderNew header = new APEHeaderNew(); // create the descriptor (only fill what we know) APEDescriptor.cID = "MAC "; APEDescriptor.nVersion = Globals.MAC_VERSION_NUMBER; APEDescriptor.nDescriptorBytes = APEDescriptor.APE_DESCRIPTOR_BYTES; APEDescriptor.nHeaderBytes = APEHeaderNew.APE_HEADER_BYTES; APEDescriptor.nSeekTableBytes = nMaxFrames * 4; APEDescriptor.nHeaderDataBytes = (nHeaderBytes == IAPECompress.CREATE_WAV_HEADER_ON_DECOMPRESSION) ? 0 : nHeaderBytes; // create the header (only fill what we know now) header.nBitsPerSample = pwfeInput.wBitsPerSample; header.nChannels = pwfeInput.nChannels; header.nSampleRate = pwfeInput.nSamplesPerSec; header.nCompressionLevel = nCompressionLevel; header.nFormatFlags = (nHeaderBytes == IAPECompress.CREATE_WAV_HEADER_ON_DECOMPRESSION) ? APEHeader.MAC_FORMAT_FLAG_CREATE_WAV_HEADER : 0; header.nBlocksPerFrame = m_nSamplesPerFrame; // write the data to the file ByteArrayWriter writer = new ByteArrayWriter(APEDescriptor.APE_DESCRIPTOR_BYTES + APEHeaderNew.APE_HEADER_BYTES); APEDescriptor.write(writer); header.write(writer); pIO.write(writer.getBytes()); // write an empty seek table m_spSeekTable = new long[nMaxFrames]; Arrays.fill(m_spSeekTable, 0); byte[] zeroTable = new byte[nMaxFrames * 4]; Arrays.fill(zeroTable, (byte) 0); pIO.write(zeroTable); m_nMaxFrames = nMaxFrames; // write the WAV data if ((pHeaderData != null) && (nHeaderBytes > 0) && (nHeaderBytes != IAPECompress.CREATE_WAV_HEADER_ON_DECOMPRESSION)) { m_spAPECompressCore.GetBitArray().GetMD5Helper().Update(pHeaderData, nHeaderBytes); pIO.write(pHeaderData, 0, nHeaderBytes); } } public void FinalizeFile(File pIO, int nNumberOfFrames, int nFinalFrameBlocks, byte[] pTerminatingData, int nTerminatingBytes, int nWAVTerminatingBytes, int nPeakLevel) throws IOException { // store the tail position int nTailPosition = (int) pIO.getFilePointer(); // append the terminating data if (nTerminatingBytes > 0) { m_spAPECompressCore.GetBitArray().GetMD5Helper().Update(pTerminatingData, nTerminatingBytes); pIO.write(pTerminatingData, 0, nTerminatingBytes); } // go to the beginning and update the information pIO.seek(0); // get the descriptor APEDescriptor descriptor = APEDescriptor.read(pIO); // get the header APEHeaderNew header = APEHeaderNew.read(pIO); // update the header header.nFinalFrameBlocks = nFinalFrameBlocks; header.nTotalFrames = nNumberOfFrames; // update the descriptor descriptor.nAPEFrameDataBytes = nTailPosition - (descriptor.nDescriptorBytes + descriptor.nHeaderBytes + descriptor.nSeekTableBytes + descriptor.nHeaderDataBytes); descriptor.nAPEFrameDataBytesHigh = 0; descriptor.nTerminatingDataBytes = nTerminatingBytes; // update the MD5 ByteArrayWriter writer = new ByteArrayWriter(APEHeaderNew.APE_HEADER_BYTES); header.write(writer); m_spAPECompressCore.GetBitArray().GetMD5Helper().Update(writer.getBytes()); writer.reset(m_nMaxFrames * 4); for (int i = 0; i < m_nMaxFrames; i++) { writer.writeUnsignedInt(m_spSeekTable[i]); } byte[] seekTable = writer.getBytes(); m_spAPECompressCore.GetBitArray().GetMD5Helper().Update(seekTable); descriptor.cFileMD5 = m_spAPECompressCore.GetBitArray().GetMD5Helper().Final(); // set the pointer and re-write the updated header and peak level pIO.seek(0); writer.reset(descriptor.APE_DESCRIPTOR_BYTES + APEHeaderNew.APE_HEADER_BYTES); descriptor.write(writer); header.write(writer); pIO.write(writer.getBytes()); // write the updated seek table pIO.write(seekTable); } public void SetSeekByte(int nFrame, int nByteOffset) { if (nFrame >= m_nMaxFrames) throw new JMACException("APE Compress Too Much Data"); m_spSeekTable[nFrame] = nByteOffset; } public void Start(File pioOutput, WaveFormat pwfeInput, int nMaxAudioBytes) throws IOException { Start(pioOutput, pwfeInput, nMaxAudioBytes, CompressionLevel.COMPRESSION_LEVEL_NORMAL, null, IAPECompress.CREATE_WAV_HEADER_ON_DECOMPRESSION); } public void Start(File pioOutput, WaveFormat pwfeInput, int nMaxAudioBytes, int nCompressionLevel) throws IOException { Start(pioOutput, pwfeInput, nMaxAudioBytes, nCompressionLevel, null, IAPECompress.CREATE_WAV_HEADER_ON_DECOMPRESSION); } public void Start(File pioOutput, WaveFormat pwfeInput, int nMaxAudioBytes, int nCompressionLevel, byte[] pHeaderData) throws IOException { Start(pioOutput, pwfeInput, nMaxAudioBytes, nCompressionLevel, pHeaderData, IAPECompress.CREATE_WAV_HEADER_ON_DECOMPRESSION); } public void Start(File pioOutput, WaveFormat pwfeInput, int nMaxAudioBytes, int nCompressionLevel, byte[] pHeaderData, int nHeaderBytes) throws IOException { // verify the parameters if (pioOutput == null || pwfeInput == null) throw new JMACException("Bad Parameters"); // verify the wave format if ((pwfeInput.nChannels != 1) && (pwfeInput.nChannels != 2)) throw new JMACException("Input File Unsupported Channel Count"); if ((pwfeInput.wBitsPerSample != 8) && (pwfeInput.wBitsPerSample != 16) && (pwfeInput.wBitsPerSample != 24)) throw new JMACException("Input File Unsupported Bit Depth"); // initialize (creates the base classes) m_nSamplesPerFrame = 73728; if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_EXTRA_HIGH) m_nSamplesPerFrame *= 4; else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_INSANE) m_nSamplesPerFrame *= 16; m_spIO = pioOutput; m_spAPECompressCore = new APECompressCore(m_spIO, pwfeInput, m_nSamplesPerFrame, nCompressionLevel); // copy the format m_wfeInput = pwfeInput; // the compression level m_nCompressionLevel = nCompressionLevel; m_nFrameIndex = 0; m_nLastFrameBlocks = m_nSamplesPerFrame; // initialize the file if (nMaxAudioBytes < 0) nMaxAudioBytes = 2147483647; long nMaxAudioBlocks = nMaxAudioBytes / pwfeInput.nBlockAlign; int nMaxFrames = (int) (nMaxAudioBlocks / m_nSamplesPerFrame); if ((nMaxAudioBlocks % m_nSamplesPerFrame) != 0) nMaxFrames++; InitializeFile(m_spIO, m_wfeInput, nMaxFrames, m_nCompressionLevel, pHeaderData, nHeaderBytes); } public int GetFullFrameBytes() { return m_nSamplesPerFrame * m_wfeInput.nBlockAlign; } public void EncodeFrame(ByteArrayReader pInputData, int nInputBytes) throws IOException { int nInputBlocks = nInputBytes / m_wfeInput.nBlockAlign; if ((nInputBlocks < m_nSamplesPerFrame) && (m_nLastFrameBlocks < m_nSamplesPerFrame)) throw new JMACException("Bad Parameters"); // update the seek table m_spAPECompressCore.GetBitArray().AdvanceToByteBoundary(); SetSeekByte(m_nFrameIndex, (int) (m_spIO.getFilePointer() + (m_spAPECompressCore.GetBitArray().GetCurrentBitIndex() / 8))); // compress m_spAPECompressCore.EncodeFrame(pInputData, nInputBytes); // update stats m_nLastFrameBlocks = nInputBlocks; m_nFrameIndex++; } public void Finish(byte[] pTerminatingData, int nTerminatingBytes, int nWAVTerminatingBytes) throws IOException { // clear the bit array m_spAPECompressCore.GetBitArray().OutputBitArray(true); // finalize the file FinalizeFile(m_spIO, m_nFrameIndex, m_nLastFrameBlocks, pTerminatingData, nTerminatingBytes, nWAVTerminatingBytes, m_spAPECompressCore.GetPeakLevel()); } private long[] m_spSeekTable; private int m_nMaxFrames; private File m_spIO; private APECompressCore m_spAPECompressCore; private WaveFormat m_wfeInput; private int m_nCompressionLevel; private int m_nSamplesPerFrame; private int m_nFrameIndex; private int m_nLastFrameBlocks; }