package net.sourceforge.jaad.aac.sbr;

import java.util.Arrays;
import net.sourceforge.jaad.aac.SampleFrequency;

/**
 * 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.
 *
 * @author in-somnia
 */
class FBT implements SBRConstants {

	/* calculate the start QMF channel for the master frequency band table */
	/* parameter is also called k0 */
	public static int qmf_start_channel(int bs_start_freq, int bs_samplerate_mode,
		SampleFrequency sample_rate) {

		int startMin = startMinTable[sample_rate.getIndex()];
		int offsetIndex = offsetIndexTable[sample_rate.getIndex()];

		if(bs_samplerate_mode!=0) {
			return startMin+OFFSET[offsetIndex][bs_start_freq];

		}
		else {
			return startMin+OFFSET[6][bs_start_freq];
		}
	}
	private static final int[] stopMinTable = {13, 15, 20, 21, 23,
		32, 32, 35, 48, 64, 70, 96};
	private static final int[][] STOP_OFFSET_TABLE = {
		{0, 2, 4, 6, 8, 11, 14, 18, 22, 26, 31, 37, 44, 51},
		{0, 2, 4, 6, 8, 11, 14, 18, 22, 26, 31, 36, 42, 49},
		{0, 2, 4, 6, 8, 11, 14, 17, 21, 25, 29, 34, 39, 44},
		{0, 2, 4, 6, 8, 11, 14, 17, 20, 24, 28, 33, 38, 43},
		{0, 2, 4, 6, 8, 11, 14, 17, 20, 24, 28, 32, 36, 41},
		{0, 2, 4, 6, 8, 10, 12, 14, 17, 20, 23, 26, 29, 32},
		{0, 2, 4, 6, 8, 10, 12, 14, 17, 20, 23, 26, 29, 32},
		{0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 20, 23, 26, 29},
		{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, -1, -2, -3, -4, -5, -6, -6, -6, -6, -6, -6, -6, -6},
		{0, -3, -6, -9, -12, -15, -18, -20, -22, -24, -26, -28, -30, -32}
	};
	/* calculate the stop QMF channel for the master frequency band table */
	/* parameter is also called k2 */

	public static int qmf_stop_channel(int bs_stop_freq, SampleFrequency sample_rate,
		int k0) {
		if(bs_stop_freq==15) {
			return Math.min(64, k0*3);
		}
		else if(bs_stop_freq==14) {
			return Math.min(64, k0*2);
		}
		else {

			int stopMin = stopMinTable[sample_rate.getIndex()];

			/* bs_stop_freq <= 13 */
			return Math.min(64, stopMin+STOP_OFFSET_TABLE[sample_rate.getIndex()][Math.min(bs_stop_freq, 13)]);
		}
	}

	/* calculate the master frequency table from k0, k2, bs_freq_scale
	 and bs_alter_scale

	 version for bs_freq_scale = 0
	 */
	public static int master_frequency_table_fs0(SBR sbr, int k0, int k2,
		boolean bs_alter_scale) {
		int incr;
		int k;
		int dk;
		int nrBands, k2Achieved;
		int k2Diff;
		int[] vDk = new int[64];

		/* mft only defined for k2 > k0 */
		if(k2<=k0) {
			sbr.N_master = 0;
			return 1;
		}

		dk = bs_alter_scale ? 2 : 1;

		if(bs_alter_scale) {
			nrBands = (((k2-k0+2)>>2)<<1);
		}
		else {
			nrBands = (((k2-k0)>>1)<<1);
		}
		nrBands = Math.min(nrBands, 63);
		if(nrBands<=0)
			return 1;

		k2Achieved = k0+nrBands*dk;
		k2Diff = k2-k2Achieved;
		for(k = 0; k<nrBands; k++) {
			vDk[k] = dk;
		}

		if(k2Diff!=0) {
			incr = (k2Diff>0) ? -1 : 1;
			k = ((k2Diff>0) ? (nrBands-1) : 0);

			while(k2Diff!=0) {
				vDk[k] -= incr;
				k += incr;
				k2Diff += incr;
			}
		}

		sbr.f_master[0] = k0;
		for(k = 1; k<=nrBands; k++) {
			sbr.f_master[k] = (sbr.f_master[k-1]+vDk[k-1]);
		}

		sbr.N_master = nrBands;
		sbr.N_master = Math.min(sbr.N_master, 64);

		return 0;
	}

	/*
	 This function finds the number of bands using this formula:
	 bands * log(a1/a0)/log(2.0) + 0.5
	 */
	public static int find_bands(int warp, int bands, int a0, int a1) {
		float div = (float) Math.log(2.0);
		if(warp!=0) div *= 1.3f;

		return (int) (bands*Math.log((float) a1/(float) a0)/div+0.5);
	}

	public static float find_initial_power(int bands, int a0, int a1) {
		return (float) Math.pow((float) a1/(float) a0, 1.0f/(float) bands);
	}

	/*
	 version for bs_freq_scale > 0
	 */
	public static int master_frequency_table(SBR sbr, int k0, int k2,
		int bs_freq_scale, boolean bs_alter_scale) {
		int k, bands;
		boolean twoRegions;
		int k1;
		int nrBand0, nrBand1;
		int[] vDk0 = new int[64], vDk1 = new int[64];
		int[] vk0 = new int[64], vk1 = new int[64];
		int[] temp1 = {6, 5, 4};
		float q, qk;
		int A_1;

		/* mft only defined for k2 > k0 */
		if(k2<=k0) {
			sbr.N_master = 0;
			return 1;
		}

		bands = temp1[bs_freq_scale-1];

		if((float) k2/(float) k0>2.2449) {
			twoRegions = true;
			k1 = k0<<1;
		}
		else {
			twoRegions = false;
			k1 = k2;
		}

		nrBand0 = (2*find_bands(0, bands, k0, k1));
		nrBand0 = Math.min(nrBand0, 63);
		if(nrBand0<=0)
			return 1;

		q = find_initial_power(nrBand0, k0, k1);
		qk = k0;
		A_1 = (int) (qk+0.5f);
		for(k = 0; k<=nrBand0; k++) {
			int A_0 = A_1;
			qk *= q;
			A_1 = (int) (qk+0.5f);
			vDk0[k] = A_1-A_0;
		}

		/* needed? */
		//qsort(vDk0, nrBand0, sizeof(vDk0[0]), longcmp);
		Arrays.sort(vDk0, 0, nrBand0);

		vk0[0] = k0;
		for(k = 1; k<=nrBand0; k++) {
			vk0[k] = vk0[k-1]+vDk0[k-1];
			if(vDk0[k-1]==0)
				return 1;
		}

		if(!twoRegions) {
			for(k = 0; k<=nrBand0; k++) {
				sbr.f_master[k] = vk0[k];
			}

			sbr.N_master = nrBand0;
			sbr.N_master = Math.min(sbr.N_master, 64);
			return 0;
		}

		nrBand1 = (2*find_bands(1 /* warped */, bands, k1, k2));
		nrBand1 = Math.min(nrBand1, 63);

		q = find_initial_power(nrBand1, k1, k2);
		qk = k1;
		A_1 = (int) (qk+0.5f);
		for(k = 0; k<=nrBand1-1; k++) {
			int A_0 = A_1;
			qk *= q;
			A_1 = (int) (qk+0.5f);
			vDk1[k] = A_1-A_0;
		}

		if(vDk1[0]<vDk0[nrBand0-1]) {
			int change;

			/* needed? */
			//qsort(vDk1, nrBand1+1, sizeof(vDk1[0]), longcmp);
			Arrays.sort(vDk1, 0, nrBand1+1);
			change = vDk0[nrBand0-1]-vDk1[0];
			vDk1[0] = vDk0[nrBand0-1];
			vDk1[nrBand1-1] = vDk1[nrBand1-1]-change;
		}

		/* needed? */
		//qsort(vDk1, nrBand1, sizeof(vDk1[0]), longcmp);
		Arrays.sort(vDk1, 0, nrBand1);
		vk1[0] = k1;
		for(k = 1; k<=nrBand1; k++) {
			vk1[k] = vk1[k-1]+vDk1[k-1];
			if(vDk1[k-1]==0)
				return 1;
		}

		sbr.N_master = nrBand0+nrBand1;
		sbr.N_master = Math.min(sbr.N_master, 64);
		for(k = 0; k<=nrBand0; k++) {
			sbr.f_master[k] = vk0[k];
		}
		for(k = nrBand0+1; k<=sbr.N_master; k++) {
			sbr.f_master[k] = vk1[k-nrBand0];
		}

		return 0;
	}

	/* calculate the derived frequency border tables from f_master */
	public static int derived_frequency_table(SBR sbr, int bs_xover_band,
		int k2) {
		int k, i = 0;
		int minus;

		/* The following relation shall be satisfied: bs_xover_band < N_Master */
		if(sbr.N_master<=bs_xover_band)
			return 1;

		sbr.N_high = sbr.N_master-bs_xover_band;
		sbr.N_low = (sbr.N_high>>1)+(sbr.N_high-((sbr.N_high>>1)<<1));

		sbr.n[0] = sbr.N_low;
		sbr.n[1] = sbr.N_high;

		for(k = 0; k<=sbr.N_high; k++) {
			sbr.f_table_res[HI_RES][k] = sbr.f_master[k+bs_xover_band];
		}

		sbr.M = sbr.f_table_res[HI_RES][sbr.N_high]-sbr.f_table_res[HI_RES][0];
		sbr.kx = sbr.f_table_res[HI_RES][0];
		if(sbr.kx>32)
			return 1;
		if(sbr.kx+sbr.M>64)
			return 1;

		minus = ((sbr.N_high&1)!=0) ? 1 : 0;

		for(k = 0; k<=sbr.N_low; k++) {
			if(k==0)
				i = 0;
			else
				i = (2*k-minus);
			sbr.f_table_res[LO_RES][k] = sbr.f_table_res[HI_RES][i];
		}

		sbr.N_Q = 0;
		if(sbr.bs_noise_bands==0) {
			sbr.N_Q = 1;
		}
		else {
			sbr.N_Q = (Math.max(1, find_bands(0, sbr.bs_noise_bands, sbr.kx, k2)));
			sbr.N_Q = Math.min(5, sbr.N_Q);
		}

		for(k = 0; k<=sbr.N_Q; k++) {
			if(k==0) {
				i = 0;
			}
			else {
				/* i = i + (int32_t)((sbr.N_low - i)/(sbr.N_Q + 1 - k)); */
				i += (sbr.N_low-i)/(sbr.N_Q+1-k);
			}
			sbr.f_table_noise[k] = sbr.f_table_res[LO_RES][i];
		}

		/* build table for mapping k to g in hf patching */
		for(k = 0; k<64; k++) {
			int g;
			for(g = 0; g<sbr.N_Q; g++) {
				if((sbr.f_table_noise[g]<=k)
					&&(k<sbr.f_table_noise[g+1])) {
					sbr.table_map_k_to_g[k] = g;
					break;
				}
			}
		}
		return 0;
	}

	/* TODO: blegh, ugly */
	/* Modified to calculate for all possible bs_limiter_bands always
	 * This reduces the number calls to this functions needed (now only on
	 * header reset)
	 */
	private static final float[] limiterBandsCompare = {1.327152f,
		1.185093f, 1.119872f};

	public static void limiter_frequency_table(SBR sbr) {

		int k, s;
		int nrLim;

		sbr.f_table_lim[0][0] = sbr.f_table_res[LO_RES][0]-sbr.kx;
		sbr.f_table_lim[0][1] = sbr.f_table_res[LO_RES][sbr.N_low]-sbr.kx;
		sbr.N_L[0] = 1;

		for(s = 1; s<4; s++) {
			int[] limTable = new int[100 /*TODO*/];
			int[] patchBorders = new int[64/*??*/];

			patchBorders[0] = sbr.kx;
			for(k = 1; k<=sbr.noPatches; k++) {
				patchBorders[k] = patchBorders[k-1]+sbr.patchNoSubbands[k-1];
			}

			for(k = 0; k<=sbr.N_low; k++) {
				limTable[k] = sbr.f_table_res[LO_RES][k];
			}
			for(k = 1; k<sbr.noPatches; k++) {
				limTable[k+sbr.N_low] = patchBorders[k];
			}

			/* needed */
			//qsort(limTable, sbr.noPatches+sbr.N_low, sizeof(limTable[0]), longcmp);
			Arrays.sort(limTable, 0, sbr.noPatches+sbr.N_low);
			k = 1;
			nrLim = sbr.noPatches+sbr.N_low-1;

			if(nrLim<0) // TODO: BIG FAT PROBLEM
				return;

			restart:
			while(k<=nrLim) {
				float nOctaves;

				if(limTable[k-1]!=0)
					nOctaves = (float) limTable[k]/(float) limTable[k-1];
				else
					nOctaves = 0;

				if(nOctaves<limiterBandsCompare[s-1]) {
					int i;
					if(limTable[k]!=limTable[k-1]) {
						boolean found = false, found2 = false;
						for(i = 0; i<=sbr.noPatches; i++) {
							if(limTable[k]==patchBorders[i])
								found = true;
						}
						if(found) {
							found2 = false;
							for(i = 0; i<=sbr.noPatches; i++) {
								if(limTable[k-1]==patchBorders[i])
									found2 = true;
							}
							if(found2) {
								k++;
								continue;
							}
							else {
								/* remove (k-1)th element */
								limTable[k-1] = sbr.f_table_res[LO_RES][sbr.N_low];
								//qsort(limTable, sbr.noPatches+sbr.N_low, sizeof(limTable[0]), longcmp);
								Arrays.sort(limTable, 0, sbr.noPatches+sbr.N_low);
								nrLim--;
								continue;
							}
						}
					}
					/* remove kth element */
					limTable[k] = sbr.f_table_res[LO_RES][sbr.N_low];
					//qsort(limTable, nrLim, sizeof(limTable[0]), longcmp);
					Arrays.sort(limTable, 0, nrLim);
					nrLim--;
					//continue;
				}
				else {
					k++;
					//continue;
				}
			}

			sbr.N_L[s] = nrLim;
			for(k = 0; k<=nrLim; k++) {
				sbr.f_table_lim[s][k] = limTable[k]-sbr.kx;
			}

		}
	}

}


package org.jcodec.codecs.mpeg12;
import org.jcodec.common.io.NIOUtils;

import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Pulls frames from MPEG elementary stream
 *
 * @author The JCodec project
 *
 */
public class SegmentReader {

    private ReadableByteChannel channel;
    private ByteBuffer buf;
    protected int curMarker;
    private int fetchSize;
    protected boolean done;
    private long pos;
    private int bytesInMarker;
    private int bufferIncrement = 32768; //1<<15

    public SegmentReader(ReadableByteChannel channel, int fetchSize) throws IOException {
        this.channel = channel;
        this.fetchSize = fetchSize;
        buf = NIOUtils.fetchFromChannel(channel, 4);
        pos = buf.remaining();
        curMarker = buf.getInt();
        bytesInMarker = 4;
    }

    // FOR TESTCASES
    public int getBufferIncrement() {
        return bufferIncrement;
    }

    // FOR TESTCASES
    public void setBufferIncrement(int bufferIncrement) {
        this.bufferIncrement = bufferIncrement;
    }

    public static enum State {
        MORE_DATA, DONE, STOP
    }

    /**
     * Reads one full segment till the next marker. Will read as much data as
     * the provided buffer fits, if the provided buffer doesn't fit all data
     * will return MORE_DATA.
     *
     * @param out
     * @return
     * @throws IOException
     */
    public final State readToNextMarkerPartial(ByteBuffer out) throws IOException {
        if (done)
            return State.STOP;
        int skipOneMarker = curMarker >= 0x100 && curMarker <= 0x1ff ? 1 : 0;
        int written = out.position();
        do {
            while (buf.hasRemaining()) {
                if (curMarker >= 0x100 && curMarker <= 0x1ff) {
                    if (skipOneMarker == 0) {
                        return State.DONE;
                    }
                    --skipOneMarker;
                }
                if (!out.hasRemaining())
                    return State.MORE_DATA;
                out.put((byte) (curMarker >>> 24));
                curMarker = (curMarker << 8) | (buf.get() & 0xff);
            }
            buf = NIOUtils.fetchFromChannel(channel, fetchSize);
            pos += buf.remaining();
        } while (buf.hasRemaining());

        written = out.position() - written;
        if (written > 0 && curMarker >= 0x100 && curMarker <= 0x1ff)
            return State.DONE;

        for (; bytesInMarker > 0 && out.hasRemaining();) {
            out.put((byte) (curMarker >>> 24));
            curMarker = (curMarker << 8);
            --bytesInMarker;
            if (curMarker >= 0x100 && curMarker <= 0x1ff)
                return State.DONE;
        }

        if (bytesInMarker == 0) {
            done = true;
            return State.STOP;
        } else {
            return State.MORE_DATA;
        }
    }

    /**
     * Reads one full segment till the next marker. Will allocate the necessary
     * buffer to hold the full segment. Internally uses a growing collection of
     * smaller buffers since the segment size is intitially unkwnown.
     *
     * @return
     * @throws IOException
     */
    public ByteBuffer readToNextMarkerNewBuffer() throws IOException {
        if (done)
            return null;
        List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
        readToNextMarkerBuffers(buffers);

        return NIOUtils.combineBuffers(buffers);
    }

    public void readToNextMarkerBuffers(List<ByteBuffer> buffers) throws IOException {
        State state;
        do {
            ByteBuffer curBuffer = ByteBuffer.allocate(bufferIncrement);
            state = readToNextMarkerPartial(curBuffer);
            curBuffer.flip();
            buffers.add(curBuffer);
        } while (state == State.MORE_DATA);
    }

    public final boolean readToNextMarker(ByteBuffer out) throws IOException {
        State state = readToNextMarkerPartial(out);
        if(state == State.MORE_DATA)
            throw new BufferOverflowException();
        return state == State.DONE;
    }

    public final boolean skipToMarker() throws IOException {
        if (done)
            return false;
        do {
            while (buf.hasRemaining()) {
                curMarker = (curMarker << 8) | (buf.get() & 0xff);
                if (curMarker >= 0x100 && curMarker <= 0x1ff) {
                    return true;
                }
            }
            buf = NIOUtils.fetchFromChannel(channel, fetchSize);
            pos += buf.remaining();
        } while (buf.hasRemaining());
        done = true;

        return false;
    }

    public final boolean read(ByteBuffer out, int length) throws IOException {
        if (done)
            return false;
        do {
            while (buf.hasRemaining()) {
                if (length-- == 0)
                    return true;
                out.put((byte) (curMarker >>> 24));
                curMarker = (curMarker << 8) | (buf.get() & 0xff);
            }
            buf = NIOUtils.fetchFromChannel(channel, fetchSize);
            pos += buf.remaining();
        } while (buf.hasRemaining());
        out.putInt(curMarker);
        done = true;

        return false;
    }

    public final long curPos() {
        return pos - buf.remaining() - 4;
    }
}


package org.jcodec.codecs.vpx;
import java.nio.ByteBuffer;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * @author The JCodec project
 *
 */
public class VPXBooleanDecoder {
        int bit_count; /* # of bits shifted out of value, at most 7 */
        ByteBuffer input;
        int offset; /* pointer to next compressed data byte */
        int range; /* always identical to encoder's range */
        int value; /* contains at least 24 significant bits */
        long callCounter=0;
        private String debugName;

        public VPXBooleanDecoder(ByteBuffer input, int offset) {
            this.input = input;
            this.offset = offset;
            initBoolDecoder();
        }

        /**
         * Empty constructor just for testing
         */
        protected VPXBooleanDecoder() {
        }

        void initBoolDecoder() {

            value = 0; /* value = first 16 input bits */

            // data.position(offset);
            value = (input.get() & 0xFF) << 8; // readUnsignedByte() << 8;
            // value = (data[offset]) << 8;
            offset++;

            range = 255; /* initial range is full */
            bit_count = 0; /* have not yet shifted out any bits */
        }

        public int readBitEq()  {
            return readBit(128);
        }

        public int readBit(int probability) {
            int bit = 0;
            int range = this.range;
            int value = this.value;
            int split = 1 + (((range - 1) * probability) >> 8);
            int bigsplit = (split << 8);

            this.callCounter++;
//            System.out.println();
//            System.out.println("this.range: " + this.range + " binary: " + Integer.toBinaryString(this.range));
//            System.out.println("split: " + split + " binary: " + Integer.toBinaryString(split));
//            System.out.println("SPLIT: " + bigsplit + " binary: " + Integer.toBinaryString(bigsplit));
//            System.out.println("value: " + value + " binary: " + Integer.toBinaryString(value));
            range = split;

            if (value >= bigsplit) {
                range = this.range - range;
                value = value - bigsplit;
                bit = 1;
            }

            int count = this.bit_count;
            int shift = leadingZeroCountInByte((byte)range);
            range <<= shift;
            value <<= shift;
            count -= shift;

            if (count <= 0) {
                value |= (input.get() & 0xFF) << (-count);
//                System.out.println("read value: " + value + " binary: " + Integer.toBinaryString(value));
                offset++;
                count += 8;
            }

            this.bit_count = count;
            this.value = value;
            this.range = range;
            return bit;
        }

        /*
         * Convenience function reads a "literal", that is, a "num_bits" wide unsigned value whose bits come high- to low-order, with each bit encoded at probability 128 (i.e., 1/2).
         */
        public int decodeInt(int sizeInBits) {
            int v = 0;
            while (sizeInBits-- > 0)
                v = (v << 1) | readBit(128);
            return v;
        }

        /* root: "0", "1" subtrees */
        /* "00" = 0th value, "01" = 1st value */
        /* "10" = 2nd value, "11" = 3rd value */
        /**
         *
         * General formula in VP8 trees.
         *

         *
 if tree element is a positive number it is treated as index of the child elements
tree[i] > 0

         *

         *
 left child is assumed to have index
i
 and value
tree[i]

         *
 right child is assumed to have index
i+1
 and value
tree[i+1]

         *

         *

         *
 a negative tree value means a leaf node was reached and it's negated value should be returned
-tree[i]

         *

         *
         * Here's a real example of a tree coded according to this formula in VP8 spec.
         *

         * const tree_index mb_segment_tree [2 * (4-1)] =
         * // +-------+---+
         * // |       |   |
         *  { 2,  4, -0, -1, -2, -3 };
         * //     |           |   |
         * //     +-----------+---+
         *

         *
         * If presented in hierarchical form this tree would look like:
         *

         * +---------------+
         * |      root     |
         * |     /    \    |
         * |    2      4   |
         * |   / \    / \  |
         * | -0  -1 -2  -3 |
         * +---------------+
         *

         *
         * On the other hand probabilities are coded only for non-leaf nodes.
         * Thus tree array has twice as many nodes as probabilities array
         * Consider (3>>1) == 1 == (2>>1), and (0>>1) == 0 == (1>>1)
         * Thus single probability element refers to single parent element in tree.
         * if (using that probability) a '0' is coded, algorithm goes to the left
         * branch, correspondingly if '1' is coded, algorithm goes to
         * the right branch (see tree structure above).
         *
         * The process is repeated until a negative tree element is found.
         *
         */
        public int readTree(int tree[],int probability[]) {
            int i = 0;

            /*
             * 1. pick corresponding probability probability[i >> 1]
             * 2. pick left or right branch from coded info decodeBool(probability)
             * 3. tree[i+decodedBool] get corresponding (left of right) value
             * 4. repeat until tree[i+decodedBool] is positive
             */
            while ((i = tree[i + readBit(probability[i >> 1])]) > 0) {
            }
            return -i; /* negate the return value */

        }

        //There is a method in the class (or one of its parents) having the same name with the method named [readTree] but is less generic
        public int readTree3(int[] tree, int prob0, int prob1) {
            int i = 0;
            if ((i = tree[i + readBit(prob0)]) > 0) {
                while ((i = tree[i + readBit(prob1)]) > 0) ;
            }
            return -i; /* negate the return value */
        }

        public int readTreeSkip(int t[], /* tree specification */
                int p[], /* corresponding interior node probabilities */
                int skip_branches) {
            int i = skip_branches * 2; /* begin at root */

            /* Descend tree until leaf is reached */
            while ((i = t[i + readBit(p[i >> 1])]) > 0) {
            }
            return -i; /* return value is negation of nonpositive index */

        }

        public void seek() {
            input.position(offset);
        }

        public String toString() {
            return "bc: " + value;
        }

        public static int getBitInBytes(byte[] bs, int i) {
            int byteIndex = i >> 3;
            int bitIndex = i & 0x07;
            return (bs[byteIndex] >> (0x07 - bitIndex)) & 0x01;
        }

        public static int getBitsInBytes(byte[] bytes, int idx, int len){
            int val = 0;
            for(int i=0;i<len;i++){
                val = (val << 1) | getBitInBytes(bytes, idx+i);
            }
            return val;
        }

        public static int leadingZeroCountInByte(byte b) {
            int i = b&0xFF;
            if (i>=128 || i == 0)
                return 0;

            return Integer.numberOfLeadingZeros(b)-24;
            /*
             * if-less alternative:
             * http://aggregate.ee.engr.uky.edu/MAGIC/#Leading Zero Count
             */
        }
    }


package org.jcodec.containers.imgseq;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.jcodec.common.Codec;
import org.jcodec.common.Demuxer;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.TrackType;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Packet.FrameType;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * A demuxer that reads image files out of a folder.
 *
 * Supports both sequences starting with 0 and 1 index.
 *
 * @author Stanislav Vitvitskyy
 *
 */
public class ImageSequenceDemuxer implements Demuxer, DemuxerTrack {

    private static final int VIDEO_FPS = 25;
    private String namePattern;
    private int frameNo;
    private Packet curFrame;
    private Codec codec;
    private int maxAvailableFrame;
    private int maxFrames;
    private String prevName;

    public ImageSequenceDemuxer(String namePattern, int maxFrames) throws IOException {
        this.namePattern = namePattern;
        this.maxFrames = maxFrames;
        this.maxAvailableFrame = -1;
        this.curFrame = loadFrame();
        // codec = JCodecUtil.detectDecoder(curFrame.getData());
        String lowerCase = namePattern.toLowerCase();
        if (lowerCase.endsWith(".png")) {
            codec = Codec.PNG;
        } else if (lowerCase.endsWith(".jpg") || lowerCase.endsWith(".jpeg")) {
            codec = Codec.JPEG;
        }
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public List<? extends DemuxerTrack> getTracks() {
        ArrayList<DemuxerTrack> tracks = new ArrayList<DemuxerTrack>();
        tracks.add(this);
        return tracks;
    }

    @Override
    public List<? extends DemuxerTrack> getVideoTracks() {
        return getTracks();
    }

    @Override
    public List<? extends DemuxerTrack> getAudioTracks() {
        return new ArrayList<DemuxerTrack>();
    }

    @Override
    public Packet nextFrame() throws IOException {
        try {
            return curFrame;
        } finally {
            curFrame = loadFrame();
        }
    }

    private Packet loadFrame() throws IOException {
        if (frameNo > maxFrames) {
            return null;
        }

        File file = null;
        do {
            String name = String.format(namePattern, frameNo);
            // In case the name doesn't contain placeholders, to prevent infinitely
            // looping around the same file.
            if (name.equals(prevName)) {
                return null;
            }
            prevName = name;
            file = new File(name);
            if (file.exists() || frameNo > 0)
                break;
            frameNo++;
        } while (frameNo < 2);

        if (file == null || !file.exists())
            return null;

        Packet ret = new Packet(NIOUtils.fetchFromFile(file), frameNo, VIDEO_FPS, 1, frameNo, FrameType.KEY, null, frameNo);
        ++frameNo;
        return ret;
    }

    private static final int MAX_MAX = 60 * 60 * 60 * 24; // Longest possible
                                                          // movie

    /**
     * Finds maximum frame of a sequence by bisecting the range.
     *
     * Performs at max at max 48 Stat calls ( 2*log2(MAX_MAX) ).
     *
     * @return
     */
    public int getMaxAvailableFrame() {
        if (maxAvailableFrame == -1) {

            int firstPoint = 0;
            for (int i = MAX_MAX; i > 0; i /= 2) {
                if (new File(String.format(namePattern, i)).exists()) {
                    firstPoint = i;
                    break;
                }
            }
            int pos = firstPoint;
            for (int interv = firstPoint / 2; interv > 1; interv /= 2) {
                if (new File(String.format(namePattern, pos + interv)).exists()) {
                    pos += interv;
                }
            }
            maxAvailableFrame = pos;
            Logger.info("Max frame found: " + maxAvailableFrame);
        }
        return Math.min(maxAvailableFrame, maxFrames);
    }

    @Override
    public DemuxerTrackMeta getMeta() {
        int durationFrames = getMaxAvailableFrame();
        return new DemuxerTrackMeta(TrackType.VIDEO, codec, (durationFrames + 1) * VIDEO_FPS, null, durationFrames + 1,
                null, null, null);
    }
}


package org.jcodec.common.dct;
import java.nio.IntBuffer;

/**
 * This class is part of JCodec ( www.jcodec.org )
 * This software is distributed under FreeBSD License
 *
 * @author Alex Zhukov
 *
 */
public class IntDCT extends DCT {
    public final static IntDCT INSTANCE = new IntDCT();
    private static final int DCTSIZE = 8;
    private static final int PASS1_BITS = 2;
    private static final int MAXJSAMPLE = 255;
    private static final int CENTERJSAMPLE = 128;
    /** 2 bits wider than legal samples */
    private static final int RANGE_MASK = (MAXJSAMPLE * 4 + 3);

    public int[] decode(int[] orig) {
        IntBuffer inptr = IntBuffer.wrap(orig);
        IntBuffer workspace = IntBuffer.allocate(64);
        IntBuffer outptr = IntBuffer.allocate(64);
        doDecode(inptr, workspace, outptr);
        return outptr.array();
    }

    protected static IntBuffer doDecode(IntBuffer inptr, IntBuffer workspace,
                                        IntBuffer outptr) {
        pass1(inptr, workspace.duplicate());
        pass2(outptr, workspace.duplicate());
        return outptr;
    }

    /**
     * Pass 2: process rows from work array, store into output array.
     *
     * Note that we must descale the results by a factor of 8 == 23, and also
     * undo the PASS1_BITS scaling.
     */
    private static void pass2(IntBuffer outptr, IntBuffer wsptr) {
        for (int ctr = 0; ctr < DCTSIZE; ctr++) {
            /* Even part: reverse the even part of the forward DCT. */
            /* The rotator is sqrt(2)c(-6). */

            int z2 = wsptr.get(2);
            int z3 = wsptr.get(6);

            int z1 = MULTIPLY(z2 + z3, FIX_0_541196100);
            int tmp2 = z1 + MULTIPLY(z3, -FIX_1_847759065);
            int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865);

            int tmp0 = ((int) wsptr.get(0) + (int) wsptr.get(4)) << CONST_BITS;
            int tmp1 = ((int) wsptr.get(0) - (int) wsptr.get(4)) << CONST_BITS;

            int tmp10 = tmp0 + tmp3;
            int tmp13 = tmp0 - tmp3;
            int tmp11 = tmp1 + tmp2;
            int tmp12 = tmp1 - tmp2;

            /*
             * Odd part per figure 8; the matrix is unitary and hence its
             * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively.
             */

            tmp0 = (int) wsptr.get(7);
            tmp1 = (int) wsptr.get(5);
            tmp2 = (int) wsptr.get(3);
            tmp3 = (int) wsptr.get(1);

            z1 = tmp0 + tmp3;
            z2 = tmp1 + tmp2;
            z3 = tmp0 + tmp2;
            int z4 = tmp1 + tmp3;
            int z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) c3 */

            tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) (-c1+c3+c5-c7) */
            tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) ( c1+c3-c5+c7) */
            tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) ( c1+c3+c5-c7) */
            tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) ( c1+c3-c5-c7) */
            z1 = MULTIPLY(z1, -FIX_0_899976223); /* sqrt(2) (c7-c3) */
            z2 = MULTIPLY(z2, -FIX_2_562915447); /* sqrt(2) (-c1-c3) */
            z3 = MULTIPLY(z3, -FIX_1_961570560); /* sqrt(2) (-c3-c5) */
            z4 = MULTIPLY(z4, -FIX_0_390180644); /* sqrt(2) (c5-c3) */

            z3 += z5;
            z4 += z5;

            tmp0 += z1 + z3;
            tmp1 += z2 + z4;
            tmp2 += z2 + z3;
            tmp3 += z1 + z4;

            /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */

            int D = CONST_BITS + PASS1_BITS + 3;
            outptr.put(range_limit(DESCALE(tmp10 + tmp3, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp11 + tmp2, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp12 + tmp1, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp13 + tmp0, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp13 - tmp0, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp12 - tmp1, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp11 - tmp2, D) & RANGE_MASK));
            outptr.put(range_limit(DESCALE(tmp10 - tmp3, D) & RANGE_MASK));

            wsptr = doAdvance(wsptr, DCTSIZE); /* advance pointer to next row */

        }
    }

    public static int range_limit(int i) {
        return idct_sample_range_limit.get(i + 256);
    }

    private final static IntBuffer sample_range_limit = IntBuffer
            .allocate((5 * (MAXJSAMPLE + 1) + CENTERJSAMPLE));
    private final static IntBuffer idct_sample_range_limit = IntBuffer
            .allocate(sample_range_limit.capacity() - 128);
    static {
        prepare_range_limit_table();
    }

    /* Allocate and fill in the sample_range_limit table */
    private static void prepare_range_limit_table() {
        sample_range_limit.position(256);
        for (int i = 0; i < 128; i++) {
            sample_range_limit.put(i);
        }
        for (int i = -128; i < 0; i++) {
            sample_range_limit.put(i);
        }
        for (int i = 0; i < 256 + 128; i++) {
            sample_range_limit.put(-1);
        }
        for (int i = 0; i < 256 + 128; i++) {
            sample_range_limit.put(0);
        }
        for (int i = 0; i < 128; i++) {
            sample_range_limit.put(i);
        }

        for (int i = 0; i < idct_sample_range_limit.capacity(); i++) {
            idct_sample_range_limit.put(sample_range_limit.get(i + 128) & 0xff);
        }

    }

    private static boolean shortcut(IntBuffer inptr, IntBuffer wsptr) {
        /*
         * Due to quantization, we will usually find that many of the input
         * coefficients are zero, especially the AC terms. We can exploit this
         * by short-circuiting the IDCT calculation for any column in which all
         * the AC terms are zero. In that case each output is equal to the DC
         * coefficient (with scale factor as needed). With typical images and
         * quantization tables, half or more of the column DCT calculations can
         * be simplified this way.
         */

        if (inptr.get(DCTSIZE * 1) == 0 && inptr.get(DCTSIZE * 2) == 0
                && inptr.get(DCTSIZE * 3) == 0 && inptr.get(DCTSIZE * 4) == 0
                && inptr.get(DCTSIZE * 5) == 0 && inptr.get(DCTSIZE * 6) == 0
                && inptr.get(DCTSIZE * 7) == 0) {
            /* AC terms all zero */
            int dcval = inptr.get(DCTSIZE * 0) << PASS1_BITS;

            wsptr.put(DCTSIZE * 0, dcval);
            wsptr.put(DCTSIZE * 1, dcval);
            wsptr.put(DCTSIZE * 2, dcval);
            wsptr.put(DCTSIZE * 3, dcval);
            wsptr.put(DCTSIZE * 4, dcval);
            wsptr.put(DCTSIZE * 5, dcval);
            wsptr.put(DCTSIZE * 6, dcval);
            wsptr.put(DCTSIZE * 7, dcval);

            inptr = advance(inptr);
            wsptr = advance(wsptr);
            return true;
        }
        return false;
    }

    /**
     * Pass 1: process columns from input, store into work array.
     *
     * Note results are scaled up by sqrt(8) compared to a true IDCT;
     * furthermore, we scale the results by 2xPASS1_BITS.
     */
    private static void pass1(IntBuffer inptr, IntBuffer wsptr) {

        for (int ctr = DCTSIZE; ctr > 0; ctr--) {
            // if (shortcircuit(inptr, wsptr))
            // continue;
            /* Even part: reverse the even part of the forward DCT. */
            /* The rotator is sqrt(2)c(-6). */

            int z2 = inptr.get(DCTSIZE * 2);
            int z3 = inptr.get(DCTSIZE * 6);
            int z1 = MULTIPLY(z2 + z3, FIX_0_541196100);
            int tmp2 = z1 + MULTIPLY(z3, -FIX_1_847759065);
            int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865);
            z2 = inptr.get(DCTSIZE * 0);
            z3 = inptr.get(DCTSIZE * 4);
            int tmp0 = (z2 + z3) << CONST_BITS;
            int tmp1 = (z2 - z3) << CONST_BITS;
            int tmp10 = tmp0 + tmp3;
            int tmp13 = tmp0 - tmp3;
            int tmp11 = tmp1 + tmp2;
            int tmp12 = tmp1 - tmp2;
            /*
             * Odd part per figure 8; the matrix is unitary and hence its
             * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively.
             */

            tmp0 = inptr.get(DCTSIZE * 7);
            tmp1 = inptr.get(DCTSIZE * 5);
            tmp2 = inptr.get(DCTSIZE * 3);
            tmp3 = inptr.get(DCTSIZE * 1);
            z1 = tmp0 + tmp3;
            z2 = tmp1 + tmp2;
            z3 = tmp0 + tmp2;
            int z4 = tmp1 + tmp3;
            int z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) c3 */

            tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) (-c1+c3+c5-c7) */
            tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) ( c1+c3-c5+c7) */
            tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) ( c1+c3+c5-c7) */
            tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) ( c1+c3-c5-c7) */
            z1 = MULTIPLY(z1, -FIX_0_899976223); /* sqrt(2) (c7-c3) */
            z2 = MULTIPLY(z2, -FIX_2_562915447); /* sqrt(2) (-c1-c3) */
            z3 = MULTIPLY(z3, -FIX_1_961570560); /* sqrt(2) (-c3-c5) */
            z4 = MULTIPLY(z4, -FIX_0_390180644); /* sqrt(2) (c5-c3) */
            z3 += z5;
            z4 += z5;

            tmp0 += z1 + z3;
            tmp1 += z2 + z4;
            tmp2 += z2 + z3;
            tmp3 += z1 + z4;

            /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */

            int D = CONST_BITS - PASS1_BITS;
            wsptr.put(DCTSIZE * 0, DESCALE(tmp10 + tmp3, D));
            wsptr.put(DCTSIZE * 7, DESCALE(tmp10 - tmp3, D));
            wsptr.put(DCTSIZE * 1, DESCALE(tmp11 + tmp2, D));
            wsptr.put(DCTSIZE * 6, DESCALE(tmp11 - tmp2, D));
            wsptr.put(DCTSIZE * 2, DESCALE(tmp12 + tmp1, D));
            wsptr.put(DCTSIZE * 5, DESCALE(tmp12 - tmp1, D));
            wsptr.put(DCTSIZE * 3, DESCALE(tmp13 + tmp0, D));
            wsptr.put(DCTSIZE * 4, DESCALE(tmp13 - tmp0, D));
            inptr = advance(inptr);
            wsptr = advance(wsptr);
        }
    }

    /**
     * advance pointers to next column
     */
    private static IntBuffer advance(IntBuffer ptr) {
        return doAdvance(ptr, 1);
    }

    private static IntBuffer doAdvance(IntBuffer ptr, int size) {
        ptr.position(ptr.position() + size);
        return ptr.slice();
    }

    static int DESCALE(int x, int n) {
        return RIGHT_SHIFT((x) + (1 << ((n) - 1)), n);
    }

    private static int RIGHT_SHIFT(int x, int shft) {
        return x >> shft;
    }

    private static int MULTIPLY(int i, int j) {
        return i * j;
    }
    private static final int CONST_BITS = 13;
    private static final int ONE_HALF = (1 << (CONST_BITS - 1));

    private static int FIX(double x) {
        return ((int) ((x) * (1 << CONST_BITS) + 0.5));
    }

    private final static int FIX_0_298631336 = FIX(0.298631336);
    private final static int FIX_0_390180644 = FIX(0.390180644);
    private final static int FIX_0_541196100 = FIX(0.541196100);
    private final static int FIX_0_765366865 = FIX(0.765366865);
    private final static int FIX_0_899976223 = FIX(0.899976223);
    private final static int FIX_1_175875602 = FIX(1.175875602);
    private final static int FIX_1_501321110 = FIX(1.501321110);
    private final static int FIX_1_847759065 = FIX(1.847759065);
    private final static int FIX_1_961570560 = FIX(1.961570560);
    private final static int FIX_2_053119869 = FIX(2.053119869);
    private final static int FIX_2_562915447 = FIX(2.562915447);
    private final static int FIX_3_072711026 = FIX(3.072711026);
}


package org.jcodec.movtool;

import org.jcodec.common.Tuple;
import org.jcodec.common.Tuple._2;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.containers.mp4.BoxFactory;
import org.jcodec.containers.mp4.BoxUtil;
import org.jcodec.containers.mp4.MP4Util;
import org.jcodec.containers.mp4.MP4Util.Atom;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.Header;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.MovieFragmentBox;

import java.io.File;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static org.jcodec.common.Preconditions.checkNotNull;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Parses MP4 header and allows custom MP4Editor to modify it, then tries to put
 * the resulting header into the same place relatively to a file.
 *
 * This might not work out, for example if the resulting header is bigger then
 * the original.
 *
 * Use this class to make blazing fast changes to MP4 files when you know your
 * are not adding anything new to the header, perhaps only patching some values
 * or removing stuff from the header.
 *
 * @author The JCodec project
 *
 */
public class InplaceMP4Editor {

    /**
     * Tries to modify movie header in place according to what's implemented in
     * the edit, the file gets pysically modified if the operation is
     * successful. No temporary file is created.
     *
     * @param file
     *            A file to be modified
     * @param edit
     *            An edit to be carried out on a movie header
     * @return Whether or not edit was successful, i.e. was there enough place
     *         to put the new header
     * @throws IOException
     * @throws Exception
     */
    public boolean modify(File file, MP4Edit edit) throws IOException {
        SeekableByteChannel fi = null;
        try {
            fi = NIOUtils.rwChannel(file);

            List<Tuple._2<Atom, ByteBuffer>> fragments = doTheFix(fi, edit);
            if (fragments == null)
                return false;

            // If everything is clean, only then actually writing stuff to the
            // file
            for (Tuple._2<Atom, ByteBuffer> fragment : fragments) {
                replaceBox(fi, fragment.v0, fragment.v1);
            }

            return true;
        } finally {
            NIOUtils.closeQuietly(fi);
        }
    }

    /**
     * Tries to modify movie header in place according to what's implemented in
     * the edit. Copies modified contents to a new file.
     *
     * Note: The header is still edited in-place, so the new file will have
     * all-the-same sample offsets.
     *
     * Note: Still subject to the same limitations as 'modify', i.e. the new
     * header must 'fit' into an old place.
     *
     * This method is useful when you can't write to the original file, for ex.
     * you don't have permission.
     *
     * @param src
     *            An original file
     * @param dst
     *            A file to store the modified copy
     * @param edit
     *            An edit logic to apply
     * @return
     * @throws IOException
     */
    public boolean copy(File src, File dst, MP4Edit edit) throws IOException {
        SeekableByteChannel fi = null;
        SeekableByteChannel fo = null;
        try {
            fi = NIOUtils.readableChannel(src);
            fo = NIOUtils.writableChannel(dst);

            List<Tuple._2<Atom, ByteBuffer>> fragments = doTheFix(fi, edit);
            if (fragments == null)
                return false;

            List<_2<Long, ByteBuffer>> fragOffsets = Tuple._2map0(fragments, new Tuple.Mapper<Atom, Long>() {
                public Long map(Atom t) {
                    return t.getOffset();
                }
            });

            // If everything is clean, only then actually start writing file
            Map<Long, ByteBuffer> rewrite = Tuple.asMap(fragOffsets);
            for (Atom atom : MP4Util.getRootAtoms(fi)) {
                ByteBuffer byteBuffer = rewrite.get(atom.getOffset());
                if (byteBuffer != null)
                    fo.write(byteBuffer);
                else
                    atom.copy(fi, fo);
            }

            return true;
        } finally {
            NIOUtils.closeQuietly(fi);
            NIOUtils.closeQuietly(fo);
        }
    }

    /**
     * Tries to modify movie header in place according to what's implemented in
     * the edit. Copies modified contents to a new file with the same name
     * erasing the original file if successful.
     *
     * This is a shortcut for 'copy' when you want the new file to have the same
     * name but for some reason can not modify the original file in place. Maybe
     * modifications of files are expensive or not supported on your filesystem.
     *
     * @param src
     *            A source and destination file
     * @param edit
     *            An edit to be applied
     * @return
     * @throws IOException
     */
    public boolean replace(File src, MP4Edit edit) throws IOException {
        File tmp = new File(src.getParentFile(), "." + src.getName());
        if (copy(src, tmp, edit)) {
            tmp.renameTo(src);
            return true;
        }
        return false;
    }

    private List<Tuple._2<Atom, ByteBuffer>> doTheFix(SeekableByteChannel fi, MP4Edit edit) throws IOException {
        Atom moovAtom = getMoov(fi);
        checkNotNull(moovAtom);

        ByteBuffer moovBuffer = fetchBox(fi, moovAtom);
        MovieBox moovBox = (MovieBox) parseBox(moovBuffer);

        List<Tuple._2<Atom, ByteBuffer>> fragments = new LinkedList<Tuple._2<Atom, ByteBuffer>>();
        if (BoxUtil.containsBox(moovBox, "mvex")) {
            List<Tuple._2<ByteBuffer, MovieFragmentBox>> temp = new LinkedList<Tuple._2<ByteBuffer, MovieFragmentBox>>();
            for (Atom fragAtom : getFragments(fi)) {
                ByteBuffer fragBuffer = fetchBox(fi, fragAtom);
                fragments.add(Tuple.pair(fragAtom, fragBuffer));
                MovieFragmentBox fragBox = (MovieFragmentBox) parseBox(fragBuffer);
                fragBox.setMovie(moovBox);
                temp.add(Tuple.pair(fragBuffer, fragBox));
            }

            edit.applyToFragment(moovBox, Tuple._2_project1(temp).toArray(new MovieFragmentBox[0]));

            for (Tuple._2<ByteBuffer, ? extends Box> frag : temp) {
                if (!rewriteBox(frag.v0, frag.v1))
                    return null;
            }
        } else
            edit.apply(moovBox);

        if (!rewriteBox(moovBuffer, moovBox))
            return null;
        fragments.add(Tuple.pair(moovAtom, moovBuffer));
        return fragments;
    }

    private void replaceBox(SeekableByteChannel fi, Atom atom, ByteBuffer buffer) throws IOException {
        fi.setPosition(atom.getOffset());
        fi.write(buffer);
    }

    private boolean rewriteBox(ByteBuffer buffer, Box box) {
        try {
            buffer.clear();
            box.write(buffer);
            if (buffer.hasRemaining()) {
                if (buffer.remaining() < 8)
                    return false;
                buffer.putInt(buffer.remaining());
                buffer.put(new byte[] { 'f', 'r', 'e', 'e' });
            }
            buffer.flip();
            return true;
        } catch (BufferOverflowException e) {
            return false;
        }
    }

    private ByteBuffer fetchBox(SeekableByteChannel fi, Atom moov) throws IOException {
        fi.setPosition(moov.getOffset());
        ByteBuffer oldMov = NIOUtils.fetchFromChannel(fi, (int) moov.getHeader().getSize());
        return oldMov;
    }

    private Box parseBox(ByteBuffer oldMov) {
        Header header = Header.read(oldMov);
        Box box = BoxUtil.parseBox(oldMov, header, BoxFactory.getDefault());
        return box;
    }

    private Atom getMoov(SeekableByteChannel f) throws IOException {
        for (Atom atom : MP4Util.getRootAtoms(f)) {
            if ("moov".equals(atom.getHeader().getFourcc())) {
                return atom;
            }
        }
        return null;
    }

    private List<Atom> getFragments(SeekableByteChannel f) throws IOException {
        List<Atom> result = new LinkedList<Atom>();
        for (Atom atom : MP4Util.getRootAtoms(f)) {
            if ("moof".equals(atom.getHeader().getFourcc())) {
                result.add(atom);
            }
        }
        return result;
    }
}


package net.sourceforge.jaad.aac.sbr;

/**
 * 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.
 *
 * @author in-somnia
 */
class HFGeneration {

	private static final int[] goalSbTab = {21, 23, 32, 43, 46, 64, 85, 93, 128, 0, 0, 0};

	private static class acorr_coef {

		float[] r01;
		float[] r02;
		float[] r11;
		float[] r12;
		float[] r22;
		float det;

		public acorr_coef() {
	        this.r01 = new float[2];
	        this.r02 = new float[2];
	        this.r11 = new float[2];
	        this.r12 = new float[2];
	        this.r22 = new float[2];
        }
	}

	public static void hf_generation(SBR sbr, float[][][] Xlow,
		float[][][] Xhigh, int ch) {
		int l, i, x;
		float[][] alpha_0 = new float[64][2], alpha_1 = new float[64][2];

		int offset = sbr.tHFAdj;
		int first = sbr.t_E[ch][0];
		int last = sbr.t_E[ch][sbr.L_E[ch]];

		calc_chirp_factors(sbr, ch);

		if((ch==0)&&(sbr.Reset))
			patch_construction(sbr);

		/* calculate the prediction coefficients */

		/* actual HF generation */
		for(i = 0; i<sbr.noPatches; i++) {
			for(x = 0; x<sbr.patchNoSubbands[i]; x++) {
				float a0_r, a0_i, a1_r, a1_i;
				float bw, bw2;
				int q, p, k, g;

				/* find the low and high band for patching */
				k = sbr.kx+x;
				for(q = 0; q<i; q++) {
					k += sbr.patchNoSubbands[q];
				}
				p = sbr.patchStartSubband[i]+x;

				g = sbr.table_map_k_to_g[k];

				bw = sbr.bwArray[ch][g];
				bw2 = bw*bw;

				/* do the patching */
				/* with or without filtering */
				if(bw2>0) {
					float temp1_r, temp2_r, temp3_r;
					float temp1_i, temp2_i, temp3_i;
					calc_prediction_coef(sbr, Xlow, alpha_0, alpha_1, p);

					a0_r = (alpha_0[p][0]*bw);
					a1_r = (alpha_1[p][0]*bw2);
					a0_i = (alpha_0[p][1]*bw);
					a1_i = (alpha_1[p][1]*bw2);

					temp2_r = (Xlow[first-2+offset][p][0]);
					temp3_r = (Xlow[first-1+offset][p][0]);
					temp2_i = (Xlow[first-2+offset][p][1]);
					temp3_i = (Xlow[first-1+offset][p][1]);
					for(l = first; l<last; l++) {
						temp1_r = temp2_r;
						temp2_r = temp3_r;
						temp3_r = (Xlow[l+offset][p][0]);
						temp1_i = temp2_i;
						temp2_i = temp3_i;
						temp3_i = (Xlow[l+offset][p][1]);

						Xhigh[l+offset][k][0]
							= temp3_r
							+((a0_r*temp2_r)
							-(a0_i*temp2_i)
							+(a1_r*temp1_r)
							-(a1_i*temp1_i));
						Xhigh[l+offset][k][1]
							= temp3_i
							+((a0_i*temp2_r)
							+(a0_r*temp2_i)
							+(a1_i*temp1_r)
							+(a1_r*temp1_i));
					}
				}
				else {
					for(l = first; l<last; l++) {
						Xhigh[l+offset][k][0] = Xlow[l+offset][p][0];
						Xhigh[l+offset][k][1] = Xlow[l+offset][p][1];
					}
				}
			}
		}

		if(sbr.Reset) {
			FBT.limiter_frequency_table(sbr);
		}
	}

	private static void auto_correlation(SBR sbr, acorr_coef ac, float[][][] buffer,
		int bd, int len) {
		float r01r = 0, r01i = 0, r02r = 0, r02i = 0, r11r = 0;
		float temp1_r, temp1_i, temp2_r, temp2_i, temp3_r, temp3_i, temp4_r, temp4_i, temp5_r, temp5_i;
		float rel = 1.0f/(1+1e-6f);
		int j;
		int offset = sbr.tHFAdj;

		temp2_r = buffer[offset-2][bd][0];
		temp2_i = buffer[offset-2][bd][1];
		temp3_r = buffer[offset-1][bd][0];
		temp3_i = buffer[offset-1][bd][1];
		// Save these because they are needed after loop
		temp4_r = temp2_r;
		temp4_i = temp2_i;
		temp5_r = temp3_r;
		temp5_i = temp3_i;

		for(j = offset; j<len+offset; j++) {
			temp1_r = temp2_r; // temp1_r = QMF_RE(buffer[j-2][bd];
			temp1_i = temp2_i; // temp1_i = QMF_IM(buffer[j-2][bd];
			temp2_r = temp3_r; // temp2_r = QMF_RE(buffer[j-1][bd];
			temp2_i = temp3_i; // temp2_i = QMF_IM(buffer[j-1][bd];
			temp3_r = buffer[j][bd][0];
			temp3_i = buffer[j][bd][1];
			r01r += temp3_r*temp2_r+temp3_i*temp2_i;
			r01i += temp3_i*temp2_r-temp3_r*temp2_i;
			r02r += temp3_r*temp1_r+temp3_i*temp1_i;
			r02i += temp3_i*temp1_r-temp3_r*temp1_i;
			r11r += temp2_r*temp2_r+temp2_i*temp2_i;
		}

		// These are actual values in temporary variable at this point
		// temp1_r = QMF_RE(buffer[len+offset-1-2][bd];
		// temp1_i = QMF_IM(buffer[len+offset-1-2][bd];
		// temp2_r = QMF_RE(buffer[len+offset-1-1][bd];
		// temp2_i = QMF_IM(buffer[len+offset-1-1][bd];
		// temp3_r = QMF_RE(buffer[len+offset-1][bd]);
		// temp3_i = QMF_IM(buffer[len+offset-1][bd]);
		// temp4_r = QMF_RE(buffer[offset-2][bd]);
		// temp4_i = QMF_IM(buffer[offset-2][bd]);
		// temp5_r = QMF_RE(buffer[offset-1][bd]);
		// temp5_i = QMF_IM(buffer[offset-1][bd]);
		ac.r12[0] = r01r
			-(temp3_r*temp2_r+temp3_i*temp2_i)
			+(temp5_r*temp4_r+temp5_i*temp4_i);
		ac.r12[1] = r01i
			-(temp3_i*temp2_r-temp3_r*temp2_i)
			+(temp5_i*temp4_r-temp5_r*temp4_i);
		ac.r22[0] = r11r
			-(temp2_r*temp2_r+temp2_i*temp2_i)
			+(temp4_r*temp4_r+temp4_i*temp4_i);

		ac.r01[0] = r01r;
		ac.r01[1] = r01i;
		ac.r02[0] = r02r;
		ac.r02[1] = r02i;
		ac.r11[0] = r11r;

		ac.det = (ac.r11[0]*ac.r22[0])-(rel*((ac.r12[0]*ac.r12[0])+(ac.r12[1]*ac.r12[1])));
	}

	/* calculate linear prediction coefficients using the covariance method */
	private static void calc_prediction_coef(SBR sbr, float[][][] Xlow,
		float[][] alpha_0, float[][] alpha_1, int k) {
		float tmp;
		acorr_coef ac = new acorr_coef();

		auto_correlation(sbr, ac, Xlow, k, sbr.numTimeSlotsRate+6);

		if(ac.det==0) {
			alpha_1[k][0] = 0;
			alpha_1[k][1] = 0;
		}
		else {
			tmp = 1.0f/ac.det;
			alpha_1[k][0] = ((ac.r01[0]*ac.r12[0])-(ac.r01[1]*ac.r12[1])-(ac.r02[0]*ac.r11[0]))*tmp;
			alpha_1[k][1] = ((ac.r01[1]*ac.r12[0])+(ac.r01[0]*ac.r12[1])-(ac.r02[1]*ac.r11[0]))*tmp;
		}

		if(ac.r11[0]==0) {
			alpha_0[k][0] = 0;
			alpha_0[k][1] = 0;
		}
		else {
			tmp = 1.0f/ac.r11[0];
			alpha_0[k][0] = -(ac.r01[0]+(alpha_1[k][0]*ac.r12[0])+(alpha_1[k][1]*ac.r12[1]))*tmp;
			alpha_0[k][1] = -(ac.r01[1]+(alpha_1[k][1]*ac.r12[0])-(alpha_1[k][0]*ac.r12[1]))*tmp;
		}

		if(((alpha_0[k][0]*alpha_0[k][0])+(alpha_0[k][1]*alpha_0[k][1])>=16.0f)
			||((alpha_1[k][0]*alpha_1[k][0])+(alpha_1[k][1]*alpha_1[k][1])>=16.0f)) {
			alpha_0[k][0] = 0;
			alpha_0[k][1] = 0;
			alpha_1[k][0] = 0;
			alpha_1[k][1] = 0;
		}
	}

	/* FIXED POINT: bwArray = COEF */
	private static float mapNewBw(int invf_mode, int invf_mode_prev) {
		switch(invf_mode) {
			case 1: /* LOW */

				if(invf_mode_prev==0) /* NONE */
					return 0.6f;
				else
					return 0.75f;

			case 2: /* MID */

				return 0.9f;

			case 3: /* HIGH */

				return 0.98f;

			default: /* NONE */

				if(invf_mode_prev==1) /* LOW */
					return 0.6f;
				else
					return 0.0f;
		}
	}

	/* FIXED POINT: bwArray = COEF */
	private static void calc_chirp_factors(SBR sbr, int ch) {
		int i;

		for(i = 0; i<sbr.N_Q; i++) {
			sbr.bwArray[ch][i] = mapNewBw(sbr.bs_invf_mode[ch][i], sbr.bs_invf_mode_prev[ch][i]);

			if(sbr.bwArray[ch][i]<sbr.bwArray_prev[ch][i])
				sbr.bwArray[ch][i] = (sbr.bwArray[ch][i]*0.75f)+(sbr.bwArray_prev[ch][i]*0.25f);
			else
				sbr.bwArray[ch][i] = (sbr.bwArray[ch][i]*0.90625f)+(sbr.bwArray_prev[ch][i]*0.09375f);

			if(sbr.bwArray[ch][i]<0.015625f)
				sbr.bwArray[ch][i] = 0.0f;

			if(sbr.bwArray[ch][i]>=0.99609375f)
				sbr.bwArray[ch][i] = 0.99609375f;

			sbr.bwArray_prev[ch][i] = sbr.bwArray[ch][i];
			sbr.bs_invf_mode_prev[ch][i] = sbr.bs_invf_mode[ch][i];
		}
	}

	private static void patch_construction(SBR sbr) {
		int i, k;
		int odd, sb;
		int msb = sbr.k0;
		int usb = sbr.kx;
		/* (uint8_t)(2.048e6/sbr.sample_rate + 0.5); */
		int goalSb = goalSbTab[sbr.sample_rate.getIndex()];

		sbr.noPatches = 0;

		if(goalSb<(sbr.kx+sbr.M)) {
			for(i = 0, k = 0; sbr.f_master[i]<goalSb; i++) {
				k = i+1;
			}
		}
		else {
			k = sbr.N_master;
		}

		if(sbr.N_master==0) {
			sbr.noPatches = 0;
			sbr.patchNoSubbands[0] = 0;
			sbr.patchStartSubband[0] = 0;

			return;
		}

		do {
			int j = k+1;

			do {
				j--;

				sb = sbr.f_master[j];
				odd = (sb-2+sbr.k0)%2;
			}
			while(sb>(sbr.k0-1+msb-odd));

			sbr.patchNoSubbands[sbr.noPatches] = Math.max(sb-usb, 0);
			sbr.patchStartSubband[sbr.noPatches] = sbr.k0-odd
				-sbr.patchNoSubbands[sbr.noPatches];

			if(sbr.patchNoSubbands[sbr.noPatches]>0) {
				usb = sb;
				msb = sb;
				sbr.noPatches++;
			}
			else {
				msb = sbr.kx;
			}

			if(sbr.f_master[k]-sb<3)
				k = sbr.N_master;
		}
		while(sb!=(sbr.kx+sbr.M));

		if((sbr.patchNoSubbands[sbr.noPatches-1]<3)&&(sbr.noPatches>1)) {
			sbr.noPatches--;
		}

		sbr.noPatches = Math.min(sbr.noPatches, 5);
	}
}


package net.sourceforge.jaad.aac.syntax;

import static net.sourceforge.jaad.aac.Profile.*;

import org.jcodec.platform.Platform;

import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.AACDecoderConfig;
import net.sourceforge.jaad.aac.Profile;
import net.sourceforge.jaad.aac.SampleFrequency;
import net.sourceforge.jaad.aac.tools.ICPrediction;
import static java.lang.System.arraycopy;

import net.sourceforge.jaad.aac.filterbank.FilterBank;

/**
 * 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.
 *
 * @author in-somnia
 */
public class ICSInfo implements SyntaxConstants, ScaleFactorBands {
    public static class LTPrediction implements SyntaxConstants {

        private static final float[] CODEBOOK = {
            0.570829f,
            0.696616f,
            0.813004f,
            0.911304f,
            0.984900f,
            1.067894f,
            1.194601f,
            1.369533f
        };
        private final int frameLength;
        private final int[] states;
        private int coef, lag, lastBand;
        private boolean lagUpdate;
        private boolean[] shortUsed, shortLagPresent, longUsed;
        private int[] shortLag;

        public LTPrediction(int frameLength) {
            this.frameLength = frameLength;
            states = new int[4*frameLength];
        }

        public void decode(IBitStream _in, ICSInfo info, Profile profile) throws AACException {
            lag = 0;
            if(profile.equals(Profile.AAC_LD)) {
                lagUpdate = _in.readBool();
                if(lagUpdate) lag = _in.readBits(10);
            }
            else lag = _in.readBits(11);
            if(lag>(frameLength<<1)) throw new AACException("LTP lag too large: "+lag);
            coef = _in.readBits(3);

            final int windowCount = info.getWindowCount();

            if(info.isEightShortFrame()) {
                shortUsed = new boolean[windowCount];
                shortLagPresent = new boolean[windowCount];
                shortLag = new int[windowCount];
                for(int w = 0; w<windowCount; w++) {
                    if((shortUsed[w] = _in.readBool())) {
                        shortLagPresent[w] = _in.readBool();
                        if(shortLagPresent[w]) shortLag[w] = _in.readBits(4);
                    }
                }
            }
            else {
                lastBand = Math.min(info.getMaxSFB(), MAX_LTP_SFB);
                longUsed = new boolean[lastBand];
                for(int i = 0; i<lastBand; i++) {
                    longUsed[i] = _in.readBool();
                }
            }
        }

        public void setPredictionUnused(int sfb) {
            if(longUsed!=null) longUsed[sfb] = false;
        }

        public void process(ICStream ics, float[] data, FilterBank filterBank, SampleFrequency sf) {
            final ICSInfo info = ics.getInfo();

            if(!info.isEightShortFrame()) {
                final int samples = frameLength<<1;
                final float[] _in = new float[2048];
                final float[] out = new float[2048];

                for(int i = 0; i<samples; i++) {
                    _in[i] = states[samples+i-lag]*CODEBOOK[coef];
                }

                filterBank.processLTP(info.getWindowSequence(), info.getWindowShape(ICSInfo.CURRENT),
                        info.getWindowShape(ICSInfo.PREVIOUS), _in, out);

                if(ics.isTNSDataPresent()) ics.getTNS().process(ics, out, sf, true);

                final int[] swbOffsets = info.getSWBOffsets();
                final int swbOffsetMax = info.getSWBOffsetMax();
                int low, high, bin;
                for(int sfb = 0; sfb<lastBand; sfb++) {
                    if(longUsed[sfb]) {
                        low = swbOffsets[sfb];
                        high = Math.min(swbOffsets[sfb+1], swbOffsetMax);

                        for(bin = low; bin<high; bin++) {
                            data[bin] += out[bin];
                        }
                    }
                }
            }
        }

        public void updateState(float[] time, float[] overlap, Profile profile) {
            int i;
            if(profile.equals(Profile.AAC_LD)) {
                for(i = 0; i<frameLength; i++) {
                    states[i] = states[i+frameLength];
                    states[frameLength+i] = states[i+(frameLength*2)];
                    states[(frameLength*2)+i] = Math.round(time[i]);
                    states[(frameLength*3)+i] = Math.round(overlap[i]);
                }
            }
            else {
                for(i = 0; i<frameLength; i++) {
                    states[i] = states[i+frameLength];
                    states[frameLength+i] = Math.round(time[i]);
                    states[(frameLength*2)+i] = Math.round(overlap[i]);
                }
            }
        }

        public static boolean isLTPProfile(Profile profile) {
            return profile.equals(Profile.AAC_LTP)||profile.equals(Profile.ER_AAC_LTP)||profile.equals(Profile.AAC_LD);
        }

        public void copy(LTPrediction ltp) {
            arraycopy(ltp.states, 0, states, 0, states.length);
            coef = ltp.coef;
            lag = ltp.lag;
            lastBand = ltp.lastBand;
            lagUpdate = ltp.lagUpdate;
            shortUsed = Platform.copyOfBool(ltp.shortUsed, ltp.shortUsed.length);
            shortLagPresent = Platform.copyOfBool(ltp.shortLagPresent, ltp.shortLagPresent.length);
            shortLag = Platform.copyOfInt(ltp.shortLag, ltp.shortLag.length);
            longUsed = Platform.copyOfBool(ltp.longUsed, ltp.longUsed.length);
        }
    }

	public static final int WINDOW_SHAPE_SINE = 0;
	public static final int WINDOW_SHAPE_KAISER = 1;
	public static final int PREVIOUS = 0;
	public static final int CURRENT = 1;

	public static enum WindowSequence {
		ONLY_LONG_SEQUENCE,
		LONG_START_SEQUENCE,
		EIGHT_SHORT_SEQUENCE,
		LONG_STOP_SEQUENCE;
	}

	public static WindowSequence windowSequenceFromInt(int i) throws AACException {
        WindowSequence[] values = WindowSequence.values();
        if (i >= values.length) {
            throw new AACException("unknown window sequence type");
        }
        return values[i];
    }

	private final int frameLength;
	private WindowSequence windowSequence;
	private int[] windowShape;
	private int maxSFB;
	//prediction
	private boolean predictionDataPresent;
	private ICPrediction icPredict;
	boolean ltpData1Present, ltpData2Present;
	private LTPrediction ltPredict1, ltPredict2;
	//windows/sfbs
	private int windowCount;
	private int windowGroupCount;
	private int[] windowGroupLength;
	private int swbCount;
	private int[] swbOffsets;

	public ICSInfo(int frameLength) {
		this.frameLength = frameLength;
		windowShape = new int[2];
		windowSequence = WindowSequence.ONLY_LONG_SEQUENCE;
		windowGroupLength = new int[MAX_WINDOW_GROUP_COUNT];
		ltpData1Present = false;
		ltpData2Present = false;
	}

	/* ========== decoding ========== */
	public void decode(IBitStream _in, AACDecoderConfig conf, boolean commonWindow) throws AACException {
		final SampleFrequency sf = conf.getSampleFrequency();
		if(sf.equals(SampleFrequency.SAMPLE_FREQUENCY_NONE)) throw new AACException("invalid sample frequency");

		_in.skipBit(); //reserved
		windowSequence = windowSequenceFromInt(_in.readBits(2));
		windowShape[PREVIOUS] = windowShape[CURRENT];
		windowShape[CURRENT] = _in.readBit();

		windowGroupCount = 1;
		windowGroupLength[0] = 1;
		if(windowSequence.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) {
			maxSFB = _in.readBits(4);
			int i;
			for(i = 0; i<7; i++) {
				if(_in.readBool()) windowGroupLength[windowGroupCount-1]++;
				else {
					windowGroupCount++;
					windowGroupLength[windowGroupCount-1] = 1;
				}
			}
			windowCount = 8;
			swbOffsets = SWB_OFFSET_SHORT_WINDOW[sf.getIndex()];
			swbCount = SWB_SHORT_WINDOW_COUNT[sf.getIndex()];
			predictionDataPresent = false;
		}
		else {
			maxSFB = _in.readBits(6);
			windowCount = 1;
			swbOffsets = SWB_OFFSET_LONG_WINDOW[sf.getIndex()];
			swbCount = SWB_LONG_WINDOW_COUNT[sf.getIndex()];
			predictionDataPresent = _in.readBool();
			if(predictionDataPresent) readPredictionData(_in, conf.getProfile(), sf, commonWindow);
		}
	}

	private void readPredictionData(IBitStream _in, Profile profile, SampleFrequency sf, boolean commonWindow) throws AACException {
	    if (AAC_MAIN == profile) {
            if(icPredict==null) icPredict = new ICPrediction();
            icPredict.decode(_in, maxSFB, sf);
	    } else if (AAC_LTP == profile) {
            if(ltpData1Present = _in.readBool()) {
                if(ltPredict1==null) ltPredict1 = new LTPrediction(frameLength);
                ltPredict1.decode(_in, this, profile);
            }
            if(commonWindow) {
                if(ltpData2Present = _in.readBool()) {
                    if(ltPredict2==null) ltPredict2 = new LTPrediction(frameLength);
                    ltPredict2.decode(_in, this, profile);
                }
            }
	    } else if(ER_AAC_LTP == profile) {
            if(!commonWindow) {
                if(ltpData1Present = _in.readBool()) {
                    if(ltPredict1==null) ltPredict1 = new LTPrediction(frameLength);
                    ltPredict1.decode(_in, this, profile);
                }
            }
	    } else {
            throw new AACException("unexpected profile for LTP: "+profile);
	    }
	}

	/* =========== gets ============ */
	public int getMaxSFB() {
		return maxSFB;
	}

	public int getSWBCount() {
		return swbCount;
	}

	public int[] getSWBOffsets() {
		return swbOffsets;
	}

	public int getSWBOffsetMax() {
		return swbOffsets[swbCount];
	}

	public int getWindowCount() {
		return windowCount;
	}

	public int getWindowGroupCount() {
		return windowGroupCount;
	}

	public int getWindowGroupLength(int g) {
		return windowGroupLength[g];
	}

	public WindowSequence getWindowSequence() {
		return windowSequence;
	}

	public boolean isEightShortFrame() {
		return windowSequence.equals(WindowSequence.EIGHT_SHORT_SEQUENCE);
	}

	public int getWindowShape(int index) {
		return windowShape[index];
	}

	public boolean isICPredictionPresent() {
		return predictionDataPresent;
	}

	public ICPrediction getICPrediction() {
		return icPredict;
	}

	public boolean isLTPrediction1Present() {
		return ltpData1Present;
	}

	public LTPrediction getLTPrediction1() {
		return ltPredict1;
	}

	public boolean isLTPrediction2Present() {
		return ltpData2Present;
	}

	public LTPrediction getLTPrediction2() {
		return ltPredict2;
	}

	public void unsetPredictionSFB(int sfb) {
		if(predictionDataPresent) icPredict.setPredictionUnused(sfb);
		if(ltpData1Present) ltPredict1.setPredictionUnused(sfb);
		if(ltpData2Present) ltPredict2.setPredictionUnused(sfb);
	}

	public void setData(ICSInfo info) {
		windowSequence = WindowSequence.valueOf(info.windowSequence.name());
		windowShape[PREVIOUS] = windowShape[CURRENT];
		windowShape[CURRENT] = info.windowShape[CURRENT];
		maxSFB = info.maxSFB;
		predictionDataPresent = info.predictionDataPresent;
		if(predictionDataPresent) icPredict = info.icPredict;
		ltpData1Present = info.ltpData1Present;
		if(ltpData1Present) {
			ltPredict1.copy(info.ltPredict1);
			ltPredict2.copy(info.ltPredict2);
		}
		windowCount = info.windowCount;
		windowGroupCount = info.windowGroupCount;
		windowGroupLength = Platform.copyOfInt(info.windowGroupLength, info.windowGroupLength.length);
		swbCount = info.swbCount;
		swbOffsets = Platform.copyOfInt(info.swbOffsets, info.swbOffsets.length);
	}
}


package org.jcodec.containers.mp4;
import org.jcodec.common.model.RationalLarge;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TimecodeSampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.demuxer.TimecodeMP4DemuxerTrack;

import java.io.IOException;
import java.util.List;

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

    /**
     * Calculates track duration considering edits
     *
     * @param track
     * @return
     */
    public static long getEditedDuration(TrakBox track) {
        List<Edit> edits = track.getEdits();
        if (edits == null)
            return track.getDuration();

        long duration = 0;
        for (Edit edit : edits) {
            duration += edit.getDuration();
        }
        return duration;
    }

    /**
     * Finds timevalue of a frame number
     *
     * might be an expensive operation sinse it traverses compressed time to
     * sample table
     *
     * @param frameNumber
     * @return
     */
    public static long frameToTimevalue(TrakBox trak, int frameNumber) {
        TimeToSampleBox stts = NodeBox.findFirstPath(trak, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts"));
        TimeToSampleEntry[] timeToSamples = stts.getEntries();
        long pts = 0;
        int sttsInd = 0, sttsSubInd = frameNumber;
        while (sttsSubInd >= timeToSamples[sttsInd].getSampleCount()) {
            sttsSubInd -= timeToSamples[sttsInd].getSampleCount();
            pts += timeToSamples[sttsInd].getSampleCount() * timeToSamples[sttsInd].getSampleDuration();
            sttsInd++;
        }

        return pts + timeToSamples[sttsInd].getSampleDuration() * sttsSubInd;
    }

    /**
     * Finds frame by timevalue
     *
     * @param tv
     * @return
     */
    public static int timevalueToFrame(TrakBox trak, long tv) {
        TimeToSampleEntry[] tts = NodeBox.findFirstPath(trak, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts")).getEntries();
        int frame = 0;
        for (int i = 0; tv > 0 && i < tts.length; i++) {
            long rem = tv / tts[i].getSampleDuration();
            tv -= tts[i].getSampleCount() * tts[i].getSampleDuration();
            frame += tv > 0 ? tts[i].getSampleCount() : rem;
        }

        return frame;
    }

    /**
     * Converts media timevalue to edited timevalue
     *
     * @param trak
     * @param mediaTv
     * @param movieTimescale
     * @return
     */
    public static long mediaToEdited(TrakBox trak, long mediaTv, int movieTimescale) {
        if (trak.getEdits() == null)
            return mediaTv;
        long accum = 0;
        for (Edit edit : trak.getEdits()) {
            if (mediaTv < edit.getMediaTime())
                return accum;
            long duration = trak.rescale(edit.getDuration(), movieTimescale);
            if (edit.getMediaTime() != -1
                    && (mediaTv >= edit.getMediaTime() && mediaTv < edit.getMediaTime() + duration)) {
                accum += mediaTv - edit.getMediaTime();
                break;
            }
            accum += duration;
        }

        return accum;
    }

    /**
     * Converts edited timevalue to media timevalue
     *
     * @param trak
     * @param mediaTv
     * @param movieTimescale
     * @return
     */
    public static long editedToMedia(TrakBox trak, long editedTv, int movieTimescale) {
        if (trak.getEdits() == null)
            return editedTv;
        long accum = 0;
        for (Edit edit : trak.getEdits()) {
            long duration = trak.rescale(edit.getDuration(), movieTimescale);

            if (accum + duration > editedTv) {
                return edit.getMediaTime() + editedTv - accum;
            }

            accum += duration;
        }

        return accum;
    }

    /**
     * Calculates frame number as it shows in quicktime player
     *
     * @param movie
     * @param mediaFrameNo
     * @return
     */
    public static int qtPlayerFrameNo(MovieBox movie, int mediaFrameNo) {
        TrakBox videoTrack = movie.getVideoTrack();

        long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());

        return tv2QTFrameNo(movie, editedTv);
    }

    public static int tv2QTFrameNo(MovieBox movie, long tv) {
        TrakBox videoTrack = movie.getVideoTrack();
        TrakBox timecodeTrack = movie.getTimecodeTrack();

        if (timecodeTrack != null && BoxUtil.containsBox2(videoTrack, "tref", "tmcd")) {
            return timevalueToTimecodeFrame(timecodeTrack, new RationalLarge(tv, videoTrack.getTimescale()),
                    movie.getTimescale());
        } else {
            return timevalueToFrame(videoTrack, tv);
        }
    }

    /**
     * Calculates and formats standard time as in Quicktime player
     *
     * @param movie
     * @param mediaFrameNo
     * @return
     */
    public static String qtPlayerTime(MovieBox movie, int mediaFrameNo) {
        TrakBox videoTrack = movie.getVideoTrack();
        long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());

        int sec = (int) (editedTv / videoTrack.getTimescale());
        return String.format("%02d", sec / 3600) + "_" + String.format("%02d", (sec % 3600) / 60) + "_"
                + String.format("%02d", sec % 60);
    }

    /**
     * Calculates and formats tape timecode as in Quicktime player
     *
     * @param timecodeTrack
     * @param tv
     * @param startCounter
     * @return
     * @throws IOException
     */
    public static String qtPlayerTimecodeFromMovie(MovieBox movie, TimecodeMP4DemuxerTrack timecodeTrack, int mediaFrameNo)
            throws IOException {
        TrakBox videoTrack = movie.getVideoTrack();
        long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());

        TrakBox tt = timecodeTrack.getBox();
        int ttTimescale = tt.getTimescale();
        long ttTv = editedToMedia(tt, editedTv * ttTimescale / videoTrack.getTimescale(), movie.getTimescale());

        return formatTimecode(
                timecodeTrack.getBox(),
                timecodeTrack.getStartTimecode()
                        + timevalueToTimecodeFrame(timecodeTrack.getBox(), new RationalLarge(ttTv, ttTimescale),
                                movie.getTimescale()));
    }

    /**
     * Calculates and formats tape timecode as in Quicktime player
     *
     * @param timecodeTrack
     * @param tv
     * @param startCounter
     * @return
     * @throws IOException
     */
    public static String qtPlayerTimecode(TimecodeMP4DemuxerTrack timecodeTrack, RationalLarge tv, int movieTimescale)
            throws IOException {
        TrakBox tt = timecodeTrack.getBox();
        int ttTimescale = tt.getTimescale();
        long ttTv = editedToMedia(tt, tv.multiplyS(ttTimescale), movieTimescale);

        return formatTimecode(
                timecodeTrack.getBox(),
                timecodeTrack.getStartTimecode()
                        + timevalueToTimecodeFrame(timecodeTrack.getBox(), new RationalLarge(ttTv, ttTimescale),
                                movieTimescale));
    }

    /**
     * Converts timevalue to frame number based on timecode track
     *
     * @param timecodeTrack
     * @param tv
     * @return
     */
    public static int timevalueToTimecodeFrame(TrakBox timecodeTrack, RationalLarge tv, int movieTimescale) {
        TimecodeSampleEntry se = (TimecodeSampleEntry) timecodeTrack.getSampleEntries()[0];
        return (int) ((2 * tv.multiplyS(se.getTimescale()) / se.getFrameDuration()) + 1) / 2;
    }

    /**
     * Formats tape timecode based on frame counter
     *
     * @param timecodeTrack
     * @param counter
     * @return
     */
    public static String formatTimecode(TrakBox timecodeTrack, int counter) {
        TimecodeSampleEntry tmcd = NodeBox.findFirstPath(timecodeTrack, TimecodeSampleEntry.class, Box.path("mdia.minf.stbl.stsd.tmcd"));
        byte nf = tmcd.getNumFrames();

        String tc = String.format("%02d", counter % nf);
        counter /= nf;
        tc = String.format("%02d", counter % 60) + ":" + tc;
        counter /= 60;
        tc = String.format("%02d", counter % 60) + ":" + tc;
        counter /= 60;
        tc = String.format("%02d", counter) + ":" + tc;

        return tc;
    }
}


package org.jcodec.codecs.common.biari;

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

    private static int[] CLEAR_MASK = new int[] { ~(0xf | (0xf << 28)), ~(0xf | (0xf0 << 28)), ~(0xf | (0xf00 << 28)),
            ~(0xf | (0xf000 << 28)), ~(0xf | (0xf0000 << 28)), ~(0xf | (0xf00000 << 28)), ~(0xf | (0xf000000 << 28)) };

    /**
     * Creates packed 4bit list with 7 values in it
     *
     * @return
     */
    public static int _7(int val0, int val1, int val2, int val3, int val4, int val5, int val6) {
        return (7 << 28) | ((val0 & 0xf) << 24) | ((val1 & 0xf) << 20) | ((val2 & 0xf) << 16) | ((val3 & 0xf) << 12)
                | ((val4 & 0xf) << 8) | ((val5 & 0xf) << 4) | ((val6 & 0xf));
    }

    public static int _3(int val0, int val1, int val2) {
        return _7(val0, val1, val2, 0, 0, 0, 0);
    }

    /**
     * Sets a 4 bit value into the list
     *
     * @param list
     * @param val
     * @param n
     * @return
     */
    public static int set(int list, int val, int n) {
        int cnt = (list >> 28) & 0xf;
        int newc = n + 1;
        cnt = newc > cnt ? newc : cnt;
        return (list & CLEAR_MASK[n]) | ((val & 0xff) << (n << 2)) | (cnt << 28);
    }

    public static int get(int list, int n) {
        if (n > 6)
            return 0;
        return (list >> (n << 2)) & 0xff;
    }
}


package org.jcodec.common.model;

/**
 *
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * @author The JCodec project
 *
 */
public final class ColorSpace {
    public static final int MAX_PLANES = 4;

    public int nComp;

    public int[] compPlane;

    public int[] compWidth;

    public int[] compHeight;

    public boolean planar;

    private String _name;

    public int bitsPerPixel;

    private ColorSpace(String name, int nComp, int[] compPlane, int[] compWidth, int[] compHeight, boolean planar) {
        this._name = name;
        this.nComp = nComp;
        this.compPlane = compPlane;
        this.compWidth = compWidth;
        this.compHeight = compHeight;
        this.planar = planar;
        this.bitsPerPixel = calcBitsPerPixel(nComp, compWidth, compHeight);
    }

    @Override
    public String toString() {
        return _name;
    }

    private static int calcBitsPerPixel(int nComp, int[] compWidth, int[] compHeight) {
        int bitsPerPixel = 0;
        for (int i = 0; i < nComp; i++) {
            bitsPerPixel += ((8 >> compWidth[i]) >> compHeight[i]);
        }
        return bitsPerPixel;
    }

    public int getWidthMask() {
        return ~(nComp > 1 ? compWidth[1] : 0);
    }

    public int getHeightMask() {
        return ~(nComp > 1 ? compHeight[1] : 0);
    }

    /**
     * Determines if two colors match. Aside from simply comparing the objects
     * this function also takes into account lables ANY, ANY_INTERLEAVED, ANY
     * PLANAR.
     *
     * @param inputColor
     * @return True if the color is the same or matches the label.
     */
    public boolean matches(ColorSpace inputColor) {
        if (inputColor == this)
            return true;
        if (inputColor == ANY || this == ANY)
            return true;
        if ((inputColor == ANY_INTERLEAVED || this == ANY_INTERLEAVED || inputColor == ANY_PLANAR || this == ANY_PLANAR)
                && inputColor.planar == this.planar)
            return true;
        return false;
    }

    /**
     * Calculates the component size based on the fullt size and color subsampling of the given component index.
     * @param size
     * @return Component size
     */
    public Size compSize(Size size, int comp) {
        if (compWidth[comp] == 0 && compHeight[comp] == 0)
            return size;
        return new Size(size.getWidth() >> compWidth[comp], size.getHeight() >> compHeight[comp]);
    }

    private static final int[] _000 = new int[] { 0, 0, 0 };
    private static final int[] _011 = new int[] { 0, 1, 1 };
    private static final int[] _012 = new int[] { 0, 1, 2 };
    public final static ColorSpace BGR = new ColorSpace("BGR", 3, _000, _000, _000, false);
    public final static ColorSpace RGB = new ColorSpace("RGB", 3, _000, _000, _000, false);
    public final static ColorSpace YUV420 = new ColorSpace("YUV420", 3, _012, _011, _011, true);
    public final static ColorSpace YUV420J = new ColorSpace("YUV420J", 3, _012, _011, _011, true);
    public final static ColorSpace YUV422 = new ColorSpace("YUV422", 3, _012, _011, _000, true);
    public final static ColorSpace YUV422J = new ColorSpace("YUV422J", 3, _012, _011, _000, true);
    public final static ColorSpace YUV444 = new ColorSpace("YUV444", 3, _012, _000, _000, true);
    public final static ColorSpace YUV444J = new ColorSpace("YUV444J", 3, _012, _000, _000, true);
    public final static ColorSpace YUV422_10 = new ColorSpace("YUV422_10", 3, _012, _011, _000, true);
    public final static ColorSpace GREY = new ColorSpace("GREY", 1, new int[] { 0 }, new int[] { 0 }, new int[] { 0 }, true);
    public final static ColorSpace MONO = new ColorSpace("MONO", 1, _000, _000, _000, true);
    public final static ColorSpace YUV444_10 = new ColorSpace("YUV444_10", 3, _012, _000, _000, true);

    /**
     * Any color space, used in the cases where any color space will do.
     */
    public final static ColorSpace ANY = new ColorSpace("ANY", 0, null, null, null, true);

    /**
     * Any planar color space, used in the cases where any planar color space will do.
     */
    public final static ColorSpace ANY_PLANAR = new ColorSpace("ANY_PLANAR", 0, null, null, null, true);

    /**
     * Any interleaved color space, used in the cases where any interleaved color space will do.
     */
    public final static ColorSpace ANY_INTERLEAVED = new ColorSpace("ANY_INTERLEAVED", 0, null, null, null, false);

    /**
     * Same color, used in filters to declare that the color stays unchanged.
     */
    public final static ColorSpace SAME = new ColorSpace("SAME", 0, null, null, null, false);

}


package org.jcodec.api;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

import org.jcodec.api.specific.AVCMP4Adaptor;
import org.jcodec.api.specific.ContainerAdaptor;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.Format;
import org.jcodec.common.JCodecUtil;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.io.FileChannelWrapper;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Picture;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;

import static org.jcodec.common.Codec.H264;
import static org.jcodec.common.Format.MOV;
import static org.jcodec.common.Format.MPEG_PS;
import static org.jcodec.common.Format.MPEG_TS;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Extracts frames from a movie into uncompressed images suitable for
 * processing.
 *
 * Supports going to random points inside of a movie ( seeking ) by frame number
 * of by second.
 *
 * NOTE: Supports only AVC ( H.264 ) in MP4 ( ISO BMF, QuickTime ) at this
 * point.
 *
 * @author The JCodec project
 *
 */
public class FrameGrab {

    private SeekableDemuxerTrack videoTrack;
    private ContainerAdaptor decoder;
    //ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread
    //FIXME: potential memory leak: non-static ThreadLocal
    private final ThreadLocal<byte[][]> buffers;;

    public static FrameGrab createFrameGrab(SeekableByteChannel _in) throws IOException, JCodecException {
        ByteBuffer header = ByteBuffer.allocate(65536);
        _in.read(header);
        header.flip();
        Format detectFormat = JCodecUtil.detectFormatBuffer(header);
		if (detectFormat == null) {
			throw new UnsupportedFormatException("Could not detect the format of the input video.");
		}
        SeekableDemuxerTrack videoTrack_;

		if (MOV == detectFormat) {
            MP4Demuxer d1 = MP4Demuxer.createMP4Demuxer(_in);
            videoTrack_ = (SeekableDemuxerTrack)d1.getVideoTrack();
        } else if (MPEG_PS == detectFormat) {
            throw new UnsupportedFormatException("MPEG PS is temporarily unsupported.");
        } else if (MPEG_TS == detectFormat) {
            throw new UnsupportedFormatException("MPEG TS is temporarily unsupported.");
        } else {
            throw new UnsupportedFormatException("Container format is not supported by JCodec");
        }
        FrameGrab fg = new FrameGrab(videoTrack_, detectDecoder(videoTrack_));
        fg.decodeLeadingFrames();
        return fg;
    }

    public FrameGrab(SeekableDemuxerTrack videoTrack, ContainerAdaptor decoder) {
        this.videoTrack = videoTrack;
        this.decoder = decoder;
        buffers = new ThreadLocal<byte[][]>();
    }

    private SeekableDemuxerTrack sdt() throws JCodecException {
        if (!(videoTrack instanceof SeekableDemuxerTrack))
            throw new JCodecException("Not a seekable track");

        return (SeekableDemuxerTrack) videoTrack;
    }

    /**
     * Position frame grabber to a specific second in a movie. As a result the
     * next decoded frame will be precisely at the requested second.
     *
     * WARNING: potentially very slow. Use only when you absolutely need precise
     * seek. Tries to seek to exactly the requested second and for this it might
     * have to decode a sequence of frames from the closes key frame. Depending
     * on GOP structure this may be as many as 500 frames.
     *
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public FrameGrab seekToSecondPrecise(double second) throws IOException, JCodecException {
        sdt().seek(second);
        decodeLeadingFrames();
        return this;
    }

    /**
     * Position frame grabber to a specific frame in a movie. As a result the
     * next decoded frame will be precisely the requested frame number.
     *
     * WARNING: potentially very slow. Use only when you absolutely need precise
     * seek. Tries to seek to exactly the requested frame and for this it might
     * have to decode a sequence of frames from the closes key frame. Depending
     * on GOP structure this may be as many as 500 frames.
     *
     * @param frameNumber
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public FrameGrab seekToFramePrecise(int frameNumber) throws IOException, JCodecException {
        sdt().gotoFrame(frameNumber);
        decodeLeadingFrames();
        return this;
    }

    /**
     * Position frame grabber to a specific second in a movie.
     *
     * Performs a sloppy seek, meaning that it may actually not seek to exact
     * second requested, instead it will seek to the closest key frame
     *
     * NOTE: fast, as it just seeks to the closest previous key frame and
     * doesn't try to decode frames in the middle
     *
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public FrameGrab seekToSecondSloppy(double second) throws IOException, JCodecException {
        sdt().seek(second);
        goToPrevKeyframe();
        return this;
    }

    /**
     * Position frame grabber to a specific frame in a movie
     *
     * Performs a sloppy seek, meaning that it may actually not seek to exact
     * frame requested, instead it will seek to the closest key frame
     *
     * NOTE: fast, as it just seeks to the closest previous key frame and
     * doesn't try to decode frames in the middle
     *
     * @param frameNumber
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public FrameGrab seekToFrameSloppy(int frameNumber) throws IOException, JCodecException {
        sdt().gotoFrame(frameNumber);
        goToPrevKeyframe();
        return this;
    }

    private void goToPrevKeyframe() throws IOException, JCodecException {
        sdt().gotoFrame(detectKeyFrame((int) sdt().getCurFrame()));
    }

    private void decodeLeadingFrames() throws IOException, JCodecException {
        SeekableDemuxerTrack sdt = sdt();

        int curFrame = (int) sdt.getCurFrame();
        int keyFrame = detectKeyFrame(curFrame);
        sdt.gotoFrame(keyFrame);

        Packet frame = sdt.nextFrame();
        if (decoder == null)
            decoder = detectDecoder(sdt);

        while (frame.getFrameNo() < curFrame) {
            decoder.decodeFrame(frame, getBuffer());
            frame = sdt.nextFrame();
        }
        sdt.gotoFrame(curFrame);
    }

    private byte[][] getBuffer() {
        byte[][] buf = buffers.get();
        if (buf == null) {
            buf = decoder.allocatePicture();
            buffers.set(buf);
        }
        return buf;
    }

    private int detectKeyFrame(int start) throws IOException {
        int[] seekFrames = videoTrack.getMeta().getSeekFrames();
        if (seekFrames == null)
            return start;
        int prev = seekFrames[0];
        for (int i = 1; i < seekFrames.length; i++) {
            if (seekFrames[i] > start)
                break;
            prev = seekFrames[i];
        }
        return prev;
    }

    private static ContainerAdaptor detectDecoder(SeekableDemuxerTrack videoTrack) throws JCodecException {
        DemuxerTrackMeta meta = videoTrack.getMeta();
        if (H264 == meta.getCodec()) {
            return new AVCMP4Adaptor(meta);
        } else {
            throw new UnsupportedFormatException("Codec is not supported");
        }
    }

    /**
     * Get frame at current position in JCodec native image
     *
     * @return A decoded picture with metadata.
     * @throws IOException
     */
    public PictureWithMetadata getNativeFrameWithMetadata() throws IOException {
        Packet frame = videoTrack.nextFrame();
        if (frame == null)
            return null;

        Picture picture = decoder.decodeFrame(frame, getBuffer());
        return new PictureWithMetadata(picture, frame.getPtsD(), frame.getDurationD(), videoTrack.getMeta().getOrientation());
    }

    /**
     * Get frame at current position in JCodec native image
     *
     * @return
     * @throws IOException
     */
    public Picture getNativeFrame() throws IOException {
        Packet frame = videoTrack.nextFrame();
        if (frame == null)
            return null;

        return decoder.decodeFrame(frame, getBuffer());
    }

    /**
     * Get frame at a specified second as JCodec image
     *
     * @param file
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getFrameAtSec(File file, double second) throws IOException, JCodecException {
        FileChannelWrapper ch = null;
        try {
            ch = NIOUtils.readableChannel(file);
            return createFrameGrab(ch).seekToSecondPrecise(second).getNativeFrame();
        } finally {
            NIOUtils.closeQuietly(ch);
        }
    }

    /**
     * Get frame at a specified second as JCodec image
     *
     * @param file
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getFrameFromChannelAtSec(SeekableByteChannel file, double second)
            throws JCodecException, IOException {
        return createFrameGrab(file).seekToSecondPrecise(second).getNativeFrame();
    }

    /**
     * Get frame at a specified frame number as JCodec image
     *
     * @param file
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getFrameFromFile(File file, int frameNumber) throws IOException, JCodecException {
        FileChannelWrapper ch = null;
        try {
            ch = NIOUtils.readableChannel(file);
            return createFrameGrab(ch).seekToFramePrecise(frameNumber).getNativeFrame();
        } finally {
            NIOUtils.closeQuietly(ch);
        }
    }

    /**
     * Get frame at a specified frame number as JCodec image
     *
     * @param file
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getFrameFromChannel(SeekableByteChannel file, int frameNumber)
            throws JCodecException, IOException {
        return createFrameGrab(file).seekToFramePrecise(frameNumber).getNativeFrame();
    }

    /**
     * Get a specified frame by number from an already open demuxer track
     *
     * @param vt
     * @param decoder
     * @param frameNumber
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getNativeFrameAtFrame(SeekableDemuxerTrack vt, ContainerAdaptor decoder, int frameNumber)
            throws IOException, JCodecException {
        return new FrameGrab(vt, decoder).seekToFramePrecise(frameNumber).getNativeFrame();
    }

    /**
     * Get a specified frame by second from an already open demuxer track
     *
     * @param vt
     * @param decoder
     * @param frameNumber
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getNativeFrameAtSec(SeekableDemuxerTrack vt, ContainerAdaptor decoder, double second)
            throws IOException, JCodecException {
        return new FrameGrab(vt, decoder).seekToSecondPrecise(second).getNativeFrame();
    }

    /**
     * Get a specified frame by number from an already open demuxer track (
     * sloppy mode, i.e. nearest keyframe )
     *
     * @param vt
     * @param decoder
     * @param frameNumber
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getNativeFrameSloppy(SeekableDemuxerTrack vt, ContainerAdaptor decoder, int frameNumber)
            throws IOException, JCodecException {
        return new FrameGrab(vt, decoder).seekToFrameSloppy(frameNumber).getNativeFrame();
    }

    /**
     * Get a specified frame by second from an already open demuxer track (
     * sloppy mode, i.e. nearest keyframe )
     *
     * @param vt
     * @param decoder
     * @param frameNumber
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static Picture getNativeFrameAtSecSloppy(SeekableDemuxerTrack vt, ContainerAdaptor decoder, double second)
            throws IOException, JCodecException {
        return new FrameGrab(vt, decoder).seekToSecondSloppy(second).getNativeFrame();
    }

    /**
     * Gets info about the media
     *
     * @return
     */
    public MediaInfo getMediaInfo() {
        return decoder.getMediaInfo();
    }

    /**
     * @return the videoTrack
     */
    public SeekableDemuxerTrack getVideoTrack() {
        return videoTrack;
    }

    /**
     * @return the decoder
     */
    public ContainerAdaptor getDecoder() {
        return decoder;
    }
}