package org.jcodec.codecs.h264;
import static java.util.Arrays.asList;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.jcodec.codecs.h264.decode.SliceHeaderReader;
import org.jcodec.codecs.h264.io.model.NALUnit;
import org.jcodec.codecs.h264.io.model.NALUnitType;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.io.model.SliceHeader;
import org.jcodec.codecs.h264.io.model.SliceType;
import org.jcodec.codecs.h264.io.write.SliceHeaderWriter;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.io.BitReader;
import org.jcodec.common.io.BitWriter;
import org.jcodec.common.io.FileChannelWrapper;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.Box.LeafBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * @author The JCodec project
 *
 */
public class H264Utils {

    public static ByteBuffer nextNALUnit(ByteBuffer buf) {
        skipToNALUnit(buf);

        if (buf.hasArray())
            return gotoNALUnitWithArray(buf);
        else
            return gotoNALUnit(buf);
    }

    public static final void skipToNALUnit(ByteBuffer buf) {
        if (!buf.hasRemaining())
            return;

        int val = 0xffffffff;
        while (buf.hasRemaining()) {
            val <<= 8;
            val |= (buf.get() & 0xff);
            if ((val & 0xffffff) == 1) {
                buf.position(buf.position());
                break;
            }
        }
    }

    /**
     * Finds next Nth H.264 bitstream NAL unit (0x00000001) and returns the data
     * that preceeds it as a ByteBuffer slice
     *
     * Segment byte order is always little endian
     *
     * TODO: emulation prevention
     *
     * @param buf
     * @return
     */
    public static final ByteBuffer gotoNALUnit(ByteBuffer buf) {

        if (!buf.hasRemaining())
            return null;

        int from = buf.position();
        ByteBuffer result = buf.slice();
        result.order(ByteOrder.BIG_ENDIAN);

        int val = 0xffffffff;
        while (buf.hasRemaining()) {
            val <<= 8;
            val |= (buf.get() & 0xff);
            if ((val & 0xffffff) == 1) {
                buf.position(buf.position() - (val == 1 ? 4 : 3));
                result.limit(buf.position() - from);
                break;
            }
        }
        return result;
    }

    /**
     * Finds next Nth H.264 bitstream NAL unit (0x00000001) and returns the data
     * that preceeds it as a ByteBuffer slice
     *
     * Segment byte order is always little endian
     *
     * @param buf
     * @return data
     */
    public static final ByteBuffer gotoNALUnitWithArray(ByteBuffer buf) {

        if (!buf.hasRemaining())
            return null;

        int from = buf.position();
        ByteBuffer result = buf.slice();
        result.order(ByteOrder.BIG_ENDIAN);

        byte[] arr = buf.array();
        int pos = from + buf.arrayOffset();
        int posFrom = pos;
        int lim = buf.limit() + buf.arrayOffset();

        while (pos < lim) {
            byte b = arr[pos];

            if ((b & 254) == 0) {
                while (b == 0 && ++pos < lim)
                    b = arr[pos];

                if (b == 1) {
                    if (pos - posFrom >= 2 && arr[pos - 1] == 0 && arr[pos - 2] == 0) {
                        int lenSize = (pos - posFrom >= 3 && arr[pos - 3] == 0) ? 4 : 3;

                        buf.position(pos + 1 - buf.arrayOffset() - lenSize);
                        result.limit(buf.position() - from);

                        return result;
                    }
                }
            }

            pos += 3;
        }

        buf.position(buf.limit());

        return result;
    }

    public static final void unescapeNAL(ByteBuffer _buf) {
        if (_buf.remaining() < 2)
            return;
        ByteBuffer _in = _buf.duplicate();
        ByteBuffer out = _buf.duplicate();
        byte p1 = _in.get();
        out.put(p1);
        byte p2 = _in.get();
        out.put(p2);
        while (_in.hasRemaining()) {
            byte b = _in.get();
            if (p1 != 0 || p2 != 0 || b != 3)
                out.put(b);
            p1 = p2;
            p2 = b;
        }
        _buf.limit(out.position());
    }

    public static final void escapeNALinplace(ByteBuffer src) {
        int[] loc = searchEscapeLocations(src);

        int old = src.limit();
        src.limit(src.limit() + loc.length);

        for (int newPos = src.limit() - 1, oldPos = old - 1, locIdx = loc.length - 1; newPos >= src
                .position(); newPos--, oldPos--) {
            src.put(newPos, src.get(oldPos));
            if (locIdx >= 0 && loc[locIdx] == oldPos) {
                newPos--;
                src.put(newPos, (byte) 3);
                locIdx--;
            }
        }
    }

    private static int[] searchEscapeLocations(ByteBuffer src) {
        IntArrayList points = IntArrayList.createIntArrayList();
        ByteBuffer search = src.duplicate();
        int p = search.getShort();
        while (search.hasRemaining()) {
            byte b = search.get();
            if (p == 0 && (b & ~3) == 0) {
                points.add(search.position() - 1);
                p = 3;
            }
            p = (p << 8) & 0xffff;
            p |= b & 0xff;
        }
        int[] array = points.toArray();
        return array;
    }

    public static final void escapeNAL(ByteBuffer src, ByteBuffer dst) {
        byte p1 = src.get(), p2 = src.get();
        dst.put(p1);
        dst.put(p2);
        while (src.hasRemaining()) {
            byte b = src.get();
            if (p1 == 0 && p2 == 0 && (b & 0xff) <= 3) {
                dst.put((byte) 3);
                p1 = p2;
                p2 = 3;
            }
            dst.put(b);
            p1 = p2;
            p2 = b;
        }
    }

    public static List<ByteBuffer> splitMOVPacket(ByteBuffer buf, AvcCBox avcC) {
        List<ByteBuffer> result = new ArrayList<ByteBuffer>();
        int nls = avcC.getNalLengthSize();
        ByteBuffer dup = buf.duplicate();
        while (dup.remaining() >= nls) {
            int len = readLen(dup, nls);
            if (len == 0)
                break;
            result.add(NIOUtils.read(dup, len));
        }
        return result;
    }

    private static int readLen(ByteBuffer dup, int nls) {
        switch (nls) {
        case 1:
            return dup.get() & 0xff;
        case 2:
            return dup.getShort() & 0xffff;
        case 3:
            return ((dup.getShort() & 0xffff) << 8) | (dup.get() & 0xff);
        case 4:
            return dup.getInt();
        default:
            throw new IllegalArgumentException("NAL Unit length size can not be " + nls);
        }
    }

    /**
     * Encodes AVC frame in ISO BMF format. Takes Annex B format.
     *
     * Scans the packet for each NAL Unit starting with 00 00 00 01 and replaces
     * this 4 byte sequence with 4 byte integer representing this NAL unit
     * length.
     *
     * @param avcFrame
     *            AVC frame encoded in Annex B NAL unit format
     */
    public static void encodeMOVPacketInplace(ByteBuffer avcFrame) {

        ByteBuffer dup = avcFrame.duplicate();
        ByteBuffer d1 = avcFrame.duplicate();

        for (int tot = d1.position();;) {
            ByteBuffer buf = H264Utils.nextNALUnit(dup);
            if (buf == null)
                break;
            d1.position(tot);
            d1.putInt(buf.remaining());
            tot += buf.remaining() + 4;
        }
    }

    /**
     * Encodes AVC frame in ISO BMF format. Takes Annex B format.
     *
     * Scans the packet for each NAL Unit starting with 00 00 00 01 and replaces
     * this 4 byte sequence with 4 byte integer representing this NAL unit
     * length.
     *
     * @param avcFrame
     *            AVC frame encoded in Annex B NAL unit format
     */
    public static ByteBuffer encodeMOVPacket(ByteBuffer avcFrame) {

        ByteBuffer dup = avcFrame.duplicate();

        List<ByteBuffer> list = new ArrayList<ByteBuffer>();
        ByteBuffer buf;
        int totalLen = 0;
        while ((buf = H264Utils.nextNALUnit(dup)) != null) {
            list.add(buf);
            totalLen += buf.remaining();
        }
        ByteBuffer result = ByteBuffer.allocate(list.size() * 4 + totalLen);
        for (ByteBuffer byteBuffer : list) {
            result.putInt(byteBuffer.remaining());
            result.put(byteBuffer);
        }
        result.flip();
        return result;
    }

    /**
     * Decodes AVC packet in ISO BMF format into Annex B format.
     *
     * Replaces NAL unit size integers with 00 00 00 01 start codes. If the
     * space allows the transformation is done inplace.
     *
     * @param result
     */
    public static ByteBuffer decodeMOVPacket(ByteBuffer result, AvcCBox avcC) {
        if (avcC.getNalLengthSize() == 4) {
            decodeMOVPacketInplace(result, avcC);
            return result;
        }
        return joinNALUnits(splitMOVPacket(result, avcC));
    }

    /**
     * Decodes AVC packet in ISO BMF format into Annex B format.
     *
     * Inplace replaces NAL unit size integers with 00 00 00 01 start codes.
     *
     * @param result
     */
    public static void decodeMOVPacketInplace(ByteBuffer result, AvcCBox avcC) {
        if (avcC.getNalLengthSize() != 4)
            throw new IllegalArgumentException("Can only inplace decode AVC MOV packet with nal_length_size = 4.");
        ByteBuffer dup = result.duplicate();
        while (dup.remaining() >= 4) {
            int size = dup.getInt();
            dup.position(dup.position() - 4);
            dup.putInt(1);
            dup.position(dup.position() + size);
        }
    }

    /**
     * Wipes AVC parameter sets ( SPS/PPS ) from the packet
     *
     * @param in
     *            AVC frame encoded in Annex B NAL unit format
     * @param out
     *            Buffer where packet without PS will be put
     * @param spsList
     *            Storage for leading SPS structures ( can be null, then all
     *            leading SPSs are discarded ).
     * @param ppsList
     *            Storage for leading PPS structures ( can be null, then all
     *            leading PPSs are discarded ).
     */
    public static void wipePS(ByteBuffer _in, ByteBuffer out, List<ByteBuffer> spsList, List<ByteBuffer> ppsList) {

        ByteBuffer dup = _in.duplicate();
        while (dup.hasRemaining()) {
            ByteBuffer buf = H264Utils.nextNALUnit(dup);
            if (buf == null)
                break;

            NALUnit nu = NALUnit.read(buf.duplicate());
            if (nu.type == NALUnitType.PPS) {
                if (ppsList != null)
                    ppsList.add(NIOUtils.duplicate(buf));
            } else if (nu.type == NALUnitType.SPS) {
                if (spsList != null)
                    spsList.add(NIOUtils.duplicate(buf));
            } else if (out != null) {
                out.putInt(1);
                out.put(buf);
            }
        }
        if (out != null)
            out.flip();
    }

    /**
     * Wipes AVC parameter sets ( SPS/PPS ) from the packet ( inplace operation
     * )
     *
     * @param in
     *            AVC frame encoded in Annex B NAL unit format
     * @param spsList
     *            Storage for leading SPS structures ( can be null, then all
     *            leading SPSs are discarded ).
     * @param ppsList
     *            Storage for leading PPS structures ( can be null, then all
     *            leading PPSs are discarded ).
     */
    public static void wipePSinplace(ByteBuffer _in, Collection<ByteBuffer> spsList, Collection<ByteBuffer> ppsList) {
        ByteBuffer dup = _in.duplicate();
        while (dup.hasRemaining()) {
            ByteBuffer buf = H264Utils.nextNALUnit(dup);
            if (buf == null)
                break;

            NALUnit nu = NALUnit.read(buf);
            if (nu.type == NALUnitType.PPS) {
                if (ppsList != null)
                    ppsList.add(NIOUtils.duplicate(buf));
                _in.position(dup.position());
            } else if (nu.type == NALUnitType.SPS) {
                if (spsList != null)
                    spsList.add(NIOUtils.duplicate(buf));
                _in.position(dup.position());
            } else if (nu.type == NALUnitType.IDR_SLICE || nu.type == NALUnitType.NON_IDR_SLICE)
                break;
        }
    }

    public static AvcCBox createAvcC(SeqParameterSet sps, PictureParameterSet pps, int nalLengthSize) {
        ByteBuffer serialSps = ByteBuffer.allocate(512);
        sps.write(serialSps);
        serialSps.flip();
        H264Utils.escapeNALinplace(serialSps);

        ByteBuffer serialPps = ByteBuffer.allocate(512);
        pps.write(serialPps);
        serialPps.flip();
        H264Utils.escapeNALinplace(serialPps);

        AvcCBox avcC = AvcCBox.createAvcCBox(sps.profileIdc, 0, sps.levelIdc, nalLengthSize, asList(serialSps),
                asList(serialPps));

        return avcC;
    }

    public static AvcCBox createAvcCFromList(List<SeqParameterSet> initSPS, List<PictureParameterSet> initPPS,
            int nalLengthSize) {
        List<ByteBuffer> serialSps = saveSPS(initSPS);
        List<ByteBuffer> serialPps = savePPS(initPPS);

        SeqParameterSet sps = initSPS.get(0);
        return AvcCBox.createAvcCBox(sps.profileIdc, 0, sps.levelIdc, nalLengthSize, serialSps, serialPps);
    }

    /**
     * @param initPPS
     * @return
     */
    public static List<ByteBuffer> savePPS(List<PictureParameterSet> initPPS) {
        List<ByteBuffer> serialPps = new ArrayList<ByteBuffer>();
        for (PictureParameterSet pps : initPPS) {
            ByteBuffer bb1 = ByteBuffer.allocate(512);
            pps.write(bb1);
            bb1.flip();
            H264Utils.escapeNALinplace(bb1);
            serialPps.add(bb1);
        }
        return serialPps;
    }

    /**
     * @param initSPS
     * @return
     */
    public static List<ByteBuffer> saveSPS(List<SeqParameterSet> initSPS) {
        List<ByteBuffer> serialSps = new ArrayList<ByteBuffer>();
        for (SeqParameterSet sps : initSPS) {
            ByteBuffer bb1 = ByteBuffer.allocate(512);
            sps.write(bb1);
            bb1.flip();
            H264Utils.escapeNALinplace(bb1);
            serialSps.add(bb1);
        }
        return serialSps;
    }

    /**
     * Creates a MP4 sample entry given AVC/H.264 codec private.
     *
     * @param codecPrivate
     *            Array containing AnnexB delimited (00 00 00 01) SPS/PPS NAL
     *            units.
     * @return MP4 sample entry
     */
    public static SampleEntry createMOVSampleEntryFromBytes(ByteBuffer codecPrivate) {
        List<ByteBuffer> rawSPS = getRawSPS(codecPrivate.duplicate());
        List<ByteBuffer> rawPPS = getRawPPS(codecPrivate.duplicate());
        return createMOVSampleEntryFromSpsPpsList(rawSPS, rawPPS, 4);
    }

    public static SampleEntry createMOVSampleEntryFromSpsPpsList(List<ByteBuffer> spsList, List<ByteBuffer> ppsList,
            int nalLengthSize) {
        AvcCBox avcC = createAvcCFromPS(spsList, ppsList, nalLengthSize);

        return createMOVSampleEntryFromAvcC(avcC);
    }

    /**
     * Creates a MP4 sample entry given AVC/H.264 codec private.
     *
     * @param codecPrivate
     *            Array containing AnnexB delimited (00 00 00 01) SPS/PPS NAL
     *            units.
     * @return MP4 sample entry
     */
    public static AvcCBox createAvcCFromBytes(ByteBuffer codecPrivate) {
        List<ByteBuffer> rawSPS = getRawSPS(codecPrivate.duplicate());
        List<ByteBuffer> rawPPS = getRawPPS(codecPrivate.duplicate());
        return createAvcCFromPS(rawSPS, rawPPS, 4);
    }

    public static AvcCBox createAvcCFromPS(List<ByteBuffer> spsList, List<ByteBuffer> ppsList, int nalLengthSize) {
        SeqParameterSet sps = readSPS(NIOUtils.duplicate(spsList.get(0)));
        return AvcCBox.createAvcCBox(sps.profileIdc, 0, sps.levelIdc, nalLengthSize, spsList, ppsList);
    }

    public static SampleEntry createMOVSampleEntryFromAvcC(AvcCBox avcC) {
        SeqParameterSet sps = SeqParameterSet.read(avcC.getSpsList().get(0).duplicate());
        int codedWidth = (sps.picWidthInMbsMinus1 + 1) << 4;
        int codedHeight = SeqParameterSet.getPicHeightInMbs(sps) << 4;

        SampleEntry se = VideoSampleEntry.videoSampleEntry("avc1", getPicSize(sps), "JCodec");
        se.add(avcC);
        return se;
    }

    public static SampleEntry createMOVSampleEntryFromSpsPps(SeqParameterSet initSPS, PictureParameterSet initPPS,
            int nalLengthSize) {
        ByteBuffer bb1 = ByteBuffer.allocate(512), bb2 = ByteBuffer.allocate(512);
        initSPS.write(bb1);
        initPPS.write(bb2);
        bb1.flip();
        bb2.flip();
        return createMOVSampleEntryFromBuffer(bb1, bb2, nalLengthSize);
    }

    public static SampleEntry createMOVSampleEntryFromBuffer(ByteBuffer sps, ByteBuffer pps, int nalLengthSize) {
        return createMOVSampleEntryFromSpsPpsList(Arrays.asList(new ByteBuffer[] { sps }),
                Arrays.asList(new ByteBuffer[] { pps }), nalLengthSize);
    }

    public static boolean iFrame(ByteBuffer _data) {
        ByteBuffer data = _data.duplicate();
        ByteBuffer segment;
        while ((segment = H264Utils.nextNALUnit(data)) != null) {
            NALUnitType type = NALUnit.read(segment).type;
            if (type == NALUnitType.IDR_SLICE || type == NALUnitType.NON_IDR_SLICE) {
                unescapeNAL(segment);
                BitReader reader = BitReader.createBitReader(segment);
                SliceHeader part1 = SliceHeaderReader.readPart1(reader);
                return part1.sliceType == SliceType.I;
            }
        }
        return false;
    }

    public static boolean isByteBufferIDRSlice(ByteBuffer _data) {
        ByteBuffer data = _data.duplicate();
        ByteBuffer segment;
        while ((segment = H264Utils.nextNALUnit(data)) != null) {
            if (NALUnit.read(segment).type == NALUnitType.IDR_SLICE)
                return true;
        }
        return false;
    }

    public static boolean idrSlice(List<ByteBuffer> _data) {
        for (ByteBuffer segment : _data) {
            if (NALUnit.read(segment.duplicate()).type == NALUnitType.IDR_SLICE)
                return true;
        }
        return false;
    }

    public static void saveRawFrame(ByteBuffer data, AvcCBox avcC, File f) throws IOException {
        SeekableByteChannel raw = NIOUtils.writableChannel(f);
        saveStreamParams(avcC, raw);
        raw.write(data.duplicate());
        raw.close();
    }

    public static void saveStreamParams(AvcCBox avcC, SeekableByteChannel raw) throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(1024);
        for (ByteBuffer byteBuffer : avcC.getSpsList()) {
            raw.write(ByteBuffer.wrap(new byte[] { 0, 0, 0, 1, 0x67 }));

            H264Utils.escapeNAL(byteBuffer.duplicate(), bb);
            bb.flip();
            raw.write(bb);
            bb.clear();
        }
        for (ByteBuffer byteBuffer : avcC.getPpsList()) {
            raw.write(ByteBuffer.wrap(new byte[] { 0, 0, 0, 1, 0x68 }));
            H264Utils.escapeNAL(byteBuffer.duplicate(), bb);
            bb.flip();
            raw.write(bb);
            bb.clear();
        }
    }

    public static List<ByteBuffer> splitFrame(ByteBuffer frame) {
        ArrayList<ByteBuffer> result = new ArrayList<ByteBuffer>();

        ByteBuffer segment;
        while ((segment = H264Utils.nextNALUnit(frame)) != null) {
            result.add(segment);
        }

        return result;
    }

    /**
     * Joins buffers containing individual NAL units into a single AnnexB
     * delimited buffer. Each NAL unit will be separated with 00 00 00 01
     * markers. Allocates a new byte buffer and writes data into it.
     *
     * @param nalUnits
     * @param out
     */
    public static ByteBuffer joinNALUnits(List<ByteBuffer> nalUnits) {
        int size = 0;
        for (ByteBuffer nal : nalUnits) {
            size += 4 + nal.remaining();
        }
        ByteBuffer allocate = ByteBuffer.allocate(size);
        joinNALUnitsToBuffer(nalUnits, allocate);
        return allocate;
    }

    /**
     * Joins buffers containing individual NAL units into a single AnnexB
     * delimited buffer. Each NAL unit will be separated with 00 00 00 01
     * markers.
     *
     * @param nalUnits
     * @param out
     */
    public static void joinNALUnitsToBuffer(List<ByteBuffer> nalUnits, ByteBuffer out) {
        for (ByteBuffer nal : nalUnits) {
            out.putInt(1);
            out.put(nal.duplicate());
        }
    }

    public static ByteBuffer getAvcCData(AvcCBox avcC) {
        ByteBuffer bb = ByteBuffer.allocate(2048);
        avcC.doWrite(bb);
        bb.flip();
        return bb;
    }

    public static AvcCBox parseAVCC(VideoSampleEntry vse) {
        Box lb = NodeBox.findFirst(vse, Box.class, "avcC");
        if (lb instanceof AvcCBox)
            return (AvcCBox) lb;
        else {
            return parseAVCCFromBuffer(((LeafBox) lb).getData().duplicate());
        }
    }

    public static ByteBuffer saveCodecPrivate(List<ByteBuffer> spsList, List<ByteBuffer> ppsList) {
        int totalCodecPrivateSize = 0;
        for (ByteBuffer byteBuffer : spsList) {
            totalCodecPrivateSize += byteBuffer.remaining() + 5;
        }
        for (ByteBuffer byteBuffer : ppsList) {
            totalCodecPrivateSize += byteBuffer.remaining() + 5;
        }

        ByteBuffer bb = ByteBuffer.allocate(totalCodecPrivateSize);
        for (ByteBuffer byteBuffer : spsList) {
            bb.putInt(1);
            bb.put((byte) 0x67);
            bb.put(byteBuffer.duplicate());
        }
        for (ByteBuffer byteBuffer : ppsList) {
            bb.putInt(1);
            bb.put((byte) 0x68);
            bb.put(byteBuffer.duplicate());
        }
        bb.flip();
        return bb;
    }

    public static ByteBuffer avcCToAnnexB(AvcCBox avcC) {
        return saveCodecPrivate(avcC.getSpsList(), avcC.getPpsList());
    }

    public static AvcCBox parseAVCCFromBuffer(ByteBuffer bb) {
        return AvcCBox.parseAvcCBox(bb);
    }

    public static ByteBuffer writeSPS(SeqParameterSet sps, int approxSize) {
        ByteBuffer output = ByteBuffer.allocate(approxSize + 8);
        sps.write(output);
        output.flip();
        H264Utils.escapeNALinplace(output);
        return output;
    }

    public static SeqParameterSet readSPS(ByteBuffer data) {
        ByteBuffer input = NIOUtils.duplicate(data);
        H264Utils.unescapeNAL(input);
        SeqParameterSet sps = SeqParameterSet.read(input);
        return sps;
    }

    public static ByteBuffer writePPS(PictureParameterSet pps, int approxSize) {
        ByteBuffer output = ByteBuffer.allocate(approxSize + 8);
        pps.write(output);
        output.flip();
        H264Utils.escapeNALinplace(output);
        return output;
    }

    public static PictureParameterSet readPPS(ByteBuffer data) {
        ByteBuffer input = NIOUtils.duplicate(data);
        H264Utils.unescapeNAL(input);
        PictureParameterSet pps = PictureParameterSet.read(input);
        return pps;
    }

    public static PictureParameterSet findPPS(List<PictureParameterSet> ppss, int id) {
        for (PictureParameterSet pps : ppss) {
            if (pps.picParameterSetId == id)
                return pps;
        }
        return null;
    }

    public static SeqParameterSet findSPS(List<SeqParameterSet> spss, int id) {
        for (SeqParameterSet sps : spss) {
            if (sps.seqParameterSetId == id)
                return sps;
        }
        return null;
    }

    public abstract static class SliceHeaderTweaker {

        protected List<SeqParameterSet> sps;
        protected List<PictureParameterSet> pps;

        protected abstract void tweak(SliceHeader sh);

        public SliceHeader run(ByteBuffer is, ByteBuffer os, NALUnit nu) {
            ByteBuffer nal = os.duplicate();

            H264Utils.unescapeNAL(is);

            BitReader reader = BitReader.createBitReader(is);
            SliceHeader sh = SliceHeaderReader.readPart1(reader);

            PictureParameterSet pp = findPPS(pps, sh.picParameterSetId);

            return part2(is, os, nu, findSPS(sps, pp.picParameterSetId), pp, nal, reader, sh);
        }

        public SliceHeader runSpsPps(ByteBuffer is, ByteBuffer os, NALUnit nu, SeqParameterSet sps,
                PictureParameterSet pps) {
            ByteBuffer nal = os.duplicate();

            H264Utils.unescapeNAL(is);

            BitReader reader = BitReader.createBitReader(is);
            SliceHeader sh = SliceHeaderReader.readPart1(reader);

            return part2(is, os, nu, sps, pps, nal, reader, sh);
        }

        private SliceHeader part2(ByteBuffer is, ByteBuffer os, NALUnit nu, SeqParameterSet sps,
                PictureParameterSet pps, ByteBuffer nal, BitReader reader, SliceHeader sh) {
            BitWriter writer = new BitWriter(os);
            SliceHeaderReader.readPart2(sh, nu, sps, pps, reader);

            tweak(sh);

            SliceHeaderWriter.write(sh, nu.type == NALUnitType.IDR_SLICE, nu.nal_ref_idc, writer);

            if (pps.entropyCodingModeFlag)
                copyDataCABAC(is, os, reader, writer);
            else
                copyDataCAVLC(is, os, reader, writer);

            nal.limit(os.position());

            H264Utils.escapeNALinplace(nal);

            os.position(nal.limit());

            return sh;
        }

        private void copyDataCAVLC(ByteBuffer is, ByteBuffer os, BitReader reader, BitWriter writer) {
            int wLeft = 8 - writer.curBit();
            if (wLeft != 0)
                writer.writeNBit(reader.readNBit(wLeft), wLeft);
            writer.flush();

            // Copy with shift
            int shift = reader.curBit();
            if (shift != 0) {
                int mShift = 8 - shift;
                int inp = reader.readNBit(mShift);
                reader.stop();

                while (is.hasRemaining()) {
                    int out = inp << shift;
                    inp = is.get() & 0xff;
                    out |= inp >> mShift;

                    os.put((byte) out);
                }
                os.put((byte) (inp << shift));
            } else {
                reader.stop();
                os.put(is);
            }
        }

        private void copyDataCABAC(ByteBuffer is, ByteBuffer os, BitReader reader, BitWriter writer) {
            long bp = reader.curBit();
            if (bp != 0) {
                long rem = reader.readNBit(8 - (int) bp);
                if ((1 << (8 - bp)) - 1 != rem)
                    throw new RuntimeException("Invalid CABAC padding");
            }

            if (writer.curBit() != 0)
                writer.writeNBit(0xff, 8 - writer.curBit());
            writer.flush();
            reader.stop();

            os.put(is);
        }
    }

    public static Size getPicSize(SeqParameterSet sps) {
        int w = (sps.picWidthInMbsMinus1 + 1) << 4;
        int h = SeqParameterSet.getPicHeightInMbs(sps) << 4;
        if (sps.frameCroppingFlag) {
            w -= (sps.frameCropLeftOffset + sps.frameCropRightOffset) << sps.chromaFormatIdc.compWidth[1];
            h -= (sps.frameCropTopOffset + sps.frameCropBottomOffset) << sps.chromaFormatIdc.compHeight[1];
        }
        return new Size(w, h);
    }

    public static List<SeqParameterSet> readSPSFromBufferList(List<ByteBuffer> spsList) {
        List<SeqParameterSet> result = new ArrayList<SeqParameterSet>();
        for (ByteBuffer byteBuffer : spsList) {
            result.add(readSPS(NIOUtils.duplicate(byteBuffer)));
        }
        return result;
    }

    public static List<PictureParameterSet> readPPSFromBufferList(List<ByteBuffer> ppsList) {
        List<PictureParameterSet> result = new ArrayList<PictureParameterSet>();
        for (ByteBuffer byteBuffer : ppsList) {
            result.add(readPPS(NIOUtils.duplicate(byteBuffer)));
        }
        return result;
    }

    public static List<ByteBuffer> writePPSList(List<PictureParameterSet> allPps) {
        List<ByteBuffer> result = new ArrayList<ByteBuffer>();
        for (PictureParameterSet pps : allPps) {
            result.add(writePPS(pps, 64));
        }
        return result;
    }

    public static List<ByteBuffer> writeSPSList(List<SeqParameterSet> allSps) {
        List<ByteBuffer> result = new ArrayList<ByteBuffer>();
        for (SeqParameterSet sps : allSps) {
            result.add(writeSPS(sps, 256));
        }
        return result;
    }

    public static void dumpFrame(FileChannelWrapper ch, SeqParameterSet[] values, PictureParameterSet[] values2,
            List<ByteBuffer> nalUnits) throws IOException {
        for (int i = 0; i < values.length; i++) {
            SeqParameterSet sps = values[i];
            NIOUtils.writeInt(ch, 1);
            NIOUtils.writeByte(ch, (byte) 0x67);
            ch.write(writeSPS(sps, 128));
        }

        for (int i = 0; i < values2.length; i++) {
            PictureParameterSet pps = values2[i];
            NIOUtils.writeInt(ch, 1);
            NIOUtils.writeByte(ch, (byte) 0x68);
            ch.write(writePPS(pps, 256));
        }

        for (ByteBuffer byteBuffer : nalUnits) {
            NIOUtils.writeInt(ch, 1);
            ch.write(byteBuffer.duplicate());
        }
    }

    public static void toNAL(ByteBuffer codecPrivate, SeqParameterSet sps, PictureParameterSet pps) {
        ByteBuffer bb1 = ByteBuffer.allocate(512), bb2 = ByteBuffer.allocate(512);
        sps.write(bb1);
        pps.write(bb2);
        bb1.flip();
        bb2.flip();

        putNAL(codecPrivate, bb1, 0x67);
        putNAL(codecPrivate, bb2, 0x68);
    }

    public static void toNALList(ByteBuffer codecPrivate, List<ByteBuffer> spsList2, List<ByteBuffer> ppsList2) {
        for (ByteBuffer byteBuffer : spsList2)
            putNAL(codecPrivate, byteBuffer, 0x67);
        for (ByteBuffer byteBuffer : ppsList2)
            putNAL(codecPrivate, byteBuffer, 0x68);
    }

    private static void putNAL(ByteBuffer codecPrivate, ByteBuffer byteBuffer, int nalType) {
        ByteBuffer dst = ByteBuffer.allocate(byteBuffer.remaining() * 2);
        escapeNAL(byteBuffer, dst);
        dst.flip();
        codecPrivate.putInt(1);
        codecPrivate.put((byte) nalType);
        codecPrivate.put(dst);
    }

    /**
     * Parses a list of SPS NAL units out of the codec private array.
     *
     * @param codecPrivate
     *            An AnnexB formatted set of SPS/PPS NAL units.
     * @return A list of ByteBuffers containing PPS NAL units.
     */
    public static List<ByteBuffer> getRawPPS(ByteBuffer codecPrivate) {
        return getRawNALUnitsOfType(codecPrivate, NALUnitType.PPS);
    }

    /**
     * Parses a list of SPS NAL units out of the codec private array.
     *
     * @param codecPrivate
     *            An AnnexB formatted set of SPS/PPS NAL units.
     * @return A list of ByteBuffers containing SPS NAL units.
     */
    public static List<ByteBuffer> getRawSPS(ByteBuffer codecPrivate) {
        return getRawNALUnitsOfType(codecPrivate, NALUnitType.SPS);
    }

    public static List<ByteBuffer> getRawNALUnitsOfType(ByteBuffer codecPrivate, NALUnitType type) {
        List<ByteBuffer> result = new ArrayList<ByteBuffer>();
        for (ByteBuffer bb : splitFrame(codecPrivate.duplicate())) {
            NALUnit nu = NALUnit.read(bb);
            if (nu.type == type) {
                result.add(bb);
            }
        }
        return result;
    }

    /**
     * A collection of functions to work with a compact representation of a motion vector.
     *
     * Motion vector is represented as long:
     *
     * ||rrrrrr|vvvvvvvvvvvv|hhhhhhhhhhhhhh||
     *
     */
    public static class Mv {
        public static int mvX(int mv) {
            return (mv << 18) >> 18;
        }

        public static int mvY(int mv) {
            return ((mv << 6) >> 20);
        }

        public static int mvRef(int mv) {
            return (mv >> 26);
        }

        public static int packMv(int mvx, int mvy, int r) {
            return ((r & 0x3f) << 26) | ((mvy & 0xfff) << 14) | (mvx & 0x3fff);
        }

        public static int mvC(int mv, int comp) {
            return comp == 0 ? mvX(mv) : mvY(mv);
        }
    }

    /**
     * A collection of functions to work with a compact representation of a
     * motion vector list.
     *
     * Motion vector list contains interleaved pairs of forward and backward
     * motion vectors packed into integers.
     *
     */
    public static class MvList {
        private int[] list;
        private static final int NA = Mv.packMv(0, 0, -1);

        public MvList(int size) {
            list = new int[size << 1];
            clear();
        }

        public void clear() {
            for (int i = 0; i < list.length; i += 2) {
                list[i] = list[i + 1] = NA;
            }
        }

        public int mv0X(int off) {
            return Mv.mvX(list[off << 1]);
        }

        public int mv0Y(int off) {
            return Mv.mvY(list[off << 1]);
        }

        public int mv0R(int off) {
            return Mv.mvRef(list[off << 1]);
        }

        public int mv1X(int off) {
            return Mv.mvX(list[(off << 1) + 1]);
        }

        public int mv1Y(int off) {
            return Mv.mvY(list[(off << 1) + 1]);
        }

        public int mv1R(int off) {
            return Mv.mvRef(list[(off << 1) + 1]);
        }

        public int getMv(int off, int forward) {
            return list[(off << 1) + forward];
        }

        public void setMv(int off, int forward, int mv) {
            list[(off << 1) + forward] = mv;
        }

        public void setPair(int off, int mv0, int mv1) {
            list[(off << 1)] = mv0;
            list[(off << 1) + 1] = mv1;
        }

        public void copyPair(int off, MvList other, int otherOff) {
            list[(off << 1)] = other.list[otherOff << 1];
            list[(off << 1) + 1] = other.list[(otherOff << 1) + 1];
        }
    }

    public static class MvList2D {
        private int[] list;
        private int stride;
        private int width;
        private int height;
        private static final int NA = Mv.packMv(0, 0, -1);

        public MvList2D(int width, int height) {
            list = new int[(width << 1) * height];
            stride = width << 1;
            this.width = width;
            this.height = height;
            clear();
        }

        public void clear() {
            for (int i = 0; i < list.length; i += 2) {
                list[i] = list[i + 1] = NA;
            }
        }

        public int mv0X(int offX, int offY) {
            return Mv.mvX(list[(offX << 1) + stride * offY]);
        }

        public int mv0Y(int offX, int offY) {
            return Mv.mvY(list[(offX << 1) + stride * offY]);
        }

        public int mv0R(int offX, int offY) {
            return Mv.mvRef(list[(offX << 1) + stride * offY]);
        }

        public int mv1X(int offX, int offY) {
            return Mv.mvX(list[(offX << 1) + stride * offY + 1]);
        }

        public int mv1Y(int offX, int offY) {
            return Mv.mvY(list[(offX << 1) + stride * offY + 1]);
        }

        public int mv1R(int offX, int offY) {
            return Mv.mvRef(list[(offX << 1) + stride * offY + 1]);
        }

        public int getMv(int offX, int offY, int forward) {
            return list[(offX << 1) + stride * offY + forward];
        }

        public void setMv(int offX, int offY, int forward, int mv) {
            list[(offX << 1) + stride * offY + forward] = mv;
        }

        public int getHeight() {
            return height;
        }

        public int getWidth() {
            return width;
        }
    }
}


package org.jcodec.codecs.h264.decode;
import static java.lang.System.arraycopy;
import static org.jcodec.common.tools.MathUtil.clip;

import org.jcodec.common.model.Picture;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Interpolator that operates on block level
 *
 * @author The JCodec project
 *
 */
public class BlockInterpolator {

    private int[] tmp1;
    private int[] tmp2;
    private byte[] tmp3;
    private LumaInterpolator[] safe;
    private LumaInterpolator[] unsafe;

    public BlockInterpolator() {
        this.tmp1 = new int[1024];
        this.tmp2 = new int[1024];
        this.tmp3 = new byte[1024];
        this.safe = initSafe();
        this.unsafe = initUnsafe();
    }
    /**
     * Get block of ( possibly interpolated ) luma pixels
     */
    public void getBlockLuma(Picture pic, Picture out, int off, int x, int y, int w, int h) {
        int xInd = x & 0x3;
        int yInd = y & 0x3;

        int xFp = x >> 2;
        int yFp = y >> 2;
        if (xFp < 2 || yFp < 2 || xFp > pic.getWidth() - w - 5 || yFp > pic.getHeight() - h - 5) {
            unsafe[(yInd << 2) + xInd].getLuma(pic.getData()[0], pic.getWidth(), pic.getHeight(), out.getPlaneData(0),
                    off, out.getPlaneWidth(0), xFp, yFp, w, h);
        } else {
            safe[(yInd << 2) + xInd].getLuma(pic.getData()[0], pic.getWidth(), pic.getHeight(), out.getPlaneData(0),
                    off, out.getPlaneWidth(0), xFp, yFp, w, h);
        }
    }

    public static void getBlockChroma(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int xInd = x & 0x7;
        int yInd = y & 0x7;

        int xFull = x >> 3;
        int yFull = y >> 3;

        if (xFull < 0 || xFull > picW - blkW - 1 || yFull < 0 || yFull > picH - blkH - 1) {
            if (xInd == 0 && yInd == 0) {
                getChroma00Unsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, blkW, blkH);
            } else if (yInd == 0) {

                getChromaX0Unsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, blkW, blkH);

            } else if (xInd == 0) {
                getChroma0XUnsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, yInd, blkW, blkH);
            } else {
                getChromaXXUnsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, yInd, blkW, blkH);
            }
        } else {
            if (xInd == 0 && yInd == 0) {
                getChroma00(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, blkW, blkH);
            } else if (yInd == 0) {

                getChromaX0(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, blkW, blkH);

            } else if (xInd == 0) {
                getChroma0X(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, yInd, blkW, blkH);
            } else {
                getChromaXX(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, yInd, blkW, blkH);
            }
        }
    }

    /**
     * Fullpel (0, 0)
     */
    static void getLuma00(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            arraycopy(pic, off, blk, blkOff, blkW);
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Fullpel (0, 0) unsafe
     */
    static void getLuma00Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        for (int j = 0; j < blkH; j++) {
            int lineStart = clip(j + y, 0, maxH) * picW;

            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = pic[lineStart + clip(x + i, 0, maxW)];
            }
            blkOff += blkStride;
        }
    }

    /**
     * Halfpel (2,0) horizontal, int argument version
     */
    static void getLuma20NoRoundInt(int[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW,
            int blkH) {

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            int off1 = -2;
            for (int i = 0; i < blkW; i++) {
                int a = pic[off + off1] + pic[off + off1 + 5];
                int b = pic[off + off1 + 1] + pic[off + off1 + 4];
                int c = pic[off + off1 + 2] + pic[off + off1 + 3];
                blk[blkOff + i] = a + 5 * ((c << 2) - b);
                ++off1;
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Halfpel (2,0) horizontal
     */
    static void getLuma20NoRound(byte[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW,
            int blkH) {

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            int off1 = -2;
            for (int i = 0; i < blkW; i++) {
                int a = pic[off + off1] + pic[off + off1 + 5];
                int b = pic[off + off1 + 1] + pic[off + off1 + 4];
                int c = pic[off + off1 + 2] + pic[off + off1 + 3];
                blk[blkOff + i] = a + 5 * ((c << 2) - b);
                ++off1;
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    static void getLuma20(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            int off1 = -2;
            for (int i = 0; i < blkW; i++) {
                int a = pic[off + off1] + pic[off + off1 + 5];
                int b = pic[off + off1 + 1] + pic[off + off1 + 4];
                int c = pic[off + off1 + 2] + pic[off + off1 + 3];
                blk[blkOff + i] = (byte) clip((a + 5 * ((c << 2) - b) + 16) >> 5, -128, 127);
                ++off1;
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Halfpel (2, 0) horizontal unsafe
     */
    static void getLuma20UnsafeNoRound(byte[] pic, int picW, int picH, int[] blk, int blkOff, int blkStride, int x,
            int y, int blkW, int blkH) {
        int maxW = picW - 1;
        int maxH = picH - 1;

        for (int i = 0; i < blkW; i++) {
            int ipos_m2 = clip(x + i - 2, 0, maxW);
            int ipos_m1 = clip(x + i - 1, 0, maxW);
            int ipos = clip(x + i, 0, maxW);
            int ipos_p1 = clip(x + i + 1, 0, maxW);
            int ipos_p2 = clip(x + i + 2, 0, maxW);
            int ipos_p3 = clip(x + i + 3, 0, maxW);

            int boff = blkOff;
            for (int j = 0; j < blkH; j++) {
                int lineStart = clip(j + y, 0, maxH) * picW;

                int a = pic[lineStart + ipos_m2] + pic[lineStart + ipos_p3];
                int b = pic[lineStart + ipos_m1] + pic[lineStart + ipos_p2];
                int c = pic[lineStart + ipos] + pic[lineStart + ipos_p1];

                blk[boff + i] = a + 5 * ((c << 2) - b);

                boff += blkStride;
            }
        }
    }

    void getLuma20Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {

        getLuma20UnsafeNoRound(pic, picW, picH, tmp1, blkOff, blkStride, x, y, blkW, blkH);

        for (int i = 0; i < blkW; i++) {
            int boff = blkOff;
            for (int j = 0; j < blkH; j++) {
                blk[boff + i] = (byte) clip((tmp1[boff + i] + 16) >> 5, -128, 127);
                boff += blkStride;
            }
        }
    }

    /**
     * Halfpel (0, 2) vertical
     */
    static void getLuma02NoRoundInt(int[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW,
            int blkH) {
        int off = (y - 2) * picW + x, picWx2 = picW + picW, picWx3 = picWx2 + picW, picWx4 = picWx3 + picW, picWx5 = picWx4
                + picW;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int a = pic[off + i] + pic[off + i + picWx5];
                int b = pic[off + i + picW] + pic[off + i + picWx4];
                int c = pic[off + i + picWx2] + pic[off + i + picWx3];

                blk[blkOff + i] = a + 5 * ((c << 2) - b);
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Halfpel (0, 2) vertical
     */
    static void getLuma02NoRound(byte[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW,
            int blkH) {
        int off = (y - 2) * picW + x, picWx2 = picW + picW, picWx3 = picWx2 + picW, picWx4 = picWx3 + picW, picWx5 = picWx4
                + picW;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int a = pic[off + i] + pic[off + i + picWx5];
                int b = pic[off + i + picW] + pic[off + i + picWx4];
                int c = pic[off + i + picWx2] + pic[off + i + picWx3];

                blk[blkOff + i] = a + 5 * ((c << 2) - b);
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    static void getLuma02(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        int off = (y - 2) * picW + x, picWx2 = picW + picW, picWx3 = picWx2 + picW, picWx4 = picWx3 + picW, picWx5 = picWx4
                + picW;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int a = pic[off + i] + pic[off + i + picWx5];
                int b = pic[off + i + picW] + pic[off + i + picWx4];
                int c = pic[off + i + picWx2] + pic[off + i + picWx3];
                blk[blkOff + i] = (byte) clip((a + 5 * ((c << 2) - b) + 16) >> 5, -128, 127);
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Hpel (0, 2) vertical unsafe
     */
    static void getLuma02UnsafeNoRound(byte[] pic, int picW, int picH, int[] blk, int blkOff, int blkStride, int x,
            int y, int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        for (int j = 0; j < blkH; j++) {
            int offP0 = clip(y + j - 2, 0, maxH) * picW;
            int offP1 = clip(y + j - 1, 0, maxH) * picW;
            int offP2 = clip(y + j, 0, maxH) * picW;
            int offP3 = clip(y + j + 1, 0, maxH) * picW;
            int offP4 = clip(y + j + 2, 0, maxH) * picW;
            int offP5 = clip(y + j + 3, 0, maxH) * picW;

            for (int i = 0; i < blkW; i++) {
                int pres_x = clip(x + i, 0, maxW);

                int a = pic[pres_x + offP0] + pic[pres_x + offP5];
                int b = pic[pres_x + offP1] + pic[pres_x + offP4];
                int c = pic[pres_x + offP2] + pic[pres_x + offP3];

                blk[blkOff + i] = a + 5 * ((c << 2) - b);
            }
            blkOff += blkStride;
        }
    }

    void getLuma02Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {

        getLuma02UnsafeNoRound(pic, picW, picH, tmp1, blkOff, blkStride, x, y, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) clip((tmp1[blkOff + i] + 16) >> 5, -128, 127);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Qpel: (1,0) horizontal
     */
    static void getLuma10(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        getLuma20(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH);

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {

            for (int i = 0; i < blkW; i++) {

                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[off + i] + 1) >> 1);
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Qpel: (1,0) horizontal unsafe
     */
    void getLuma10Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        getLuma20Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            int lineStart = clip(j + y, 0, maxH) * picW;

            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i, 0, maxW)] + 1) >> 1);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Qpel (3,0) horizontal
     */
    static void getLuma30(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        getLuma20(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH);

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((pic[off + i + 1] + blk[blkOff + i] + 1) >> 1);
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Qpel horizontal (3, 0) unsafe
     */
    void getLuma30Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        getLuma20Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            int lineStart = clip(j + y, 0, maxH) * picW;

            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i + 1, 0, maxW)] + 1) >> 1);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Qpel vertical (0, 1)
     */
    static void getLuma01(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        getLuma02(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH);

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[off + i] + 1) >> 1);
            }
            off += picW;
            blkOff += blkStride;
        }
    }

    /**
     * Qpel vertical (0, 1) unsafe
     */
    void getLuma01Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        getLuma02Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            int lineStart = clip(y + j, 0, maxH) * picW;
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i, 0, maxW)] + 1) >> 1);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Qpel vertical (0, 3)
     */
    static void getLuma03(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        getLuma02(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH);

        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[off + i + picW] + 1) >> 1);
            }
            off += picW;
            blkOff += blkStride;

        }
    }

    /**
     * Qpel vertical (0, 3) unsafe
     */
    void getLuma03Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        getLuma02Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            int lineStart = clip(y + j + 1, 0, maxH) * picW;
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i, 0, maxW)] + 1) >> 1);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Hpel horizontal, Qpel vertical (2, 1)
     *
     */
    void getLuma21(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        getLuma20NoRound(pic, picW, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7);
        getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH);

        int off = blkW << 1;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += blkW;
        }
    }

    /**
     * Qpel vertical (2, 1) unsafe
     */
    void getLuma21Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20UnsafeNoRound(pic, picW, imgH, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7);
        getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH);

        int off = blkW << 1;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += blkW;
        }
    }

    /**
     * Hpel horizontal, Hpel vertical (2, 2)
     */
    void getLuma22(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        getLuma20NoRound(pic, picW, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7);
        getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) (clip((tmp2[blkOff + i] + 512) >> 10, -128, 127));
            }
            blkOff += blkStride;
        }
    }

    /**
     * Hpel (2, 2) unsafe
     */
    void getLuma22Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20UnsafeNoRound(pic, picW, imgH, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7);
        getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH);

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Hpel horizontal, Qpel vertical (2, 3)
     *
     */
    void getLuma23(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        getLuma20NoRound(pic, picW, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7);
        getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH);

        int off = blkW << 1;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i + blkW] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += blkW;
        }
    }

    /**
     * Qpel (2, 3) unsafe
     */
    void getLuma23Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20UnsafeNoRound(pic, picW, imgH, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7);
        getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH);

        int off = blkW << 1;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i + blkW] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += blkW;
        }
    }

    /**
     * Qpel horizontal, Hpel vertical (1, 2)
     */
    void getLuma12(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {

        int tmpW = blkW + 7;

        getLuma02NoRound(pic, picW, tmp1, 0, tmpW, x - 2, y, tmpW, blkH);
        getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH);

        int off = 2;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += tmpW;
        }
    }

    /**
     * Qpel (1, 2) unsafe
     */
    void getLuma12Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int tmpW = blkW + 7;

        getLuma02UnsafeNoRound(pic, picW, imgH, tmp1, 0, tmpW, x - 2, y, tmpW, blkH);
        getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH);

        int off = 2;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += tmpW;
        }
    }

    /**
     * Qpel horizontal, Hpel vertical (3, 2)
     */
    void getLuma32(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        int tmpW = blkW + 7;

        getLuma02NoRound(pic, picW, tmp1, 0, tmpW, x - 2, y, tmpW, blkH);
        getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH);

        int off = 2;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i + 1] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += tmpW;
        }
    }

    /**
     * Qpel (3, 2) unsafe
     */
    void getLuma32Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int tmpW = blkW + 7;

        getLuma02UnsafeNoRound(pic, picW, imgH, tmp1, 0, tmpW, x - 2, y, tmpW, blkH);
        getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH);

        int off = 2;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127);
                int rounded2 = clip((tmp1[off + i + 1] + 16) >> 5, -128, 127);
                blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1);
            }
            blkOff += blkStride;
            off += tmpW;
        }
    }

    /**
     * Qpel horizontal, Qpel vertical (3, 3)
     */
    void getLuma33(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        getLuma20(pic, picW, blk, blkOff, blkStride, x, y + 1, blkW, blkH);
        getLuma02(pic, picW, tmp3, 0, blkW, x + 1, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel (3, 3) unsafe
     */
    void getLuma33Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20Unsafe(pic, picW, imgH, blk, blkOff, blkStride, x, y + 1, blkW, blkH);
        getLuma02Unsafe(pic, picW, imgH, tmp3, 0, blkW, x + 1, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel horizontal, Qpel vertical (1, 1)
     */
    void getLuma11(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        getLuma20(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        getLuma02(pic, picW, tmp3, 0, blkW, x, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel (1, 1) unsafe
     */
    void getLuma11Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20Unsafe(pic, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        getLuma02Unsafe(pic, picW, imgH, tmp3, 0, blkW, x, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel horizontal, Qpel vertical (1, 3)
     */
    void getLuma13(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) {
        getLuma20(pic, picW, blk, blkOff, blkStride, x, y + 1, blkW, blkH);
        getLuma02(pic, picW, tmp3, 0, blkW, x, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel (1, 3) unsafe
     */
    void getLuma13Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20Unsafe(pic, picW, imgH, blk, blkOff, blkStride, x, y + 1, blkW, blkH);
        getLuma02Unsafe(pic, picW, imgH, tmp3, 0, blkW, x, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel horizontal, Qpel vertical (3, 1)
     */
    void getLuma31(byte[] pels, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW,
            int blkH) {
        getLuma20(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        getLuma02(pels, picW, tmp3, 0, blkW, x + 1, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    /**
     * Qpel (3, 1) unsafe
     */
    void getLuma31Unsafe(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        getLuma20Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        getLuma02Unsafe(pels, picW, imgH, tmp3, 0, blkW, x + 1, y, blkW, blkH);

        merge(blk, tmp3, blkOff, blkStride, blkW, blkH);
    }

    private static void merge(byte[] first, byte[] second, int blkOff, int blkStride, int blkW, int blkH) {
        int tOff = 0;
        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                first[blkOff + i] = (byte) ((first[blkOff + i] + second[tOff + i] + 1) >> 1);
            }
            blkOff += blkStride;
            tOff += blkW;
        }
    }

    /**
     * Chroma (0,0)
     */
    private static void getChroma00(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int off = y * picW + x;
        for (int j = 0; j < blkH; j++) {
            arraycopy(pic, off, blk, blkOff, blkW);
            off += picW;
            blkOff += blkStride;
        }
    }

    private static void getChroma00Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y,
            int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        for (int j = 0; j < blkH; j++) {
            int lineStart = clip(j + y, 0, maxH) * picW;

            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = pic[lineStart + clip(x + i, 0, maxW)];
            }
            blkOff += blkStride;
        }
    }

    /**
     * Chroma (X,0)
     */
    private static void getChroma0X(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX,
            int fullY, int fracY, int blkW, int blkH) {
        int w00 = fullY * picW + fullX;
        int w01 = w00 + (fullY < picH - 1 ? picW : 0);
        int eMy = 8 - fracY;

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {

                blk[blkOff + i] = (byte) ((eMy * pels[w00 + i] + fracY * pels[w01 + i] + 4) >> 3);
            }
            w00 += picW;
            w01 += picW;
            blkOff += blkStride;
        }
    }

    private static void getChroma0XUnsafe(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX,
            int fullY, int fracY, int blkW, int blkH) {

        int maxW = picW - 1;
        int maxH = picH - 1;
        int eMy = 8 - fracY;

        for (int j = 0; j < blkH; j++) {
            int off00 = clip(fullY + j, 0, maxH) * picW;
            int off01 = clip(fullY + j + 1, 0, maxH) * picW;

            for (int i = 0; i < blkW; i++) {
                int w00 = clip(fullX + i, 0, maxW) + off00;
                int w01 = clip(fullX + i, 0, maxW) + off01;

                blk[blkOff + i] = (byte) ((eMy * pels[w00] + fracY * pels[w01] + 4) >> 3);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Chroma (X,0)
     */
    private static void getChromaX0(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int fullX,
            int fullY, int fracX, int blkW, int blkH) {
        int w00 = fullY * picW + fullX;
        int w10 = w00 + (fullX < picW - 1 ? 1 : 0);
        int eMx = 8 - fracX;

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                blk[blkOff + i] = (byte) ((eMx * pels[w00 + i] + fracX * pels[w10 + i] + 4) >> 3);
            }
            w00 += picW;
            w10 += picW;
            blkOff += blkStride;
        }
    }

    private static void getChromaX0Unsafe(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX,
            int fullY, int fracX, int blkW, int blkH) {
        int eMx = 8 - fracX;
        int maxW = picW - 1;
        int maxH = picH - 1;

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int w00 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i, 0, maxW);
                int w10 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i + 1, 0, maxW);

                blk[blkOff + i] = (byte) ((eMx * pels[w00] + fracX * pels[w10] + 4) >> 3);
            }
            blkOff += blkStride;
        }
    }

    /**
     * Chroma (X,X)
     */
    private static void getChromaXX(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX,
            int fullY, int fracX, int fracY, int blkW, int blkH) {
        int w00 = fullY * picW + fullX;
        int w01 = w00 + (fullY < picH - 1 ? picW : 0);
        int w10 = w00 + (fullX < picW - 1 ? 1 : 0);
        int w11 = w10 + w01 - w00;
        int eMx = 8 - fracX;
        int eMy = 8 - fracY;

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {

                blk[blkOff + i] = (byte) ((eMx * eMy * pels[w00 + i] + fracX * eMy * pels[w10 + i] + eMx * fracY
                        * pels[w01 + i] + fracX * fracY * pels[w11 + i] + 32) >> 6);
            }
            blkOff += blkStride;
            w00 += picW;
            w01 += picW;
            w10 += picW;
            w11 += picW;
        }
    }

    private static void getChromaXXUnsafe(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX,
            int fullY, int fracX, int fracY, int blkW, int blkH) {
        int maxH = picH - 1;
        int maxW = picW - 1;

        int eMx = 8 - fracX;
        int eMy = 8 - fracY;

        for (int j = 0; j < blkH; j++) {
            for (int i = 0; i < blkW; i++) {
                int w00 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i, 0, maxW);
                int w01 = clip(fullY + j + 1, 0, maxH) * picW + clip(fullX + i, 0, maxW);
                int w10 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i + 1, 0, maxW);
                int w11 = clip(fullY + j + 1, 0, maxH) * picW + clip(fullX + i + 1, 0, maxW);

                blk[blkOff + i] = (byte) ((eMx * eMy * pels[w00] + fracX * eMy * pels[w10] + eMx * fracY * pels[w01]
                        + fracX * fracY * pels[w11] + 32) >> 6);
            }
            blkOff += blkStride;
        }
    }

    private interface LumaInterpolator {
        void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW,
                int blkH);
    }

    private LumaInterpolator[] initSafe() {
    final BlockInterpolator self = this;
    return new LumaInterpolator[] {

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma00(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma10(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma20(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma30(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma01(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma11(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma21(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma31(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma02(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma12(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma22(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma32(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma03(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma13(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma23(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma33(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    } };
    }

    private LumaInterpolator[] initUnsafe() {
    final BlockInterpolator self = this;
    return new LumaInterpolator[] {

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            BlockInterpolator.getLuma00Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma10Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma20Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma30Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma01Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma11Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma21Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma31Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma02Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma12Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma22Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma32Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma03Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma13Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    },

    new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma23Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    }, new LumaInterpolator() {
        @Override
        public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y,
                int blkW, int blkH) {
            self.getLuma33Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH);
        }
    } };
    }
}


package org.jcodec.codecs.aac.blocks;
import org.jcodec.codecs.aac.ChannelPosition;
import org.jcodec.common.io.BitReader;

import java.io.IOException;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Decode program configuration element; reference: table 4.2.
 *
 * @author The JCodec project
 *
 */
public class BlockPCE extends Block {

    private static final int MAX_ELEM_ID = 16;

    public static class ChannelMapping {
        RawDataBlockType syn_ele;
        int someInt;
        ChannelPosition position;
    }

    public void parse(BitReader _in) {

        _in.readNBit(2); // object_type

        int samplingIndex = (int) _in.readNBit(4);

        int num_front = (int) _in.readNBit(4);
        int num_side = (int) _in.readNBit(4);
        int num_back = (int) _in.readNBit(4);
        int num_lfe = (int) _in.readNBit(2);
        int num_assoc_data = (int) _in.readNBit(3);
        int num_cc = (int) _in.readNBit(4);

        if (_in.read1Bit() != 0)
            _in.readNBit(4); // mono_mixdown_tag
        if (_in.read1Bit() != 0)
            _in.readNBit(4); // stereo_mixdown_tag

        if (_in.read1Bit() != 0)
            _in.readNBit(3); // mixdown_coeff_index and pseudo_surround

        // if (!_in.moreData(4 * (num_front + num_side + num_back + num_lfe +
        // num_assoc_data + num_cc))) {
        // throw new RuntimeException("Overread");
        // }
        ChannelMapping[] layout_map = new ChannelMapping[MAX_ELEM_ID * 4];

        int tags = 0;
        decodeChannelMap(layout_map, tags, ChannelPosition.AAC_CHANNEL_FRONT, _in, num_front);
        tags = num_front;
        decodeChannelMap(layout_map, tags, ChannelPosition.AAC_CHANNEL_SIDE, _in, num_side);
        tags += num_side;
        decodeChannelMap(layout_map, tags, ChannelPosition.AAC_CHANNEL_BACK, _in, num_back);
        tags += num_back;
        decodeChannelMap(layout_map, tags, ChannelPosition.AAC_CHANNEL_LFE, _in, num_lfe);
        tags += num_lfe;

        _in.skip(4 * num_assoc_data);

        decodeChannelMap(layout_map, tags, ChannelPosition.AAC_CHANNEL_CC, _in, num_cc);
        tags += num_cc;

        _in.align();

        /* comment field, first byte is length */
        int comment_len = (int) _in.readNBit(8) * 8;
        // if (!_in.moreData(comment_len)) {
        // throw new RuntimeException("Overread");
        // }
        _in.skip(comment_len);
    }

    /**
     * Decode an array of 4 bit element IDs, optionally interleaved with a
     * stereo/mono switching bit.
     *
     * @throws IOException
     */
    private void decodeChannelMap(ChannelMapping layout_map[], int offset, ChannelPosition type, BitReader _in, int n) {
        while (n-- > 0) {
            RawDataBlockType syn_ele = null;
            switch (type) {
            case AAC_CHANNEL_FRONT:
            case AAC_CHANNEL_BACK:
            case AAC_CHANNEL_SIDE:
                syn_ele = RawDataBlockType.values()[_in.read1Bit()];
                break;
            case AAC_CHANNEL_CC:
                _in.read1Bit();
                syn_ele = RawDataBlockType.TYPE_CCE;
                break;
            case AAC_CHANNEL_LFE:
                syn_ele = RawDataBlockType.TYPE_LFE;
                break;
            }
            layout_map[offset].syn_ele = syn_ele;
            layout_map[offset].someInt = (int) _in.readNBit(4);
            layout_map[offset].position = type;
            offset++;
        }
    }
}


package net.sourceforge.jaad.aac;

/**
 * This class is part of JAAD ( jaadec.sourceforge.net ) that is distributed
 * under the Public Domain license. Code changes provided by the JCodec project
 * are distributed under FreeBSD license.
 *
 * Different AAC profiles.
 * The function Decoder.canDecode specifies if the decoder can
 * handle a given format.
 * More precisely, the ISO standard calls these 'object types'.
 * @author in-somnia
 */
public final class Profile {

public final static Profile UNKNOWN = new Profile(-1, "unknown", false);
public final static Profile AAC_MAIN = new Profile(1, "AAC Main Profile", true);
public final static Profile AAC_LC = new Profile(2, "AAC Low Complexity", true);
public final static Profile AAC_SSR = new Profile(3, "AAC Scalable Sample Rate", false);
public final static Profile AAC_LTP = new Profile(4, "AAC Long Term Prediction", false);
public final static Profile AAC_SBR = new Profile(5, "AAC SBR", true);
public final static Profile AAC_SCALABLE = new Profile(6, "Scalable AAC", false);
public final static Profile TWIN_VQ = new Profile(7, "TwinVQ", false);
public final static Profile AAC_LD = new Profile(11, "AAC Low Delay", false);
public final static Profile ER_AAC_LC = new Profile(17, "Error Resilient AAC Low Complexity", true);
public final static Profile ER_AAC_SSR = new Profile(18, "Error Resilient AAC SSR", false);
public final static Profile ER_AAC_LTP = new Profile(19, "Error Resilient AAC Long Term Prediction", false);
public final static Profile ER_AAC_SCALABLE = new Profile(20, "Error Resilient Scalable AAC", false);
public final static Profile ER_TWIN_VQ = new Profile(21, "Error Resilient TwinVQ", false);
public final static Profile ER_BSAC = new Profile(22, "Error Resilient BSAC", false);
public final static Profile ER_AAC_LD = new Profile(23, "Error Resilient AAC Low Delay", false);

	private static final Profile[] ALL = {
		AAC_MAIN, AAC_LC, AAC_SSR, AAC_LTP, AAC_SBR, AAC_SCALABLE, TWIN_VQ,
		null, null, null, AAC_LD, null, null, null, null, null, ER_AAC_LC, ER_AAC_SSR,
		ER_AAC_LTP, ER_AAC_SCALABLE, ER_TWIN_VQ, ER_BSAC, ER_AAC_LD
	};

	/**
	 * Returns a profile instance for the given index. If the index is not
	 * between 1 and 23 inclusive, UNKNOWN is returned.
	 * @return a profile with the given index
	 */
	public static Profile forInt(int i) {
		Profile p;
		if(i<=0||i>ALL.length) p = UNKNOWN;
		else p = ALL[i-1];
		return p;
	}
	private final int num;
	private final String descr;
	private final boolean supported;

	private Profile(int num, String descr, boolean supported) {
		this.num = num;
		this.descr = descr;
		this.supported = supported;
	}

	/**
	 * Returns this profile's index between 1 and 23 or -1 if this is the
	 * UNKNOWN instance.
	 * @return the profile's index
	 */
	public int getIndex() {
		return num;
	}

	/**
	 * Returns a short description of this profile.
	 * @return the profile's description
	 */
	public String getDescription() {
		return descr;
	}

	/**
	 * Returns a string representation of this profile. The method is
	 * identical to getDescription().
	 * @return the profile's description
	 */
	@Override
	public String toString() {
		return descr;
	}

	/**
	 * Returns a boolean, indicating if this profile can be decoded by the
	 * Decoder.
	 * @see Decoder#canDecode(net.sourceforge.jaad.aac.Profile)
	 * @return true if the profile is supported
	 */
	public boolean isDecodingSupported() {
		return supported;
	}

	/**
	 * Returns a boolean, indicating if this profile contains error resilient
	 * tools. That is, if it's index is higher than 16, since the first error
	 * resilient profile is ER_AAC_LC (17).
	 * This method is mainly used internally.
	 * @return true if the profile uses error resilience
	 */
	public boolean isErrorResilientProfile() {
		return num>16;
	}
}


package org.jcodec.scale;

import org.jcodec.common.Ints;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.common.tools.MathUtil;

/**
 * Resamples image interpolating points using Lanczos sinc over sine windowed
 * filter.
 *
 * @author Stanislav Vitvitskiy
 */
public abstract class BaseResampler {
    private final ThreadLocal<int[]> tempBuffers;
    private Size toSize;
    private Size fromSize;
    private final double scaleFactorX;
    private final double scaleFactorY;

    public BaseResampler(Size from, Size to) {
        this.toSize = to;
        this.fromSize = from;
        scaleFactorX = (double) from.getWidth() / to.getWidth();
        scaleFactorY = (double) from.getHeight() / to.getHeight();
        tempBuffers = new ThreadLocal<int[]>();
    }

    private static byte getPel(Picture pic, int plane, int x, int y) {
        if (x < 0)
            x = 0;
        if (y < 0)
            y = 0;
        int w = pic.getPlaneWidth(plane);
        if (x > w - 1)
            x = w - 1;
        int h = pic.getPlaneHeight(plane);
        if (y > h - 1)
            y = h - 1;

        return pic.getData()[plane][x + y * w];
    }

    protected abstract short[] getTapsX(int dstX);

    protected abstract short[] getTapsY(int dstY);

    protected abstract int nTaps();

    /**
     * Converts floating point taps to fixed precision taps.
     *
     * @param taps
     *            The 64 bit double representation
     * @param precBits
     *            Precision bits
     * @param out
     *            Taps converted to fixed precision
     */
    public static void normalizeAndGenerateFixedPrecision(double[] taps, int precBits, short[] out) {
        double sum = 0;
        for (int i = 0; i < taps.length; i++) {
            sum += taps[i];
        }
        int sumFix = 0;
        int precNum = 1 << precBits;
        for (int i = 0; i < taps.length; i++) {
            double d = (taps[i] * precNum) / sum + precNum;
            int s = (int) d;
            taps[i] = d - s;
            out[i] = (short) (s - precNum);
            sumFix += out[i];
        }
        long tapsTaken = 0;
        while (sumFix < precNum) {
            int maxI = -1;
            for (int i = 0; i < taps.length; i++) {
                if ((tapsTaken & (1 << i)) == 0 && (maxI == -1 || taps[i] > taps[maxI]))
                    maxI = i;
            }
            out[maxI]++;
            sumFix++;
            tapsTaken |= (1 << maxI);
        }

        for (int i = 0; i < taps.length; i++) {
            taps[i] += out[i];
            if ((tapsTaken & (1 << i)) != 0)
                taps[i] -= 1;
        }
    }

    /**
     * Interpolates points using a 2d convolution
     */
    //Wrong usage of Javascript keyword:in
    public void resample(Picture src, Picture dst) {
        int[] temp = tempBuffers.get();
        int taps = nTaps();
        if (temp == null) {
            temp = new int[toSize.getWidth() * (fromSize.getHeight() + taps)];
            tempBuffers.set(temp);
        }
        for (int p = 0; p < src.getColor().nComp; p++) {
            // Horizontal pass
            for (int y = 0; y < src.getPlaneHeight(p) + taps; y++) {
                for (int x = 0; x < dst.getPlaneWidth(p); x++) {
                    short[] tapsXs = getTapsX(x);
                    int srcX = (int) (scaleFactorX * x) - taps / 2 + 1;

                    int sum = 0;
                    for (int i = 0; i < taps; i++) {
                        sum += (getPel(src, p, srcX + i, y - taps / 2 + 1) + 128) * tapsXs[i];
                    }
                    temp[y * toSize.getWidth() + x] = sum;
                }
            }

            // Vertical pass
            for (int y = 0; y < dst.getPlaneHeight(p); y++) {
                for (int x = 0; x < dst.getPlaneWidth(p); x++) {
                    short[] tapsYs = getTapsY(y);
                    int srcY = (int) (scaleFactorY * y);

                    int sum = 0;
                    for (int i = 0; i < taps; i++) {
                        sum += temp[x + (srcY + i) * toSize.getWidth()] * tapsYs[i];
                    }
                    dst.getPlaneData(p)[y * dst.getPlaneWidth(p) + x] = (byte) (MathUtil.clip((sum + 8192) >> 14, 0,
                            255) - 128);
                }
            }
        }
    }
}