All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jcodec.containers.mp4.demuxer.TimecodeMP4DemuxerTrack Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
package org.jcodec.containers.mp4.demuxer;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.TapeTimecode;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.QTTimeUtil;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TimecodeSampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 * 
 * Timecode track, provides timecode information for video track
 * 
 * @author The JCodec project
 * 
 */
public class TimecodeMP4DemuxerTrack {

    private TrakBox box;
    private TimeToSampleEntry[] timeToSamples;
    private int[] sampleCache;
    private TimecodeSampleEntry tse;
    private SeekableByteChannel input;
    private MovieBox movie;
    private long[] chunkOffsets;
    private SampleToChunkEntry[] sampleToChunks;

    public TimecodeMP4DemuxerTrack(MovieBox movie, TrakBox trak, SeekableByteChannel input) throws IOException {
        this.box = trak;
        this.input = input;
        this.movie = movie;

        NodeBox stbl = trak.getMdia().getMinf().getStbl();

        TimeToSampleBox stts = NodeBox.findFirst(stbl, TimeToSampleBox.class, "stts");
        SampleToChunkBox stsc = NodeBox.findFirst(stbl, SampleToChunkBox.class, "stsc");
        ChunkOffsetsBox stco = NodeBox.findFirst(stbl, ChunkOffsetsBox.class, "stco");
        ChunkOffsets64Box co64 = NodeBox.findFirst(stbl, ChunkOffsets64Box.class, "co64");

        timeToSamples = stts.getEntries();
        chunkOffsets = stco != null ? stco.getChunkOffsets() : co64.getChunkOffsets();
        sampleToChunks = stsc.getSampleToChunk();
        if (chunkOffsets.length == 1) {
            cacheSamples(sampleToChunks, chunkOffsets);
        }

        tse = (TimecodeSampleEntry) box.getSampleEntries()[0];
    }

    public MP4Packet getTimecode(MP4Packet pkt) throws IOException {

        long tv = QTTimeUtil.editedToMedia(box, box.rescale(pkt.getPts(), pkt.getTimescale()), movie.getTimescale());
        int sample;
        int ttsInd = 0, ttsSubInd = 0;
        for (sample = 0; sample < sampleCache.length - 1; sample++) {
            int dur = timeToSamples[ttsInd].getSampleDuration();
            if (tv < dur)
                break;
            tv -= dur;
            ttsSubInd++;
            if (ttsInd < timeToSamples.length - 1 && ttsSubInd >= timeToSamples[ttsInd].getSampleCount())
                ttsInd++;
        }

        int frameNo = (int) ((((2 * tv * tse.getTimescale()) / box.getTimescale()) / tse.getFrameDuration()) + 1) / 2;

        return MP4Packet.createMP4PacketWithTimecode(pkt, _getTimecode(getTimecodeSample(sample), frameNo, tse));
    }

    private int getTimecodeSample(int sample) throws IOException {
        if (sampleCache != null)
            return sampleCache[sample];
        else {
            synchronized (input) {
                int stscInd, stscSubInd;
                for (stscInd = 0, stscSubInd = sample; stscInd < sampleToChunks.length
                        && stscSubInd >= sampleToChunks[stscInd].getCount(); stscSubInd -= sampleToChunks[stscInd]
                        .getCount(), stscInd++)
                    ;
                long offset = chunkOffsets[stscInd]
                        + (Math.min(stscSubInd, sampleToChunks[stscInd].getCount() - 1) << 2);
                if (input.position() != offset)
                    input.setPosition(offset);
                ByteBuffer buf = NIOUtils.fetchFromChannel(input, 4);
                return buf.getInt();
            }
        }
    }

    private TapeTimecode _getTimecode(int startCounter, int frameNo, TimecodeSampleEntry entry) {
        int frame = dropFrameAdjust(frameNo + startCounter, entry);
        int sec = frame / entry.getNumFrames();
        return new TapeTimecode((short) (sec / 3600), (byte) ((sec / 60) % 60), (byte) (sec % 60),
                (byte) (frame % entry.getNumFrames()), entry.isDropFrame());
    }

    private int dropFrameAdjust(int frame, TimecodeSampleEntry entry) {
        if (entry.isDropFrame()) {
            long D = frame / 17982;
            long M = frame % 17982;
            frame += 18 * D + 2 * ((M - 2) / 1798);
        }
        return frame;
    }

    private void cacheSamples(SampleToChunkEntry[] sampleToChunks, long[] chunkOffsets) throws IOException {
        synchronized (input) {
            int stscInd = 0;
            IntArrayList ss = IntArrayList.createIntArrayList();
            for (int chunkNo = 0; chunkNo < chunkOffsets.length; chunkNo++) {
                int nSamples = sampleToChunks[stscInd].getCount();
                if (stscInd < sampleToChunks.length - 1 && chunkNo + 1 >= sampleToChunks[stscInd + 1].getFirst())
                    stscInd++;
                long offset = chunkOffsets[chunkNo];
                input.setPosition(offset);
                ByteBuffer buf = NIOUtils.fetchFromChannel(input, nSamples * 4);
                for (int i = 0; i < nSamples; i++) {
                    ss.add(buf.getInt());
                }
            }
            sampleCache = ss.toArray();
        }
    }

    /**
     * 
     * @return
     * @throws IOException 
     * @deprecated Use getTimecode to automatically populate tape timecode for
     *             each frame
     */
    public int getStartTimecode() throws IOException {
        return getTimecodeSample(0);
    }

    public TrakBox getBox() {
        return box;
    }

    public int parseTimecode(String tc) {
        String[] split = tc.split(":");
        TimecodeSampleEntry tmcd = NodeBox.findFirstPath(box, TimecodeSampleEntry.class, Box.path("mdia.minf.stbl.stsd.tmcd"));
        byte nf = tmcd.getNumFrames();

        return Integer.parseInt(split[3]) + Integer.parseInt(split[2]) * nf + Integer.parseInt(split[1]) * 60 * nf
                + Integer.parseInt(split[0]) * 3600 * nf;
    }

    public int timeCodeToFrameNo(String timeCode) throws Exception {
        if (isValidTimeCode(timeCode)) {
            int movieFrame = parseTimecode(timeCode.trim()) - sampleCache[0];
            int frameRate = tse.getNumFrames();
            long framesInTimescale = movieFrame * tse.getTimescale();
            long mediaToEdited = QTTimeUtil.mediaToEdited(box, framesInTimescale / frameRate, movie.getTimescale())
                    * frameRate;
            return (int) (mediaToEdited / box.getTimescale());
        }
        return -1;
    }

    private static boolean isValidTimeCode(String input) {
        Pattern p = Pattern.compile("[0-9][0-9]:[0-5][0-9]:[0-5][0-9]:[0-2][0-9]");
        Matcher m = p.matcher(input);
        if (input != null && !input.trim().equals("") && m.matches()) {
            return true;
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy