org.jcodec.codecs.mpeg12.MPEGDecoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcodec Show documentation
Show all versions of jcodec Show documentation
Pure Java implementation of video/audio codecs and formats
package org.jcodec.codecs.mpeg12;
import static junit.framework.Assert.assertEquals;
import static org.jcodec.codecs.mpeg12.MPEGConst.BLOCK_TO_CC;
import static org.jcodec.codecs.mpeg12.MPEGConst.EXTENSION_START_CODE;
import static org.jcodec.codecs.mpeg12.MPEGConst.GROUP_START_CODE;
import static org.jcodec.codecs.mpeg12.MPEGConst.PICTURE_START_CODE;
import static org.jcodec.codecs.mpeg12.MPEGConst.SEQUENCE_HEADER_CODE;
import static org.jcodec.codecs.mpeg12.MPEGConst.SLICE_START_CODE_FIRST;
import static org.jcodec.codecs.mpeg12.MPEGConst.SLICE_START_CODE_LAST;
import static org.jcodec.codecs.mpeg12.MPEGConst.SQUEEZE_X;
import static org.jcodec.codecs.mpeg12.MPEGConst.SQUEEZE_Y;
import static org.jcodec.codecs.mpeg12.MPEGConst.USER_DATA_START_CODE;
import static org.jcodec.codecs.mpeg12.MPEGConst.mbTypeVal;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcAddressIncrement;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcCBP;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcCoeff0;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcCoeff1;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcDCSizeChroma;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcDCSizeLuma;
import static org.jcodec.codecs.mpeg12.MPEGConst.vlcMBType;
import static org.jcodec.codecs.mpeg12.MPEGUtil.gotoNextMarker;
import static org.jcodec.codecs.mpeg12.MPEGUtil.nextSegment;
import static org.jcodec.codecs.mpeg12.bitstream.PictureCodingExtension.Frame;
import static org.jcodec.codecs.mpeg12.bitstream.SequenceExtension.Chroma420;
import static org.jcodec.codecs.mpeg12.bitstream.SequenceExtension.Chroma422;
import static org.jcodec.codecs.mpeg12.bitstream.SequenceExtension.Chroma444;
import static org.jcodec.codecs.mpeg12.bitstream.SequenceHeader.Sequence_Display_Extension;
import static org.jcodec.codecs.mpeg12.bitstream.SequenceHeader.Sequence_Extension;
import static org.jcodec.codecs.mpeg12.bitstream.SequenceHeader.Sequence_Scalable_Extension;
import static org.jcodec.common.model.ColorSpace.YUV420;
import static org.jcodec.common.model.ColorSpace.YUV422;
import static org.jcodec.common.model.ColorSpace.YUV444;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.jcodec.codecs.mpeg12.MPEGConst.MBType;
import org.jcodec.codecs.mpeg12.bitstream.GOPHeader;
import org.jcodec.codecs.mpeg12.bitstream.PictureHeader;
import org.jcodec.codecs.mpeg12.bitstream.SequenceExtension;
import org.jcodec.codecs.mpeg12.bitstream.SequenceHeader;
import org.jcodec.codecs.mpeg12.bitstream.SequenceScalableExtension;
import org.jcodec.common.VideoDecoder;
import org.jcodec.common.dct.SparseIDCT;
import org.jcodec.common.io.BitReader;
import org.jcodec.common.io.VLC;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Rect;
import org.jcodec.common.model.Size;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* MPEG 1/2 Decoder
*
* Supports I/P/B frames, frame/field/interlaced frame encoding
*
* Conforms to H.262 ( ISO/IEC 13818-2, ISO/IEC 11172-2 ) specifications
*
* @author The JCodec project
*
*/
public class MPEGDecoder implements VideoDecoder {
protected SequenceHeader sh;
protected GOPHeader gh;
private int maxCoeff;
private Picture[] refFrames = new Picture[2];
private Picture[] refFields = new Picture[2];
public MPEGDecoder(SequenceHeader sh, GOPHeader gh) {
this.sh = sh;
this.gh = gh;
}
public MPEGDecoder() {
this(64);
}
public MPEGDecoder(int maxCoeff) {
this.maxCoeff = maxCoeff;
}
public class Context {
int[] intra_dc_predictor = new int[3];
public int mbWidth;
int mbNo;
public int codedWidth;
public int codedHeight;
public int mbHeight;
public ColorSpace color;
public MBType lastPredB;
public int[][] qMats;
public int[] scan;
}
public Picture decodeFrame(ByteBuffer ByteBuffer, int[][] buf) {
PictureHeader ph = readHeader(ByteBuffer);
if (refFrames[0] == null && ph.picture_coding_type > 1 || refFrames[1] == null && ph.picture_coding_type > 2) {
throw new RuntimeException("Not enough references to decode " + (ph.picture_coding_type == 1 ? "P" : "B")
+ " frame");
}
Context context = initContext(sh, ph);
Picture pic = new Picture(context.codedWidth, context.codedHeight, buf, context.color, new Rect(0, 0,
sh.horizontal_size, sh.vertical_size));
if (ph.pictureCodingExtension != null && ph.pictureCodingExtension.picture_structure != Frame) {
decodePicture(context, ph, ByteBuffer, buf, ph.pictureCodingExtension.picture_structure - 1, 1);
ph = readHeader(ByteBuffer);
context = initContext(sh, ph);
decodePicture(context, ph, ByteBuffer, buf, ph.pictureCodingExtension.picture_structure - 1, 1);
} else {
decodePicture(context, ph, ByteBuffer, buf, 0, 0);
}
if (ph.picture_coding_type == PictureHeader.IntraCoded
|| ph.picture_coding_type == PictureHeader.PredictiveCoded) {
Picture unused = refFrames[1];
refFrames[1] = refFrames[0];
refFrames[0] = copyAndCreateIfNeeded(pic, unused);
}
return pic;
}
private Picture copyAndCreateIfNeeded(Picture src, Picture dst) {
if (dst == null || !dst.compatible(src)) {
dst = src.createCompatible();
}
dst.copyFrom(src);
return dst;
}
private PictureHeader readHeader(ByteBuffer buffer) {
PictureHeader ph = null;
ByteBuffer segment;
while ((segment = nextSegment(buffer)) != null) {
int code = segment.get(3);
segment.position(4);
switch (code) {
case SEQUENCE_HEADER_CODE:
SequenceHeader newSh = SequenceHeader.read(segment);
if (sh != null) {
newSh.copyExtensions(sh);
}
sh = newSh;
break;
case GROUP_START_CODE:
gh = GOPHeader.read(segment);
break;
case PICTURE_START_CODE:
ph = PictureHeader.read(segment);
break;
case EXTENSION_START_CODE:
int extType = segment.get(4) >> 4;
if (extType == Sequence_Extension || extType == Sequence_Scalable_Extension
|| extType == Sequence_Display_Extension)
SequenceHeader.readExtension(segment, sh);
else
PictureHeader.readExtension(segment, ph, sh);
break;
case USER_DATA_START_CODE:
// do nothing
break;
default:
buffer.reset();
break;
}
}
return ph;
}
private Context initContext(SequenceHeader sh, PictureHeader ph) {
Context context = new Context();
context.codedWidth = (sh.horizontal_size + 15) & ~0xf;
context.codedHeight = getCodedHeight(sh, ph);
context.mbWidth = (sh.horizontal_size + 15) >> 4;
context.mbHeight = (sh.vertical_size + 15) >> 4;
int chromaFormat = Chroma420;
if (sh.sequenceExtension != null)
chromaFormat = sh.sequenceExtension.chroma_format;
context.color = getColor(chromaFormat);
context.scan = MPEGConst.scan[ph.pictureCodingExtension == null ? 0 : ph.pictureCodingExtension.alternate_scan];
int[] inter = sh.non_intra_quantiser_matrix == null ? zigzag(MPEGConst.defaultQMatInter, context.scan)
: sh.non_intra_quantiser_matrix;
int[] intra = sh.intra_quantiser_matrix == null ? zigzag(MPEGConst.defaultQMatIntra, context.scan)
: sh.intra_quantiser_matrix;
context.qMats = new int[][] { inter, inter, intra, intra };
if (ph.quantMatrixExtension != null) {
if (ph.quantMatrixExtension.non_intra_quantiser_matrix != null)
context.qMats[0] = ph.quantMatrixExtension.non_intra_quantiser_matrix;
if (ph.quantMatrixExtension.chroma_non_intra_quantiser_matrix != null)
context.qMats[1] = ph.quantMatrixExtension.chroma_non_intra_quantiser_matrix;
if (ph.quantMatrixExtension.intra_quantiser_matrix != null)
context.qMats[2] = ph.quantMatrixExtension.intra_quantiser_matrix;
if (ph.quantMatrixExtension.chroma_intra_quantiser_matrix != null)
context.qMats[3] = ph.quantMatrixExtension.chroma_intra_quantiser_matrix;
}
return context;
}
private int[] zigzag(int[] array, int[] scan) {
int[] result = new int[64];
for (int i = 0; i < scan.length; i++)
result[i] = array[scan[i]];
return result;
}
public static int getCodedHeight(SequenceHeader sh, PictureHeader ph) {
int field = ph.pictureCodingExtension != null && ph.pictureCodingExtension.picture_structure != Frame ? 1 : 0;
return (((sh.vertical_size >> field) + 15) & ~0xf) << field;
}
public Picture decodePicture(Context context, PictureHeader ph, ByteBuffer buffer, int[][] buf, int vertOff,
int vertStep) {
int planeSize = context.codedWidth * context.codedHeight;
if (buf.length < 3 || buf[0].length < planeSize || buf[1].length < planeSize || buf[2].length < planeSize) {
throw new RuntimeException("ByteBuffer too small to hold output picture [" + context.codedWidth + "x"
+ context.codedHeight + "]");
}
try {
ByteBuffer segment;
while ((segment = nextSegment(buffer)) != null) {
if (segment.get(3) >= SLICE_START_CODE_FIRST && segment.get(3) <= SLICE_START_CODE_LAST) {
segment.position(4);
try {
decodeSlice(ph, segment.get(3) & 0xff, context, buf, new BitReader(segment), vertOff, vertStep);
} catch (RuntimeException e) {
e.printStackTrace();
}
} else if (segment.get(3) >= 0xB3 && segment.get(3) != 0xB6 && segment.get(3) != 0xB7) {
throw new RuntimeException("Unexpected start code " + segment.get(3));
} else if (segment.get(3) == 0x0) {
buffer.reset();
break;
}
}
Picture pic = new Picture(context.codedWidth, context.codedHeight, buf, context.color);
if ((ph.picture_coding_type == PictureHeader.IntraCoded || ph.picture_coding_type == PictureHeader.PredictiveCoded)
&& ph.pictureCodingExtension != null && ph.pictureCodingExtension.picture_structure != Frame) {
refFields[ph.pictureCodingExtension.picture_structure - 1] = copyAndCreateIfNeeded(pic,
refFields[ph.pictureCodingExtension.picture_structure - 1]);
}
return pic;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private ColorSpace getColor(int chromaFormat) {
switch (chromaFormat) {
case Chroma420:
return YUV420;
case Chroma422:
return YUV422;
case Chroma444:
return YUV444;
}
return null;
}
public void decodeSlice(PictureHeader ph, int verticalPos, Context context, int[][] buf, BitReader in, int vertOff,
int vertStep) throws IOException {
int stride = context.codedWidth;
resetDCPredictors(context, ph);
int mbRow = verticalPos - 1;
if (sh.vertical_size > 2800) {
mbRow += (in.readNBit(3) << 7);
}
if (sh.sequenceScalableExtension != null
&& sh.sequenceScalableExtension.scalable_mode == SequenceScalableExtension.DATA_PARTITIONING) {
int priorityBreakpoint = in.readNBit(7);
}
int qScaleCode = in.readNBit(5);
if (in.read1Bit() == 1) {
int intraSlice = in.read1Bit();
in.skip(7);
while (in.read1Bit() == 1)
in.readNBit(8);
}
MPEGPred pred = new MPEGPred(ph.pictureCodingExtension != null ? ph.pictureCodingExtension.f_code
: new int[][] { new int[] { ph.forward_f_code, ph.forward_f_code },
new int[] { ph.backward_f_code, ph.backward_f_code } },
sh.sequenceExtension != null ? sh.sequenceExtension.chroma_format : Chroma420,
ph.pictureCodingExtension != null && ph.pictureCodingExtension.top_field_first == 0 ? false : true);
int[] ctx = new int[] { qScaleCode };
for (int prevAddr = mbRow * context.mbWidth - 1; in.checkNBit(23) != 0;) {
// TODO: decode skipped!!!
prevAddr = decodeMacroblock(ph, context, prevAddr, ctx, buf, stride, in, vertOff, vertStep, pred);
context.mbNo++;
}
}
private void resetDCPredictors(Context context, PictureHeader ph) {
int rval = 1 << 7;
if (ph.pictureCodingExtension != null)
rval = 1 << (7 + ph.pictureCodingExtension.intra_dc_precision);
context.intra_dc_predictor[0] = context.intra_dc_predictor[1] = context.intra_dc_predictor[2] = rval;
}
public int decodeMacroblock(PictureHeader ph, Context context, int prevAddr, int[] qScaleCode, int[][] buf,
int stride, BitReader bits, int vertOff, int vertStep, MPEGPred pred) {
int mbAddr = prevAddr;
while (bits.checkNBit(11) == 0x8) {
bits.skip(11);
mbAddr += 33;
}
mbAddr += vlcAddressIncrement.readVLC(bits) + 1;
int chromaFormat = Chroma420;
if (sh.sequenceExtension != null)
chromaFormat = sh.sequenceExtension.chroma_format;
for (int i = prevAddr + 1; i < mbAddr; i++) {
int[][] predFwd = new int[][] { new int[256], new int[1 << (chromaFormat + 5)],
new int[1 << (chromaFormat + 5)] };
int mbX = i % context.mbWidth;
int mbY = i / context.mbWidth;
if (ph.picture_coding_type == PictureHeader.PredictiveCoded)
pred.reset();
mvZero(context, ph, pred, mbX, mbY, predFwd);
put(predFwd, buf, stride, chromaFormat, mbX, mbY, context.codedWidth, context.codedHeight >> vertStep,
vertOff, vertStep);
}
VLC vlcMBType = vlcMBType(ph.picture_coding_type, sh.sequenceScalableExtension);
MBType[] mbTypeVal = mbTypeVal(ph.picture_coding_type, sh.sequenceScalableExtension);
MBType mbType = mbTypeVal[vlcMBType.readVLC(bits)];
if (mbType.macroblock_intra != 1 || (mbAddr - prevAddr) > 1) {
resetDCPredictors(context, ph);
}
int spatial_temporal_weight_code = 0;
if (mbType.spatial_temporal_weight_code_flag == 1 && ph.pictureSpatialScalableExtension != null
&& ph.pictureSpatialScalableExtension.spatial_temporal_weight_code_table_index != 0) {
spatial_temporal_weight_code = bits.readNBit(2);
}
int motion_type = -1;
if (mbType.macroblock_motion_forward != 0 || mbType.macroblock_motion_backward != 0) {
if (ph.pictureCodingExtension == null || ph.pictureCodingExtension.picture_structure == Frame
&& ph.pictureCodingExtension.frame_pred_frame_dct == 1)
motion_type = 2;
else
motion_type = bits.readNBit(2);
}
int dctType = 0;
if (ph.pictureCodingExtension != null && ph.pictureCodingExtension.picture_structure == Frame
&& ph.pictureCodingExtension.frame_pred_frame_dct == 0
&& (mbType.macroblock_intra != 0 || mbType.macroblock_pattern != 0)) {
dctType = bits.read1Bit();
}
// buf[3][mbAddr] = dctType;
if (mbType.macroblock_quant != 0) {
qScaleCode[0] = bits.readNBit(5);
}
boolean concealmentMv = ph.pictureCodingExtension != null
&& ph.pictureCodingExtension.concealment_motion_vectors != 0;
int[][] predFwd = null;
int mbX = mbAddr % context.mbWidth;
int mbY = mbAddr / context.mbWidth;
if (mbType.macroblock_intra == 1) {
if (concealmentMv) {
// TODO read consealment vectors
} else
pred.reset();
} else if (mbType.macroblock_motion_forward != 0) {
int refIdx = ph.picture_coding_type == PictureHeader.PredictiveCoded ? 0 : 1;
predFwd = new int[][] { new int[256], new int[1 << (chromaFormat + 5)], new int[1 << (chromaFormat + 5)] };
if (ph.pictureCodingExtension == null || ph.pictureCodingExtension.picture_structure == Frame) {
pred.predictInFrame(refFrames[refIdx], mbX << 4, mbY << 4, predFwd, bits, motion_type, 0,
spatial_temporal_weight_code);
} else {
if (ph.picture_coding_type == PictureHeader.PredictiveCoded) {
pred.predictInField(refFields, mbX << 4, mbY << 4, predFwd, bits, motion_type, 0,
ph.pictureCodingExtension.picture_structure - 1);
} else {
pred.predictInField(new Picture[] { refFrames[refIdx], refFrames[refIdx] }, mbX << 4, mbY << 4,
predFwd, bits, motion_type, 0, ph.pictureCodingExtension.picture_structure - 1);
}
}
} else if (ph.picture_coding_type == PictureHeader.PredictiveCoded) {
predFwd = new int[][] { new int[256], new int[1 << (chromaFormat + 5)], new int[1 << (chromaFormat + 5)] };
pred.reset();
mvZero(context, ph, pred, mbX, mbY, predFwd);
}
int[][] predBack = null;
if (mbType.macroblock_motion_backward != 0) {
predBack = new int[][] { new int[256], new int[1 << (chromaFormat + 5)], new int[1 << (chromaFormat + 5)] };
if (ph.pictureCodingExtension == null || ph.pictureCodingExtension.picture_structure == Frame) {
pred.predictInFrame(refFrames[0], mbX << 4, mbY << 4, predBack, bits, motion_type, 1,
spatial_temporal_weight_code);
} else {
pred.predictInField(new Picture[] { refFrames[0], refFrames[0] }, mbX << 4, mbY << 4, predBack, bits,
motion_type, 1, ph.pictureCodingExtension.picture_structure - 1);
}
}
context.lastPredB = mbType;
int[][] pp = mbType.macroblock_intra == 1 ? new int[][] { new int[256], new int[1 << (chromaFormat + 5)],
new int[1 << (chromaFormat + 5)] } : buildPred(predFwd, predBack);
if (mbType.macroblock_intra != 0 && concealmentMv)
assertEquals(1, bits.read1Bit()); // Marker
int cbp = mbType.macroblock_intra == 1 ? 0xfff : 0;
if (mbType.macroblock_pattern != 0) {
cbp = readCbPattern(bits);
}
VLC vlcCoeff = vlcCoeff0;
if (ph.pictureCodingExtension != null && mbType.macroblock_intra == 1
&& ph.pictureCodingExtension.intra_vlc_format == 1)
vlcCoeff = vlcCoeff1;
int[] qScaleTab = ph.pictureCodingExtension != null && ph.pictureCodingExtension.q_scale_type == 1 ? MPEGConst.qScaleTab2
: MPEGConst.qScaleTab1;
int qScale = qScaleTab[qScaleCode[0]];
int intra_dc_mult = 8;
if (ph.pictureCodingExtension != null)
intra_dc_mult = 8 >> ph.pictureCodingExtension.intra_dc_precision;
int blkCount = 6 + (chromaFormat == Chroma420 ? 0 : (chromaFormat == Chroma422 ? 2 : 6));
int[] block = new int[64];
for (int i = 0, cbpMask = 1 << (blkCount - 1); i < blkCount; i++, cbpMask >>= 1) {
if ((cbp & cbpMask) == 0)
continue;
int[] qmat = context.qMats[(i >= 4 ? 1 : 0) + (mbType.macroblock_intra << 1)];
if (mbType.macroblock_intra == 1)
blockIntra(bits, vlcCoeff, pp[BLOCK_TO_CC[i]], context.intra_dc_predictor, i, context.scan,
sh.hasExtensions() || ph.hasExtensions() ? 12 : 8, intra_dc_mult, qScale, qmat);
else
blockInter(bits, vlcCoeff, pp[BLOCK_TO_CC[i]], context.scan,
sh.hasExtensions() || ph.hasExtensions() ? 12 : 8, qScale, qmat);
mapBlock(block, pp[BLOCK_TO_CC[i]], i, dctType, chromaFormat);
}
put(pp, buf, stride, chromaFormat, mbX, mbY, context.codedWidth, context.codedHeight >> vertStep, vertOff,
vertStep);
return mbAddr;
}
private void mapBlock(int[] block, int[] out, int blkIdx, int dctType, int chromaFormat) {
int stepVert = chromaFormat == Chroma420 && (blkIdx == 4 || blkIdx == 5) ? 0 : dctType;
int log2stride = blkIdx < 4 ? 4 : 4 - SQUEEZE_X[chromaFormat];
int blkIdxExt = blkIdx + (dctType << 4);
int x = MPEGConst.BLOCK_POS_X[blkIdxExt];
int y = MPEGConst.BLOCK_POS_Y[blkIdxExt];
int off = (y << log2stride) + x, stride = 1 << (log2stride + stepVert);
for (int i = 0, coeff = 0; i < 8; i++, coeff += 8) {
out[off] += block[coeff];
out[off + 1] += block[coeff + 1];
out[off + 2] += block[coeff + 2];
out[off + 3] += block[coeff + 3];
out[off + 4] += block[coeff + 4];
out[off + 5] += block[coeff + 5];
out[off + 6] += block[coeff + 6];
out[off + 7] += block[coeff + 7];
off += stride;
}
}
private static final int[][] buildPred(int[][] predFwd, int[][] predBack) {
if (predFwd != null && predBack != null) {
avgPred(predFwd, predBack);
return predFwd;
} else if (predFwd != null)
return predFwd;
else if (predBack != null)
return predBack;
else
throw new RuntimeException("Omited pred in B-frames --> invalid");
}
private static final void avgPred(int[][] predFwd, int[][] predBack) {
for (int i = 0; i < predFwd.length; i++) {
for (int j = 0; j < predFwd[i].length; j += 4) {
predFwd[i][j] = (predFwd[i][j] + predBack[i][j] + 1) >> 1;
predFwd[i][j + 1] = (predFwd[i][j + 1] + predBack[i][j + 1] + 1) >> 1;
predFwd[i][j + 2] = (predFwd[i][j + 2] + predBack[i][j + 2] + 1) >> 1;
predFwd[i][j + 3] = (predFwd[i][j + 3] + predBack[i][j + 3] + 1) >> 1;
}
}
}
private void mvZero(Context context, PictureHeader ph, MPEGPred pred, int mbX, int mbY, int[][] mbPix) {
if (ph.picture_coding_type == PictureHeader.PredictiveCoded) {
pred.predict16x16NoMV(refFrames[0], mbX << 4, mbY << 4, ph.pictureCodingExtension == null ? Frame
: ph.pictureCodingExtension.picture_structure, 0, mbPix);
} else {
int[][] pp = mbPix;
if (context.lastPredB.macroblock_motion_backward == 1) {
pred.predict16x16NoMV(refFrames[0], mbX << 4, mbY << 4, ph.pictureCodingExtension == null ? Frame
: ph.pictureCodingExtension.picture_structure, 1, pp);
pp = new int[][] { new int[mbPix[0].length], new int[mbPix[1].length], new int[mbPix[2].length] };
}
if (context.lastPredB.macroblock_motion_forward == 1) {
pred.predict16x16NoMV(refFrames[1], mbX << 4, mbY << 4, ph.pictureCodingExtension == null ? Frame
: ph.pictureCodingExtension.picture_structure, 0, pp);
if (mbPix != pp)
avgPred(mbPix, pp);
}
}
}
protected void put(int[][] mbPix, int[][] buf, int stride, int chromaFormat, int mbX, int mbY, int width,
int height, int vertOff, int vertStep) {
int chromaStride = (stride + (1 << SQUEEZE_X[chromaFormat]) - 1) >> SQUEEZE_X[chromaFormat];
int chromaMBW = 4 - SQUEEZE_X[chromaFormat];
int chromaMBH = 4 - SQUEEZE_Y[chromaFormat];
putSub(buf[0], (mbY << 4) * (stride << vertStep) + vertOff * stride + (mbX << 4), stride << vertStep, mbPix[0],
4, 4);
putSub(buf[1], (mbY << chromaMBH) * (chromaStride << vertStep) + vertOff * chromaStride + (mbX << chromaMBW),
chromaStride << vertStep, mbPix[1], chromaMBW, chromaMBH);
putSub(buf[2], (mbY << chromaMBH) * (chromaStride << vertStep) + vertOff * chromaStride + (mbX << chromaMBW),
chromaStride << vertStep, mbPix[2], chromaMBW, chromaMBH);
}
private final void putSub(int[] big, int off, int stride, int[] block, int mbW, int mbH) {
int blOff = 0;
if (mbW == 3) {
for (int i = 0; i < (1 << mbH); i++) {
big[off] = clip(block[blOff]);
big[off + 1] = clip(block[blOff + 1]);
big[off + 2] = clip(block[blOff + 2]);
big[off + 3] = clip(block[blOff + 3]);
big[off + 4] = clip(block[blOff + 4]);
big[off + 5] = clip(block[blOff + 5]);
big[off + 6] = clip(block[blOff + 6]);
big[off + 7] = clip(block[blOff + 7]);
blOff += 8;
off += stride;
}
} else {
for (int i = 0; i < (1 << mbH); i++) {
big[off] = clip(block[blOff]);
big[off + 1] = clip(block[blOff + 1]);
big[off + 2] = clip(block[blOff + 2]);
big[off + 3] = clip(block[blOff + 3]);
big[off + 4] = clip(block[blOff + 4]);
big[off + 5] = clip(block[blOff + 5]);
big[off + 6] = clip(block[blOff + 6]);
big[off + 7] = clip(block[blOff + 7]);
big[off + 8] = clip(block[blOff + 8]);
big[off + 9] = clip(block[blOff + 9]);
big[off + 10] = clip(block[blOff + 10]);
big[off + 11] = clip(block[blOff + 11]);
big[off + 12] = clip(block[blOff + 12]);
big[off + 13] = clip(block[blOff + 13]);
big[off + 14] = clip(block[blOff + 14]);
big[off + 15] = clip(block[blOff + 15]);
blOff += 16;
off += stride;
}
}
}
private static final int clip(int val) {
return val < 0 ? 0 : (val > 255 ? 255 : val);
}
private static final int quantInter(int level, int quant) {
return (((level << 1) + 1) * quant) >> 5;
}
private static final int quantInterSigned(int level, int quant) {
return level >= 0 ? quantInter(level, quant) : -quantInter(-level, quant);
}
private final void blockIntra(BitReader bits, VLC vlcCoeff, int[] block, int[] intra_dc_predictor, int blkIdx,
int[] scan, int escSize, int intra_dc_mult, int qScale, int[] qmat) {
int cc = BLOCK_TO_CC[blkIdx];
int size = (cc == 0 ? vlcDCSizeLuma : vlcDCSizeChroma).readVLC(bits);
int delta = (size != 0) ? mpegSigned(bits, size) : 0;
intra_dc_predictor[cc] = intra_dc_predictor[cc] + delta;
int dc = intra_dc_predictor[cc] * intra_dc_mult;
SparseIDCT.start(block, dc);
for (int idx = 0; idx < maxCoeff;) {
int readVLC = vlcCoeff.readVLC(bits);
int level;
if (readVLC >= 0) {
idx += (readVLC >> 12) + 1;
level = toSigned(((readVLC & 0xfff) * qScale * qmat[idx]) >> 4, bits.read1Bit());
} else if (readVLC == -2) {
idx += bits.readNBit(6) + 1;
level = twosSigned(bits, escSize) * qScale * qmat[idx];
level = level >= 0 ? (level >> 4) : -(-level >> 4);
} else
break;
SparseIDCT.coeff(block, scan[idx], level);
}
SparseIDCT.finish(block);
}
private final void blockInter(BitReader bits, VLC vlcCoeff, int[] block, int[] scan, int escSize, int qScale,
int[] qmat) {
int idx = -1;
if (vlcCoeff == vlcCoeff0 && bits.checkNBit(1) == 1) {
bits.read1Bit();
int dc = toSigned(quantInter(1, qScale * qmat[0]), bits.read1Bit());
SparseIDCT.start(block, dc);
idx++;
} else {
SparseIDCT.start(block, 0);
}
for (; idx < maxCoeff;) {
int readVLC = vlcCoeff.readVLC(bits);
int ac;
if (readVLC >= 0) {
idx += (readVLC >> 12) + 1;
ac = toSigned(quantInter(readVLC & 0xfff, qScale * qmat[idx]), bits.read1Bit());
} else if (readVLC == -2) {
idx += bits.readNBit(6) + 1;
ac = quantInterSigned(twosSigned(bits, escSize), qScale * qmat[idx]);
} else
break;
SparseIDCT.coeff(block, scan[idx], ac);
}
SparseIDCT.finish(block);
}
private static final int twosSigned(BitReader bits, int size) {
int shift = 32 - size;
return (bits.readNBit(size) << shift) >> shift;
}
private static final int mpegSigned(BitReader bits, int size) {
int val = bits.readNBit(size);
int sign = (val >>> (size - 1)) ^ 0x1;
return val + sign - (sign << size);
}
private static final int toSigned(int val, int s) {
int sign = (s << 31) >> 31;
return (val ^ sign) - sign;
}
private final int readCbPattern(BitReader bits) {
int cbp420 = vlcCBP.readVLC(bits);
if (sh.sequenceExtension == null || sh.sequenceExtension.chroma_format == SequenceExtension.Chroma420)
return cbp420;
else if (sh.sequenceExtension.chroma_format == SequenceExtension.Chroma422)
return (cbp420 << 2) | bits.readNBit(2);
else if (sh.sequenceExtension.chroma_format == SequenceExtension.Chroma444)
return (cbp420 << 6) | bits.readNBit(6);
throw new RuntimeException("Unsupported chroma format: " + sh.sequenceExtension.chroma_format);
}
@Override
public int probe(ByteBuffer data) {
data = data.duplicate();
data.order(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < 2; i++) {
if (gotoNextMarker(data) == null)
break;
if (!data.hasRemaining())
break;
int marker = data.getInt();
if (marker == 0x100 || (marker >= 0x1b0 && marker <= 0x1b8))
return 50 - i * 10;
else if (marker > 0x100 && marker < 0x1b0)
return 20 - i * 10;
}
return 0;
}
public static Size getSize(ByteBuffer data) {
SequenceHeader sh = getSequenceHeader(data.duplicate());
return new Size(sh.horizontal_size, sh.vertical_size);
}
private static SequenceHeader getSequenceHeader(ByteBuffer data) {
ByteBuffer segment = nextSegment(data);
while (segment != null) {
int marker = segment.getInt();
if (marker == (0x100 | SEQUENCE_HEADER_CODE)) {
return SequenceHeader.read(segment);
}
segment = nextSegment(data);
}
return null;
}
}