package org.jcodec.codecs.vp8;
import static org.jcodec.codecs.vp8.VP8Util.MAX_MODE_LF_DELTAS;
import static org.jcodec.codecs.vp8.VP8Util.MAX_REF_LF_DELTAS;
import static org.jcodec.codecs.vp8.VP8Util.getBitInBytes;
import static org.jcodec.codecs.vp8.VP8Util.getBitsInBytes;
import static org.jcodec.codecs.vp8.VP8Util.getDefaultCoefProbs;
import static org.jcodec.codecs.vp8.VP8Util.getMacroblockCount;
import static org.jcodec.codecs.vp8.VP8Util.keyFrameYModeProb;
import static org.jcodec.codecs.vp8.VP8Util.keyFrameYModeTree;
import static org.jcodec.codecs.vp8.VP8Util.vp8CoefUpdateProbs;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.codecs.vp8.Macroblock.Subblock;
import org.jcodec.codecs.vp8.VP8Util.QuantizationParams;
import org.jcodec.codecs.vp8.VP8Util.SubblockConstants;
import org.jcodec.common.Assert;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public class VP8Decoder {
private Macroblock[][] mbs;
private int width;
private int height;
public void decode(ByteBuffer frame) throws IOException {
byte[] firstThree = new byte[3];
frame.get(firstThree);
boolean keyFrame = getBitInBytes(firstThree, 0) == 0;
// System.out.println("frame type: " + (keyFrame ? "key" : ""));
int version = getBitsInBytes(firstThree, 1, 3);
// System.out.println("version: " + version);
boolean showFrame = getBitInBytes(firstThree, 4) > 0;
// System.out.println("show frame: " + showFrame);
int partitionSize = getBitsInBytes(firstThree, 5, 19);
// System.out.println("partition size: " + partitionSize);
String threeByteToken = printHexByte(frame.get()) + " "
+ printHexByte(frame.get()) + " " + printHexByte(frame.get());
// System.out.println("three byte token: " +threeByteToken );
int twoBytesWidth = (frame.get() & 0xFF) | (frame.get() & 0xFF) << 8;
int twoBytesHeight = (frame.get() & 0xFF) | (frame.get() & 0xFF) << 8;
width = (twoBytesWidth & 0x3fff);
height = (twoBytesHeight & 0x3fff);
// System.out.println("size: " + width + "x" + height);
// System.out.println("horizontal scale: " + (twoBytesWidth >> 14));
// System.out.println("vertical scale: " + (twoBytesHeight >> 14));
int numberOfMBRows = getMacroblockCount(height);
int numberOfMBCols = getMacroblockCount(width);
// System.out.println("macroblocks: " + numberOfMBRows + "x" +
// numberOfMBCols);
/** Init macroblocks and subblocks */
mbs = new Macroblock[numberOfMBRows + 2][numberOfMBCols + 2];
for (int row = 0; row < numberOfMBRows + 2; row++)
for (int col = 0; col < numberOfMBCols + 2; col++)
mbs[row][col] = new Macroblock(row, col);
int headerOffset = frame.position();
// System.out.println("header offset: " + headerOffset);
BooleanArithmeticDecoder headerDecoder = new BooleanArithmeticDecoder(
frame, 0);
boolean isYUVColorSpace = (headerDecoder.decodeBit() == 0);
// System.out.println("color space: " + (isYUVColorSpace ? "YUV" :
// "UNKNOWN"));
boolean clampingRequired = headerDecoder.decodeBit() == 0;
// System.out.println("clamp type: " + (clampingRequired ?
// "clampt to match [0,255]" : "no clamping needed"));
int segmentation = headerDecoder.decodeBit();
// System.out.println("segmentation: " + (segmentation == 1 ? "yes" :
// "no"));
Assert.assertEquals(0, segmentation);
int simpleFilter = headerDecoder.decodeBit();
// System.out.println("simpleFilter: " + simpleFilter);
int filterLevel = headerDecoder.decodeInt(6);
int filterType = (filterLevel == 0) ? 0 : (simpleFilter > 0) ? 1 : 2;
// System.out.println("filterLevel: " + filterLevel);
int sharpnessLevel = headerDecoder.decodeInt(3);
// System.out.println("sharpnessLevel: " + sharpnessLevel);
int loopFilterDeltaFlag = headerDecoder.decodeBit();
// System.out.println("loopFilterDeltaFlag: " + (loopFilterDeltaFlag ==
// 1 ? "yes" : "no"));
Assert.assertEquals(1, loopFilterDeltaFlag);
int loopFilterDeltaUpdate = headerDecoder.decodeBit();
// System.out.println("loopFilterDeltaUpdate: " + (loopFilterDeltaUpdate
// == 1 ? "yes" : "no"));
Assert.assertEquals(1, loopFilterDeltaUpdate);
int[] refLoopFilterDeltas = new int[MAX_REF_LF_DELTAS];
int[] modeLoopFilterDeltas = new int[MAX_MODE_LF_DELTAS];
for (int i = 0; i < MAX_REF_LF_DELTAS; i++) {
if (headerDecoder.decodeBit() > 0) {
refLoopFilterDeltas[i] = headerDecoder.decodeInt(6);
;
if (headerDecoder.decodeBit() > 0) // Apply sign
refLoopFilterDeltas[i] = refLoopFilterDeltas[i] * -1;
// System.out.println("ref_lf_deltas[" + i + "]: " +
// refLoopFilterDeltas[i]);
}
}
for (int i = 0; i < MAX_MODE_LF_DELTAS; i++) {
if (headerDecoder.decodeBit() > 0) {
modeLoopFilterDeltas[i] = headerDecoder.decodeInt(6);
if (headerDecoder.decodeBit() > 0) // Apply sign
modeLoopFilterDeltas[i] = modeLoopFilterDeltas[i] * -1;
// System.out.println("mode_lf_deltas[" + i + "]: " +
// modeLoopFilterDeltas[i]);
}
}
int log2OfPartCnt = headerDecoder.decodeInt(2);
// System.out.println("log2OfPartitionsCount: " + log2OfPartCnt);
Assert.assertEquals(0, log2OfPartCnt);
int partitionsCount = 1;
long runningSize = 0;
long zSize = frame.limit() - (partitionSize + headerOffset);
ByteBuffer tokenBuffer = frame.duplicate();
tokenBuffer.position(partitionSize + headerOffset);
BooleanArithmeticDecoder decoder = new BooleanArithmeticDecoder(
tokenBuffer, 0);
int yacIndex = headerDecoder.decodeInt(7);
// System.out.println("yacQi: " + yacIndex);
int ydcDelta = ((headerDecoder.decodeBit() > 0) ? VP8Util
.delta(headerDecoder) : 0);
// System.out.println("ydcDelta: " + ydcDelta);
int y2dcDelta = ((headerDecoder.decodeBit() > 0) ? VP8Util
.delta(headerDecoder) : 0);
// System.out.println("y2dcDelta: " + y2dcDelta);
int y2acDelta = ((headerDecoder.decodeBit() > 0) ? VP8Util
.delta(headerDecoder) : 0);
// System.out.println("y2acDelta: " + y2acDelta);
int chromaDCDelta = ((headerDecoder.decodeBit() > 0) ? VP8Util
.delta(headerDecoder) : 0);
// System.out.println("uvdcDelta: " + chromaDCDelta);
int chromaACDelta = ((headerDecoder.decodeBit() > 0) ? VP8Util
.delta(headerDecoder) : 0);
// System.out.println("uvacDelta: " + chromaACDelta);
boolean refreshProbs = headerDecoder.decodeBit() == 0;
// System.out.println("refreshEntropyProbs: " + refreshProbs);
QuantizationParams quants = new QuantizationParams(yacIndex, ydcDelta,
y2dcDelta, y2acDelta, chromaDCDelta, chromaACDelta);
int[][][][] coefProbs = getDefaultCoefProbs();
for (int i = 0; i < VP8Util.BLOCK_TYPES; i++)
for (int j = 0; j < VP8Util.COEF_BANDS; j++)
for (int k = 0; k < VP8Util.PREV_COEF_CONTEXTS; k++)
for (int l = 0; l < VP8Util.MAX_ENTROPY_TOKENS - 1; l++) {
if (headerDecoder
.decodeBool(vp8CoefUpdateProbs[i][j][k][l]) > 0) {
int newp = headerDecoder.decodeInt(8);
coefProbs[i][j][k][l] = newp;
// System.out.println("coefProbs["+i+"]["+j+"]["+k+"]["+l+"] = "+newp);
}
}
// Read the mb_no_coeff_skip flag
int macroBlockNoCoeffSkip = (int) headerDecoder.decodeBit();
// System.out.println("macroBlockNoCoeffSkip: " +
// macroBlockNoCoeffSkip);
Assert.assertEquals(1, macroBlockNoCoeffSkip);
int probSkipFalse = headerDecoder.decodeInt(8);
// System.out.println("probSkipFalse: " + probSkipFalse);
for (int mbRow = 0; mbRow < numberOfMBRows; mbRow++) {
for (int mbCol = 0; mbCol < numberOfMBCols; mbCol++) {
Macroblock mb = mbs[mbRow + 1][mbCol + 1];
// System.out.println("mbSkipCoeff: "
// +headerDecoder.decodeBool(probSkipFalse));
if ((segmentation > 0)) {
throw new UnsupportedOperationException(
"TODO: frames with multiple segments are not supported yet");
}
if (loopFilterDeltaFlag > 0) {
int level = filterLevel;
level = level + refLoopFilterDeltas[0];
level = (level < 0) ? 0 : (level > 63) ? 63 : level;
mb.filterLevel = level;
} else {
throw new UnsupportedOperationException(
"TODO: frames with loopFilterDeltaFlag <= 0 are not supported yet");
// mb.filterLevel =
// segmentQuants.getSegQuants()[mb.segmentId].getFilterStrength();
// logger.error("TODO:");
}
if (macroBlockNoCoeffSkip > 0) {
mb.skipCoeff = headerDecoder.decodeBool(probSkipFalse);
}
mb.lumaMode = headerDecoder.readTree(keyFrameYModeTree,
keyFrameYModeProb);
// 1 is added to account for non-displayed framing macroblocks,
// which are used for prediction only.
if (mb.lumaMode == SubblockConstants.B_PRED) {
for (int sbRow = 0; sbRow < 4; sbRow++) {
for (int sbCol = 0; sbCol < 4; sbCol++) {
Subblock sb = mb.ySubblocks[sbRow][sbCol];
Subblock A = sb.getAbove(VP8Util.PLANE.Y1, mbs);
Subblock L = sb.getLeft(VP8Util.PLANE.Y1, mbs);
sb.mode = headerDecoder
.readTree(
SubblockConstants.subblockModeTree,
SubblockConstants.keyFrameSubblockModeProb[A.mode][L.mode]);
}
}
} else {
int fixedMode;
switch (mb.lumaMode) {
case SubblockConstants.DC_PRED:
fixedMode = SubblockConstants.B_DC_PRED;
break;
case SubblockConstants.V_PRED:
fixedMode = SubblockConstants.B_VE_PRED;
break;
case SubblockConstants.H_PRED:
fixedMode = SubblockConstants.B_HE_PRED;
break;
case SubblockConstants.TM_PRED:
fixedMode = SubblockConstants.B_TM_PRED;
break;
default:
fixedMode = SubblockConstants.B_DC_PRED;
break;
}
for (int x = 0; x < 4; x++)
for (int y = 0; y < 4; y++)
mb.ySubblocks[y][x].mode = fixedMode;
}
mb.chromaMode = headerDecoder.readTree(VP8Util.vp8UVModeTree,
VP8Util.vp8KeyFrameUVModeProb);
}
}
for (int mbRow = 0; mbRow < numberOfMBRows; mbRow++) {
for (int mbCol = 0; mbCol < numberOfMBCols; mbCol++) {
Macroblock mb = mbs[mbRow + 1][mbCol + 1];
mb.decodeMacroBlock(mbs, decoder, coefProbs);
mb.dequantMacroBlock(mbs, quants);
}
}
if (filterType > 0 && filterLevel != 0) {
if (filterType == 2) {
FilterUtil.loopFilterUV(mbs, sharpnessLevel, keyFrame);
FilterUtil.loopFilterY(mbs, sharpnessLevel, keyFrame);
} else if (filterType == 1) {
// loopFilterSimple(frame);
}
}
}
public Picture getPicture() {
Picture p = Picture.create(width, height, ColorSpace.YUV420);
int[] luma = p.getPlaneData(0);
// int strideLuma = p.getPlaneWidth(0);
int[] cb = p.getPlaneData(1);
int[] cr = p.getPlaneData(2);
// int strideChroma = p.getPlaneWidth(1);
int mbWidth = getMacroblockCount(width);
int mbHeight = getMacroblockCount(height);
int strideLuma = mbWidth * 16;
int strideChroma = mbWidth * 8;
for (int mbRow = 0; mbRow < mbHeight; mbRow++) {
for (int mbCol = 0; mbCol < mbWidth; mbCol++) {
Macroblock mb = mbs[mbRow + 1][mbCol + 1];
for (int lumaRow = 0; lumaRow < 4; lumaRow++)
for (int lumaCol = 0; lumaCol < 4; lumaCol++)
for (int lumaPRow = 0; lumaPRow < 4; lumaPRow++)
for (int lumaPCol = 0; lumaPCol < 4; lumaPCol++) {
int y = (mbRow << 4) + (lumaRow << 2)
+ lumaPRow;
int x = (mbCol << 4) + (lumaCol << 2)
+ lumaPCol;
if (x >= strideLuma
|| y >= luma.length / strideLuma)
continue;
int yy = mb.ySubblocks[lumaRow][lumaCol].val[lumaPRow
* 4 + lumaPCol];
luma[strideLuma * y + x] = yy;
}
for (int chromaRow = 0; chromaRow < 2; chromaRow++)
for (int chromaCol = 0; chromaCol < 2; chromaCol++)
for (int chromaPRow = 0; chromaPRow < 4; chromaPRow++)
for (int chromaPCol = 0; chromaPCol < 4; chromaPCol++) {
int y = (mbRow << 3) + (chromaRow << 2)
+ chromaPRow;
int x = (mbCol << 3) + (chromaCol << 2)
+ chromaPCol;
if (x >= strideChroma
|| y >= cb.length / strideChroma)
continue;
int u = mb.uSubblocks[chromaRow][chromaCol].val[chromaPRow
* 4 + chromaPCol];
int v = mb.vSubblocks[chromaRow][chromaCol].val[chromaPRow
* 4 + chromaPCol];
cb[strideChroma * y + x] = u;
cr[strideChroma * y + x] = v;
}
}
}
return p;
}
public static String printHexByte(byte b) {
return "0x" + Integer.toHexString(b & 0xFF);
}
}