package org.jcodec.codecs.h264.io;
import static org.jcodec.codecs.h264.decode.CAVLCReader.readU;
import static org.jcodec.codecs.h264.decode.CAVLCReader.readZeroBitCount;
import static org.jcodec.common.model.ColorSpace.YUV422;
import static org.jcodec.common.model.ColorSpace.YUV444;
import org.jcodec.codecs.h264.H264Const;
import org.jcodec.codecs.h264.io.model.MBType;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.common.io.BitReader;
import org.jcodec.common.io.BitWriter;
import org.jcodec.common.io.VLC;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.tools.MathUtil;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Non-CABAC H.264 symbols read/write routines
*
* @author Jay Codec
*
*/
public class CAVLC {
private ColorSpace color;
private VLC chromaDCVLC;
private int[] tokensLeft;
private int[] tokensTop;
private int mbWidth;
private int mbMask;
public CAVLC(SeqParameterSet sps, PictureParameterSet pps, int mbW, int mbH) {
this.color = sps.chroma_format_idc;
this.chromaDCVLC = codeTableChromaDC();
this.mbWidth = sps.pic_width_in_mbs_minus1 + 1;
this.mbMask = (1 << mbH) - 1;
tokensLeft = new int[4];
tokensTop = new int[mbWidth << mbW];
}
public int writeACBlock(BitWriter out, int blkIndX, int blkIndY, MBType leftMBType, MBType topMBType, int[] coeff,
VLC[] totalZerosTab, int firstCoeff, int maxCoeff, int[] scan) {
VLC coeffTokenTab = getCoeffTokenVLCForLuma(blkIndX != 0, leftMBType, tokensLeft[blkIndY & mbMask],
blkIndY != 0, topMBType, tokensTop[blkIndX]);
int coeffToken = writeBlockGen(out, coeff, totalZerosTab, firstCoeff, maxCoeff, scan, coeffTokenTab);
tokensLeft[blkIndY & mbMask] = coeffToken;
tokensTop[blkIndX] = coeffToken;
return coeffToken;
}
public void writeChrDCBlock(BitWriter out, int[] coeff, VLC[] totalZerosTab, int firstCoeff, int maxCoeff,
int[] scan) {
writeBlockGen(out, coeff, totalZerosTab, firstCoeff, maxCoeff, scan, getCoeffTokenVLCForChromaDC());
}
public void writeLumaDCBlock(BitWriter out, int blkIndX, int blkIndY, MBType leftMBType, MBType topMBType,
int[] coeff, VLC[] totalZerosTab, int firstCoeff, int maxCoeff, int[] scan) {
VLC coeffTokenTab = getCoeffTokenVLCForLuma(blkIndX != 0, leftMBType, tokensLeft[blkIndY & mbMask],
blkIndY != 0, topMBType, tokensTop[blkIndX]);
writeBlockGen(out, coeff, totalZerosTab, firstCoeff, maxCoeff, scan, coeffTokenTab);
}
private int writeBlockGen(BitWriter out, int[] coeff, VLC[] totalZerosTab, int firstCoeff, int maxCoeff,
int[] scan, VLC coeffTokenTab) {
int trailingOnes = 0, totalCoeff = 0, totalZeros = 0;
int[] runBefore = new int[maxCoeff];
int[] levels = new int[maxCoeff];
for (int i = 0; i < maxCoeff; i++) {
int c = coeff[scan[i + firstCoeff]];
if (c == 0) {
runBefore[totalCoeff]++;
totalZeros++;
} else {
levels[totalCoeff++] = c;
}
}
if (totalCoeff < maxCoeff)
totalZeros -= runBefore[totalCoeff];
for (trailingOnes = 0; trailingOnes < totalCoeff && trailingOnes < 3
&& Math.abs(levels[totalCoeff - trailingOnes - 1]) == 1; trailingOnes++)
;
int coeffToken = H264Const.coeffToken(totalCoeff, trailingOnes);
coeffTokenTab.writeVLC(out, coeffToken);
if (totalCoeff > 0) {
writeTrailingOnes(out, levels, totalCoeff, trailingOnes);
writeLevels(out, levels, totalCoeff, trailingOnes);
if (totalCoeff < maxCoeff) {
totalZerosTab[totalCoeff - 1].writeVLC(out, totalZeros);
writeRuns(out, runBefore, totalCoeff, totalZeros);
}
}
return coeffToken;
}
private void writeTrailingOnes(BitWriter out, int[] levels, int totalCoeff, int trailingOne) {
for (int i = totalCoeff - 1; i >= totalCoeff - trailingOne; i--)
out.write1Bit(levels[i] >>> 31);
}
private void writeLevels(BitWriter out, int[] levels, int totalCoeff, int trailingOnes) {
int suffixLen = totalCoeff > 10 && trailingOnes < 3 ? 1 : 0;
for (int i = totalCoeff - trailingOnes - 1; i >= 0; i--) {
int absLev = unsigned(levels[i]);
if (i == totalCoeff - trailingOnes - 1 && trailingOnes < 3)
absLev -= 2;
int prefix = absLev >> suffixLen;
if (suffixLen == 0 && prefix < 14 || suffixLen > 0 && prefix < 15) {
out.writeNBit(1, prefix + 1);
out.writeNBit(absLev, suffixLen);
} else if (suffixLen == 0 && absLev < 30) {
out.writeNBit(1, 15);
out.writeNBit(absLev - 14, 4);
} else {
if (suffixLen == 0)
absLev -= 15;
int len, code;
for (len = 12; (code = absLev - (len + 3 << suffixLen) - (1 << len) + 4096) >= (1 << len); len++)
;
out.writeNBit(1, len + 4);
out.writeNBit(code, len);
}
if (suffixLen == 0)
suffixLen = 1;
if (MathUtil.abs(levels[i]) > (3 << (suffixLen - 1)) && suffixLen < 6)
suffixLen++;
}
}
private final int unsigned(int signed) {
int sign = signed >>> 31;
int s = signed >> 31;
return (((signed ^ s) - s) << 1) + sign - 2;
}
private void writeRuns(BitWriter out, int[] run, int totalCoeff, int totalZeros) {
for (int i = totalCoeff - 1; i > 0 && totalZeros > 0; i--) {
H264Const.run[Math.min(6, totalZeros - 1)].writeVLC(out, run[i]);
totalZeros -= run[i];
}
}
public VLC getCoeffTokenVLCForLuma(boolean leftAvailable, MBType leftMBType, int leftToken, boolean topAvailable,
MBType topMBType, int topToken) {
int nc = codeTableLuma(leftAvailable, leftMBType, leftToken, topAvailable, topMBType, topToken);
return H264Const.CoeffToken[Math.min(nc, 8)];
}
public VLC getCoeffTokenVLCForChromaDC() {
return chromaDCVLC;
}
protected int codeTableLuma(boolean leftAvailable, MBType leftMBType, int leftToken, boolean topAvailable,
MBType topMBType, int topToken) {
int nA = leftMBType == null ? 0 : totalCoeff(leftToken);
int nB = topMBType == null ? 0 : totalCoeff(topToken);
if (leftAvailable && topAvailable)
return (nA + nB + 1) >> 1;
else if (leftAvailable)
return nA;
else if (topAvailable)
return nB;
else
return 0;
}
protected VLC codeTableChromaDC() {
if (color == ColorSpace.YUV420J) {
return H264Const.coeffTokenChromaDCY420;
} else if (color == YUV422) {
return H264Const.coeffTokenChromaDCY422;
} else if (color == YUV444) {
return H264Const.CoeffToken[0];
}
return null;
}
public int readCoeffs(BitReader _in, VLC coeffTokenTab, VLC[] totalZerosTab, int[] coeffLevel, int firstCoeff,
int nCoeff, int[] zigzag) {
int coeffToken = coeffTokenTab.readVLC(_in);
int totalCoeff = totalCoeff(coeffToken);
int trailingOnes = trailingOnes(coeffToken);
// System.out.println("Coeff token. Total: " + totalCoeff +
// ", trailOne: " + trailingOnes);
// blockType.getMaxCoeffs();
// if (blockType == BlockType.BLOCK_CHROMA_DC)
// maxCoeff = 16 / (color.compWidth[1] * color.compHeight[1]);
if (totalCoeff > 0) {
int suffixLength = totalCoeff > 10 && trailingOnes < 3 ? 1 : 0;
int[] level = new int[totalCoeff];
int i;
for (i = 0; i < trailingOnes; i++)
level[i] = 1 - 2 * _in.read1Bit();
for (; i < totalCoeff; i++) {
int level_prefix = readZeroBitCount(_in, "");
int levelSuffixSize = suffixLength;
if (level_prefix == 14 && suffixLength == 0)
levelSuffixSize = 4;
if (level_prefix >= 15)
levelSuffixSize = level_prefix - 3;
int levelCode = (Min(15, level_prefix) << suffixLength);
if (levelSuffixSize > 0) {
int level_suffix = readU(_in, levelSuffixSize, "RB: level_suffix");
levelCode += level_suffix;
}
if (level_prefix >= 15 && suffixLength == 0)
levelCode += 15;
if (level_prefix >= 16)
levelCode += (1 << (level_prefix - 3)) - 4096;
if (i == trailingOnes && trailingOnes < 3)
levelCode += 2;
if (levelCode % 2 == 0)
level[i] = (levelCode + 2) >> 1;
else
level[i] = (-levelCode - 1) >> 1;
if (suffixLength == 0)
suffixLength = 1;
if (Abs(level[i]) > (3 << (suffixLength - 1)) && suffixLength < 6)
suffixLength++;
}
int zerosLeft;
if (totalCoeff < nCoeff) {
if (coeffLevel.length == 4) {
zerosLeft = H264Const.totalZeros4[totalCoeff - 1].readVLC(_in);
} else if (coeffLevel.length == 8) {
zerosLeft = H264Const.totalZeros8[totalCoeff - 1].readVLC(_in);
} else {
zerosLeft = H264Const.totalZeros16[totalCoeff - 1].readVLC(_in);
}
} else
zerosLeft = 0;
int[] runs = new int[totalCoeff];
int r;
for (r = 0; r < totalCoeff - 1 && zerosLeft > 0; r++) {
int run = H264Const.run[Math.min(6, zerosLeft - 1)].readVLC(_in);
zerosLeft -= run;
runs[r] = run;
}
runs[r] = zerosLeft;
for (int j = totalCoeff - 1, cn = 0; j >= 0 && cn < nCoeff; j--, cn++) {
cn += runs[j];
coeffLevel[zigzag[cn + firstCoeff]] = level[j];
}
}
// System.out.print("[");
// for (int i = 0; i < nCoeff; i++)
// System.out.print(coeffLevel[i + firstCoeff] + ", ");
// System.out.println("]");
return coeffToken;
}
private static int Min(int i, int level_prefix) {
return i < level_prefix ? i : level_prefix;
}
private static int Abs(int i) {
return i < 0 ? -i : i;
}
public static final int totalCoeff(int coeffToken) {
return coeffToken >> 4;
}
public static final int trailingOnes(int coeffToken) {
return coeffToken & 0xf;
}
public static int[] NO_ZIGZAG = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
public void readChromaDCBlock(BitReader reader, int[] coeff, boolean leftAvailable, boolean topAvailable) {
VLC coeffTokenTab = getCoeffTokenVLCForChromaDC();
readCoeffs(reader, coeffTokenTab, coeff.length == 16 ? H264Const.totalZeros16
: (coeff.length == 8 ? H264Const.totalZeros8 : H264Const.totalZeros4), coeff, 0, coeff.length,
NO_ZIGZAG);
}
public void readLumaDCBlock(BitReader reader, int[] coeff, int mbX, boolean leftAvailable, MBType leftMbType,
boolean topAvailable, MBType topMbType, int[] zigzag4x4) {
VLC coeffTokenTab = getCoeffTokenVLCForLuma(leftAvailable, leftMbType, tokensLeft[0], topAvailable, topMbType,
tokensTop[mbX << 2]);
readCoeffs(reader, coeffTokenTab, H264Const.totalZeros16, coeff, 0, 16, zigzag4x4);
}
public int readACBlock(BitReader reader, int[] coeff, int blkIndX, int blkIndY, boolean leftAvailable,
MBType leftMbType, boolean topAvailable, MBType topMbType, int firstCoeff, int nCoeff, int[] zigzag4x4) {
VLC coeffTokenTab = getCoeffTokenVLCForLuma(leftAvailable, leftMbType, tokensLeft[blkIndY & mbMask],
topAvailable, topMbType, tokensTop[blkIndX]);
int readCoeffs = readCoeffs(reader, coeffTokenTab, H264Const.totalZeros16, coeff, firstCoeff, nCoeff, zigzag4x4);
tokensLeft[blkIndY & mbMask] = tokensTop[blkIndX] = readCoeffs;
return totalCoeff(readCoeffs);
}
public void setZeroCoeff(int blkIndX, int blkIndY) {
tokensLeft[blkIndY & mbMask] = tokensTop[blkIndX] = 0;
}
}