/* * 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.tools.ByteArrayWriter; import davaguine.jmac.tools.File; import davaguine.jmac.tools.JMACException; import davaguine.jmac.tools.MD5; import java.io.IOException; import java.util.Arrays; /** * Author: Dmitry Vaguine * Date: 04.05.2004 * Time: 16:41:39 */ public class BitArray { private final static int BIT_ARRAY_ELEMENTS = 4096; // the number of elements in the bit array (4 MB) private final static int BIT_ARRAY_BYTES = BIT_ARRAY_ELEMENTS * 4; // the number of bytes in the bit array private final static int BIT_ARRAY_BITS = BIT_ARRAY_BYTES * 8; // the number of bits in the bit array private final static int MAX_ELEMENT_BITS = 128; private final static int REFILL_BIT_THRESHOLD = BIT_ARRAY_BITS - MAX_ELEMENT_BITS; private final static long CODE_BITS = 32; private final static long TOP_VALUE = (((long) 1) << (CODE_BITS - 1)); private final static long SHIFT_BITS = (CODE_BITS - 9); private final static long BOTTOM_VALUE = (TOP_VALUE >> 8); private final static long[] K_SUM_MIN_BOUNDARY = {0, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648L, 0, 0, 0, 0}; private final static long[] RANGE_TOTAL = {0, 19578, 36160, 48417, 56323, 60899, 63265, 64435, 64971, 65232, 65351, 65416, 65447, 65466, 65476, 65482, 65485, 65488, 65490, 65491, 65492, 65493, 65494, 65495, 65496, 65497, 65498, 65499, 65500, 65501, 65502, 65503, 65504, 65505, 65506, 65507, 65508, 65509, 65510, 65511, 65512, 65513, 65514, 65515, 65516, 65517, 65518, 65519, 65520, 65521, 65522, 65523, 65524, 65525, 65526, 65527, 65528, 65529, 65530, 65531, 65532, 65533, 65534, 65535}; private final static long[] RANGE_WIDTH = {19578, 16582, 12257, 7906, 4576, 2366, 1170, 536, 261, 119, 65, 31, 19, 10, 6, 3, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; private final static int MODEL_ELEMENTS = 64; private final static int RANGE_OVERFLOW_SHIFT = 16; // construction / destruction public BitArray(File pIO) { // allocate memory for the bit array m_pBitArray = new int[BIT_ARRAY_ELEMENTS]; Arrays.fill(m_pBitArray, 0); // initialize other variables m_nCurrentBitIndex = 0; m_pIO = pIO; } protected void finalize() { m_pBitArray = null; } private void NormalizeRangeCoder() { while (m_RangeCoderInfo.range <= BOTTOM_VALUE) { if (m_RangeCoderInfo.low < (0xFF << SHIFT_BITS)) { putc(m_RangeCoderInfo.buffer); for (; m_RangeCoderInfo.help > 0; m_RangeCoderInfo.help--) { putc_nocap(0xFF); } m_RangeCoderInfo.buffer = (short) ((m_RangeCoderInfo.low >> SHIFT_BITS) & 0xff); } else if ((m_RangeCoderInfo.low & TOP_VALUE) > 0) { putc(m_RangeCoderInfo.buffer + 1); m_nCurrentBitIndex += (m_RangeCoderInfo.help * 8); m_RangeCoderInfo.help = 0; m_RangeCoderInfo.buffer = (short) ((m_RangeCoderInfo.low >> SHIFT_BITS) & 0xff); } else { m_RangeCoderInfo.help++; } m_RangeCoderInfo.low = (m_RangeCoderInfo.low << 8) & (TOP_VALUE - 1); m_RangeCoderInfo.range <<= 8; } } private void EncodeFast(long RANGE_WIDTH, long RANGE_TOTAL, int SHIFT) { NormalizeRangeCoder(); long nTemp = m_RangeCoderInfo.range >> (SHIFT); m_RangeCoderInfo.range = nTemp * (RANGE_WIDTH); m_RangeCoderInfo.low += nTemp * (RANGE_TOTAL); } private void EncodeDirect(long VALUE, int SHIFT) { NormalizeRangeCoder(); m_RangeCoderInfo.range = m_RangeCoderInfo.range >> (SHIFT); m_RangeCoderInfo.low += m_RangeCoderInfo.range * (VALUE); } private void putc(long VALUE) { m_pBitArray[(int) (m_nCurrentBitIndex >> 5)] |= ((VALUE) & 0xFF) << (24 - (m_nCurrentBitIndex & 31)); m_nCurrentBitIndex += 8; } private void putc_nocap(long VALUE) { m_pBitArray[(int) (m_nCurrentBitIndex >> 5)] |= (VALUE) << (24 - (m_nCurrentBitIndex & 31)); m_nCurrentBitIndex += 8; } public void checkValue(long value) { if (value < 0 || value > 4294967295L) throw new JMACException("Wrong Value: " + value); } // encoding public void EncodeUnsignedLong(long n) throws IOException { // make sure there are at least 8 bytes in the buffer if (m_nCurrentBitIndex > (BIT_ARRAY_BYTES - 8)) OutputBitArray(); // encode the value int nBitArrayIndex = (int) (m_nCurrentBitIndex >> 5); int nBitIndex = (int) (m_nCurrentBitIndex & 31); if (nBitIndex == 0) m_pBitArray[nBitArrayIndex] = (int) n; else { m_pBitArray[nBitArrayIndex] |= n >> nBitIndex; m_pBitArray[nBitArrayIndex + 1] = (int) (n << (32 - nBitIndex)); } m_nCurrentBitIndex += 32; } public void EncodeValue(int nEncode, BitArrayState BitArrayState) throws IOException { // make sure there is room for the data // this is a little slower than ensuring a huge block to start with, but it's safer if (m_nCurrentBitIndex > REFILL_BIT_THRESHOLD) OutputBitArray(); // convert to unsigned nEncode = (nEncode > 0) ? nEncode * 2 - 1 : -nEncode * 2; int nOriginalKSum = BitArrayState.nKSum; // update nKSum BitArrayState.nKSum += ((nEncode + 1) / 2) - ((BitArrayState.nKSum + 16) >> 5); // update k if (BitArrayState.nKSum < K_SUM_MIN_BOUNDARY[BitArrayState.k]) BitArrayState.k--; else if (BitArrayState.nKSum >= K_SUM_MIN_BOUNDARY[BitArrayState.k + 1]) BitArrayState.k++; // figure the pivot value int nPivotValue = Math.max(nOriginalKSum / 32, 1); int nOverflow = nEncode / nPivotValue; int nBase = nEncode - (nOverflow * nPivotValue); // store the overflow if (nOverflow < (MODEL_ELEMENTS - 1)) EncodeFast(RANGE_WIDTH[nOverflow], RANGE_TOTAL[nOverflow], RANGE_OVERFLOW_SHIFT); else { // store the "special" overflow (tells that perfect k is encoded next) EncodeFast(RANGE_WIDTH[MODEL_ELEMENTS - 1], RANGE_TOTAL[MODEL_ELEMENTS - 1], RANGE_OVERFLOW_SHIFT); // code the overflow using straight bits EncodeDirect((nOverflow >> 16) & 0xFFFF, 16); EncodeDirect(nOverflow & 0xFFFF, 16); } // code the base { if (nPivotValue >= (1 << 16)) { int nPivotValueBits = 0; while ((nPivotValue >> nPivotValueBits) > 0) nPivotValueBits++; int nSplitFactor = 1 << (nPivotValueBits - 16); // we know that base is smaller than pivot coming into this // however, after we divide both by an integer, they could be the same // we account by adding one to the pivot, but this hurts compression // by (1 / nSplitFactor) -- therefore we maximize the split factor // that gets one added to it // encode the pivot as two pieces int nPivotValueA = (nPivotValue / nSplitFactor) + 1; int nPivotValueB = nSplitFactor; int nBaseA = nBase / nSplitFactor; int nBaseB = nBase % nSplitFactor; { NormalizeRangeCoder(); long nTemp = m_RangeCoderInfo.range / nPivotValueA; m_RangeCoderInfo.range = nTemp; m_RangeCoderInfo.low += nTemp * nBaseA; } { NormalizeRangeCoder(); long nTemp = m_RangeCoderInfo.range / nPivotValueB; m_RangeCoderInfo.range = nTemp; m_RangeCoderInfo.low += nTemp * nBaseB; } } else { NormalizeRangeCoder(); long nTemp = m_RangeCoderInfo.range / nPivotValue; m_RangeCoderInfo.range = nTemp; m_RangeCoderInfo.low += nTemp * nBase; } } } public void EncodeBits(long nValue, int nBits) throws IOException { // make sure there is room for the data // this is a little slower than ensuring a huge block to start with, but it's safer if (m_nCurrentBitIndex > REFILL_BIT_THRESHOLD) OutputBitArray(); EncodeDirect(nValue, nBits); } // output (saving) public void OutputBitArray() throws IOException { OutputBitArray(false); } private ByteArrayWriter m_pWriter = new ByteArrayWriter(); public void OutputBitArray(boolean bFinalize) throws IOException { // write the entire file to disk long nBytesToWrite = 0; m_pWriter.reset(m_pBitArray.length * 4); for (int i = 0; i < m_pBitArray.length; i++) m_pWriter.writeInt(m_pBitArray[i]); if (bFinalize) { nBytesToWrite = ((m_nCurrentBitIndex >> 5) * 4) + 4; m_MD5.Update(m_pWriter.getBytes(), (int) nBytesToWrite); m_pIO.write(m_pWriter.getBytes(), 0, (int) nBytesToWrite); // reset the bit pointer m_nCurrentBitIndex = 0; } else { nBytesToWrite = (m_nCurrentBitIndex >> 5) * 4; m_MD5.Update(m_pWriter.getBytes(), (int) nBytesToWrite); m_pIO.write(m_pWriter.getBytes(), 0, (int) nBytesToWrite); // move the last value to the front of the bit array m_pBitArray[0] = m_pBitArray[(int) (m_nCurrentBitIndex >> 5)]; m_nCurrentBitIndex = (m_nCurrentBitIndex & 31); // zero the rest of the memory (may not need the +1 because of frame byte alignment) Arrays.fill(m_pBitArray, 1, (int) (Math.min(nBytesToWrite + 1, BIT_ARRAY_BYTES - 1) / 4 + 1), 0); } } // other functions public void Finalize() { NormalizeRangeCoder(); long nTemp = (m_RangeCoderInfo.low >> SHIFT_BITS) + 1; if (nTemp > 0xFF) // we have a carry { putc(m_RangeCoderInfo.buffer + 1); for (; m_RangeCoderInfo.help > 0; m_RangeCoderInfo.help--) putc(0); } else // no carry { putc(m_RangeCoderInfo.buffer); for (; m_RangeCoderInfo.help > 0; m_RangeCoderInfo.help--) putc(((char) 0xFF)); } // we must output these bytes so the core can properly work at the end of the stream putc(nTemp & 0xFF); putc(0); putc(0); putc(0); } public void AdvanceToByteBoundary() { while ((m_nCurrentBitIndex % 8) > 0) m_nCurrentBitIndex++; } public long GetCurrentBitIndex() { return m_nCurrentBitIndex; } public void FlushState(BitArrayState BitArrayState) { // k and ksum BitArrayState.k = 10; BitArrayState.nKSum = (1 << BitArrayState.k) * 16; } public void FlushBitArray() { // advance to a byte boundary (for alignment) AdvanceToByteBoundary(); // the range coder m_RangeCoderInfo.low = 0; // full code range m_RangeCoderInfo.range = TOP_VALUE; m_RangeCoderInfo.buffer = 0; m_RangeCoderInfo.help = 0; // no bytes to follow } public MD5 GetMD5Helper() { return m_MD5; } // data members private int[] m_pBitArray; private File m_pIO; private long m_nCurrentBitIndex; private RangeCoderStructCompress m_RangeCoderInfo = new RangeCoderStructCompress(); private MD5 m_MD5 = new MD5(); }