package ua.stu.scplib.structure;
import java.util.ArrayList;
/**
* <p>A class to implement Huffman decoding as used by the SCP-ECG standard.</p>
*
* @see com.pixelmed.scpecg.DefaultHuffmanTable
* @see com.pixelmed.scpecg.HuffmanTable
*
* @author stu
*/
public class HuffmanDecoder {
// private static final String identString = "@(#) $Header: /userland/cvs/pixelmed/imgbook/com/pixelmed/scpecg/HuffmanDecoder.java,v 1.9 2004/02/22 19:54:34 dclunie Exp $";
private int[] extractBitMask = { 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 };
private long[] signDetectMask = {
0x0000000000000000l,
0x0000000000000001l, 0x0000000000000002l, 0x0000000000000004l, 0x0000000000000008l,
0x0000000000000010l, 0x0000000000000020l, 0x0000000000000040l, 0x0000000000000080l /* 8 bits */,
0x0000000000000100l, 0x0000000000000200l, 0x0000000000000400l, 0x0000000000000800l,
0x0000000000001000l, 0x0000000000002000l, 0x0000000000004000l, 0x0000000000008000l /* 16 bits */
};
private long[] signExtendMask = {
0xfffffffffffff000l,
0xffffffffffffffffl, 0xfffffffffffffffel, 0xfffffffffffffffcl, 0xfffffffffffffff8l,
0xfffffffffffffff0l, 0xffffffffffffffe0l, 0xffffffffffffffc0l, 0xffffffffffffff80l /* 8 bits */,
0xffffffffffffff00l, 0xfffffffffffffe00l, 0xfffffffffffffc00l, 0xfffffffffffff800l,
0xfffffffffffff000l, 0xffffffffffffe000l, 0xffffffffffffc000l, 0xffffffffffff8000l /* 16 bits */
};
private String dump(long[] array) {
StringBuffer buffer = new StringBuffer();
for (int i=0; i<array.length; ++i) {
if (i > 0) {
buffer.append(",");
}
buffer.append("0x");
buffer.append(Long.toHexString(array[i]));
}
return buffer.toString();
}
private byte[] bytesToDecompress;
private int availableBytes;
private int byteIndex;
private int bitIndex;
private int currentByte;
private long currentBits;
private int haveBits;
private int decompressedValueCount; // number of values decompressed so far (used to know whether lastValue and secondLastValue loaded)
private short lastValue; // last value decompressed
private short secondLastValue; // 2nd last value decompressed
private int huffmanTableLength;
private int[] bitsPerPrefix;
private int[] bitsPerEntireCode;
private int[] tableModeSwitch;
private long[] huffmanPrefixCodes;
private int[] valuesRepresentedByCodes;
private int differenceDataUsed; // 0=no,1=1st difference,2=2nd difference
private int multiplier; // since samples were truncated before encoding
private ArrayList huffmanTablesList; // the tables supplied by the constructor
private final long reverseBits(long src,int n) {
long dst = 0;
while (n-- > 0) {
dst = (dst<<1) | (src&0x1);
src = src>>1;
}
return dst;
}
private final long[] swapSuppliedHuffmanTableBaseCodes(long[] reversedHuffmanPrefixCodes,int[] bitsPerPrefix) {
//System.err.println("Supplied prefix codes:\n"+dump(reversedHuffmanPrefixCodes));
int n=reversedHuffmanPrefixCodes.length;
long[] correctedHuffmanPrefixCodes = new long[n];
for (int i=0; i<n; ++i) {
correctedHuffmanPrefixCodes[i]=reverseBits(reversedHuffmanPrefixCodes[i],bitsPerPrefix[i]);
}
//System.err.println("Reversed prefix codes:\n"+dump(correctedHuffmanPrefixCodes));
return correctedHuffmanPrefixCodes;
}
private final void getEnoughBits(int wantBits) throws Exception {
while (haveBits < wantBits) {
if (bitIndex > 7) {
if (byteIndex < availableBytes) {
currentByte=bytesToDecompress[byteIndex++];
//System.err.println("currentByte now =0x"+Integer.toHexString(currentByte));
bitIndex=0;
}
else {
throw new Exception("No more bits (having decompressed "+byteIndex+" dec bytes)");
}
}
long newBit = (currentByte & extractBitMask[bitIndex++]) == 0 ? 0 : 1;
currentBits = (currentBits << 1) + newBit;
++haveBits;
}
}
private void loadHuffmanTableInUse(int number) {
//System.err.println("loadHuffmanTableInUse: "+number);
HuffmanTable useHuffmanTable = null;
if (Section2.useDefaultTable(number)) {
//System.err.println("Loading default HuffmanTable");
useHuffmanTable = new DefaultHuffmanTable();
}
else if (huffmanTablesList != null) {
//System.err.println("Loading HuffmanTable "+number);
useHuffmanTable = (HuffmanTable)(huffmanTablesList.get(number));
}
huffmanTableLength=useHuffmanTable.getNumberOfCodeStructuresInTable();
bitsPerPrefix=useHuffmanTable.getNumberOfBitsInPrefix();
bitsPerEntireCode=useHuffmanTable.getNumberOfBitsInEntireCode();
tableModeSwitch=useHuffmanTable.getTableModeSwitch();
valuesRepresentedByCodes=useHuffmanTable.getBaseValueRepresentedByBaseCode();
huffmanPrefixCodes=swapSuppliedHuffmanTableBaseCodes(useHuffmanTable.getBaseCode(),bitsPerPrefix);
}
/**
* <p>Construct a Huffman decoder for the supplied encoded data, as read from an SCP-ECG file.</p>
*
* @param bytesToDecompress the compressed data
* @param differenceDataUsed 0 = no, 1 = 1 difference value, 2 = 2 difference values
* @param multiplier a value by which to scale the decoded values
* @param numberOfHuffmanTables how many tables are available for use
* @param huffmanTablesList the Huffman tables themselves
*/
public HuffmanDecoder(byte[] bytesToDecompress,int differenceDataUsed,int multiplier,int numberOfHuffmanTables,ArrayList huffmanTablesList) {
this.differenceDataUsed=differenceDataUsed;
this.multiplier=multiplier;
this.bytesToDecompress = bytesToDecompress;
availableBytes = bytesToDecompress.length;
decompressedValueCount=0;
byteIndex = 0;
bitIndex = 8; // force fetching byte the first time
haveBits = 0; // don't have any bits to start with
this.huffmanTablesList=huffmanTablesList;
loadHuffmanTableInUse(Section2.useDefaultTable(numberOfHuffmanTables) ? numberOfHuffmanTables : 0); // start with default or first
}
/**
* <p>Decode a single value.</p>
*
* @return the decoded value
*/
public final short decode() throws Exception {
short value = 0; // initializer irrelevant but quietens compiler
boolean gotValue = false;
do {
int tableIndex = 0;
while (tableIndex < huffmanTableLength) {
//System.err.println("tableIndex = "+tableIndex);
int wantPrefixBits = bitsPerPrefix[tableIndex];
//System.err.println("wantPrefixBits = "+wantPrefixBits);
// assert wantPrefixBits != 0
// assert wantPrefixBits < 32 (length of one int in Java)
getEnoughBits(wantPrefixBits); // modifies currentBits
//System.err.println("currentBits 0x"+Long.toHexString(currentBits));
//System.err.println("compare to 0x"+Long.toHexString(huffmanPrefixCodes[tableIndex]));
if (currentBits == huffmanPrefixCodes[tableIndex]) {
//System.err.println("Found prefix 0x"+Long.toHexString(currentBits));
break;
}
++tableIndex;
}
if (tableIndex >= huffmanTableLength) {
throw new Exception("Code prefix not in table");
}
if (tableModeSwitch[tableIndex] == 0) { // Note that 0 is the flag to switch; 1 indicates no switch
int newTableNumber = valuesRepresentedByCodes[tableIndex]; // The base value is used as the new table number
//System.err.println("Table Mode Switch to "+newTableNumber);
loadHuffmanTableInUse(newTableNumber);
continue; // NB. without changing values or incrementing decompressedValueCount !!
}
else if (bitsPerPrefix[tableIndex] == bitsPerEntireCode[tableIndex]) {
//System.err.println("Value from table ="+valuesRepresentedByCodes[tableIndex]+" dec, 0x"+Long.toHexString(valuesRepresentedByCodes[tableIndex]));
value=(short)valuesRepresentedByCodes[tableIndex];
//System.err.println("As short ="+value+" dec");
}
else { // assume greater, else table would be malformed
int numberOfOriginalBits = bitsPerEntireCode[tableIndex]-bitsPerPrefix[tableIndex];
//System.err.println("numberOfOriginalBits ="+numberOfOriginalBits+" dec");
currentBits=0;
haveBits=0;
getEnoughBits(numberOfOriginalBits); // modifies currentBits
//System.err.println("Value from bitstream ="+currentBits+" dec, 0x"+Long.toHexString(currentBits));
if ((currentBits & signDetectMask[numberOfOriginalBits]) != 0) {
currentBits|=signExtendMask[numberOfOriginalBits];
//System.err.println("Sign extended ="+currentBits+" dec, 0x"+Long.toHexString(currentBits));
}
value=(short)currentBits;
//System.err.println("As short ="+value+" dec");
}
// for differences use unmultiplied cached last values, not those in return value array since they have been multiplied
if (differenceDataUsed == 1) {
if (decompressedValueCount > 0) {
value = (short)(value + lastValue);
}
// else first value is sent raw (still with Huffman prefix though) leave value alone
}
else if (differenceDataUsed == 2) {
if (decompressedValueCount > 1) {
value = (short)(value + 2*lastValue - secondLastValue);
}
// else first value is sent raw (still with Huffman prefix though) leave value alone
}
else if (differenceDataUsed != 0) {
throw new Exception("Unrecognized difference encoding method "+differenceDataUsed);
}
//System.err.println("After difference ="+value+" dec");
//System.err.print(", "+value);
secondLastValue=lastValue;
lastValue=value;
currentBits=0;
haveBits=0;
++decompressedValueCount;
gotValue=true;
//System.err.println("");
} while (!gotValue);
//if (value*multiplier != (short)(value*multiplier)) {
// System.err.println("Multiplier overflow with initial value="+value+" dec");
//}
value*=multiplier;
//System.err.println("After multiplier ="+value+" dec");
//System.err.print(", "+value);
return value;
}
/**
* <p>Decode a specified number of values.</p>
*
* @param nValuesWanted the number of decoded values wanted
* @return the decoded values
*/
public final short[] decode(int nValuesWanted) throws Exception {
//System.err.println(com.pixelmed.utils.HexDump.dump(bytesToDecompress));
short values[] = new short[nValuesWanted];
for (int count=0; count < nValuesWanted; ++count) {
//System.err.println("count = "+count);
values[count]=decode();
}
//System.err.println("Got "+nValuesWanted+" dec, used "+byteIndex+" of "+availableBytes+" dec bytes, at "+bitIndex+" bit");
return values;
}
/**
* <p>Dump the current decoder state as a <code>String</code>.</p>
*
* @return the current decoder state as a <code>String</code>
*/
public String toString() {
StringBuffer strbuf = new StringBuffer();
strbuf.append("availableBytes=");
strbuf.append(availableBytes);
strbuf.append(" dec, byteIndex=");
strbuf.append(byteIndex);
strbuf.append(" dec, bitIndex=");
strbuf.append(bitIndex);
strbuf.append(" dec, decompressedValueCount=");
strbuf.append(decompressedValueCount);
strbuf.append(" dec");
return strbuf.toString();
}
/**
* <p>For testing.</p>
*
* <p>Decodes the byte stream in the example specified in the SCP-ECG standard.</p>
*
* @param arg none
*/
/*public static void main(String arg[]) {
{
int nSamples = 28;
byte[] test = { (byte)0xFF, (byte)0x83, (byte)0x7F, (byte)0xE0, (byte)0xE6,
(byte)0xF1, // prEN 1064:2002 says F5, but wrong ... binary values in table are F1
(byte)0x53,
(byte)0x65, (byte)0x59, (byte)0xB6, (byte)0x5B, (byte)0x96, (byte)0x4B, (byte)0x96,
(byte)0x00 // prEN 1064:2002 doesn't show this byte, but one extra 0 bit is needed to match table of binary value
};
short[] decodedResultWithoutMultiplication = {
13, 14, 15, 14, 16, 18, 19, 20, 22, 22, 23, 23, 23, 22, 22, 20, 17, 15, 12, 8, 6, 3, 1, 0, -2, -2, -3, -3
};
short[] decodedResultWithMultiplication = {
63, 70, 74, 71, 79, 89, 96, 102, 108, 112, 114, 116, 116, 112, 110, 100, 87, 74, 59, 42, 28, 13, 5, -1, -8, -11, -13, -17
};
System.out.println("Decoded (should be exact):");
HuffmanDecoder decoder = new HuffmanDecoder(test,2differenceDataUsed,1amplitudeValueMultiplier,19999,null);
try {
short[] decompressedData = decoder.decode(nSamples);
for (int i=0; i<nSamples; ++i) {
System.out.println("\t["+i+"] \tgot "+decompressedData[i]+" \texpected "+decodedResultWithoutMultiplication[i]+
" \tdifference "+(decompressedData[i]-decodedResultWithoutMultiplication[i]));
}
}
catch (Exception e) {
e.printStackTrace(System.err);
}
System.out.println("Multiplied (expect to see quantization error):");
decoder = new HuffmanDecoder(test,2differenceDataUsed,5amplitudeValueMultiplier,19999,null);
try {
short[] decompressedData = decoder.decode(nSamples);
for (int i=0; i<nSamples; ++i) {
System.out.println("\t["+i+"] \tgot "+decompressedData[i]+" \texpected "+decodedResultWithMultiplication[i]+
" \tdifference "+(decompressedData[i]-decodedResultWithMultiplication[i]));
}
}
catch (Exception e) {
e.printStackTrace(System.err);
}
}
}*/
}