package org.jcodec.api.transcode;

import static org.jcodec.common.Codec.*;
import static org.jcodec.common.Format.*;
import static org.jcodec.common.io.NIOUtils.readableFileChannel;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import net.sourceforge.jaad.aac.AACException;

import org.jcodec.api.transcode.PixelStore.LoanerPicture;
import org.jcodec.codecs.aac.AACDecoder;
import org.jcodec.codecs.h264.BufferH264ES;
import org.jcodec.codecs.h264.H264Decoder;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.mjpeg.JpegDecoder;
import org.jcodec.codecs.mjpeg.JpegToThumb2x2;
import org.jcodec.codecs.mjpeg.JpegToThumb4x4;
import org.jcodec.codecs.mpeg12.MPEGDecoder;
import org.jcodec.codecs.mpeg12.Mpeg2Thumb2x2;
import org.jcodec.codecs.mpeg12.Mpeg2Thumb4x4;
import org.jcodec.codecs.mpeg4.MPEG4Decoder;
import org.jcodec.codecs.png.PNGDecoder;
import org.jcodec.codecs.prores.ProresDecoder;
import org.jcodec.codecs.prores.ProresToThumb;
import org.jcodec.codecs.prores.ProresToThumb2x2;
import org.jcodec.codecs.prores.ProresToThumb4x4;
import org.jcodec.codecs.raw.RAWVideoDecoder;
import org.jcodec.codecs.vpx.VP8Decoder;
import org.jcodec.codecs.wav.WavDemuxer;
import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.AudioDecoder;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.Codec;
import org.jcodec.common.Demuxer;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.Format;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.Tuple._3;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.VideoDecoder;
import org.jcodec.common.io.IOUtils;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.AudioBuffer;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Packet.FrameType;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.containers.imgseq.ImageSequenceDemuxer;
import org.jcodec.containers.mkv.demuxer.MKVDemuxer;
import org.jcodec.containers.mp3.MPEGAudioDemuxer;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mps.MPSDemuxer;
import org.jcodec.containers.mps.MTSDemuxer;
import org.jcodec.containers.mps.MPEGDemuxer.MPEGDemuxerTrack;
import org.jcodec.containers.webp.WebpDemuxer;
import org.jcodec.containers.y4m.Y4MDemuxer;

/**
 * A source producing uncompressed video/audio streams out of a compressed file.
 *
 * The buffers for the frames coming out of this source will be borrowed from a
 * pixel store and must be returned to it for maximum efficiency.
 *
 * @author Stanislav Vitvitskiy
 */
public class SourceImpl implements Source, PacketSource {
    private String sourceName;
    private SeekableByteChannel sourceStream;

    private Demuxer demuxVideo;
    private Demuxer demuxAudio;

    private Format inputFormat;

    private DemuxerTrack videoInputTrack;
    private DemuxerTrack audioInputTrack;

    private _3<Integer, Integer, Codec> inputVideoCodec;
    private _3<Integer, Integer, Codec> inputAudioCodec;

    private List<VideoFrameWithPacket> frameReorderBuffer;
    private List<Packet> videoPacketReorderBuffer;
    private PixelStore pixelStore;
    private VideoCodecMeta videoCodecMeta;
    private AudioCodecMeta audioCodecMeta;

    private AudioDecoder audioDecoder;
    private VideoDecoder videoDecoder;

    private int downscale = 1;

    public static MPEGDecoder createMpegDecoder(int downscale) {
        if (downscale == 2)
            return new Mpeg2Thumb4x4();
        else if (downscale == 4)
            return new Mpeg2Thumb2x2();
        else
            return new MPEGDecoder();
    }

    public static ProresDecoder createProresDecoder(int downscale) {
        if (2 == downscale) {
            return new ProresToThumb4x4();
        } else if (4 == downscale) {
            return new ProresToThumb2x2();
        } else if (8 == downscale) {
            return new ProresToThumb();
        } else {
            return new ProresDecoder();
        }
    }

    public void initDemuxer() throws FileNotFoundException, IOException {
        if (inputFormat != IMG)
            sourceStream = readableFileChannel(sourceName);

        if (MOV == inputFormat) {
            demuxVideo = demuxAudio = MP4Demuxer.createMP4Demuxer(sourceStream);
        } else if (MKV == inputFormat) {
            demuxVideo = demuxAudio = new MKVDemuxer(sourceStream);
        } else if (IMG == inputFormat) {
            demuxVideo = new ImageSequenceDemuxer(sourceName, Integer.MAX_VALUE);
        } else if (WEBP == inputFormat) {
            demuxVideo = new WebpDemuxer(sourceStream);
        } else if (MPEG_PS == inputFormat) {
            demuxVideo = demuxAudio = new MPSDemuxer(sourceStream);
        } else if (Y4M == inputFormat) {
            Y4MDemuxer y4mDemuxer = new Y4MDemuxer(sourceStream);
            demuxVideo = demuxAudio = y4mDemuxer;
            videoInputTrack = y4mDemuxer;
        } else if (Format.H264 == inputFormat) {
            demuxVideo = new BufferH264ES(NIOUtils.fetchAllFromChannel(sourceStream));
        } else if (WAV == inputFormat) {
            demuxAudio = new WavDemuxer(sourceStream);
        } else if (MPEG_AUDIO == inputFormat) {
            demuxAudio = new MPEGAudioDemuxer(sourceStream);
        } else if (MPEG_TS == inputFormat) {
            MTSDemuxer mtsDemuxer = new MTSDemuxer(sourceStream);
            MPSDemuxer mpsDemuxer = null;
            if (inputVideoCodec != null) {
                mpsDemuxer = new MPSDemuxer(mtsDemuxer.getProgram(inputVideoCodec.v0));
                videoInputTrack = openTSTrack(mpsDemuxer, inputVideoCodec.v1);
                demuxVideo = mpsDemuxer;
            }
            if (inputAudioCodec != null) {
                if (inputVideoCodec == null || inputVideoCodec.v0 != inputAudioCodec.v0) {
                    mpsDemuxer = new MPSDemuxer(mtsDemuxer.getProgram(inputAudioCodec.v0));
                }
                audioInputTrack = openTSTrack(mpsDemuxer, inputAudioCodec.v1);
                demuxAudio = mpsDemuxer;
            }
            for (int pid : mtsDemuxer.getPrograms()) {
                if ((inputVideoCodec == null || pid != inputVideoCodec.v0)
                        && (inputAudioCodec == null || pid != inputAudioCodec.v0)) {
                    Logger.info("Unused program: " + pid);
                    mtsDemuxer.getProgram(pid).close();
                }
            }
        } else {
            throw new RuntimeException("Input format: " + inputFormat + " is not supported.");
        }

        if (demuxVideo != null && inputVideoCodec != null) {
            List<? extends DemuxerTrack> videoTracks = demuxVideo.getVideoTracks();
            if (videoTracks.size() > 0) {
                videoInputTrack = videoTracks.get(inputVideoCodec.v1);
            }
        }
        if (demuxAudio != null && inputAudioCodec != null) {
            List<? extends DemuxerTrack> audioTracks = demuxAudio.getAudioTracks();
            if (audioTracks.size() > 0) {
                audioInputTrack = audioTracks.get(inputAudioCodec.v1);
            }
        }
    }

    /**
     * Seeks to a previous key frame prior or on the given frame, if the track is
     * not seekable returns 0.
     *
     * @param frame
     *            A frame to seek
     * @return Frame number of a key frame or 0 if the track is not seekable.
     * @throws IOException
     */
    protected int seekToKeyFrame(int frame) throws IOException {
        if (videoInputTrack instanceof SeekableDemuxerTrack) {
            SeekableDemuxerTrack seekable = (SeekableDemuxerTrack) videoInputTrack;
            seekable.gotoSyncFrame(frame);
            return (int) seekable.getCurFrame();
        } else {
            Logger.warn("Can not seek in " + videoInputTrack + " container.");
            return -1;
        }
    }

    private MPEGDemuxerTrack openTSTrack(MPSDemuxer demuxerVideo, Integer selectedTrack) {
        int trackNo = 0;
        for (MPEGDemuxerTrack track : demuxerVideo.getTracks()) {
            if (trackNo == selectedTrack) {
                return track;
            } else
                track.ignore();
            ++trackNo;
        }
        return null;
    }

    @Override
    public Packet inputVideoPacket() throws IOException {
        while (true) {
            Packet packet = getNextVideoPacket();
            if (packet != null)
                videoPacketReorderBuffer.add(packet);
            if (packet == null || videoPacketReorderBuffer.size() > Transcoder.REORDER_BUFFER_SIZE) {
                if (videoPacketReorderBuffer.size() == 0)
                    return null;
                Packet out = videoPacketReorderBuffer.remove(0);
                int duration = Integer.MAX_VALUE;
                for (Packet packet2 : videoPacketReorderBuffer) {
                    int cand = (int) (packet2.getPts() - out.getPts());
                    if (cand > 0 && cand < duration)
                        duration = cand;
                }
                if (duration != Integer.MAX_VALUE)
                    out.setDuration(duration);
                return out;
            }
        }
    }

    private Packet getNextVideoPacket() throws IOException {
        if (videoInputTrack == null)
            return null;
        Packet nextFrame = videoInputTrack.nextFrame();
        // if (nextFrame != null)
        // Logger.debug(String.format("Input frame: pts=%d, duration=%d",
        // nextFrame.getPts(), nextFrame.getDuration()));

        if (videoDecoder == null) {
            videoDecoder = createVideoDecoder(inputVideoCodec.v2, downscale, nextFrame.getData(), null);
            if (videoDecoder != null) {
                videoCodecMeta = videoDecoder.getCodecMeta(nextFrame.getData());
            }
        }

        return nextFrame;
    }

    @Override
    public Packet inputAudioPacket() throws IOException {
        if (audioInputTrack == null)
            return null;
        Packet audioPkt = audioInputTrack.nextFrame();
        if (audioDecoder == null && audioPkt != null) {
            audioDecoder = createAudioDecoder(audioPkt.getData());
            if (audioDecoder != null)
                audioCodecMeta = audioDecoder.getCodecMeta(audioPkt.getData());
        }

        return audioPkt;
    }

    public DemuxerTrackMeta getTrackVideoMeta() {
        if (videoInputTrack == null)
            return null;
        return videoInputTrack.getMeta();
    }

    public DemuxerTrackMeta getAudioMeta() {
        if (audioInputTrack == null)
            return null;
        return audioInputTrack.getMeta();
    }

    public boolean haveAudio() {
        return audioInputTrack != null;
    }

    @Override
    public void finish() {
        if (sourceStream != null)
            IOUtils.closeQuietly(sourceStream);
    }

    public SourceImpl(String sourceName, Format inputFormat, _3<Integer, Integer, Codec> inputVideoCodec,
            _3<Integer, Integer, Codec> inputAudioCodec) {
        this.sourceName = sourceName;
        this.inputFormat = inputFormat;
        this.inputVideoCodec = inputVideoCodec;
        this.inputAudioCodec = inputAudioCodec;
        frameReorderBuffer = new ArrayList<VideoFrameWithPacket>();
        videoPacketReorderBuffer = new ArrayList<Packet>();
    }

    @Override
    public void init(PixelStore pixelStore) throws IOException {
        this.pixelStore = pixelStore;

        initDemuxer();
    }

    private AudioDecoder createAudioDecoder(ByteBuffer codecPrivate) throws AACException {
        if (AAC == inputAudioCodec.v2) {
            return new AACDecoder(codecPrivate);
        } else if (PCM == inputAudioCodec.v2) {
            return new RawAudioDecoder(getAudioMeta().getAudioCodecMeta().getFormat());
        }
        return null;
    }

    private VideoDecoder createVideoDecoder(Codec codec, int downscale, ByteBuffer codecPrivate,
            VideoCodecMeta videoCodecMeta) {
        if (Codec.H264 == codec) {
            return H264Decoder.createH264DecoderFromCodecPrivate(codecPrivate);
        } else if (PNG == codec) {
            return new PNGDecoder();
        } else if (MPEG2 == codec) {
            return createMpegDecoder(downscale);
        } else if (PRORES == codec) {
            return createProresDecoder(downscale);
        } else if (VP8 == codec) {
            return new VP8Decoder();
        } else if (JPEG == codec) {
            return createJpegDecoder(downscale);
        } else if (MPEG4 == codec) {
            return new MPEG4Decoder();
        } else if (Codec.RAW == codec) {
            Size dim = videoCodecMeta.getSize();
            return new RAWVideoDecoder(dim.getWidth(), dim.getHeight());
        }
        return null;
    }

    public Picture decodeVideo(ByteBuffer data, Picture target1) {
        return videoDecoder.decodeFrame(data, target1.getData());
    }

    protected ByteBuffer decodeAudio(ByteBuffer audioPkt) throws IOException {
        if (inputAudioCodec.v2 == PCM) {
            return audioPkt;
        } else {
            AudioBuffer decodeFrame = audioDecoder.decodeFrame(audioPkt, null);
            return decodeFrame.getData();
        }
    }

    private static class RawAudioDecoder implements AudioDecoder {
        private AudioFormat format;

        public RawAudioDecoder(AudioFormat format) {
            this.format = format;
        }

        @Override
        public AudioBuffer decodeFrame(ByteBuffer frame, ByteBuffer dst) throws IOException {
            return new AudioBuffer(frame, format, frame.remaining() / format.getFrameSize());
        }

        @Override
        public AudioCodecMeta getCodecMeta(ByteBuffer data) throws IOException {
            return org.jcodec.common.AudioCodecMeta.fromAudioFormat(format);
        }
    }

    @Override
    public void seekFrames(int seekFrames) throws IOException {
        if (seekFrames == 0)
            return;
        // How many frames need to be skipped from the previouse key frame,
        // if the track is not seekable this will be equal to the
        // seekFrames.
        int skipFrames = seekFrames - seekToKeyFrame(seekFrames);

        // All the frames starting from the key frame must be actually
        // decoded in the decoder so that the decoder is 'warmed up'
        Packet inVideoPacket;
        for (; skipFrames > 0 && (inVideoPacket = getNextVideoPacket()) != null;) {
            LoanerPicture loanerBuffer = getPixelBuffer(inVideoPacket.getData());
            Picture decodedFrame = decodeVideo(inVideoPacket.getData(), loanerBuffer.getPicture());
            if (decodedFrame == null) {
                pixelStore.putBack(loanerBuffer);
                continue;
            }
            frameReorderBuffer.add(new VideoFrameWithPacket(inVideoPacket, new LoanerPicture(decodedFrame, 1)));
            if (frameReorderBuffer.size() > Transcoder.REORDER_BUFFER_SIZE) {
                Collections.sort(frameReorderBuffer);
                VideoFrameWithPacket removed = frameReorderBuffer.remove(0);
                skipFrames--;
                if (removed.getFrame() != null)
                    pixelStore.putBack(removed.getFrame());
            }
        }
    }

    private void detectFrameType(Packet inVideoPacket) {
        if (inputVideoCodec.v2 != Codec.H264) {
            return;
        }
        inVideoPacket.setFrameType(
                H264Utils.isByteBufferIDRSlice(inVideoPacket.getData()) ? FrameType.KEY : FrameType.INTER);
    }

    /**
     * Returns a pixel buffer of a suitable size to hold the given video frame. The
     * video size is taken either from the video metadata or by analyzing the
     * incoming video packet.
     *
     * @param firstFrame
     * @return
     */
    protected LoanerPicture getPixelBuffer(ByteBuffer firstFrame) {
        VideoCodecMeta videoMeta = getVideoCodecMeta();
        Size size = videoMeta.getSize();
        return pixelStore.getPicture((size.getWidth() + 15) & ~0xf, (size.getHeight() + 15) & ~0xf,
                videoMeta.getColor());
    }

    @Override
    public VideoCodecMeta getVideoCodecMeta() {
        if (videoCodecMeta != null)
            return videoCodecMeta;
        DemuxerTrackMeta meta = getTrackVideoMeta();
        if (meta != null && meta.getVideoCodecMeta() != null) {
            videoCodecMeta = meta.getVideoCodecMeta();
        }
        return videoCodecMeta;
    }

    @Override
    public VideoFrameWithPacket getNextVideoFrame() throws IOException {
        Packet inVideoPacket;
        while ((inVideoPacket = getNextVideoPacket()) != null) {
            if (inVideoPacket.getFrameType() == FrameType.UNKNOWN) {
                detectFrameType(inVideoPacket);
            }
            Picture decodedFrame = null;
            LoanerPicture pixelBuffer = getPixelBuffer(inVideoPacket.getData());
            decodedFrame = decodeVideo(inVideoPacket.getData(), pixelBuffer.getPicture());
            if (decodedFrame == null) {
                pixelStore.putBack(pixelBuffer);
                continue;
            }
            frameReorderBuffer.add(new VideoFrameWithPacket(inVideoPacket, new LoanerPicture(decodedFrame, 1)));
            if (frameReorderBuffer.size() > Transcoder.REORDER_BUFFER_SIZE) {
                return removeFirstFixDuration(frameReorderBuffer);
            }
        }

        // We don't have any more compressed video packets
        if (frameReorderBuffer.size() > 0) {
            return removeFirstFixDuration(frameReorderBuffer);
        }

        // We don't have any more compressed video packets and nothing's in
        // the buffers
        return null;
    }

    private VideoFrameWithPacket removeFirstFixDuration(List<VideoFrameWithPacket> reorderBuffer) {
        Collections.sort(reorderBuffer);
        VideoFrameWithPacket frame = reorderBuffer.remove(0);
        if (!reorderBuffer.isEmpty()) {
            // Setting duration
            VideoFrameWithPacket nextFrame = reorderBuffer.get(0);
            frame.getPacket().setDuration(nextFrame.getPacket().getPts() - frame.getPacket().getPts());
        }
        return frame;
    }

    @Override
    public AudioFrameWithPacket getNextAudioFrame() throws IOException {
        Packet audioPkt = inputAudioPacket();
        if (audioPkt == null)
            return null;
        AudioBuffer audioBuffer;
        if (inputAudioCodec.v2 == PCM) {
            DemuxerTrackMeta audioMeta = getAudioMeta();
            audioBuffer = new AudioBuffer(audioPkt.getData(), audioMeta.getAudioCodecMeta().getFormat(),
                    audioMeta.getTotalFrames());
        } else {
            audioBuffer = audioDecoder.decodeFrame(audioPkt.getData(), null);
        }
        return new AudioFrameWithPacket(audioBuffer, audioPkt);
    }

    public _3<Integer, Integer, Codec> getIntputVideoCodec() {
        return inputVideoCodec;
    }

    public _3<Integer, Integer, Codec> getInputAudioCode() {
        return inputAudioCodec;
    }

    @Override
    public void setOption(Options option, Object value) {
        if (option == Options.DOWNSCALE)
            downscale = (Integer) value;
    }

    @Override
    public AudioCodecMeta getAudioCodecMeta() {
        if (audioInputTrack != null && audioInputTrack.getMeta() != null
                && audioInputTrack.getMeta().getAudioCodecMeta() != null) {
            return audioInputTrack.getMeta().getAudioCodecMeta();
        }
        return audioCodecMeta;
    }

    @Override
    public boolean isVideo() {
        if (!inputFormat.isVideo())
            return false;
        List<? extends DemuxerTrack> tracks = demuxVideo.getVideoTracks();
        return tracks != null && tracks.size() > 0;
    }

    @Override
    public boolean isAudio() {
        if (!inputFormat.isAudio())
            return false;
        List<? extends DemuxerTrack> tracks = demuxAudio.getAudioTracks();
        return tracks != null && tracks.size() > 0;
    }

    public static JpegDecoder createJpegDecoder(int downscale) {
        if (downscale == 2) {
            return new JpegToThumb4x4();
        } else if (downscale == 4) {
            return new JpegToThumb2x2();
        } else {
            return new JpegDecoder();
        }
    }
}


package org.jcodec.codecs.prores;
import static java.lang.Math.min;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_CHROMA_APCH;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_CHROMA_APCN;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_CHROMA_APCO;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_CHROMA_APCS;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_LUMA_APCH;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_LUMA_APCN;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_LUMA_APCO;
import static org.jcodec.codecs.prores.ProresConsts.QMAT_LUMA_APCS;
import static org.jcodec.codecs.prores.ProresConsts.dcCodebooks;
import static org.jcodec.codecs.prores.ProresConsts.firstDCCodebook;
import static org.jcodec.codecs.prores.ProresConsts.interlaced_scan;
import static org.jcodec.codecs.prores.ProresConsts.levCodebooks;
import static org.jcodec.codecs.prores.ProresConsts.progressive_scan;
import static org.jcodec.codecs.prores.ProresConsts.runCodebooks;
import static org.jcodec.common.dct.SimpleIDCT10Bit.fdctProres10;
import static org.jcodec.common.model.ColorSpace.YUV422;
import static org.jcodec.common.tools.MathUtil.log2;
import static org.jcodec.common.tools.MathUtil.sign;

import org.jcodec.common.VideoEncoder;
import org.jcodec.common.io.BitWriter;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Rect;
import org.jcodec.common.tools.ImageOP;

import java.nio.ByteBuffer;

/**
 *
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Apple ProRes encoder
 *
 * @author The JCodec project
 *
 */
public class ProresEncoder extends VideoEncoder {

    private static final int LOG_DEFAULT_SLICE_MB_WIDTH = 3;
    private static final int DEFAULT_SLICE_MB_WIDTH = 1 << LOG_DEFAULT_SLICE_MB_WIDTH;

    public final static class Profile {
        public final static Profile PROXY = new Profile("PROXY", QMAT_LUMA_APCO, QMAT_CHROMA_APCO, "apco", 1000, 4, 8);
        public final static Profile LT = new Profile("LT", QMAT_LUMA_APCS, QMAT_CHROMA_APCS, "apcs", 2100, 1, 9);
        public final static Profile STANDARD = new Profile("STANDARD", QMAT_LUMA_APCN, QMAT_CHROMA_APCN, "apcn", 3500,
                1, 6);
        public final static Profile HQ = new Profile("HQ", QMAT_LUMA_APCH, QMAT_CHROMA_APCH, "apch", 5400, 1, 6);

        private final static Profile[] _values = new Profile[] { PROXY, LT, STANDARD, HQ };

        public static Profile[] values() {
            return _values;
        }

        public static Profile valueOf(String name) {
            String nameU = name.toUpperCase();
            for (ProresEncoder.Profile profile2 : _values) {
                if (name.equals(nameU))
                    return profile2;
            }
            return null;
        }

        final String name;
        final int[] qmatLuma;
        final int[] qmatChroma;
        final public String fourcc;
        // Per 1024 pixels
        final int bitrate;
        final int firstQp;
        final int lastQp;

        private Profile(String name, int[] qmatLuma, int[] qmatChroma, String fourcc, int bitrate, int firstQp,
                int lastQp) {
            this.name = name;
            this.qmatLuma = qmatLuma;
            this.qmatChroma = qmatChroma;
            this.fourcc = fourcc;
            this.bitrate = bitrate;
            this.firstQp = firstQp;
            this.lastQp = lastQp;
        }

    };

    protected Profile profile;
    private int[][] scaledLuma;
    private int[][] scaledChroma;
    private boolean interlaced;

    public static ProresEncoder createProresEncoder(String profile, boolean interlaced) {
        return new ProresEncoder(profile == null ? ProresEncoder.Profile.HQ : ProresEncoder.Profile.valueOf(profile), interlaced);
    }

    public ProresEncoder(Profile profile, boolean interlaced) {
        this.profile = profile;
        scaledLuma = scaleQMat(profile.qmatLuma, 1, 16);
        scaledChroma = scaleQMat(profile.qmatChroma, 1, 16);
        this.interlaced = interlaced;
    }

    private int[][] scaleQMat(int[] qmatLuma, int start, int count) {
        int[][] result = new int[count][];
        for (int i = 0; i < count; i++) {
            result[i] = new int[qmatLuma.length];
            for (int j = 0; j < qmatLuma.length; j++)
                result[i][j] = qmatLuma[j] * (i + start);
        }
        return result;
    }

    public static final void writeCodeword(BitWriter writer, Codebook codebook, int val) {
        int firstExp = ((codebook.switchBits + 1) << codebook.riceOrder);
        if (val >= firstExp) {
            val -= firstExp;
            val += (1 << codebook.expOrder); // Offset to zero

            int exp = log2(val);
            int zeros = exp - codebook.expOrder + codebook.switchBits + 1;
            for (int i = 0; i < zeros; i++)
                writer.write1Bit(0);
            writer.write1Bit(1);
            writer.writeNBit(val, exp);

        } else if (codebook.riceOrder > 0) {
            for (int i = 0; i < (val >> codebook.riceOrder); i++)
                writer.write1Bit(0);
            writer.write1Bit(1);
            writer.writeNBit(val & ((1 << codebook.riceOrder) - 1), codebook.riceOrder);
        } else {
            for (int i = 0; i < val; i++)
                writer.write1Bit(0);
            writer.write1Bit(1);
        }
    }

    private static final int qScale(int[] qMat, int ind, int val) {
        return val / qMat[ind];
    }

    private static final int toGolumb(int val) {
        return (val << 1) ^ (val >> 31);
    }

    private static final int toGolumbSign(int val, int sign) {
        if (val == 0)
            return 0;
        return (val << 1) + sign;
    }

    private static final int diffSign(int val, int sign) {
        return (val >> 31) ^ sign;
    }

    public static final int getLevel(int val) {
        int sign = (val >> 31);
        return (val ^ sign) - sign;
    }

    static final void writeDCCoeffs(BitWriter bits, int[] qMat, int[] _in, int blocksPerSlice) {
        int prevDc = qScale(qMat, 0, _in[0] - 16384);
        writeCodeword(bits, firstDCCodebook, toGolumb(prevDc));

        int code = 5, sign = 0, idx = 64;
        for (int i = 1; i < blocksPerSlice; i++, idx += 64) {
            int newDc = qScale(qMat, 0, _in[idx] - 16384);
            int delta = newDc - prevDc;
            int newCode = toGolumbSign(getLevel(delta), diffSign(delta, sign));
            writeCodeword(bits, dcCodebooks[min(code, 6)], newCode);
            code = newCode;
            sign = delta >> 31;
            prevDc = newDc;
        }
    }

    static final void writeACCoeffs(BitWriter bits, int[] qMat, int[] _in, int blocksPerSlice, int[] scan,
            int maxCoeff) {
        int prevRun = 4;
        int prevLevel = 2;

        int run = 0;
        for (int i = 1; i < maxCoeff; i++) {
            int indp = scan[i];
            for (int j = 0; j < blocksPerSlice; j++) {
                int val = qScale(qMat, indp, _in[(j << 6) + indp]);
                if (val == 0)
                    run++;
                else {
                    writeCodeword(bits, runCodebooks[min(prevRun, 15)], run);
                    prevRun = run;
                    run = 0;
                    int level = getLevel(val);
                    writeCodeword(bits, levCodebooks[min(prevLevel, 9)], level - 1);
                    prevLevel = level;
                    bits.write1Bit(sign(val));
                }
            }
        }
    }

    static final void encodeOnePlane(BitWriter bits, int blocksPerSlice, int[] qMat, int[] scan, int[] _in) {

        writeDCCoeffs(bits, qMat, _in, blocksPerSlice);
        writeACCoeffs(bits, qMat, _in, blocksPerSlice, scan, 64);
    }

    //Wrong usage of Javascript keyword:in
    private void dctOnePlane(int blocksPerSlice, byte[] src, byte[] hibd, int[] dst) {
        for (int i = 0; i < src.length; i++) {
            dst[i] = ((src[i] + 128) << 2);
        }
        if (hibd != null) {
            for (int i = 0; i < src.length; i++) {
                dst[i] += hibd[i];
            }
        }

        for (int i = 0; i < blocksPerSlice; i++) {
            fdctProres10(dst, i << 6);
        }
    }

    protected int encodeSlice(ByteBuffer out, int[][] scaledLuma, int[][] scaledChroma, int[] scan, int sliceMbCount,
            int mbX, int mbY, Picture result, int prevQp, int mbWidth, int mbHeight, boolean unsafe, int vStep,
            int vOffset) {

        Picture striped = splitSlice(result, mbX, mbY, sliceMbCount, unsafe, vStep, vOffset);
        int[][] ac = new int[][] { new int[sliceMbCount << 8], new int[sliceMbCount << 7], new int[sliceMbCount << 7] };
        byte[][] data = striped.getData();
        byte[][] lowBits = striped.getLowBits();
        dctOnePlane(sliceMbCount << 2, data[0], lowBits == null ? null : lowBits[0], ac[0]);
        dctOnePlane(sliceMbCount << 1, data[1], lowBits == null ? null : lowBits[1], ac[1]);
        dctOnePlane(sliceMbCount << 1, data[2], lowBits == null ? null : lowBits[2], ac[2]);

        int est = (sliceMbCount >> 2) * profile.bitrate;
        int low = est - (est >> 3); // 12% bitrate fluctuation
        int high = est + (est >> 3);

        int qp = prevQp;

        out.put((byte) (6 << 3)); // hdr size
        ByteBuffer fork = out.duplicate();
        NIOUtils.skip(out, 5);
        int rem = out.position();
        int[] sizes = new int[3];
        encodeSliceData(out, scaledLuma[qp - 1], scaledChroma[qp - 1], scan, sliceMbCount, ac, qp, sizes);
        if (bits(sizes) > high && qp < profile.lastQp) {
            do {
                ++qp;
                out.position(rem);
                encodeSliceData(out, scaledLuma[qp - 1], scaledChroma[qp - 1], scan, sliceMbCount, ac, qp, sizes);
            } while (bits(sizes) > high && qp < profile.lastQp);
        } else if (bits(sizes) < low && qp > profile.firstQp) {
            do {
                --qp;
                out.position(rem);
                encodeSliceData(out, scaledLuma[qp - 1], scaledChroma[qp - 1], scan, sliceMbCount, ac, qp, sizes);
            } while (bits(sizes) < low && qp > profile.firstQp);
        }

        fork.put((byte) qp);
        fork.putShort((short) sizes[0]);
        fork.putShort((short) sizes[1]);

        return qp;
    }

    static final int bits(int[] sizes) {
        return sizes[0] + sizes[1] + sizes[2] << 3;
    }

    protected static final void encodeSliceData(ByteBuffer out, int[] qmatLuma, int[] qmatChroma, int[] scan,
            int sliceMbCount, int[][] ac, int qp, int[] sizes) {

        sizes[0] = onePlane(out, sliceMbCount << 2, qmatLuma, scan, ac[0]);
        sizes[1] = onePlane(out, sliceMbCount << 1, qmatChroma, scan, ac[1]);
        sizes[2] = onePlane(out, sliceMbCount << 1, qmatChroma, scan, ac[2]);
    }

    static final int onePlane(ByteBuffer out, int blocksPerSlice, int[] qmatLuma, int[] scan, int[] data) {
        int rem = out.position();
        BitWriter bits = new BitWriter(out);
        encodeOnePlane(bits, blocksPerSlice, qmatLuma, scan, data);
        bits.flush();
        return out.position() - rem;
    }

    protected void encodePicture(ByteBuffer out, int[][] scaledLuma, int[][] scaledChroma, int[] scan,
            Picture picture, int vStep, int vOffset) {

        int mbWidth = (picture.getWidth() + 15) >> 4;
        int shift = 4 + vStep;
        int round = (1 << shift) - 1;
        int mbHeight = (picture.getHeight() + round) >> shift;
        int qp = profile.firstQp;

        int nSlices = calcNSlices(mbWidth, mbHeight);
        writePictureHeader(LOG_DEFAULT_SLICE_MB_WIDTH, nSlices, out);
        ByteBuffer fork = out.duplicate();
        NIOUtils.skip(out, nSlices << 1);

        int i = 0;
        int[] total = new int[nSlices];
        for (int mbY = 0; mbY < mbHeight; mbY++) {
            int mbX = 0;
            int sliceMbCount = DEFAULT_SLICE_MB_WIDTH;
            while (mbX < mbWidth) {
                while (mbWidth - mbX < sliceMbCount)
                    sliceMbCount >>= 1;

                int sliceStart = out.position();
                boolean unsafeBottom = (picture.getHeight() % 16) != 0 && mbY == mbHeight - 1;
                boolean unsafeRight = (picture.getWidth() % 16) != 0 && mbX + sliceMbCount == mbWidth;
                qp = encodeSlice(out, scaledLuma, scaledChroma, scan, sliceMbCount, mbX, mbY, picture, qp, mbWidth,
                        mbHeight, unsafeBottom || unsafeRight, vStep, vOffset);
                fork.putShort((short) (out.position() - sliceStart));
                total[i++] = (short) (out.position() - sliceStart);

                mbX += sliceMbCount;
            }
        }
    }

    public static void writePictureHeader(int logDefaultSliceMbWidth, int nSlices, ByteBuffer out) {
        int headerLen = 8;
        out.put((byte) (headerLen << 3));
        out.putInt(0);
        out.putShort((short) nSlices);
        out.put((byte) (logDefaultSliceMbWidth << 4));
    }

    private int calcNSlices(int mbWidth, int mbHeight) {
        int nSlices = mbWidth >> LOG_DEFAULT_SLICE_MB_WIDTH;
        for (int i = 0; i < LOG_DEFAULT_SLICE_MB_WIDTH; i++) {
            nSlices += (mbWidth >> i) & 0x1;
        }
        return nSlices * mbHeight;
    }

    private Picture splitSlice(Picture result, int mbX, int mbY, int sliceMbCount, boolean unsafe, int vStep,
            int vOffset) {
        Picture out = Picture.createCroppedHiBD(sliceMbCount << 4, 16, result.getLowBitsNum(), YUV422, null);
        if (unsafe) {
            int mbHeightPix = 16 << vStep;
            Picture filled = Picture.create(sliceMbCount << 4, mbHeightPix, YUV422);
            ImageOP.subImageWithFillPic8(result, filled,
                    new Rect(mbX << 4, mbY << (4 + vStep), sliceMbCount << 4, mbHeightPix));

            split(filled, out, 0, 0, sliceMbCount, vStep, vOffset);
        } else {
            split(result, out, mbX, mbY, sliceMbCount, vStep, vOffset);
        }

        return out;
    }

    //Wrong usage of Javascript keyword:in
    private void split(Picture src, Picture dst, int mbX, int mbY, int sliceMbCount, int vStep, int vOffset) {
        byte[][] inData = src.getData();
        byte[][] inhbdData = src.getLowBits();

        byte[][] outData = dst.getData();
        byte[][] outhbdData = dst.getLowBits();

        doSplit(inData[0], outData[0], src.getPlaneWidth(0), mbX, mbY, sliceMbCount, 0, vStep, vOffset);
        doSplit(inData[1], outData[1], src.getPlaneWidth(1), mbX, mbY, sliceMbCount, 1, vStep, vOffset);
        doSplit(inData[2], outData[2], src.getPlaneWidth(2), mbX, mbY, sliceMbCount, 1, vStep, vOffset);

        if (src.getLowBits() != null) {
            doSplit(inhbdData[0], outhbdData[0], src.getPlaneWidth(0), mbX, mbY, sliceMbCount, 0, vStep, vOffset);
            doSplit(inhbdData[1], outhbdData[1], src.getPlaneWidth(1), mbX, mbY, sliceMbCount, 1, vStep, vOffset);
            doSplit(inhbdData[2], outhbdData[2], src.getPlaneWidth(2), mbX, mbY, sliceMbCount, 1, vStep, vOffset);
        }
    }

    private void doSplit(byte[] _in, byte[] out, int stride, int mbX, int mbY, int sliceMbCount, int chroma, int vStep,
            int vOffset) {
        int outOff = 0;
        int off = (mbY << 4) * (stride << vStep) + (mbX << (4 - chroma)) + stride * vOffset;
        stride <<= vStep;

        for (int i = 0; i < sliceMbCount; i++) {
            splitBlock(_in, stride, off, out, outOff);
            splitBlock(_in, stride, off + (stride << 3), out, outOff + (128 >> chroma));

            if (chroma == 0) {
                splitBlock(_in, stride, off + 8, out, outOff + 64);
                splitBlock(_in, stride, off + (stride << 3) + 8, out, outOff + 192);
            }

            outOff += (256 >> chroma);
            off += (16 >> chroma);
        }
    }

    private void splitBlock(byte[] y, int stride, int off, byte[] out, int outOff) {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++)
                out[outOff++] = y[off++];
            off += stride - 8;
        }
    }

    @Override
    public EncodedFrame encodeFrame(Picture pic, ByteBuffer buffer) {
        ByteBuffer out = buffer.duplicate();
        ByteBuffer fork = out.duplicate();

        int[] scan = interlaced ? interlaced_scan : progressive_scan;
        writeFrameHeader(out, new ProresConsts.FrameHeader(0, pic.getCroppedWidth(), pic.getCroppedHeight(),
                interlaced ? 1 : 0, true, scan, profile.qmatLuma, profile.qmatChroma, 2));

        encodePicture(out, scaledLuma, scaledChroma, scan, pic, interlaced ? 1 : 0, 0);
        if (interlaced)
            encodePicture(out, scaledLuma, scaledChroma, scan, pic, interlaced ? 1 : 0, 1);
        out.flip();
        fork.putInt(out.remaining());

        return new EncodedFrame(out, true);
    }

    public static void writeFrameHeader(ByteBuffer outp, ProresConsts.FrameHeader header) {

        short headerSize = 148;
        outp.putInt(headerSize + 8 + header.payloadSize);
        outp.put(new byte[] { 'i', 'c', 'p', 'f' });

        outp.putShort(headerSize); // header size
        outp.putShort((short) 0);

        outp.put(new byte[] { 'a', 'p', 'l', '0' });

        outp.putShort((short) header.width);
        outp.putShort((short) header.height);

        outp.put((byte) (header.frameType == 0 ? 0x83 : 0x87)); // {10}(422){00}[{00}(frame),{01}(field)}{11}

        outp.put(new byte[] { 0, 2, 2, 6, 32, 0 });

        outp.put((byte) 3); // flags2
        writeQMat(outp, header.qMatLuma);
        writeQMat(outp, header.qMatChroma);
    }

    static final void writeQMat(ByteBuffer out, int[] qmat) {
        for (int i = 0; i < 64; i++)
            out.put((byte) qmat[i]);
    }

    @Override
    public ColorSpace[] getSupportedColorSpaces() {
        return new ColorSpace[] { ColorSpace.YUV422 };
    }

    @Override
    public int estimateBufferSize(Picture frame) {
        return (3 * frame.getWidth() * frame.getHeight()) / 2;
    }
}


package net.sourceforge.jaad.aac.gain;
import static java.lang.System.arraycopy;

import org.jcodec.platform.Platform;

import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.syntax.IBitStream;
import net.sourceforge.jaad.aac.syntax.ICSInfo.WindowSequence;

/**
 * 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 GainControl implements GCConstants {

	private final int frameLen, lbLong, lbShort;
	private final IMDCT imdct;
	private final IPQF ipqf;
	private final float[] buffer1, _function;
	private final float[][] buffer2, overlap;
	private int maxBand;
	private int[][][] level, levelPrev;
	private int[][][] location, locationPrev;

	public GainControl(int frameLen) {
		this.frameLen = frameLen;
		lbLong = frameLen/BANDS;
		lbShort = lbLong/8;
		imdct = new IMDCT(frameLen);
		ipqf = new IPQF();
		levelPrev = new int[0][][];
		locationPrev = new int[0][][];
		buffer1 = new float[frameLen/2];
		buffer2 = new float[BANDS][lbLong];
		_function = new float[lbLong*2];
		overlap = new float[BANDS][lbLong*2];
	}

	public void decode(IBitStream _in, WindowSequence winSeq) throws AACException {
		maxBand = _in.readBits(2)+1;

		int wdLen, locBits, locBits2 = 0;
		switch(winSeq) {
			case ONLY_LONG_SEQUENCE:
				wdLen = 1;
				locBits = 5;
				locBits2 = 5;
				break;
			case EIGHT_SHORT_SEQUENCE:
				wdLen = 8;
				locBits = 2;
				locBits2 = 2;
				break;
			case LONG_START_SEQUENCE:
				wdLen = 2;
				locBits = 4;
				locBits2 = 2;
				break;
			case LONG_STOP_SEQUENCE:
				wdLen = 2;
				locBits = 4;
				locBits2 = 5;
				break;
			default:
				return;
		}
		level = new int[maxBand][wdLen][];
		location = new int[maxBand][wdLen][];

		int wd, k, len, bits;
		for(int bd = 1; bd<maxBand; bd++) {
			for(wd = 0; wd<wdLen; wd++) {
				len = _in.readBits(3);
				level[bd][wd] = new int[len];
				location[bd][wd] = new int[len];
				for(k = 0; k<len; k++) {
					level[bd][wd][k] = _in.readBits(4);
					bits = (wd==0) ? locBits : locBits2;
					location[bd][wd][k] = _in.readBits(bits);
				}
			}
		}
	}

	public void process(float[] data, int winShape, int winShapePrev, WindowSequence winSeq) throws AACException {
		imdct.process(data, buffer1, winShape, winShapePrev, winSeq);

		for(int i = 0; i<BANDS; i++) {
			compensate(buffer1, buffer2, winSeq, i);
		}

		ipqf.process(buffer2, frameLen, maxBand, data);
	}

	/**
	 * gain compensation and overlap-add:
	 * - the gain control function is calculated
	 * - the gain control function applies to IMDCT output samples as a another IMDCT window
	 * - the reconstructed time domain signal produces by overlap-add
	 */
	private void compensate(float[] _in, float[][] out, WindowSequence winSeq, int band) {
		int j;
		if(winSeq.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) {
			int a, b;
			for(int k = 0; k<8; k++) {
				//calculation
				calculateFunctionData(lbShort*2, band, winSeq, k);
				//applying
				for(j = 0; j<lbShort*2; j++) {
					a = band*lbLong*2+k*lbShort*2+j;
					_in[a] *= _function[j];
				}
				//overlapping
				for(j = 0; j<lbShort; j++) {
					a = j+lbLong*7/16+lbShort*k;
					b = band*lbLong*2+k*lbShort*2+j;
					overlap[band][a] += _in[b];
				}
				//store for next frame
				for(j = 0; j<lbShort; j++) {
					a = j+lbLong*7/16+lbShort*(k+1);
					b = band*lbLong*2+k*lbShort*2+lbShort+j;

					overlap[band][a] = _in[b];
				}
				locationPrev[band][0] = Platform.copyOfInt(location[band][k], location[band][k].length);
				levelPrev[band][0] = Platform.copyOfInt(level[band][k], level[band][k].length);
			}
			arraycopy(overlap[band], 0, out[band], 0, lbLong);
			arraycopy(overlap[band], lbLong, overlap[band], 0, lbLong);
		}
		else {
			//calculation
			calculateFunctionData(lbLong*2, band, winSeq, 0);
			//applying
			for(j = 0; j<lbLong*2; j++) {
				_in[band*lbLong*2+j] *= _function[j];
			}
			//overlapping
			for(j = 0; j<lbLong; j++) {
				out[band][j] = overlap[band][j]+_in[band*lbLong*2+j];
			}
			//store for next frame
			for(j = 0; j<lbLong; j++) {
				overlap[band][j] = _in[band*lbLong*2+lbLong+j];
			}
			final int lastBlock = winSeq.equals(WindowSequence.ONLY_LONG_SEQUENCE) ? 1 : 0;
			locationPrev[band][0] = Platform.copyOfInt(location[band][lastBlock], location[band][lastBlock].length);
			levelPrev[band][0] = Platform.copyOfInt(level[band][lastBlock], level[band][lastBlock].length);
		}
	}

	//produces gain control function data, stores it in 'function' array
	private void calculateFunctionData(int samples, int band, WindowSequence winSeq, int blockID) {
		final int[] locA = new int[10];
		final float[] levA = new float[10];
		final float[] modFunc = new float[samples];
		final float[] buf1 = new float[samples/2];
		final float[] buf2 = new float[samples/2];
		final float[] buf3 = new float[samples/2];

		int maxLocGain0 = 0, maxLocGain1 = 0, maxLocGain2 = 0;
		switch(winSeq) {
			case ONLY_LONG_SEQUENCE:
			case EIGHT_SHORT_SEQUENCE:
				maxLocGain0 = maxLocGain1 = samples/2;
				maxLocGain2 = 0;
				break;
			case LONG_START_SEQUENCE:
				maxLocGain0 = samples/2;
				maxLocGain1 = samples*7/32;
				maxLocGain2 = samples/16;
				break;
			case LONG_STOP_SEQUENCE:
				maxLocGain0 = samples/16;
				maxLocGain1 = samples*7/32;
				maxLocGain2 = samples/2;
				break;
		}

		//calculate the fragment modification functions
		//for the first half region
		calculateFMD(band, 0, true, maxLocGain0, samples, locA, levA, buf1);

		//for the latter half region
		int block = (winSeq.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) ? blockID : 0;
		float secLevel = calculateFMD(band, block, false, maxLocGain1, samples, locA, levA, buf2);

		//for the non-overlapped region
		if(winSeq.equals(WindowSequence.LONG_START_SEQUENCE)||winSeq.equals(WindowSequence.LONG_STOP_SEQUENCE)) {
			calculateFMD(band, 1, false, maxLocGain2, samples, locA, levA, buf3);
		}

		//calculate a gain modification function
		int i;
		int flatLen = 0;
		if(winSeq.equals(WindowSequence.LONG_STOP_SEQUENCE)) {
			flatLen = samples/2-maxLocGain0-maxLocGain1;
			for(i = 0; i<flatLen; i++) {
				modFunc[i] = 1.0f;
			}
		}
		if(winSeq.equals(WindowSequence.ONLY_LONG_SEQUENCE)||winSeq.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) levA[0] = 1.0f;

		for(i = 0; i<maxLocGain0; i++) {
			modFunc[i+flatLen] = levA[0]*secLevel*buf1[i];
		}
		for(i = 0; i<maxLocGain1; i++) {
			modFunc[i+flatLen+maxLocGain0] = levA[0]*buf2[i];
		}

		if(winSeq.equals(WindowSequence.LONG_START_SEQUENCE)) {
			for(i = 0; i<maxLocGain2; i++) {
				modFunc[i+maxLocGain0+maxLocGain1] = buf3[i];
			}
			flatLen = samples/2-maxLocGain1-maxLocGain2;
			for(i = 0; i<flatLen; i++) {
				modFunc[i+maxLocGain0+maxLocGain1+maxLocGain2] = 1.0f;
			}
		}
		else if(winSeq.equals(WindowSequence.LONG_STOP_SEQUENCE)) {
			for(i = 0; i<maxLocGain2; i++) {
				modFunc[i+flatLen+maxLocGain0+maxLocGain1] = buf3[i];
			}
		}

		//calculate a gain control function
		for(i = 0; i<samples; i++) {
			_function[i] = 1.0f/modFunc[i];
		}
	}

	/*
	 * calculates a fragment modification function by interpolating the gain
	 * values of the gain change positions
	 */
	private float calculateFMD(int bd, int wd, boolean prev, int maxLocGain, int samples,
			int[] loc, float[] lev, float[] fmd) {
		final int[] m = new int[samples/2];
		final int[] lct = prev ? locationPrev[bd][wd] : location[bd][wd];
		final int[] lvl = prev ? levelPrev[bd][wd] : level[bd][wd];
		final int length = lct.length;

		int lngain;
		int i;
		for(i = 0; i<length; i++) {
			loc[i+1] = 8*lct[i]; //gainc
			lngain = getGainChangePointID(lvl[i]); //gainc
			if(lngain<0) lev[i+1] = 1.0f/(float) Math.pow(2, -lngain);
			else lev[i+1] = (float) Math.pow(2, lngain);
		}

		//set start point values
		loc[0] = 0;
		if(length==0) lev[0] = 1.0f;
		else lev[0] = lev[1];
		float secLevel = lev[0];

		//set end point values
		loc[length+1] = maxLocGain;
		lev[length+1] = 1.0f;

		int j;
		for(i = 0; i<maxLocGain; i++) {
			m[i] = 0;
			for(j = 0; j<=length+1; j++) {
				if(loc[j]<=i) m[i] = j;
			}
		}

		for(i = 0; i<maxLocGain; i++) {
			if((i>=loc[m[i]])&&(i<=loc[m[i]]+7)) fmd[i] = interpolateGain(lev[m[i]], lev[m[i]+1], i-loc[m[i]]);
			else fmd[i] = lev[m[i]+1];
		}

		return secLevel;
	}

	/**
	 * transformes the exponent value of the gain to the id of the gain change
	 * point
	 */
	private int getGainChangePointID(int lngain) {
		for(int i = 0; i<ID_GAIN; i++) {
			if(lngain==LN_GAIN[i]) return i;
		}
		return 0; //shouldn't happen
	}

	/**
	 * calculates a fragment modification function
	 * the interpolated gain value between the gain values of two gain change
	 * positions is calculated by the formula:
	 * f(a,b,j) = 2^(((8-j)log2(a)+j*log2(b))/8)
	 */
	private float interpolateGain(float alev0, float alev1, int iloc) {
		final float a0 = (float) (Math.log(alev0)/Math.log(2));
		final float a1 = (float) (Math.log(alev1)/Math.log(2));
		return (float) Math.pow(2.0f, (((8-iloc)*a0+iloc*a1)/8));
	}
}


package org.jcodec.algo;

import org.jcodec.api.NotSupportedException;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Integer to byte and vice versa conversion routines
 *
 * @author The JCodec project
 *
 */
public class DataConvert {

    public static int[] from16BE(byte[] b) {
        int[] result = new int[b.length >> 1];
        int off = 0;
        for (int i = 0; i < result.length; i++) {
            result[i] = ((b[off++] & 0xff) << 8) | (b[off++] & 0xff);
        }

        return result;
    }

    public static int[] from24BE(byte[] b) {
        int[] result = new int[b.length / 3];
        int off = 0;
        for (int i = 0; i < result.length; i++) {
            result[i] = ((b[off++] & 0xff) << 16) | ((b[off++] & 0xff) << 8) | (b[off++] & 0xff);
        }

        return result;
    }

    public static int[] from16LE(byte[] b) {
        int[] result = new int[b.length >> 1];
        int off = 0;
        for (int i = 0; i < result.length; i++) {
            result[i] = (b[off++] & 0xff) | ((b[off++] & 0xff) << 8);
        }

        return result;
    }

    public static int[] from24LE(byte[] b) {
        int[] result = new int[b.length / 3];
        int off = 0;
        for (int i = 0; i < result.length; i++) {
            result[i] = (b[off++] & 0xff) | ((b[off++] & 0xff) << 8) | ((b[off++] & 0xff) << 16);
        }

        return result;
    }

    public static byte[] to16BE(int[] ia) {
        byte[] result = new byte[ia.length << 1];
        int off = 0;
        for (int i = 0; i < ia.length; i++) {
            result[off++] = (byte) ((ia[i] >> 8) & 0xff);
            result[off++] = (byte) (ia[i] & 0xff);
        }

        return result;
    }

    public static byte[] to24BE(int[] ia) {
        byte[] result = new byte[ia.length * 3];
        int off = 0;
        for (int i = 0; i < ia.length; i++) {
            result[off++] = (byte) ((ia[i] >> 16) & 0xff);
            result[off++] = (byte) ((ia[i] >> 8) & 0xff);
            result[off++] = (byte) (ia[i] & 0xff);
        }

        return result;
    }

    public static byte[] to16LE(int[] ia) {
        byte[] result = new byte[ia.length << 1];
        int off = 0;
        for (int i = 0; i < ia.length; i++) {
            result[off++] = (byte) (ia[i] & 0xff);
            result[off++] = (byte) ((ia[i] >> 8) & 0xff);
        }

        return result;
    }

    public static byte[] to24LE(int[] ia) {
        byte[] result = new byte[ia.length * 3];
        int off = 0;
        for (int i = 0; i < ia.length; i++) {
            result[off++] = (byte) (ia[i] & 0xff);
            result[off++] = (byte) ((ia[i] >> 8) & 0xff);
            result[off++] = (byte) ((ia[i] >> 16) & 0xff);
        }

        return result;
    }

    /**
     * Generic byte-array to integer-array conversion
     *
     * Converts each depth-bit sequence from the input byte array into integer
     *
     * @param b
     *            Input bytes
     * @param depth
     *            Bit depth of the integers
     * @param isBe
     *            If integers are big-endian or little-endian
     * @return
     */
    public static int[] fromByte(byte[] b, int depth, boolean isBe) {
        if (depth == 24)
            if (isBe)
                return from24BE(b);
            else
                return from24LE(b);
        else if (depth == 16)
            if (isBe)
                return from16BE(b);
            else
                return from16LE(b);
        throw new NotSupportedException("Conversion from " + depth + "bit "
                + (isBe ? "big endian" : "little endian") + " is not supported.");
    }

    /**
     * Generic integer-array to byte-array conversion
     *
     * Converts each integer into depth-bit sequence in the output byte array
     *
     * @param ia
     *            Integer array
     * @param depth
     *            Bit depth of the integers
     * @param isBe
     *            If integers are big-endian or little-endian
     * @return
     */
    public static byte[] toByte(int[] ia, int depth, boolean isBe) {
        if (depth == 24)
            if (isBe)
                return to24BE(ia);
            else
                return to24LE(ia);
        else if (depth == 16)
            if (isBe)
                return to16BE(ia);
            else
                return to16LE(ia);
        throw new NotSupportedException("Conversion to " + depth + "bit " + (isBe ? "big endian" : "little endian")
                + " is not supported.");
    }
}


package org.jcodec.common;
import static org.jcodec.common.tools.MathUtil.clip;

import org.jcodec.api.NotSupportedException;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * @author The JCodec project
 *
 */
public class AudioUtil {
    private static final float f24 = 8388607f;
    private static final float f16 = 32767f;

    public final static float r16 = 1f / 32768f;
    public final static float r24 = 1f / 8388608f;

    /**
     * Converts PCM samples stored in buf and described with format to float
     * array representation
     *
     * @param format
     *            Supported formats - *_*_S16_LE, *_*_S24_LE, *_*_S16_BE,
     *            *_*_S24_LE
     * @param buf
     * @param floatBuf
     * @return Total number of samples read from the buffer
     */
    public static void toFloat(AudioFormat format, ByteBuffer buf, FloatBuffer floatBuf) {
        if (!format.isSigned())
            throw new NotSupportedException("Unsigned PCM is not supported ( yet? ).");

        if (format.getSampleSizeInBits() != 16 && format.getSampleSizeInBits() != 24)
            throw new NotSupportedException(format.getSampleSizeInBits() + " bit PCM is not supported ( yet? ).");

        if (format.isBigEndian()) {
            if (format.getSampleSizeInBits() == 16) {
                toFloat16BE(buf, floatBuf);
            } else {
                toFloat24BE(buf, floatBuf);
            }
        } else {
            if (format.getSampleSizeInBits() == 16) {
                toFloat16LE(buf, floatBuf);
            } else {
                toFloat24LE(buf, floatBuf);
            }
        }
    }

    /**
     * Converts float PCM samples stored in floatBuf to integer representation
     * according to format and stores them in buf
     *
     * @param format
     *            Supported formats - *_*_S16_LE, *_*_S24_LE, *_*_S16_BE,
     *            *_*_S24_LE
     * @param buf
     * @param floatBuf
     * @return Total number of samples written to the buffer
     */
    public static void fromFloat(FloatBuffer floatBuf, AudioFormat format, ByteBuffer buf) {
        if (!format.isSigned())
            throw new NotSupportedException("Unsigned PCM is not supported ( yet? ).");

        if (format.getSampleSizeInBits() != 16 && format.getSampleSizeInBits() != 24)
            throw new NotSupportedException(format.getSampleSizeInBits() + " bit PCM is not supported ( yet? ).");

        if (format.isBigEndian()) {
            if (format.getSampleSizeInBits() == 16) {
                fromFloat16BE(buf, floatBuf);
            } else {
                fromFloat24BE(buf, floatBuf);
            }
        } else {
            if (format.getSampleSizeInBits() == 16) {
                fromFloat16LE(buf, floatBuf);
            } else {
                fromFloat24LE(buf, floatBuf);
            }
        }
    }

    private static void toFloat24LE(ByteBuffer buf, FloatBuffer out) {
        while (buf.remaining() >= 3 && out.hasRemaining()) {
            out.put(r24 * ((((buf.get() & 0xff) << 8) | ((buf.get() & 0xff) << 16) | ((buf.get() & 0xff) << 24)) >> 8));
        }
    }

    private static void toFloat16LE(ByteBuffer buf, FloatBuffer out) {
        while (buf.remaining() >= 2 && out.hasRemaining()) {
            out.put(r16 * (short) ((buf.get() & 0xff) | ((buf.get() & 0xff) << 8)));
        }
    }

    private static void toFloat24BE(ByteBuffer buf, FloatBuffer out) {
        while (buf.remaining() >= 3 && out.hasRemaining()) {
            out.put(r24 * ((((buf.get() & 0xff) << 24) | ((buf.get() & 0xff) << 16) | ((buf.get() & 0xff) << 8)) >> 8));
        }
    }

    private static void toFloat16BE(ByteBuffer buf, FloatBuffer out) {
        while (buf.remaining() >= 2 && out.hasRemaining()) {
            out.put(r16 * (short) (((buf.get() & 0xff) << 8) | (buf.get() & 0xff)));
        }
    }

    private static void fromFloat24LE(ByteBuffer buf, FloatBuffer _in) {
        while (buf.remaining() >= 3 && _in.hasRemaining()) {
            int val = clip((int) (_in.get() * f24), -0x800000, 0x7fffff) & 0xffffff;
            buf.put((byte) val);
            buf.put((byte) (val >> 8));
            buf.put((byte) (val >> 16));
        }
    }

    private static void fromFloat16LE(ByteBuffer buf, FloatBuffer _in) {
        while (buf.remaining() >= 2 && _in.hasRemaining()) {
            int val = clip((int) (_in.get() * f16), -0x8000, 0x7fff) & 0xffff;
            buf.put((byte) val);
            buf.put((byte) (val >> 8));
        }
    }

    private static void fromFloat24BE(ByteBuffer buf, FloatBuffer _in) {
        while (buf.remaining() >= 3 && _in.hasRemaining()) {
            int val = clip((int) (_in.get() * f24), -0x800000, 0x7fffff) & 0xffffff;
            buf.put((byte) (val >> 16));
            buf.put((byte) (val >> 8));
            buf.put((byte) val);
        }
    }

    private static void fromFloat16BE(ByteBuffer buf, FloatBuffer _in) {
        while (buf.remaining() >= 2 && _in.hasRemaining()) {
            int val = clip((int) (_in.get() * f16), -0x8000, 0x7fff) & 0xffff;
            buf.put((byte) (val >> 8));
            buf.put((byte) val);
        }
    }

    public static int fromInt(int[] data, int len, AudioFormat format, ByteBuffer buf) {
        if (!format.isSigned())
            throw new NotSupportedException("Unsigned PCM is not supported ( yet? ).");

        if (format.getSampleSizeInBits() != 16 && format.getSampleSizeInBits() != 24)
            throw new NotSupportedException(format.getSampleSizeInBits() + " bit PCM is not supported ( yet? ).");

        if (format.isBigEndian()) {
            if (format.getSampleSizeInBits() == 16) {
                return fromInt16BE(buf, data, len);
            } else {
                return fromInt24BE(buf, data, len);
            }
        } else {
            if (format.getSampleSizeInBits() == 16) {
                return fromInt16LE(buf, data, len);
            } else {
                return fromInt24LE(buf, data, len);
            }
        }
    }

    private static int fromInt24LE(ByteBuffer buf, int[] out, int len) {
        int samples = 0;
        while (buf.remaining() >= 3 && samples < len) {
            int val = out[samples++];
            buf.put((byte) val);
            buf.put((byte) (val >> 8));
            buf.put((byte) (val >> 16));
        }
        return samples;
    }

    private static int fromInt16LE(ByteBuffer buf, int[] out, int len) {
        int samples = 0;
        while (buf.remaining() >= 2 && samples < len) {
            int val = out[samples++];
            buf.put((byte) val);
            buf.put((byte) (val >> 8));
        }
        return samples;
    }

    private static int fromInt24BE(ByteBuffer buf, int[] out, int len) {
        int samples = 0;
        while (buf.remaining() >= 3 && samples < len) {
            int val = out[samples++];
            buf.put((byte) (val >> 16));
            buf.put((byte) (val >> 8));
            buf.put((byte) val);
        }
        return samples;
    }

    private static int fromInt16BE(ByteBuffer buf, int[] out, int len) {
        int samples = 0;
        while (buf.remaining() >= 2 && samples < len) {
            int val = out[samples++];
            buf.put((byte) (val >> 8));
            buf.put((byte) val);
        }
        return samples;
    }

    public static int toInt(AudioFormat format, ByteBuffer buf, int[] samples) {
        if (!format.isSigned())
            throw new NotSupportedException("Unsigned PCM is not supported ( yet? ).");

        if (format.getSampleSizeInBits() != 16 && format.getSampleSizeInBits() != 24)
            throw new NotSupportedException(format.getSampleSizeInBits() + " bit PCM is not supported ( yet? ).");

        if (format.isBigEndian()) {
            if (format.getSampleSizeInBits() == 16) {
                return toInt16BE(buf, samples);
            } else {
                return toInt24BE(buf, samples);
            }
        } else {
            if (format.getSampleSizeInBits() == 16) {
                return toInt16LE(buf, samples);
            } else {
                return toInt24LE(buf, samples);
            }
        }
    }

    private static int toInt24LE(ByteBuffer buf, int[] out) {
        int samples = 0;
        while (buf.remaining() >= 3 && samples < out.length) {
            out[samples++] = (((buf.get() & 0xff) << 8) | ((buf.get() & 0xff) << 16) | ((buf.get() & 0xff) << 24)) >> 8;
        }
        return samples;
    }

    private static int toInt16LE(ByteBuffer buf, int[] out) {
        int samples = 0;
        while (buf.remaining() >= 2 && samples < out.length) {
            out[samples++] = (short) ((buf.get() & 0xff) | ((buf.get() & 0xff) << 8));
        }
        return samples;
    }

    private static int toInt24BE(ByteBuffer buf, int[] out) {
        int samples = 0;
        while (buf.remaining() >= 3 && samples < out.length) {
            out[samples++] = (((buf.get() & 0xff) << 24) | ((buf.get() & 0xff) << 16) | ((buf.get() & 0xff) << 8)) >> 8;
        }
        return samples;
    }

    private static int toInt16BE(ByteBuffer buf, int[] out) {
        int samples = 0;
        while (buf.remaining() >= 2 && samples < out.length) {
            out[samples++] = (short) (((buf.get() & 0xff) << 8) | (buf.get() & 0xff));
        }
        return samples;
    }

    /**
     * Interleaves audio samples in ins into outb using sample size from the
     * format
     *
     * @param format
     * @param ins
     * @param outb
     */
    public static void interleave(AudioFormat format, ByteBuffer[] ins, ByteBuffer outb) {
        int bytesPerSample = format.getSampleSizeInBits() >> 3;
        int bytesPerFrame = bytesPerSample * ins.length;

        int max = 0;
        for (int i = 0; i < ins.length; i++)
            if (ins[i].remaining() > max)
                max = ins[i].remaining();

        for (int frames = 0; frames < max && outb.remaining() >= bytesPerFrame; frames++) {
            for (int j = 0; j < ins.length; j++) {
                if (ins[j].remaining() < bytesPerSample) {
                    for (int i = 0; i < bytesPerSample; i++)
                        outb.put((byte) 0);
                } else {
                    for (int i = 0; i < bytesPerSample; i++) {
                        outb.put(ins[j].get());
                    }
                }
            }
        }
    }

    /**
     * Deinterleaves audio samples from inb into outs using sample size from
     * format
     *
     * @param format
     * @param inb
     * @param outs
     */
    public static void deinterleave(AudioFormat format, ByteBuffer inb, ByteBuffer[] outs) {
        int bytesPerSample = format.getSampleSizeInBits() >> 3;
        int bytesPerFrame = bytesPerSample * outs.length;

        while (inb.remaining() >= bytesPerFrame) {
            for (int j = 0; j < outs.length; j++) {
                for (int i = 0; i < bytesPerSample; i++) {
                    outs[j].put(inb.get());
                }
            }
        }
    }
}


package org.jcodec.containers.mp4.boxes;

import java.util.List;
import java.util.ListIterator;

import org.jcodec.common.model.Rational;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.MP4TrackType;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Creates MP4 file out of a set of samples
 *
 * @author The JCodec project
 *
 */
public class TrakBox extends NodeBox {

    public static String fourcc() {
        return "trak";
    }

    public static TrakBox createTrakBox() {
        return new TrakBox(new Header(fourcc()));
    }

    public TrakBox(Header atom) {
        super(atom);
    }

    public void setDataRef(String url) {
        MediaInfoBox minf = getMdia().getMinf();
        DataInfoBox dinf = minf.getDinf();
        if (dinf == null) {
            dinf = DataInfoBox.createDataInfoBox();
            minf.add(dinf);
        }
        DataRefBox dref = dinf.getDref();
        UrlBox urlBox = UrlBox.createUrlBox(url);
        if (dref == null) {
            dref = DataRefBox.createDataRefBox();
            dinf.add(dref);
            dref.add(urlBox);
        } else {
            ListIterator<Box> lit = dref.boxes.listIterator();
            while (lit.hasNext()) {
                FullBox box = (FullBox) lit.next();
                if ((box.getFlags() & 0x1) != 0)
                    lit.set(urlBox);
            }
        }
    }

    public MediaBox getMdia() {
        return NodeBox.findFirst(this, MediaBox.class, "mdia");
    }

    public TrackHeaderBox getTrackHeader() {
        return NodeBox.findFirst(this, TrackHeaderBox.class, "tkhd");
    }

    public List<Edit> getEdits() {
        EditListBox elst = NodeBox.findFirstPath(this, EditListBox.class, Box.path("edts.elst"));
        if (elst == null)
            return null;
        return elst.getEdits();
    }

    public void setEdits(List<Edit> edits) {
        NodeBox edts = NodeBox.findFirst(this, NodeBox.class, "edts");
        if (edts == null) {
            edts = new NodeBox(new Header("edts"));
            this.add(edts);
        }
        edts.removeChildren(new String[]{"elst"});

        edts.add(EditListBox.createEditListBox(edits));
        getTrackHeader().setDuration(getEditedDuration(this));
    }

    public boolean isVideo() {
        return "vide".equals(getHandlerType());
    }

    public boolean isTimecode() {
        return "tmcd".equals(getHandlerType());
    }

    public String getHandlerType() {
        HandlerBox handlerBox = NodeBox.findFirstPath(this, HandlerBox.class, Box.path("mdia.hdlr"));
        if (handlerBox == null)
            return null;
        String type = handlerBox.getComponentSubType();
        return type;
    }

    public boolean isAudio() {
        return "soun".equals(getHandlerType());
    }

    /**
     * Gets 'media timescale' of this track. This is the timescale used to
     * represent the durations of samples inside mdia/minf/stbl/stts box.
     *
     * @return 'media timescale' of the track.
     */
    public int getTimescale() {
        return NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).getTimescale();
    }

    /**
     * Sets the 'media timescale' of this track. This is the time timescale used
     * to represent sample durations.
     *
     * @param timescale
     *            A new 'media timescale' of this track.
     */
    public void setTimescale(int timescale) {
        NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).setTimescale(timescale);
    }

    public long rescale(long tv, long ts) {
        return (tv * getTimescale()) / ts;
    }

    public void setDuration(long duration) {
        getTrackHeader().setDuration(duration);
    }

    public long getDuration() {
        return getTrackHeader().getDuration();
    }

    public long getMediaDuration() {
        return NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).getDuration();
    }

    public boolean isPureRef() {
        MediaInfoBox minf = getMdia().getMinf();
        DataInfoBox dinf = minf.getDinf();
        if (dinf == null) {
            return false;
        }
        DataRefBox dref = dinf.getDref();
        if (dref == null)
            return false;

        for (Box box : dref.boxes) {
            if ((((FullBox) box).getFlags() & 0x1) != 0)
                return false;
        }
        return true;
    }

    public boolean hasDataRef() {
        DataInfoBox dinf = getMdia().getMinf().getDinf();
        if (dinf == null) {
            return false;
        }
        DataRefBox dref = dinf.getDref();
        if (dref == null)
            return false;

        boolean result = false;
        for (Box box : dref.boxes) {
            result |= (((FullBox) box).getFlags() & 0x1) != 0x1;
        }
        return result;
    }

    public Rational getPAR() {
        PixelAspectExt pasp = NodeBox.findFirstPath(this, PixelAspectExt.class, new String[] { "mdia", "minf", "stbl", "stsd", null, "pasp" });
        return pasp == null ? new Rational(1, 1) : pasp.getRational();
    }

    public void setPAR(Rational par) {
        SampleEntry[] sampleEntries = getSampleEntries();
        for (int i = 0; i < sampleEntries.length; i++) {
            SampleEntry sampleEntry = sampleEntries[i];
            sampleEntry.removeChildren(new String[]{"pasp"});
            sampleEntry.add(PixelAspectExt.createPixelAspectExt(par));
        }
    }

    public SampleEntry[] getSampleEntries() {
        return NodeBox.findAllPath(this, SampleEntry.class, new String[]{"mdia", "minf", "stbl", "stsd", null});
    }

    public void setClipRect(short x, short y, short width, short height) {
        NodeBox clip = NodeBox.findFirst(this, NodeBox.class, "clip");
        if (clip == null) {
            clip = new NodeBox(new Header("clip"));
            add(clip);
        }
        clip.replace("crgn", ClipRegionBox.createClipRegionBox(x, y, width, height));
    }

    public long getSampleCount() {
        return NodeBox.findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz")).getCount();
    }

    public void setAperture(Size sar, Size dar) {
        removeChildren(new String[]{"tapt"});
        NodeBox tapt = new NodeBox(new Header("tapt"));
        tapt.add(ClearApertureBox.createClearApertureBox(dar.getWidth(), dar.getHeight()));
        tapt.add(ProductionApertureBox.createProductionApertureBox(dar.getWidth(), dar.getHeight()));
        tapt.add(EncodedPixelBox.createEncodedPixelBox(sar.getWidth(), sar.getHeight()));
        add(tapt);
    }

    public void setDimensions(Size dd) {
        getTrackHeader().setWidth((float) dd.getWidth());
        getTrackHeader().setHeight((float) dd.getHeight());
    }

    public int getFrameCount() {
        SampleSizesBox stsz = NodeBox.findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz"));
        return stsz.getDefaultSize() != 0 ? stsz.getCount() : stsz.getSizes().length;
    }

    public String getName() {
        NameBox nb = NodeBox.findFirstPath(this, NameBox.class, Box.path("udta.name"));
        return nb == null ? null : nb.getName();
    }

    public void fixMediaTimescale(int ts) {
        MediaHeaderBox mdhd = NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd"));
        int oldTs = mdhd.getTimescale();

        mdhd.setTimescale(ts);
        mdhd.setDuration((ts * mdhd.getDuration()) / oldTs);
        List<Edit> edits = getEdits();
        if (edits != null) {
            for (Edit edit : edits) {
                edit.setMediaTime((ts * edit.getMediaTime()) / oldTs);
            }
        }
        TimeToSampleBox tts = NodeBox.findFirstPath(this, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts"));
        TimeToSampleEntry[] entries = tts.getEntries();
        for (int i = 0; i < entries.length; i++) {
            TimeToSampleEntry tte = entries[i];
            tte.setSampleDuration((ts * tte.getSampleDuration()) / oldTs);
        }
    }

    public void setName(String string) {
        NodeBox udta = NodeBox.findFirst(this, NodeBox.class, "udta");
        if (udta == null) {
            udta = new NodeBox(new Header("udta"));
            this.add(udta);
        }
        udta.removeChildren(new String[]{"name"});
        udta.add(NameBox.createNameBox(string));
    }

    /**
     * Retrieves coded size of this video track.
     *
     * Note: May be different from video display dimension.
     *
     * @return
     */
    public Size getCodedSize() {
        SampleEntry se = getSampleEntries()[0];
        if (!(se instanceof VideoSampleEntry))
            throw new IllegalArgumentException("Not a video track");
        VideoSampleEntry vse = (VideoSampleEntry) se;

        return new Size(vse.getWidth(), vse.getHeight());
    }

    public TimeToSampleBox getStts() {
        return NodeBox.findFirstPath(this, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts"));
    }

    public ChunkOffsetsBox getStco() {
        return NodeBox.findFirstPath(this, ChunkOffsetsBox.class, Box.path("mdia.minf.stbl.stco" ));
    }

    public ChunkOffsets64Box getCo64() {
        return NodeBox.findFirstPath(this, ChunkOffsets64Box.class, Box.path("mdia.minf.stbl.co64" ));
    }

    public SampleSizesBox getStsz() {
        return NodeBox.findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz" ));
    }

    public SampleToChunkBox getStsc() {
        return NodeBox.findFirstPath(this, SampleToChunkBox.class, Box.path("mdia.minf.stbl.stsc" ));
    }

    public SampleDescriptionBox getStsd() {
        return NodeBox.findFirstPath(this, SampleDescriptionBox.class, Box.path("mdia.minf.stbl.stsd" ));
    }

    public SyncSamplesBox getStss() {
        return NodeBox.findFirstPath(this, SyncSamplesBox.class, Box.path("mdia.minf.stbl.stss" ));
    }

    public CompositionOffsetsBox getCtts() {
        return NodeBox.findFirstPath(this, CompositionOffsetsBox.class, Box.path("mdia.minf.stbl.ctts" ));
    }

    public static MP4TrackType getTrackType(TrakBox trak) {
        HandlerBox handler = NodeBox.findFirstPath(trak, HandlerBox.class, Box.path("mdia.hdlr"));
        return handler == null ? null : MP4TrackType.fromHandler(handler.getComponentSubType());
    }

    /**
     * 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;
    }
}


package org.jcodec.codecs.h264.decode.aso;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * A helper class that builds macroblock to slice group maps needed by ASO
 * (Arbitrary Slice Order)
 *
 * @author The JCodec project
 *
 */
public class SliceGroupMapBuilder {

    /**
     *
     * Interleaved slice group map. Each slice group fills a number of cells
     * equal to the appropriate run length, then followed by the next slice
     * group.
     *
     * Example:
     *
     * 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1,
     *
     */
    public static int[] buildInterleavedMap(int picWidthInMbs, int picHeightInMbs, int[] runLength) {

        int numSliceGroups = runLength.length;
        int picSizeInMbs = picWidthInMbs * picHeightInMbs;
        int[] groups = new int[picSizeInMbs];

        int i = 0;
        do {
            for (int iGroup = 0; iGroup < numSliceGroups && i < picSizeInMbs; i += runLength[iGroup++]) {
                for (int j = 0; j < runLength[iGroup] && i + j < picSizeInMbs; j++) {
                    groups[i + j] = iGroup;
                }
            }
        } while (i < picSizeInMbs);

        return groups;
    }

    /**
     * A dispersed map. Every odd line starts from the (N / 2)th group
     *
     * Example:
     *
     * 0, 1, 2, 3, 0, 1, 2, 3 2, 3, 0, 1, 2, 3, 0, 1 0, 1, 2, 3, 0, 1, 2, 3 2,
     * 3, 0, 1, 2, 3, 0, 1 0, 1, 2, 3, 0, 1, 2, 3 2, 3, 0, 1, 2, 3, 0, 1 0, 1,
     * 2, 3, 0, 1, 2, 3 2, 3, 0, 1, 2, 3, 0, 1 0, 1, 2, 3, 0, 1, 2, 3 2, 3, 0,
     * 1, 2, 3, 0, 1
     *
     */
    public static int[] buildDispersedMap(int picWidthInMbs, int picHeightInMbs, int numSliceGroups) {

        int picSizeInMbs = picWidthInMbs * picHeightInMbs;
        int[] groups = new int[picSizeInMbs];

        for (int i = 0; i < picSizeInMbs; i++) {
            int group = ((i % picWidthInMbs) + (((i / picWidthInMbs) * numSliceGroups) / 2)) % numSliceGroups;
            groups[i] = group;
        }

        return groups;
    }

    /**
     *
     * A foreground macroblock to slice group map. Macroblocks of the last slice
     * group are the background, all the others represent rectangles covering
     * areas with top-left corner specified by topLeftAddr[group] and bottom
     * right corner specified by bottomRightAddr[group].
     *
     * @param picWidthInMbs
     * @param picHeightInMbs
     * @param numSliceGroups
     *            Total number of slice groups
     * @param topLeftAddr
     *            Addresses of macroblocks that are top-left corners of
     *            respective slice groups
     * @param bottomRightAddr
     *            Addresses macroblocks that are bottom-right corners of
     *            respective slice groups
     * @return
     */
    public static int[] buildForegroundMap(int picWidthInMbs, int picHeightInMbs, int numSliceGroups,
            int[] topLeftAddr, int[] bottomRightAddr) {

        int picSizeInMbs = picWidthInMbs * picHeightInMbs;
        int[] groups = new int[picSizeInMbs];

        for (int i = 0; i < picSizeInMbs; i++)
            groups[i] = numSliceGroups - 1;

        int tot = 0;
        for (int iGroup = numSliceGroups - 2; iGroup >= 0; iGroup--) {

            int yTopLeft = topLeftAddr[iGroup] / picWidthInMbs;
            int xTopLeft = topLeftAddr[iGroup] % picWidthInMbs;
            int yBottomRight = bottomRightAddr[iGroup] / picWidthInMbs;
            int xBottomRight = bottomRightAddr[iGroup] % picWidthInMbs;

            int sz = (yBottomRight - yTopLeft + 1) * (xBottomRight - xTopLeft + 1);
            tot += sz;

            int ind = 0;
            for (int y = yTopLeft; y <= yBottomRight; y++)
                for (int x = xTopLeft; x <= xBottomRight; x++) {
                    int mbAddr = y * picWidthInMbs + x;
                    groups[mbAddr] = iGroup;
                }
        }

        return groups;
    }

    /**
     * A boxout macroblock to slice group mapping. Only applicable when there's
     * exactly 2 slice groups. Slice group 1 is a background, while slice group
     * 0 is a box in the middle of the frame.
     *
     * @param picWidthInMbs
     * @param picHeightInMbs
     * @param changeDirection
     * @param numberOfMbsInBox
     *            number of macroblocks in slice group 0
     * @return
     */
    public static int[] buildBoxOutMap(int picWidthInMbs, int picHeightInMbs, boolean changeDirection,
            int numberOfMbsInBox) {

        int picSizeInMbs = picWidthInMbs * picHeightInMbs;
        int[] groups = new int[picSizeInMbs];
        int changeDirectionInt = changeDirection ? 1 : 0;

        for (int i = 0; i < picSizeInMbs; i++)
            groups[i] = 1;

        int x = (picWidthInMbs - changeDirectionInt) / 2;
        int y = (picHeightInMbs - changeDirectionInt) / 2;
        int leftBound = x;
        int topBound = y;
        int rightBound = x;
        int bottomBound = y;
        int xDir = changeDirectionInt - 1;
        int yDir = changeDirectionInt;

        boolean mapUnitVacant = false;
        for (int k = 0; k < numberOfMbsInBox; k += (mapUnitVacant ? 1 : 0)) {
            int mbAddr = y * picWidthInMbs + x;
            mapUnitVacant = (groups[mbAddr] == 1);
            if (mapUnitVacant) {
                groups[mbAddr] = 0;
            }
            if (xDir == -1 && x == leftBound) {
                leftBound = Max(leftBound - 1, 0);
                x = leftBound;
                xDir = 0;
                yDir = 2 * changeDirectionInt - 1;
            } else if (xDir == 1 && x == rightBound) {
                rightBound = Min(rightBound + 1, picWidthInMbs - 1);
                x = rightBound;
                xDir = 0;
                yDir = 1 - 2 * changeDirectionInt;
            } else if (yDir == -1 && y == topBound) {
                topBound = Max(topBound - 1, 0);
                y = topBound;
                xDir = 1 - 2 * changeDirectionInt;
                yDir = 0;
            } else if (yDir == 1 && y == bottomBound) {
                bottomBound = Min(bottomBound + 1, picHeightInMbs - 1);
                y = bottomBound;
                xDir = 2 * changeDirectionInt - 1;
                yDir = 0;
            } else {
                x += xDir;
                y += yDir;
            }
        }

        return groups;
    }

    private static int Min(int i, int j) {
        return i < j ? i : j;
    }

    private static int Max(int i, int j) {
        return i > j ? i : j;
    }

    /**
     *
     * A macroblock to slice group map that fills frame in raster scan.
     *
     * @param picWidthInMbs
     * @param picHeightInMbs
     * @param sizeOfUpperLeftGroup
     * @param changeDirection
     * @return
     */
    public static int[] buildRasterScanMap(int picWidthInMbs, int picHeightInMbs, int sizeOfUpperLeftGroup,
            boolean changeDirection) {

        int picSizeInMbs = picWidthInMbs * picHeightInMbs;
        int[] groups = new int[picSizeInMbs];
        int changeDirectionInt = changeDirection ? 1 : 0;

        int i;
        for (i = 0; i < sizeOfUpperLeftGroup; i++) {
            groups[i] = changeDirectionInt;
        }

        for (; i < picSizeInMbs; i++) {
            groups[i] = 1 - changeDirectionInt;
        }

        return groups;
    }

    /**
     * A macroblock to slice group map that fills frame column by column
     *
     * @param picWidthInMbs
     * @param picHeightInMbs
     * @param sizeOfUpperLeftGroup
     * @param changeDirection
     * @return
     */
    public static int[] buildWipeMap(int picWidthInMbs, int picHeightInMbs, int sizeOfUpperLeftGroup,
            boolean changeDirection) {

        int picSizeInMbs = picWidthInMbs * picHeightInMbs;
        int[] groups = new int[picSizeInMbs];
        int changeDirectionInt = changeDirection ? 1 : 0;

        int k = 0;
        for (int j = 0; j < picWidthInMbs; j++) {
            for (int i = 0; i < picHeightInMbs; i++) {
                int mbAddr = i * picWidthInMbs + j;
                if (k++ < sizeOfUpperLeftGroup) {
                    groups[mbAddr] = changeDirectionInt;
                } else {
                    groups[mbAddr] = 1 - changeDirectionInt;
                }
            }
        }

        return groups;
    }
}


package org.jcodec.containers.mxf.model;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.platform.Platform;

import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.Date;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * @author The JCodec project
 *
 */
public abstract class MXFMetadata {
    protected UL ul;
    protected UL uid;

    public MXFMetadata(UL ul) {
        this.ul = ul;
    }

    public abstract void readBuf(ByteBuffer bb);

    /**
     * Utility method to read a batch of ULS
     *
     * @param _bb
     * @return
     */
    protected static UL[] readULBatch(ByteBuffer _bb) {
        int count = _bb.getInt();
        _bb.getInt();
        UL[] result = new UL[count];
        for (int i = 0; i < count; i++) {
            result[i] = UL.read(_bb);
        }
        return result;
    }

    /**
     * Utility method to read a batch of int32
     *
     * @param _bb
     * @return
     */
    protected static int[] readInt32Batch(ByteBuffer _bb) {
        int count = _bb.getInt();
        _bb.getInt();
        int[] result = new int[count];
        for (int i = 0; i < count; i++) {
            result[i] = _bb.getInt();
        }
        return result;
    }

    protected static Date readDate(ByteBuffer _bb) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, _bb.getShort());
        calendar.set(Calendar.MONTH, _bb.get());
        calendar.set(Calendar.DAY_OF_MONTH, _bb.get());
        calendar.set(Calendar.HOUR, _bb.get());
        calendar.set(Calendar.MINUTE, _bb.get());
        calendar.set(Calendar.SECOND, _bb.get());
        calendar.set(Calendar.MILLISECOND, (_bb.get() & 0xff) << 2);

        return calendar.getTime();

    }

    protected String readUtf16String(ByteBuffer _bb) {
        byte[] array;
        if (_bb.getShort(_bb.limit() - 2) != 0) {
            array = NIOUtils.toArray(_bb);
        } else {
            array = NIOUtils.toArray((ByteBuffer) _bb.limit(_bb.limit() - 2));
        }
        return Platform.stringFromCharset(array, Platform.UTF_16);
    }

    public UL getUl() {
        return ul;
    }

    public UL getUid() {
        return uid;
    }
}


package org.jcodec.api.awt;

import org.jcodec.api.FrameGrab;
import org.jcodec.api.JCodecException;
import org.jcodec.api.PictureWithMetadata;
import org.jcodec.api.UnsupportedFormatException;
import org.jcodec.api.specific.ContainerAdaptor;
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.Picture;
import org.jcodec.scale.AWTUtil;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * 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.
 *
 * NOTE: AWT specific routines
 *
 * @author The JCodec project
 *
 */
public class AWTFrameGrab extends FrameGrab {
    public static AWTFrameGrab createAWTFrameGrab(SeekableByteChannel _in) throws IOException, JCodecException {
        FrameGrab fg = FrameGrab.createFrameGrab(_in);
        return new AWTFrameGrab(fg.getVideoTrack(), fg.getDecoder());
    }

    public AWTFrameGrab(SeekableDemuxerTrack videoTrack, ContainerAdaptor decoder) {
        super(videoTrack, decoder);
    }

    /**
     * Get frame at a specified second as AWT image
     *
     * @param file
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static BufferedImage getFrame(File file, double second) throws IOException, JCodecException {
        FileChannelWrapper ch = null;
        try {
            ch = NIOUtils.readableChannel(file);
            return ((AWTFrameGrab) createAWTFrameGrab(ch).seekToSecondPrecise(second)).getFrameWithOrientation();
        } finally {
            NIOUtils.closeQuietly(ch);
        }
    }

    /**
     * Get frame at a specified second as AWT image
     *
     * @param file
     * @param second
     * @return
     * @throws UnsupportedFormatException
     * @throws IOException
     */
    public static BufferedImage getFrame(SeekableByteChannel file, double second) throws JCodecException, IOException {
        return ((AWTFrameGrab) createAWTFrameGrab(file).seekToSecondPrecise(second)).getFrame();
    }

    /**
     * Get frame at current position in AWT image
     *
     * @return
     * @throws IOException
     */
    public BufferedImage getFrame() throws IOException {
        Picture nativeFrame = getNativeFrame();
        return nativeFrame == null ? null : AWTUtil.toBufferedImage(nativeFrame);
    }

    public BufferedImage getFrameWithOrientation() throws IOException {
        PictureWithMetadata nativeFrame = getNativeFrameWithMetadata();
        return nativeFrame == null ? null : AWTUtil.toBufferedImage(nativeFrame.getPicture(), nativeFrame.getOrientation());
    }

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

    /**
     * Get frame at a specified frame number as AWT image
     *
     * @param file
     * @param second
     * @return
     * @throws IOException
     * @throws JCodecException
     */
    public static BufferedImage getFrame(SeekableByteChannel file, int frameNumber) throws JCodecException, IOException {
        return ((AWTFrameGrab) createAWTFrameGrab(file).seekToFramePrecise(frameNumber)).getFrame();
    }

    /**
     * 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 BufferedImage getFrame(SeekableDemuxerTrack vt, ContainerAdaptor decoder, int frameNumber)
            throws IOException, JCodecException {
        return ((AWTFrameGrab) new AWTFrameGrab(vt, decoder).seekToFramePrecise(frameNumber)).getFrame();
    }

    /**
     * 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 BufferedImage getFrame(SeekableDemuxerTrack vt, ContainerAdaptor decoder, double second)
            throws IOException, JCodecException {
        return ((AWTFrameGrab) new AWTFrameGrab(vt, decoder).seekToSecondPrecise(second)).getFrame();
    }

    /**
     * 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 BufferedImage getFrameSloppy(SeekableDemuxerTrack vt, ContainerAdaptor decoder, int frameNumber)
            throws IOException, JCodecException {
        return ((AWTFrameGrab) new AWTFrameGrab(vt, decoder).seekToFrameSloppy(frameNumber)).getFrame();
    }

    /**
     * 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 BufferedImage getFrameSloppy(SeekableDemuxerTrack vt, ContainerAdaptor decoder, double second)
            throws IOException, JCodecException {
        return ((AWTFrameGrab) new AWTFrameGrab(vt, decoder).seekToSecondSloppy(second)).getFrame();
    }

}


package org.jcodec.containers.mp3;

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

import org.jcodec.common.AudioCodecMeta;
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.UsedViaReflection;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Packet.FrameType;

/**
 * Demuxer for MPEG 1/2 audio layer 1,2,3 (MP3).
 *
 * Extracts raw MPEG audio frames from the ES.
 *
 * See http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm for more detail.
 *
 * @author Stanislav Vitvitskiy
 */
public class MPEGAudioDemuxer implements Demuxer, DemuxerTrack {
    private static int field(int off, int size) {
        return (((1 << size) - 1) << 16) | off;
    }

    private static final int MAX_FRAME_SIZE = 1728;
    private static final int MIN_FRAME_SIZE = 52;
    private static final int CHANNELS = field(6, 2);
    private static final int PADDING = field(9, 1);
    private static final int SAMPLE_RATE = field(10, 2);
    private static final int BITRATE = field(12, 4);
    private static final int VERSION = field(19, 2);
    private static final int LAYER = field(17, 2);
    private static final int SYNC = field(21, 11);

    private static final int MPEG1 = 0x3;
    private static final int MPEG2 = 0x2;
    private static final int MPEG25 = 0x0;

    private static int bitrateTable[][][] = {
            { { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 },
                    { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 },
                    { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 } },
            { { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 },
                    { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 },
                    { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 } } };
    private static int freqTab[] = { 44100, 48000, 32000 };
    private static int rateReductTab[] = { 2, 0, 1, 0 };

    private static int getField(int header, int field) {
        return (header >> (field & 0xffff)) & (field >> 16);
    }

    private SeekableByteChannel ch;
    private List<DemuxerTrack> tracks;
    private int frameNo;
    private ByteBuffer readBuffer;
    private int runningFour;
    private boolean eof;
    private DemuxerTrackMeta meta;
    private int sampleRate;

    public MPEGAudioDemuxer(SeekableByteChannel ch) throws IOException {
        this.ch = ch;
        readBuffer = ByteBuffer.allocate(1 << 18); // 256K
        readMoreData();
        if (readBuffer.remaining() < 4) {
            eof = true;
        } else {
            runningFour = readBuffer.getInt();
            if (!validHeader(runningFour)) {
                eof = skipJunk();
            }
            extractMeta();
        }

        tracks = new ArrayList<DemuxerTrack>();
        tracks.add(this);
    }

    private void extractMeta() {
        if (!validHeader(runningFour))
            return;
        int layer = 3 - getField(runningFour, LAYER);
        int channelCount = getField(runningFour, CHANNELS) == 3 ? 1 : 2;

        int version = getField(runningFour, VERSION);
        sampleRate = freqTab[getField(runningFour, SAMPLE_RATE)] >> rateReductTab[version];
        AudioCodecMeta codecMeta = AudioCodecMeta.createAudioCodecMeta(".mp3", 16, channelCount, sampleRate,
                ByteOrder.LITTLE_ENDIAN, false, null, null);
        Codec codec = layer == 2 ? Codec.MP3 : (layer == 1 ? Codec.MP2 : Codec.MP1);
        meta = new DemuxerTrackMeta(TrackType.AUDIO, codec, 0, null, 0, null, null, codecMeta);
    }

    @Override
    public void close() throws IOException {
        ch.close();
    }

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

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

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

    @Override
    public Packet nextFrame() throws IOException {
        if (eof)
            return null;
        if (!validHeader(runningFour)) {
            eof = skipJunk();
        }
        int frameSize = calcFrameSize(runningFour);
        ByteBuffer frame = ByteBuffer.allocate(frameSize);
        eof = readFrame(frame);
        frame.flip();

        Packet pkt = new Packet(frame, frameNo * 1152, sampleRate, 1152, frameNo, FrameType.KEY, null, 0);

        ++frameNo;

        return pkt;
    }

    private static boolean validHeader(int four) {
        if (getField(four, SYNC) != 0x7ff)
            return false;
        if (getField(four, LAYER) == 0)
            return false;
        if (getField(four, SAMPLE_RATE) == 3)
            return false;
        if (getField(four, BITRATE) == 0xf)
            return false;
        return true;
    }

    private void readMoreData() throws IOException {
        readBuffer.clear();
        ch.read(readBuffer);
        readBuffer.flip();
    }

    private static int calcFrameSize(int header) {
        int bitrateIdx = getField(header, BITRATE);
        int layer = 3 - getField(header, LAYER);
        int version = getField(header, VERSION);
        int mpeg2 = version != 3 ? 1 : 0;
        int bitRate = bitrateTable[mpeg2][layer][bitrateIdx] * 1000;
        int sampleRate = freqTab[getField(header, SAMPLE_RATE)] >> rateReductTab[version];
        int padding = getField(header, PADDING);
        int lsf = version == MPEG25 || version == MPEG2 ? 1 : 0;
        switch (layer) {
        case 0:
            return ((bitRate * 12) / sampleRate + padding) * 4;
        case 1:
            return (bitRate * 144) / sampleRate + padding;
        default:
        case 2:
            return (bitRate * 144) / (sampleRate << lsf) + padding;
        }
    }

    private boolean readFrame(ByteBuffer frame) throws IOException {
        boolean eof = false;
        while (frame.hasRemaining()) {
            frame.put((byte) (runningFour >> 24));
            runningFour <<= 8;
            if (!readBuffer.hasRemaining())
                readMoreData();
            if (readBuffer.hasRemaining())
                runningFour |= readBuffer.get() & 0xff;
            else
                eof = true;
        }
        return eof;
    }

    private boolean skipJunk() throws IOException {
        boolean eof = false;
        int total = 0;
        while (!validHeader(runningFour)) {
            if (!readBuffer.hasRemaining())
                readMoreData();
            if (!readBuffer.hasRemaining()) {
                eof = true;
                break;
            }
            runningFour <<= 8;
            runningFour |= readBuffer.get() & 0xff;
            ++total;
        }
        Logger.warn(String.format("[mp3demuxer] Skipped %d bytes of junk", total));
        return eof;
    }

    @Override
    public DemuxerTrackMeta getMeta() {
        return meta;
    }

    /**
     * Used to auto-detect MPEG Audio (MP3) files
     *
     * @param b
     *            Buffer containing a snippet of data
     * @return Score from 0 to 100
     */
    @UsedViaReflection
    public static int probe(final ByteBuffer b) {
        ByteBuffer fork = b.duplicate();

        int valid = 0, total = 0;
        int header = fork.getInt();
        do {
            if (!validHeader(header))
                header = skipJunkBB(header, fork);
            int size = calcFrameSize(header);
            if (fork.remaining() < size)
                break;
            ++total;
            if (size > 0)
                NIOUtils.skip(fork, size - 4);
            else
                header = skipJunkBB(header, fork);
            if (fork.remaining() >= 4) {
                header = fork.getInt();
                if (size >= MIN_FRAME_SIZE && size <= MAX_FRAME_SIZE && validHeader(header))
                    valid++;
            }
        } while (fork.remaining() >= 4);

        return (100 * valid) / total;
    }

    private static int skipJunkBB(int header, ByteBuffer fork) {
        while (!validHeader(header) && fork.hasRemaining()) {
            header <<= 8;
            header |= fork.get() & 0xff;
        }
        return header;
    }
}