package org.jcodec.codecs.vp8; import static java.nio.ByteBuffer.wrap; import static org.jcodec.codecs.vpx.VPXBooleanDecoder.leadingZeroCountInByte; import org.jcodec.codecs.vpx.VPXBooleanDecoder; import org.jcodec.common.ArithmeticCoderTest; import org.jcodec.common.io.IOUtils; import org.junit.Assert; import org.junit.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.System; import java.nio.ByteBuffer; public class BooleanCodingTest { @Test public void testLeadingZero() throws Exception { Assert.assertEquals(7, leadingZeroCountInByte((byte)1)); Assert.assertEquals(0, leadingZeroCountInByte((byte)129)); Assert.assertEquals(0, leadingZeroCountInByte((byte)256)); Assert.assertEquals(7, leadingZeroCountInByte((byte)257)); Assert.assertEquals(1, leadingZeroCountInByte((byte)383)); } @Test public void testBitInByte() throws Exception { Assert.assertEquals(1, VPXBooleanDecoder.getBitInBytes(new byte[] { (byte) 0x80 }, 0)); Assert.assertEquals(0, VPXBooleanDecoder.getBitInBytes(new byte[] { (byte) 0x80 }, 1)); Assert.assertEquals(1, VPXBooleanDecoder.getBitInBytes(new byte[] { (byte) 0x90 }, 3)); Assert.assertEquals(1, VPXBooleanDecoder.getBitInBytes(new byte[] { (byte) 0x91 }, 7)); Assert.assertEquals(1, VPXBooleanDecoder.getBitInBytes(new byte[] { 0x00, (byte) 0x91 }, 15)); } @Test public void test() throws IOException { byte[] b = IOUtils.toByteArray(new FileInputStream(new File("src/test/resources/part1.vp8.mb"))); VPXBooleanDecoder bac = new VPXBooleanDecoder(ByteBuffer.wrap(b), 0); Assert.assertEquals("clear type is expected to be 0", 0, bac.decodeBit()); Assert.assertEquals("clamp type is expected to be 0", 0, bac.decodeBit()); Assert.assertEquals("segmentation is expected to be disabled", 0, bac.decodeBit()); Assert.assertEquals("simple filter disabled", 0, bac.decodeBit()); Assert.assertEquals("filter level is 8", 8, bac.decodeInt(6)); Assert.assertEquals("sharpness level is 0", 0, bac.decodeInt(3)); // System.out.println("clear type is expected to be 0" + " " + 0 + " " + bac.decodeBit()); // System.out.println("clamp type is expected to be 0" + " " + 0 + " " + bac.decodeBit()); // System.out.println("segmentation is expected to be disabled" + " " + 0 + " " + bac.decodeBit()); // System.out.println("simple filter disabled" + " " + 0 + " " + bac.decodeBit()); // System.out.println("filter level is 8" + " " + 8 + " " + bac.decodeInt(6)); // System.out.println("Sharpness level: " + bac.decodeInt(3)); // System.out.println("mode_ref_lf_delta_update: " + bac.decodeBit()); Assert.assertEquals(1, bac.decodeBit()); } public static class BooleanArithmeticEncoder { private ByteBuffer output; int range; /* 128 <= range <= 255 */ int bottom; /* minimum value of remaining output */ int bitCount; /* # of shifts before an output byte is available */ public BooleanArithmeticEncoder(ByteBuffer output) { this.output = output; range = 255; bottom = 0; bitCount = 24; } /** * copy-paste from http://tools.ietf.org/html/rfc6386 * @param probability * @param value */ public void encode(int probability, int value) { int split = 1 + (((range - 1) * probability) >> 8); if (value != 0) { bottom += split; /* move up bottom of interval */ range -= split; /* with corresponding decrease in range */ } else range = split; /* decrease range, leaving bottom alone */ while (range < 128) { // This should also get replaced with BooleanArithmeticDecoder.leadingZeroCountInByte range <<= 1; if ((bottom & (1 << 31)) != 0) /* detect carry */ add_one_to_output(output); bottom <<= 1; /* before shifting bottom */ --bitCount; if (bitCount == 0) { /* write out high byte of bottom ... */ output.put((byte) (bottom >> 24)); bottom &= (1 << 24) - 1; /* ... keeping low 3 bytes */ bitCount = 8; /* 8 shifts until next output */ } } } /* Call this function (exactly once) after encoding the last bool value for the partition being written, copy-paste from http://tools.ietf.org/html/rfc6386 */ public void flushRemaining(){ int c = bitCount; int v = bottom; if ((v & (1 << (32 - c))) != 0) /* propagate (unlikely) carry */ add_one_to_output(output); v <<= c & 7; /* before shifting remaining output */ c >>= 3; /* to top of internal buffer */ while (--c >= 0) v <<= 8; c = 4; while (--c >= 0) { /* write remaining data, possibly padded */ output.put((byte) (v >>> 24)); v <<= 8; } } public static void add_one_to_output(ByteBuffer buffer) { byte[] ar = buffer.array(); int idx = buffer.position(); while( idx > 0 && (ar[--idx] == (byte)255)){ ar[idx] = 0; } ar[idx]++; } } @Test public void testAdd() throws Exception { ByteBuffer bb = ByteBuffer.allocate(128); bb.put((byte)24); bb.put((byte)255); BooleanArithmeticEncoder.add_one_to_output(bb); Assert.assertEquals(2, bb.position()); Assert.assertEquals((byte)0, bb.get(bb.position()-1)); Assert.assertEquals((byte)25, bb.get(bb.position()-2)); } @Test public void testOut() throws Exception { ByteBuffer bb = ByteBuffer.allocate(128); bb.put((byte)24); bb.put((byte)255); Assert.assertEquals(2, bb.position()); Assert.assertEquals((byte)255, bb.get(bb.position()-1)); Assert.assertEquals((byte)24, bb.get(bb.position()-2)); } @Test public void testDecodeEncodedSequence() throws Exception { BooleanArithmeticEncoder bae = new BooleanArithmeticEncoder(ByteBuffer.allocate(16)); int probability = 155; int[] data = new int[]{1,0,0,0,0,1,1,0,0,0,0,1}; for (int d : data) bae.encode(probability, d); bae.flushRemaining(); byte[] array = bae.output.array(); System.out.println(ArithmeticCoderTest.printArrayAsHex(array)); VPXBooleanDecoder bac = new VPXBooleanDecoder(wrap(array), 0); for (int d : data) Assert.assertTrue(d == bac.decodeBool(probability)); } }