package org.jcodec.codecs.mjpeg;
import static org.jcodec.codecs.mjpeg.JpegConst.naturalOrder;
import org.jcodec.api.UnhandledStateException;
import org.jcodec.codecs.mjpeg.tools.Asserts;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.VideoDecoder;
import org.jcodec.common.dct.SimpleIDCT10Bit;
import org.jcodec.common.io.BitReader;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.VLC;
import org.jcodec.common.io.VLCBuilder;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture8Bit;
import org.jcodec.common.model.Rect;
import org.jcodec.common.model.Size;
import org.jcodec.common.tools.MathUtil;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public class JpegDecoder extends VideoDecoder {
private boolean interlace;
private boolean topFieldFirst;
int[] buf;
public JpegDecoder() {
this.buf = new int[64];
}
public static JpegDecoder createJpegDecoder(int downscale) {
if (downscale == 2) {
return new JpegToThumb4x4();
} else if (downscale == 4) {
return new JpegToThumb2x2();
} else {
return new JpegDecoder();
}
}
public void setInterlace(boolean interlace, boolean topFieldFirst) {
this.interlace = interlace;
this.topFieldFirst = topFieldFirst;
}
private Picture8Bit decodeScan(ByteBuffer data, FrameHeader header, ScanHeader scan, VLC[] huffTables,
int[][] quant, byte[][] data2, int field, int step) {
int blockW = header.getHmax();
int blockH = header.getVmax();
int mcuW = blockW << 3;
int mcuH = blockH << 3;
int width = header.width;
int height = header.height;
int xBlocks = (width + mcuW - 1) >> (blockW + 2);
int yBlocks = (height + mcuH - 1) >> (blockH + 2);
int nn = blockW + blockH;
Picture8Bit result = new Picture8Bit(xBlocks << (blockW + 2), yBlocks << (blockH + 2), data2,
nn == 4 ? ColorSpace.YUV420J : (nn == 3 ? ColorSpace.YUV422J : ColorSpace.YUV444J),
new Rect(0, 0, width, height));
BitReader bits = BitReader.createBitReader(data);
int[] dcPredictor = new int[] { 1024, 1024, 1024 };
for (int by = 0; by < yBlocks; by++)
for (int bx = 0; bx < xBlocks && bits.moreData(); bx++)
decodeMCU(bits, dcPredictor, quant, huffTables, result, bx, by, blockW, blockH, field, step);
return result;
}
void putBlock(byte[] plane, int stride, int[] patch, int x, int y, int field, int step) {
int dstride = step * stride;
for (int i = 0, off = field * stride + y * dstride + x, poff = 0; i < 8; i++) {
for (int j = 0; j < 8; j++)
plane[j + off] = (byte) (MathUtil.clip(patch[j + poff], 0, 255) - 128);
off += dstride;
poff += 8;
}
}
void decodeMCU(BitReader bits, int[] dcPredictor, int[][] quant, VLC[] huff, Picture8Bit result, int bx, int by,
int blockH, int blockV, int field, int step) {
int sx = bx << (blockH - 1);
int sy = by << (blockV - 1);
for (int i = 0; i < blockV; i++)
for (int j = 0; j < blockH; j++) {
decodeBlock(bits, dcPredictor, quant, huff, result, buf, (sx + j) << 3, (sy + i) << 3, 0, 0, field,
step);
}
decodeBlock(bits, dcPredictor, quant, huff, result, buf, bx << 3, by << 3, 1, 1, field, step);
decodeBlock(bits, dcPredictor, quant, huff, result, buf, bx << 3, by << 3, 2, 1, field, step);
}
void decodeBlock(BitReader bits, int[] dcPredictor, int[][] quant, VLC[] huff, Picture8Bit result, int[] buf,
int blkX, int blkY, int plane, int chroma, int field, int step) {
Arrays.fill(buf, 0);
dcPredictor[plane] = buf[0] = readDCValue(bits, huff[chroma]) * quant[chroma][0] + dcPredictor[plane];
readACValues(bits, buf, huff[chroma + 2], quant[chroma]);
SimpleIDCT10Bit.idct10(buf, 0);
putBlock(result.getPlaneData(plane), result.getPlaneWidth(plane), buf, blkX, blkY, field, step);
}
int readDCValue(BitReader _in, VLC table) {
int code = table.readVLC16(_in);
return code != 0 ? toValue(_in.readNBit(code), code) : 0;
}
void readACValues(BitReader _in, int[] target, VLC table, int[] quantTable) {
int code;
int curOff = 1;
do {
code = table.readVLC16(_in);
if (code == 0xF0) {
curOff += 16;
} else if (code > 0) {
int rle = code >> 4;
curOff += rle;
int len = code & 0xf;
target[naturalOrder[curOff]] = toValue(_in.readNBit(len), len) * quantTable[curOff];
curOff++;
}
} while (code != 0 && curOff < 64);
}
public static final int toValue(int raw, int length) {
return (length >= 1 && raw < (1 << length - 1)) ? -(1 << length) + 1 + raw : raw;
}
public Picture8Bit decodeFrame8Bit(ByteBuffer data, byte[][] data2) {
if (interlace) {
Picture8Bit r1 = decodeField(data, data2, topFieldFirst ? 0 : 1, 2);
Picture8Bit r2 = decodeField(data, data2, topFieldFirst ? 1 : 0, 2);
return Picture8Bit.createPicture8Bit(r1.getWidth(), r1.getHeight() << 1, data2, r1.getColor());
} else {
return decodeField(data, data2, 0, 1);
}
}
public Picture8Bit decodeField(ByteBuffer data, byte[][] data2, int field, int step) {
Picture8Bit result = null;
FrameHeader header = null;
VLC[] huffTables = new VLC[] { JpegConst.YDC_DEFAULT, JpegConst.CDC_DEFAULT, JpegConst.YAC_DEFAULT,
JpegConst.CAC_DEFAULT };
int[][] quant = new int[][] { JpegConst.DEFAULT_QUANT_LUMA, JpegConst.DEFAULT_QUANT_CHROMA };
ScanHeader scan = null;
while (data.hasRemaining()) {
int marker = data.get() & 0xff;
if (marker == 0)
continue;
if (marker != 0xFF)
throw new RuntimeException(
"@" + Long.toHexString(data.position()) + " Marker expected: 0x" + Integer.toHexString(marker));
int b;
while ((b = data.get() & 0xff) == 0xff)
;
// Debug.trace("%s", JpegConst.toString(b));
if (b == JpegConst.SOF0) {
header = FrameHeader.read(data);
// Debug.trace(" %s", image.frame);
} else if (b == JpegConst.DHT) {
int len1 = data.getShort() & 0xffff;
ByteBuffer buf = NIOUtils.read(data, len1 - 2);
while (buf.hasRemaining()) {
int tableNo = buf.get() & 0xff;
huffTables[(tableNo & 1) | ((tableNo >> 3) & 2)] = readHuffmanTable(buf);
}
} else if (b == JpegConst.DQT) {
int len4 = data.getShort() & 0xffff;
ByteBuffer buf = NIOUtils.read(data, len4 - 2);
while (buf.hasRemaining()) {
int ind = buf.get() & 0xff;
quant[ind] = readQuantTable(buf);
}
} else if (b == JpegConst.SOS) {
if (scan != null) {
throw new UnhandledStateException("unhandled - more than one scan header");
}
scan = ScanHeader.read(data);
// Debug.trace(" %s", image.scan);
result = decodeScan(readToMarker(data), header, scan, huffTables, quant, data2, field, step);
} else if (b == JpegConst.SOI || (b >= JpegConst.RST0 && b <= JpegConst.RST7)) {
// Nothing
} else if (b == JpegConst.EOI) {
break;
} else if (b >= JpegConst.APP0 && b <= JpegConst.COM) {
int len3 = data.getShort() & 0xffff;
NIOUtils.read(data, len3 - 2);
} else if (b == JpegConst.DRI) {
int lr = data.getShort() & 0xffff;
int ri = data.getShort() & 0xffff;
// Debug.trace("DRI Lr: %d Ri: %d", lr, ri);
Asserts.assertEquals(0, ri);
} else {
throw new UnhandledStateException("unhandled marker " + JpegConst.markerToString(b));
}
}
return result;
}
private static ByteBuffer readToMarker(ByteBuffer data) {
ByteBuffer out = ByteBuffer.allocate(data.remaining());
while (data.hasRemaining()) {
byte b0 = data.get();
if (b0 == -1) {
byte b1 = data.get();
if (b1 == 0)
out.put((byte) -1);
else {
data.position(data.position() - 2);
break;
}
} else
out.put(b0);
}
out.flip();
return out;
}
private static VLC readHuffmanTable(ByteBuffer data) {
VLCBuilder builder = new VLCBuilder();
byte[] levelSizes = NIOUtils.toArray(NIOUtils.read(data, 16));
int levelStart = 0;
for (int i = 0; i < 16; i++) {
int length = levelSizes[i] & 0xff;
for (int c = 0; c < length; c++) {
int val = data.get() & 0xff;
int code = levelStart++;
builder.setInt(code, i + 1, val);
}
levelStart <<= 1;
}
return builder.getVLC();
}
private static int[] readQuantTable(ByteBuffer data) {
int[] result = new int[64];
for (int i = 0; i < 64; i++) {
result[i] = data.get() & 0xff;
}
return result;
}
@Override
public VideoCodecMeta getCodecMeta(ByteBuffer data) {
FrameHeader header = null;
while (data.hasRemaining()) {
while (data.hasRemaining() && (data.get() & 0xff) != 0xff)
continue;
int type;
while ((type = data.get() & 0xff) == 0xff)
;
if (type == JpegConst.SOF0) {
header = FrameHeader.read(data);
break;
}
}
return new VideoCodecMeta(new Size(header.width, header.height), ColorSpace.YUV420J);
}
}