package org.jcodec.codecs.prores; import static java.lang.Math.max; import static java.lang.Math.min; import static org.jcodec.codecs.prores.ProresDecoder.bitstream; import static org.jcodec.codecs.prores.ProresDecoder.clip; import static org.jcodec.codecs.prores.ProresDecoder.readACCoeffs; import static org.jcodec.codecs.prores.ProresDecoder.readDCCoeffs; import static org.jcodec.codecs.prores.ProresDecoder.scaleMat; import static org.jcodec.codecs.prores.ProresEncoder.writeACCoeffs; import static org.jcodec.codecs.prores.ProresEncoder.writeDCCoeffs; import org.jcodec.codecs.prores.ProresConsts.FrameHeader; import org.jcodec.codecs.prores.ProresConsts.PictureHeader; import org.jcodec.common.io.BitReader; import org.jcodec.common.io.BitWriter; import java.nio.ByteBuffer; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Turns a ProRes frame into ProRes proxy frame * * @author The JCodec project * */ public class ProresToProxy { private int[] qMatLumaTo; private int[] qMatChromaTo; private int frameSize; private static final int START_QP = 6; private int bitsPer1024; private int bitsPer1024High; private int bitsPer1024Low; private int nCoeffs; public ProresToProxy(int width, int height, int frameSize) { qMatLumaTo = ProresConsts.QMAT_LUMA_APCO; qMatChromaTo = ProresConsts.QMAT_CHROMA_APCO; this.frameSize = frameSize; int headerBytes = (height >> 4) * (((width >> 4) + 7) >> 3) * 8 + 148; int dataBits = (frameSize - headerBytes) << 3; bitsPer1024 = (dataBits << 10) / (width * height); bitsPer1024High = bitsPer1024 - bitsPer1024 / 10; bitsPer1024Low = bitsPer1024 - bitsPer1024 / 20; nCoeffs = max(min(33000 / (width * height >> 8), 64), 4); } public int getFrameSize() { return frameSize; } void requant(BitReader ib, BitWriter ob, int blocksPerSlice, int[] qMatFrom, int[] qMatTo, int[] scan, int mbX, int mbY, int plane) { int[] out = new int[blocksPerSlice << 6]; try { readDCCoeffs(ib, qMatFrom, out, blocksPerSlice, 64); readACCoeffs(ib, qMatFrom, out, blocksPerSlice, scan, nCoeffs, 6); } catch (RuntimeException e) { } for (int i = 0; i < out.length; i++) out[i] <<= 2; writeDCCoeffs(ob, qMatTo, out, blocksPerSlice); writeACCoeffs(ob, qMatTo, out, blocksPerSlice, scan, nCoeffs); ob.flush(); } public void transcode(ByteBuffer inBuf, ByteBuffer outBuf) { ByteBuffer fork = outBuf.duplicate(); FrameHeader fh = ProresDecoder.readFrameHeader(inBuf); ProresEncoder.writeFrameHeader(outBuf, fh); int beforePicture = outBuf.position(); if (fh.frameType == 0) { transcodePicture(inBuf, outBuf, fh); } else { transcodePicture(inBuf, outBuf, fh); transcodePicture(inBuf, outBuf, fh); } fh.qMatLuma = qMatLumaTo; fh.qMatChroma = qMatChromaTo; fh.payloadSize = outBuf.position() - beforePicture; ProresEncoder.writeFrameHeader(fork, fh); } private void transcodePicture(ByteBuffer inBuf, ByteBuffer outBuf, FrameHeader fh) { PictureHeader ph = ProresDecoder.readPictureHeader(inBuf); ProresEncoder.writePictureHeader(ph.log2SliceMbWidth, ph.sliceSizes.length, outBuf); ByteBuffer sliceSizes = outBuf.duplicate(); outBuf.position(outBuf.position() + (ph.sliceSizes.length << 1)); int mbX = 0, mbY = 0; int mbWidth = (fh.width + 15) >> 4; int sliceMbCount = 1 << ph.log2SliceMbWidth; int balance = 0, qp = START_QP; for (int i = 0; i < ph.sliceSizes.length; i++) { while (mbWidth - mbX < sliceMbCount) sliceMbCount >>= 1; int savedPoint = outBuf.position(); transcodeSlice(inBuf, outBuf, fh.qMatLuma, fh.qMatChroma, fh.scan, sliceMbCount, mbX, mbY, ph.sliceSizes[i], qp); short encodedSize = (short) (outBuf.position() - savedPoint); sliceSizes.putShort(encodedSize); int max = (sliceMbCount * bitsPer1024High >> 5) + 6; int low = (sliceMbCount * bitsPer1024Low >> 5) + 6; if (encodedSize > max && qp < 128) { qp++; if ((encodedSize > max + balance) && qp < 128) qp++; } else { if (encodedSize < low && qp > 2 && balance > 0) qp--; } balance += max - encodedSize; mbX += sliceMbCount; if (mbX == mbWidth) { sliceMbCount = 1 << ph.log2SliceMbWidth; mbX = 0; mbY++; } } } private void transcodeSlice(ByteBuffer inBuf, ByteBuffer outBuf, int[] qMatLuma, int[] qMatChroma, int[] scan, int sliceMbCount, int mbX, int mbY, short sliceSize, int qp) { int hdrSize = (inBuf.get() & 0xff) >> 3; int qScaleOrig = clip(inBuf.get() & 0xff, 1, 224); int qScale = qScaleOrig > 128 ? qScaleOrig - 96 << 2 : qScaleOrig; int yDataSize = inBuf.getShort(); int uDataSize = inBuf.getShort(); int vDataSize = sliceSize - uDataSize - yDataSize - hdrSize; outBuf.put((byte) (6 << 3)); // hdr size outBuf.put((byte) qp); // qscale ByteBuffer beforeSizes = outBuf.duplicate(); outBuf.putInt(0); int beforeY = outBuf.position(); requant(bitstream(inBuf, yDataSize), new BitWriter(outBuf), sliceMbCount << 2, scaleMat(qMatLuma, qScale), scaleMat(qMatLumaTo, qp), scan, mbX, mbY, 0); int beforeCb = outBuf.position(); requant(bitstream(inBuf, uDataSize), new BitWriter(outBuf), sliceMbCount << 1, scaleMat(qMatChroma, qScale), scaleMat(qMatChromaTo, qp), scan, mbX, mbY, 1); int beforeCr = outBuf.position(); requant(bitstream(inBuf, vDataSize), new BitWriter(outBuf), sliceMbCount << 1, scaleMat(qMatChroma, qScale), scaleMat(qMatChromaTo, qp), scan, mbX, mbY, 2); beforeSizes.putShort((short) (beforeCb - beforeY)); beforeSizes.putShort((short) (beforeCr - beforeCb)); } }