/*
* 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;
}