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

org.jcodec.containers.mkv.muxer.MKVMuxer Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
package org.jcodec.containers.mkv.muxer;
import static org.jcodec.containers.mkv.MKVType.CodecID;
import static org.jcodec.containers.mkv.MKVType.Cues;
import static org.jcodec.containers.mkv.MKVType.DateUTC;
import static org.jcodec.containers.mkv.MKVType.DocType;
import static org.jcodec.containers.mkv.MKVType.DocTypeReadVersion;
import static org.jcodec.containers.mkv.MKVType.DocTypeVersion;
import static org.jcodec.containers.mkv.MKVType.EBML;
import static org.jcodec.containers.mkv.MKVType.EBMLMaxIDLength;
import static org.jcodec.containers.mkv.MKVType.EBMLMaxSizeLength;
import static org.jcodec.containers.mkv.MKVType.EBMLReadVersion;
import static org.jcodec.containers.mkv.MKVType.EBMLVersion;
import static org.jcodec.containers.mkv.MKVType.Info;
import static org.jcodec.containers.mkv.MKVType.MuxingApp;
import static org.jcodec.containers.mkv.MKVType.Name;
import static org.jcodec.containers.mkv.MKVType.PixelHeight;
import static org.jcodec.containers.mkv.MKVType.PixelWidth;
import static org.jcodec.containers.mkv.MKVType.Segment;
import static org.jcodec.containers.mkv.MKVType.TimecodeScale;
import static org.jcodec.containers.mkv.MKVType.TrackEntry;
import static org.jcodec.containers.mkv.MKVType.TrackNumber;
import static org.jcodec.containers.mkv.MKVType.TrackType;
import static org.jcodec.containers.mkv.MKVType.TrackUID;
import static org.jcodec.containers.mkv.MKVType.Tracks;
import static org.jcodec.containers.mkv.MKVType.Video;
import static org.jcodec.containers.mkv.MKVType.WritingApp;
import static org.jcodec.containers.mkv.MKVType.createByType;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.Codec;
import org.jcodec.common.Muxer;
import org.jcodec.common.MuxerTrack;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.containers.mkv.CuesFactory;
import org.jcodec.containers.mkv.MKVType;
import org.jcodec.containers.mkv.SeekHeadFactory;
import org.jcodec.containers.mkv.boxes.EbmlBase;
import org.jcodec.containers.mkv.boxes.EbmlBin;
import org.jcodec.containers.mkv.boxes.EbmlDate;
import org.jcodec.containers.mkv.boxes.EbmlFloat;
import org.jcodec.containers.mkv.boxes.EbmlMaster;
import org.jcodec.containers.mkv.boxes.EbmlString;
import org.jcodec.containers.mkv.boxes.EbmlUint;
import org.jcodec.containers.mkv.boxes.MkvBlock;
import org.jcodec.containers.mkv.muxer.MKVMuxerTrack.MKVMuxerTrackType;

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

    private List tracks;
    private MKVMuxerTrack audioTrack;
    private MKVMuxerTrack videoTrack;
    private EbmlMaster mkvInfo;
    private EbmlMaster mkvTracks;
    private EbmlMaster mkvCues;
    private EbmlMaster mkvSeekHead;
    private List clusterList;
    private SeekableByteChannel sink;
    
    private static Map codec2mkv = new HashMap();
    static {
        codec2mkv.put(Codec.H264, "V_MPEG4/ISO/AVC");
        codec2mkv.put(Codec.VP8, "V_VP8");
        codec2mkv.put(Codec.VP9, "V_VP9");
    }

    public MKVMuxer(SeekableByteChannel s) {
        this.sink = s;
        this.tracks = new ArrayList();
        this.clusterList = new LinkedList();
    }

    public MKVMuxerTrack createVideoTrack(VideoCodecMeta meta, String codecId) {
        if (videoTrack == null) {
            videoTrack = new MKVMuxerTrack();
            tracks.add(videoTrack);
            videoTrack.codecId = codecId;
            videoTrack.videoMeta = meta;
            videoTrack.trackNo = tracks.size();
        }
        return videoTrack;
    }

    public void finish() throws IOException {
        List mkvFile = new ArrayList();
        EbmlMaster ebmlHeader = defaultEbmlHeader();
        mkvFile.add(ebmlHeader);

        EbmlMaster segmentElem = (EbmlMaster) createByType(Segment);
        mkvInfo = muxInfo();
        mkvTracks = muxTracks();
        mkvCues = (EbmlMaster) createByType(Cues);
        mkvSeekHead = muxSeekHead();
        muxCues();

        segmentElem.add(mkvSeekHead);
        segmentElem.add(mkvInfo);
        segmentElem.add(mkvTracks);
        segmentElem.add(mkvCues);
        for (EbmlMaster aCluster : clusterList)
            segmentElem.add(aCluster);
        mkvFile.add(segmentElem);

        for (EbmlMaster el : mkvFile)
            el.mux(sink);
    }

    private EbmlMaster defaultEbmlHeader() {
        EbmlMaster master = (EbmlMaster) createByType(EBML);

        createLong(master, EBMLVersion, 1);
        createLong(master, EBMLReadVersion, 1);
        createLong(master, EBMLMaxIDLength, 4);
        createLong(master, EBMLMaxSizeLength, 8);

        createString(master, DocType, "webm");
        createLong(master, DocTypeVersion, 2);
        createLong(master, DocTypeReadVersion, 2);

        return master;
    }

    private EbmlMaster muxInfo() {
        EbmlMaster master = (EbmlMaster) createByType(Info);
        int frameDurationInNanoseconds = MKVMuxerTrack.NANOSECONDS_IN_A_MILISECOND * 40;
        createLong(master, TimecodeScale, frameDurationInNanoseconds);
        createString(master, WritingApp, "JCodec");
        createString(master, MuxingApp, "JCodec");

        List tracks2 = tracks;
        long max = 0;
        for (MKVMuxerTrack track : tracks2) {
            MkvBlock lastBlock = track.trackBlocks.get(track.trackBlocks.size() - 1);
            if (lastBlock.absoluteTimecode > max)
                max = lastBlock.absoluteTimecode;
        }
        createDouble(master, MKVType.Duration, (max + 1) * frameDurationInNanoseconds * 1.0);
        createDate(master, DateUTC, new Date());
        return master;
    }

    private EbmlMaster muxTracks() {
        EbmlMaster master = (EbmlMaster) createByType(Tracks);
        for (int i = 0; i < tracks.size(); i++) {
            MKVMuxerTrack track = tracks.get(i);
            EbmlMaster trackEntryElem = (EbmlMaster) createByType(TrackEntry);

            createLong(trackEntryElem, TrackNumber, track.trackNo);

            createLong(trackEntryElem, TrackUID, track.trackNo);
            if (MKVMuxerTrackType.VIDEO.equals(track.type)) {
                createLong(trackEntryElem, TrackType, (byte) 0x01);
                createString(trackEntryElem, Name, "Track " + (i + 1) + " Video");
                createString(trackEntryElem, CodecID, track.codecId);
                //                createChild(trackEntryElem, CodecPrivate, codecMeta.getCodecPrivate());
                //                VideoCodecMeta vcm = (VideoCodecMeta) codecMeta;

                EbmlMaster trackVideoElem = (EbmlMaster) createByType(Video);
                createLong(trackVideoElem, PixelWidth, track.videoMeta.getSize().getWidth());
                createLong(trackVideoElem, PixelHeight, track.videoMeta.getSize().getHeight());

                trackEntryElem.add(trackVideoElem);

            } else {
                createLong(trackEntryElem, TrackType, (byte) 0x02);
                createString(trackEntryElem, Name, "Track " + (i + 1) + " Audio");
                createString(trackEntryElem, CodecID, track.codecId);
                //                createChild(trackEntryElem, CodecPrivate, codecMeta.getCodecPrivate());
            }

            master.add(trackEntryElem);
        }
        return master;
    }

    private void muxCues() {
        CuesFactory cf = new CuesFactory(mkvSeekHead.size() + mkvInfo.size() + mkvTracks.size(),
                videoTrack.trackNo);
        for (MkvBlock aBlock : videoTrack.trackBlocks) {
            EbmlMaster mkvCluster = singleBlockedCluster(aBlock);
            clusterList.add(mkvCluster);
            cf.add(CuesFactory.CuePointMock.make(mkvCluster));
        }

        EbmlMaster indexedCues = cf.createCues();

        for (EbmlBase aCuePoint : indexedCues.children)
            mkvCues.add(aCuePoint);
    }

    private EbmlMaster singleBlockedCluster(MkvBlock aBlock) {
        EbmlMaster mkvCluster = createByType(MKVType.Cluster);
        createLong(mkvCluster, MKVType.Timecode, aBlock.absoluteTimecode - aBlock.timecode);
        mkvCluster.add(aBlock);
        return mkvCluster;
    }

    private EbmlMaster muxSeekHead() {
        SeekHeadFactory shi = new SeekHeadFactory();
        shi.add(mkvInfo);
        shi.add(mkvTracks);
        shi.add(mkvCues);
        return shi.indexSeekHead();
    }

    public static void createLong(EbmlMaster parent, MKVType type, long value) {
        EbmlUint se = (EbmlUint) createByType(type);
        se.setUint(value);
        parent.add(se);
    }

    public static void createString(EbmlMaster parent, MKVType type, String value) {
        EbmlString se = (EbmlString) createByType(type);
        se.setString(value);
        parent.add(se);
    }

    public static void createDate(EbmlMaster parent, MKVType type, Date value) {
        EbmlDate se = (EbmlDate) createByType(type);
        se.setDate(value);
        parent.add(se);
    }

    public static void createBuffer(EbmlMaster parent, MKVType type, ByteBuffer value) {
        EbmlBin se = (EbmlBin) createByType(type);
        se.setBuf(value);
        parent.add(se);
    }

    public static void createDouble(EbmlMaster parent, MKVType type, double value) {
        try {
            EbmlFloat se = (EbmlFloat) createByType(type);
            se.setDouble(value);
            parent.add(se);
        } catch (ClassCastException cce) {
            throw new RuntimeException("Element of type " + type + " can't be cast to EbmlFloat", cce);
        }
    }

    @Override
    public MuxerTrack addVideoTrack(Codec codec, VideoCodecMeta meta) {
        return createVideoTrack(meta, codec2mkv.get(codec));
    }

    @Override
    public MuxerTrack addAudioTrack(Codec codec, AudioCodecMeta meta) {
        audioTrack = new MKVMuxerTrack();
        tracks.add(audioTrack);
        audioTrack.codecId = codec2mkv.get(codec);
        audioTrack.trackNo = tracks.size();
        return audioTrack;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy