www in docker support

This commit is contained in:
MaddoScientisto 2026-04-22 18:41:37 +02:00
commit c227fce036
2145 changed files with 399596 additions and 58 deletions

View file

@ -0,0 +1,133 @@
package org.jcodec.containers.mkv;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.containers.mkv.boxes.EbmlMaster;
import org.jcodec.containers.mkv.boxes.EbmlUint;
import org.jcodec.containers.mkv.util.EbmlUtil;
public class CuesFactory {
List<CuePointMock> a;
private final long offsetBase;
private long currentDataOffset = 0L;
private long videoTrackNr;
public CuesFactory(long offset, long videoTrack) {
this.a = new ArrayList<>();
this.offsetBase = offset;
this.videoTrackNr = videoTrack;
this.currentDataOffset += this.offsetBase;
}
public void addFixedSize(CuePointMock z) {
z.elementOffset = this.currentDataOffset;
z.cueClusterPositionSize = 8;
this.currentDataOffset += z.size;
this.a.add(z);
}
public void add(CuePointMock z) {
z.elementOffset = this.currentDataOffset;
z.cueClusterPositionSize = EbmlUint.calculatePayloadSize(z.elementOffset);
this.currentDataOffset += z.size;
this.a.add(z);
}
public EbmlMaster createCues() {
int estimatedSize = computeCuesSize();
EbmlMaster cues = MKVType.<EbmlMaster>createByType(MKVType.Cues);
for (CuePointMock cpm : this.a) {
EbmlMaster cuePoint = MKVType.<EbmlMaster>createByType(MKVType.CuePoint);
EbmlUint cueTime = MKVType.<EbmlUint>createByType(MKVType.CueTime);
cueTime.setUint(cpm.timecode);
cuePoint.add(cueTime);
EbmlMaster cueTrackPositions = MKVType.<EbmlMaster>createByType(MKVType.CueTrackPositions);
EbmlUint cueTrack = MKVType.<EbmlUint>createByType(MKVType.CueTrack);
cueTrack.setUint(this.videoTrackNr);
cueTrackPositions.add(cueTrack);
EbmlUint cueClusterPosition = MKVType.<EbmlUint>createByType(MKVType.CueClusterPosition);
cueClusterPosition.setUint(cpm.elementOffset + (long)estimatedSize);
if (cueClusterPosition.data.limit() != cpm.cueClusterPositionSize)
System.err.println("estimated size of CueClusterPosition differs from the one actually used. ElementId: " + EbmlUtil.toHexString(cpm.id) + " " + cueClusterPosition.getData().limit() + " vs " + cpm.cueClusterPositionSize);
cueTrackPositions.add(cueClusterPosition);
cuePoint.add(cueTrackPositions);
cues.add(cuePoint);
}
return cues;
}
public int computeCuesSize() {
int cuesSize = estimateSize();
boolean reindex = false;
while (true) {
reindex = false;
for (CuePointMock z : this.a) {
int minByteSize = EbmlUint.calculatePayloadSize(z.elementOffset + (long)cuesSize);
if (minByteSize > z.cueClusterPositionSize) {
System.out.println("" + minByteSize + ">" + minByteSize);
System.err.println("Size " + cuesSize + " seems too small for element " + EbmlUtil.toHexString(z.id) + " increasing size by one.");
z.cueClusterPositionSize++;
cuesSize++;
reindex = true;
break;
}
if (minByteSize < z.cueClusterPositionSize)
throw new RuntimeException("Downsizing the index is not well thought through");
}
if (!reindex)
return cuesSize;
}
}
public int estimateFixedSize(int numberOfClusters) {
int s = 34 * numberOfClusters;
s += MKVType.Cues.id.length + EbmlUtil.ebmlLength((long)s);
return s;
}
public int estimateSize() {
int s = 0;
for (CuePointMock cpm : this.a)
s += estimateCuePointSize(EbmlUint.calculatePayloadSize(cpm.timecode), EbmlUint.calculatePayloadSize(this.videoTrackNr), EbmlUint.calculatePayloadSize(cpm.elementOffset));
s += MKVType.Cues.id.length + EbmlUtil.ebmlLength((long)s);
return s;
}
public static int estimateCuePointSize(int timecodeSizeInBytes, int trackNrSizeInBytes, int clusterPositionSizeInBytes) {
int cueTimeSize = MKVType.CueTime.id.length + EbmlUtil.ebmlLength((long)timecodeSizeInBytes) + timecodeSizeInBytes;
int cueTrackPositionSize = MKVType.CueTrack.id.length + EbmlUtil.ebmlLength((long)trackNrSizeInBytes) + trackNrSizeInBytes + MKVType.CueClusterPosition.id.length +
EbmlUtil.ebmlLength((long)clusterPositionSizeInBytes) + clusterPositionSizeInBytes;
cueTrackPositionSize += MKVType.CueTrackPositions.id.length + EbmlUtil.ebmlLength((long)cueTrackPositionSize);
int cuePointSize = MKVType.CuePoint.id.length + EbmlUtil.ebmlLength((long)(cueTimeSize + cueTrackPositionSize)) + cueTimeSize + cueTrackPositionSize;
return cuePointSize;
}
public static class CuePointMock {
public int cueClusterPositionSize;
public long elementOffset;
private long timecode;
private long size;
private byte[] id;
public static CuePointMock make(EbmlMaster c) {
MKVType[] path = { MKVType.Cluster, MKVType.Timecode };
EbmlUint tc = (EbmlUint)MKVType.findFirst(c, path);
return doMake(c.id, tc.getUint(), c.size());
}
public static CuePointMock doMake(byte[] id, long timecode, long size) {
CuePointMock mock = new CuePointMock();
mock.id = id;
mock.timecode = timecode;
mock.size = size;
return mock;
}
}
}

View file

@ -0,0 +1,147 @@
package org.jcodec.containers.mkv;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.containers.mkv.boxes.EbmlBase;
import org.jcodec.containers.mkv.boxes.EbmlBin;
import org.jcodec.containers.mkv.boxes.EbmlMaster;
import org.jcodec.containers.mkv.boxes.EbmlVoid;
import org.jcodec.containers.mkv.util.EbmlUtil;
public class MKVParser {
private SeekableByteChannel channel;
private LinkedList<EbmlMaster> trace;
public MKVParser(SeekableByteChannel channel) {
this.channel = channel;
this.trace = new LinkedList<>();
}
public List<EbmlMaster> parse() throws IOException {
List<EbmlMaster> tree = new ArrayList<>();
EbmlBase e = null;
while ((e = nextElement()) != null) {
if (!isKnownType(e.id))
System.err.println("Unspecified header: " + EbmlUtil.toHexString(e.id) + " at " + e.offset);
while (!possibleChild(this.trace.peekFirst(), e))
closeElem(this.trace.removeFirst(), tree);
openElem(e);
if (e instanceof EbmlMaster) {
this.trace.push((EbmlMaster)e);
continue;
}
if (e instanceof EbmlBin) {
EbmlBin bin = (EbmlBin)e;
EbmlMaster traceTop = this.trace.peekFirst();
if (traceTop.dataOffset + (long)traceTop.dataLen < e.dataOffset + (long)e.dataLen) {
this.channel.setPosition(traceTop.dataOffset + (long)traceTop.dataLen);
} else {
try {
bin.readChannel(this.channel);
} catch (OutOfMemoryError oome) {
throw new RuntimeException(String.valueOf(e.type) + " 0x" + String.valueOf(e.type) + " size: " + EbmlUtil.toHexString(bin.id) + " offset: 0x" + Long.toHexString((long)bin.dataLen), oome);
}
}
this.trace.peekFirst().add(e);
continue;
}
if (e instanceof EbmlVoid) {
((EbmlVoid)e).skip(this.channel);
continue;
}
throw new RuntimeException("Currently there are no elements that are neither Master nor Binary, should never actually get here");
}
while (this.trace.peekFirst() != null)
closeElem(this.trace.removeFirst(), tree);
return tree;
}
private boolean possibleChild(EbmlMaster parent, EbmlBase child) {
if (parent != null && MKVType.Cluster.equals(parent.type) && child != null && !MKVType.Cluster.equals(child.type) && !MKVType.Info.equals(child.type) && !MKVType.SeekHead.equals(child.type) && !MKVType.Tracks.equals(child.type) &&
!MKVType.Cues.equals(child.type) && !MKVType.Attachments.equals(child.type) && !MKVType.Tags.equals(child.type) && !MKVType.Chapters.equals(child.type))
return true;
return MKVType.possibleChild(parent, child);
}
private void openElem(EbmlBase e) {}
private void closeElem(EbmlMaster e, List<EbmlMaster> tree) {
if (this.trace.peekFirst() == null) {
tree.add(e);
} else {
this.trace.peekFirst().add(e);
}
}
private EbmlBase nextElement() throws IOException {
long offset = this.channel.position();
if (offset >= this.channel.size())
return null;
byte[] typeId = readEbmlId(this.channel);
while (typeId == null && !isKnownType(typeId) && offset < this.channel.size()) {
offset++;
this.channel.setPosition(offset);
typeId = readEbmlId(this.channel);
}
long dataLen = readEbmlInt(this.channel);
EbmlBase elem = MKVType.createById(typeId, offset);
elem.offset = offset;
elem.typeSizeLength = (int)(this.channel.position() - offset);
elem.dataOffset = this.channel.position();
elem.dataLen = (int)dataLen;
return elem;
}
public boolean isKnownType(byte[] b) {
if (!this.trace.isEmpty() && MKVType.Cluster.equals(((EbmlMaster)this.trace.peekFirst()).type))
return true;
return MKVType.isSpecifiedHeader(b);
}
public static byte[] readEbmlId(SeekableByteChannel source) throws IOException {
if (source.position() == source.size())
return null;
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.limit(1);
source.read(buffer);
buffer.flip();
byte firstByte = buffer.get();
int numBytes = EbmlUtil.computeLength(firstByte);
if (numBytes == 0)
return null;
if (numBytes > 1) {
buffer.limit(numBytes);
source.read(buffer);
}
buffer.flip();
ByteBuffer val = ByteBuffer.allocate(buffer.remaining());
val.put(buffer);
return val.array();
}
public static long readEbmlInt(SeekableByteChannel source) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.limit(1);
source.read(buffer);
buffer.flip();
byte firstByte = buffer.get();
int length = EbmlUtil.computeLength(firstByte);
if (length == 0)
throw new RuntimeException("Invalid ebml integer size.");
buffer.limit(length);
source.read(buffer);
buffer.position(1);
long value = (long)(firstByte & 255 >>> length);
length--;
while (length > 0) {
value = value << 8L | (long)(buffer.get() & 0xFF);
length--;
}
return value;
}
}

View file

@ -0,0 +1,661 @@
package org.jcodec.containers.mkv;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.EbmlSint;
import org.jcodec.containers.mkv.boxes.EbmlString;
import org.jcodec.containers.mkv.boxes.EbmlUint;
import org.jcodec.containers.mkv.boxes.EbmlVoid;
import org.jcodec.containers.mkv.boxes.MkvBlock;
import org.jcodec.containers.mkv.boxes.MkvSegment;
import org.jcodec.containers.mkv.util.EbmlUtil;
import org.jcodec.platform.Platform;
public final class MKVType {
private static final List<MKVType> _values = new ArrayList<>();
public static final MKVType Void = new MKVType("Void", new byte[] { (byte)-20 }, EbmlVoid.class);
public static final MKVType CRC32 = new MKVType("CRC32", new byte[] { (byte)-65 }, EbmlBin.class);
public static final MKVType EBML = new MKVType("EBML", new byte[] { (byte)26, (byte)69, (byte)-33, (byte)-93 }, EbmlMaster.class);
public static final MKVType EBMLVersion = new MKVType("EBMLVersion", new byte[] { (byte)66, (byte)-122 }, EbmlUint.class);
public static final MKVType EBMLReadVersion = new MKVType("EBMLReadVersion", new byte[] { (byte)66, (byte)-9 }, EbmlUint.class);
public static final MKVType EBMLMaxIDLength = new MKVType("EBMLMaxIDLength", new byte[] { (byte)66, (byte)-14 }, EbmlUint.class);
public static final MKVType EBMLMaxSizeLength = new MKVType("EBMLMaxSizeLength", new byte[] { (byte)66, (byte)-13 }, EbmlUint.class);
public static final MKVType DocType = new MKVType("DocType", new byte[] { (byte)66, (byte)-126 }, EbmlString.class);
public static final MKVType DocTypeVersion = new MKVType("DocTypeVersion", new byte[] { (byte)66, (byte)-121 }, EbmlUint.class);
public static final MKVType DocTypeReadVersion = new MKVType("DocTypeReadVersion", new byte[] { (byte)66, (byte)-123 }, EbmlUint.class);
public static final MKVType Segment = new MKVType("Segment", MkvSegment.SEGMENT_ID, MkvSegment.class);
public static final MKVType SeekHead = new MKVType("SeekHead", new byte[] { (byte)17, (byte)77, (byte)-101, (byte)116 }, EbmlMaster.class);
public static final MKVType Seek = new MKVType("Seek", new byte[] { (byte)77, (byte)-69 }, EbmlMaster.class);
public static final MKVType SeekID = new MKVType("SeekID", new byte[] { (byte)83, (byte)-85 }, EbmlBin.class);
public static final MKVType SeekPosition = new MKVType("SeekPosition", new byte[] { (byte)83, (byte)-84 }, EbmlUint.class);
public static final MKVType Info = new MKVType("Info", new byte[] { (byte)21, (byte)73, (byte)-87, (byte)102 }, EbmlMaster.class);
public static final MKVType SegmentUID = new MKVType("SegmentUID", new byte[] { (byte)115, (byte)-92 }, EbmlBin.class);
public static final MKVType SegmentFilename = new MKVType("SegmentFilename", new byte[] { (byte)115, (byte)-124 }, EbmlString.class);
public static final MKVType PrevUID = new MKVType("PrevUID", new byte[] { (byte)60, (byte)-71, (byte)35 }, EbmlBin.class);
public static final MKVType PrevFilename = new MKVType("PrevFilename", new byte[] { (byte)60, (byte)-125, (byte)-85 }, EbmlString.class);
public static final MKVType NextUID = new MKVType("NextUID", new byte[] { (byte)62, (byte)-71, (byte)35 }, EbmlBin.class);
public static final MKVType NextFilenam = new MKVType("NextFilenam", new byte[] { (byte)62, (byte)-125, (byte)-69 }, EbmlString.class);
public static final MKVType SegmentFamily = new MKVType("SegmentFamily", new byte[] { (byte)68, (byte)68 }, EbmlBin.class);
public static final MKVType ChapterTranslate = new MKVType("ChapterTranslate", new byte[] { (byte)105, (byte)36 }, EbmlMaster.class);
public static final MKVType ChapterTranslateEditionUID = new MKVType("ChapterTranslateEditionUID", new byte[] { (byte)105, (byte)-4 }, EbmlUint.class);
public static final MKVType ChapterTranslateCodec = new MKVType("ChapterTranslateCodec", new byte[] { (byte)105, (byte)-65 }, EbmlUint.class);
public static final MKVType ChapterTranslateID = new MKVType("ChapterTranslateID", new byte[] { (byte)105, (byte)-91 }, EbmlBin.class);
public static final MKVType TimecodeScale = new MKVType("TimecodeScale", new byte[] { (byte)42, (byte)-41, (byte)-79 }, EbmlUint.class);
public static final MKVType Duration = new MKVType("Duration", new byte[] { (byte)68, (byte)-119 }, EbmlFloat.class);
public static final MKVType DateUTC = new MKVType("DateUTC", new byte[] { (byte)68, (byte)97 }, EbmlDate.class);
public static final MKVType Title = new MKVType("Title", new byte[] { (byte)123, (byte)-87 }, EbmlString.class);
public static final MKVType MuxingApp = new MKVType("MuxingApp", new byte[] { (byte)77, (byte)Byte.MIN_VALUE }, EbmlString.class);
public static final MKVType WritingApp = new MKVType("WritingApp", new byte[] { (byte)87, (byte)65 }, EbmlString.class);
public static final MKVType Cluster = new MKVType("Cluster", EbmlMaster.CLUSTER_ID, EbmlMaster.class);
public static final MKVType Timecode = new MKVType("Timecode", new byte[] { (byte)-25 }, EbmlUint.class);
public static final MKVType SilentTracks = new MKVType("SilentTracks", new byte[] { (byte)88, (byte)84 }, EbmlMaster.class);
public static final MKVType SilentTrackNumber = new MKVType("SilentTrackNumber", new byte[] { (byte)88, (byte)-41 }, EbmlUint.class);
public static final MKVType Position = new MKVType("Position", new byte[] { (byte)-89 }, EbmlUint.class);
public static final MKVType PrevSize = new MKVType("PrevSize", new byte[] { (byte)-85 }, EbmlUint.class);
public static final MKVType SimpleBlock = new MKVType("SimpleBlock", MkvBlock.SIMPLEBLOCK_ID, MkvBlock.class);
public static final MKVType BlockGroup = new MKVType("BlockGroup", new byte[] { (byte)-96 }, EbmlMaster.class);
public static final MKVType Block = new MKVType("Block", MkvBlock.BLOCK_ID, MkvBlock.class);
public static final MKVType BlockAdditions = new MKVType("BlockAdditions", new byte[] { (byte)117, (byte)-95 }, EbmlMaster.class);
public static final MKVType BlockMore = new MKVType("BlockMore", new byte[] { (byte)-90 }, EbmlMaster.class);
public static final MKVType BlockAddID = new MKVType("BlockAddID", new byte[] { (byte)-18 }, EbmlUint.class);
public static final MKVType BlockAdditional = new MKVType("BlockAdditional", new byte[] { (byte)-91 }, EbmlBin.class);
public static final MKVType BlockDuration = new MKVType("BlockDuration", new byte[] { (byte)-101 }, EbmlUint.class);
public static final MKVType ReferencePriority = new MKVType("ReferencePriority", new byte[] { (byte)-6 }, EbmlUint.class);
public static final MKVType ReferenceBlock = new MKVType("ReferenceBlock", new byte[] { (byte)-5 }, EbmlSint.class);
public static final MKVType CodecState = new MKVType("CodecState", new byte[] { (byte)-92 }, EbmlBin.class);
public static final MKVType Slices = new MKVType("Slices", new byte[] { (byte)-114 }, EbmlMaster.class);
public static final MKVType TimeSlice = new MKVType("TimeSlice", new byte[] { (byte)-24 }, EbmlMaster.class);
public static final MKVType LaceNumber = new MKVType("LaceNumber", new byte[] { (byte)-52 }, EbmlUint.class);
public static final MKVType Tracks = new MKVType("Tracks", new byte[] { (byte)22, (byte)84, (byte)-82, (byte)107 }, EbmlMaster.class);
public static final MKVType TrackEntry = new MKVType("TrackEntry", new byte[] { (byte)-82 }, EbmlMaster.class);
public static final MKVType TrackNumber = new MKVType("TrackNumber", new byte[] { (byte)-41 }, EbmlUint.class);
public static final MKVType TrackUID = new MKVType("TrackUID", new byte[] { (byte)115, (byte)-59 }, EbmlUint.class);
public static final MKVType TrackType = new MKVType("TrackType", new byte[] { (byte)-125 }, EbmlUint.class);
public static final MKVType FlagEnabled = new MKVType("FlagEnabled", new byte[] { (byte)-71 }, EbmlUint.class);
public static final MKVType FlagDefault = new MKVType("FlagDefault", new byte[] { (byte)-120 }, EbmlUint.class);
public static final MKVType FlagForced = new MKVType("FlagForced", new byte[] { (byte)85, (byte)-86 }, EbmlUint.class);
public static final MKVType FlagLacing = new MKVType("FlagLacing", new byte[] { (byte)-100 }, EbmlUint.class);
public static final MKVType MinCache = new MKVType("MinCache", new byte[] { (byte)109, (byte)-25 }, EbmlUint.class);
public static final MKVType MaxCache = new MKVType("MaxCache", new byte[] { (byte)109, (byte)-8 }, EbmlUint.class);
public static final MKVType DefaultDuration = new MKVType("DefaultDuration", new byte[] { (byte)35, (byte)-29, (byte)-125 }, EbmlUint.class);
public static final MKVType MaxBlockAdditionID = new MKVType("MaxBlockAdditionID", new byte[] { (byte)85, (byte)-18 }, EbmlUint.class);
public static final MKVType Name = new MKVType("Name", new byte[] { (byte)83, (byte)110 }, EbmlString.class);
public static final MKVType Language = new MKVType("Language", new byte[] { (byte)34, (byte)-75, (byte)-100 }, EbmlString.class);
public static final MKVType CodecID = new MKVType("CodecID", new byte[] { (byte)-122 }, EbmlString.class);
public static final MKVType CodecPrivate = new MKVType("CodecPrivate", new byte[] { (byte)99, (byte)-94 }, EbmlBin.class);
public static final MKVType CodecName = new MKVType("CodecName", new byte[] { (byte)37, (byte)-122, (byte)-120 }, EbmlString.class);
public static final MKVType AttachmentLink = new MKVType("AttachmentLink", new byte[] { (byte)116, (byte)70 }, EbmlUint.class);
public static final MKVType CodecDecodeAll = new MKVType("CodecDecodeAll", new byte[] { (byte)-86 }, EbmlUint.class);
public static final MKVType TrackOverlay = new MKVType("TrackOverlay", new byte[] { (byte)111, (byte)-85 }, EbmlUint.class);
public static final MKVType TrackTranslate = new MKVType("TrackTranslate", new byte[] { (byte)102, (byte)36 }, EbmlMaster.class);
public static final MKVType TrackTranslateEditionUID = new MKVType("TrackTranslateEditionUID", new byte[] { (byte)102, (byte)-4 }, EbmlUint.class);
public static final MKVType TrackTranslateCodec = new MKVType("TrackTranslateCodec", new byte[] { (byte)102, (byte)-65 }, EbmlUint.class);
public static final MKVType TrackTranslateTrackID = new MKVType("TrackTranslateTrackID", new byte[] { (byte)102, (byte)-91 }, EbmlBin.class);
public static final MKVType Video = new MKVType("Video", new byte[] { (byte)-32 }, EbmlMaster.class);
public static final MKVType FlagInterlaced = new MKVType("FlagInterlaced", new byte[] { (byte)-102 }, EbmlUint.class);
public static final MKVType StereoMode = new MKVType("StereoMode", new byte[] { (byte)83, (byte)-72 }, EbmlUint.class);
public static final MKVType AlphaMode = new MKVType("AlphaMode", new byte[] { (byte)83, (byte)-64 }, EbmlUint.class);
public static final MKVType PixelWidth = new MKVType("PixelWidth", new byte[] { (byte)-80 }, EbmlUint.class);
public static final MKVType PixelHeight = new MKVType("PixelHeight", new byte[] { (byte)-70 }, EbmlUint.class);
public static final MKVType PixelCropBottom = new MKVType("PixelCropBottom", new byte[] { (byte)84, (byte)-86 }, EbmlUint.class);
public static final MKVType PixelCropTop = new MKVType("PixelCropTop", new byte[] { (byte)84, (byte)-69 }, EbmlUint.class);
public static final MKVType PixelCropLeft = new MKVType("PixelCropLeft", new byte[] { (byte)84, (byte)-52 }, EbmlUint.class);
public static final MKVType PixelCropRight = new MKVType("PixelCropRight", new byte[] { (byte)84, (byte)-35 }, EbmlUint.class);
public static final MKVType DisplayWidth = new MKVType("DisplayWidth", new byte[] { (byte)84, (byte)-80 }, EbmlUint.class);
public static final MKVType DisplayHeight = new MKVType("DisplayHeight", new byte[] { (byte)84, (byte)-70 }, EbmlUint.class);
public static final MKVType DisplayUnit = new MKVType("DisplayUnit", new byte[] { (byte)84, (byte)-78 }, EbmlUint.class);
public static final MKVType AspectRatioType = new MKVType("AspectRatioType", new byte[] { (byte)84, (byte)-77 }, EbmlUint.class);
public static final MKVType ColourSpace = new MKVType("ColourSpace", new byte[] { (byte)46, (byte)-75, (byte)36 }, EbmlBin.class);
public static final MKVType Audio = new MKVType("Audio", new byte[] { (byte)-31 }, EbmlMaster.class);
public static final MKVType SamplingFrequency = new MKVType("SamplingFrequency", new byte[] { (byte)-75 }, EbmlFloat.class);
public static final MKVType OutputSamplingFrequency = new MKVType("OutputSamplingFrequency", new byte[] { (byte)120, (byte)-75 }, EbmlFloat.class);
public static final MKVType Channels = new MKVType("Channels", new byte[] { (byte)-97 }, EbmlUint.class);
public static final MKVType BitDepth = new MKVType("BitDepth", new byte[] { (byte)98, (byte)100 }, EbmlUint.class);
public static final MKVType TrackOperation = new MKVType("TrackOperation", new byte[] { (byte)-30 }, EbmlMaster.class);
public static final MKVType TrackCombinePlanes = new MKVType("TrackCombinePlanes", new byte[] { (byte)-29 }, EbmlMaster.class);
public static final MKVType TrackPlane = new MKVType("TrackPlane", new byte[] { (byte)-28 }, EbmlMaster.class);
public static final MKVType TrackPlaneUID = new MKVType("TrackPlaneUID", new byte[] { (byte)-27 }, EbmlUint.class);
public static final MKVType TrackPlaneType = new MKVType("TrackPlaneType", new byte[] { (byte)-26 }, EbmlUint.class);
public static final MKVType TrackJoinBlocks = new MKVType("TrackJoinBlocks", new byte[] { (byte)-23 }, EbmlMaster.class);
public static final MKVType TrackJoinUID = new MKVType("TrackJoinUID", new byte[] { (byte)-19 }, EbmlUint.class);
public static final MKVType ContentEncodings = new MKVType("ContentEncodings", new byte[] { (byte)109, (byte)Byte.MIN_VALUE }, EbmlMaster.class);
public static final MKVType ContentEncoding = new MKVType("ContentEncoding", new byte[] { (byte)98, (byte)64 }, EbmlMaster.class);
public static final MKVType ContentEncodingOrder = new MKVType("ContentEncodingOrder", new byte[] { (byte)80, (byte)49 }, EbmlUint.class);
public static final MKVType ContentEncodingScope = new MKVType("ContentEncodingScope", new byte[] { (byte)80, (byte)50 }, EbmlUint.class);
public static final MKVType ContentEncodingType = new MKVType("ContentEncodingType", new byte[] { (byte)80, (byte)51 }, EbmlUint.class);
public static final MKVType ContentCompression = new MKVType("ContentCompression", new byte[] { (byte)80, (byte)52 }, EbmlMaster.class);
public static final MKVType ContentCompAlgo = new MKVType("ContentCompAlgo", new byte[] { (byte)66, (byte)84 }, EbmlUint.class);
public static final MKVType ContentCompSettings = new MKVType("ContentCompSettings", new byte[] { (byte)66, (byte)85 }, EbmlBin.class);
public static final MKVType ContentEncryption = new MKVType("ContentEncryption", new byte[] { (byte)80, (byte)53 }, EbmlMaster.class);
public static final MKVType ContentEncAlgo = new MKVType("ContentEncAlgo", new byte[] { (byte)71, (byte)-31 }, EbmlUint.class);
public static final MKVType ContentEncKeyID = new MKVType("ContentEncKeyID", new byte[] { (byte)71, (byte)-30 }, EbmlBin.class);
public static final MKVType ContentSignature = new MKVType("ContentSignature", new byte[] { (byte)71, (byte)-29 }, EbmlBin.class);
public static final MKVType ContentSigKeyID = new MKVType("ContentSigKeyID", new byte[] { (byte)71, (byte)-28 }, EbmlBin.class);
public static final MKVType ContentSigAlgo = new MKVType("ContentSigAlgo", new byte[] { (byte)71, (byte)-27 }, EbmlUint.class);
public static final MKVType ContentSigHashAlgo = new MKVType("ContentSigHashAlgo", new byte[] { (byte)71, (byte)-26 }, EbmlUint.class);
public static final MKVType Cues = new MKVType("Cues", new byte[] { (byte)28, (byte)83, (byte)-69, (byte)107 }, EbmlMaster.class);
public static final MKVType CuePoint = new MKVType("CuePoint", new byte[] { (byte)-69 }, EbmlMaster.class);
public static final MKVType CueTime = new MKVType("CueTime", new byte[] { (byte)-77 }, EbmlUint.class);
public static final MKVType CueTrackPositions = new MKVType("CueTrackPositions", new byte[] { (byte)-73 }, EbmlMaster.class);
public static final MKVType CueTrack = new MKVType("CueTrack", new byte[] { (byte)-9 }, EbmlUint.class);
public static final MKVType CueClusterPosition = new MKVType("CueClusterPosition", new byte[] { (byte)-15 }, EbmlUint.class);
public static final MKVType CueRelativePosition = new MKVType("CueRelativePosition", new byte[] { (byte)-16 }, EbmlUint.class);
public static final MKVType CueDuration = new MKVType("CueDuration", new byte[] { (byte)-78 }, EbmlUint.class);
public static final MKVType CueBlockNumber = new MKVType("CueBlockNumber", new byte[] { (byte)83, (byte)120 }, EbmlUint.class);
public static final MKVType CueCodecState = new MKVType("CueCodecState", new byte[] { (byte)-22 }, EbmlUint.class);
public static final MKVType CueReference = new MKVType("CueReference", new byte[] { (byte)-37 }, EbmlMaster.class);
public static final MKVType CueRefTime = new MKVType("CueRefTime", new byte[] { (byte)-106 }, EbmlUint.class);
public static final MKVType Attachments = new MKVType("Attachments", new byte[] { (byte)25, (byte)65, (byte)-92, (byte)105 }, EbmlMaster.class);
public static final MKVType AttachedFile = new MKVType("AttachedFile", new byte[] { (byte)97, (byte)-89 }, EbmlMaster.class);
public static final MKVType FileDescription = new MKVType("FileDescription", new byte[] { (byte)70, (byte)126 }, EbmlString.class);
public static final MKVType FileName = new MKVType("FileName", new byte[] { (byte)70, (byte)110 }, EbmlString.class);
public static final MKVType FileMimeType = new MKVType("FileMimeType", new byte[] { (byte)70, (byte)96 }, EbmlString.class);
public static final MKVType FileData = new MKVType("FileData", new byte[] { (byte)70, (byte)92 }, EbmlBin.class);
public static final MKVType FileUID = new MKVType("FileUID", new byte[] { (byte)70, (byte)-82 }, EbmlUint.class);
public static final MKVType Chapters = new MKVType("Chapters", new byte[] { (byte)16, (byte)67, (byte)-89, (byte)112 }, EbmlMaster.class);
public static final MKVType EditionEntry = new MKVType("EditionEntry", new byte[] { (byte)69, (byte)-71 }, EbmlMaster.class);
public static final MKVType EditionUID = new MKVType("EditionUID", new byte[] { (byte)69, (byte)-68 }, EbmlUint.class);
public static final MKVType EditionFlagHidden = new MKVType("EditionFlagHidden", new byte[] { (byte)69, (byte)-67 }, EbmlUint.class);
public static final MKVType EditionFlagDefault = new MKVType("EditionFlagDefault", new byte[] { (byte)69, (byte)-37 }, EbmlUint.class);
public static final MKVType EditionFlagOrdered = new MKVType("EditionFlagOrdered", new byte[] { (byte)69, (byte)-35 }, EbmlUint.class);
public static final MKVType ChapterAtom = new MKVType("ChapterAtom", new byte[] { (byte)-74 }, EbmlMaster.class);
public static final MKVType ChapterUID = new MKVType("ChapterUID", new byte[] { (byte)115, (byte)-60 }, EbmlUint.class);
public static final MKVType ChapterStringUID = new MKVType("ChapterStringUID", new byte[] { (byte)86, (byte)84 }, EbmlString.class);
public static final MKVType ChapterTimeStart = new MKVType("ChapterTimeStart", new byte[] { (byte)-111 }, EbmlUint.class);
public static final MKVType ChapterTimeEnd = new MKVType("ChapterTimeEnd", new byte[] { (byte)-110 }, EbmlUint.class);
public static final MKVType ChapterFlagHidden = new MKVType("ChapterFlagHidden", new byte[] { (byte)-104 }, EbmlUint.class);
public static final MKVType ChapterFlagEnabled = new MKVType("ChapterFlagEnabled", new byte[] { (byte)69, (byte)-104 }, EbmlUint.class);
public static final MKVType ChapterSegmentUID = new MKVType("ChapterSegmentUID", new byte[] { (byte)110, (byte)103 }, EbmlBin.class);
public static final MKVType ChapterSegmentEditionUID = new MKVType("ChapterSegmentEditionUID", new byte[] { (byte)110, (byte)-68 }, EbmlUint.class);
public static final MKVType ChapterPhysicalEquiv = new MKVType("ChapterPhysicalEquiv", new byte[] { (byte)99, (byte)-61 }, EbmlUint.class);
public static final MKVType ChapterTrack = new MKVType("ChapterTrack", new byte[] { (byte)-113 }, EbmlMaster.class);
public static final MKVType ChapterTrackNumber = new MKVType("ChapterTrackNumber", new byte[] { (byte)-119 }, EbmlUint.class);
public static final MKVType ChapterDisplay = new MKVType("ChapterDisplay", new byte[] { (byte)Byte.MIN_VALUE }, EbmlMaster.class);
public static final MKVType ChapString = new MKVType("ChapString", new byte[] { (byte)-123 }, EbmlString.class);
public static final MKVType ChapLanguage = new MKVType("ChapLanguage", new byte[] { (byte)67, (byte)124 }, EbmlString.class);
public static final MKVType ChapCountry = new MKVType("ChapCountry", new byte[] { (byte)67, (byte)126 }, EbmlString.class);
public static final MKVType ChapProcess = new MKVType("ChapProcess", new byte[] { (byte)105, (byte)68 }, EbmlMaster.class);
public static final MKVType ChapProcessCodecID = new MKVType("ChapProcessCodecID", new byte[] { (byte)105, (byte)85 }, EbmlUint.class);
public static final MKVType ChapProcessPrivate = new MKVType("ChapProcessPrivate", new byte[] { (byte)69, (byte)13 }, EbmlBin.class);
public static final MKVType ChapProcessCommand = new MKVType("ChapProcessCommand", new byte[] { (byte)105, (byte)17 }, EbmlMaster.class);
public static final MKVType ChapProcessTime = new MKVType("ChapProcessTime", new byte[] { (byte)105, (byte)34 }, EbmlUint.class);
public static final MKVType ChapProcessData = new MKVType("ChapProcessData", new byte[] { (byte)105, (byte)51 }, EbmlBin.class);
public static final MKVType Tags = new MKVType("Tags", new byte[] { (byte)18, (byte)84, (byte)-61, (byte)103 }, EbmlMaster.class);
public static final MKVType Tag = new MKVType("Tag", new byte[] { (byte)115, (byte)115 }, EbmlMaster.class);
public static final MKVType Targets = new MKVType("Targets", new byte[] { (byte)99, (byte)-64 }, EbmlMaster.class);
public static final MKVType TargetTypeValue = new MKVType("TargetTypeValue", new byte[] { (byte)104, (byte)-54 }, EbmlUint.class);
public static final MKVType TargetType = new MKVType("TargetType", new byte[] { (byte)99, (byte)-54 }, EbmlString.class);
public static final MKVType TagTrackUID = new MKVType("TagTrackUID", new byte[] { (byte)99, (byte)-59 }, EbmlUint.class);
public static final MKVType TagEditionUID = new MKVType("TagEditionUID", new byte[] { (byte)99, (byte)-55 }, EbmlUint.class);
public static final MKVType TagChapterUID = new MKVType("TagChapterUID", new byte[] { (byte)99, (byte)-60 }, EbmlUint.class);
public static final MKVType TagAttachmentUID = new MKVType("TagAttachmentUID", new byte[] { (byte)99, (byte)-58 }, EbmlUint.class);
public static final MKVType SimpleTag = new MKVType("SimpleTag", new byte[] { (byte)103, (byte)-56 }, EbmlMaster.class);
public static final MKVType TagName = new MKVType("TagName", new byte[] { (byte)69, (byte)-93 }, EbmlString.class);
public static final MKVType TagLanguage = new MKVType("TagLanguage", new byte[] { (byte)68, (byte)122 }, EbmlString.class);
public static final MKVType TagDefault = new MKVType("TagDefault", new byte[] { (byte)68, (byte)-124 }, EbmlUint.class);
public static final MKVType TagString = new MKVType("TagString", new byte[] { (byte)68, (byte)-121 }, EbmlString.class);
public static final MKVType TagBinary = new MKVType("TagBinary", new byte[] { (byte)68, (byte)-123 }, EbmlBin.class);
public static MKVType[] firstLevelHeaders = new MKVType[] {
SeekHead, Info, Cluster, Tracks, Cues, Attachments, Chapters, Tags, EBMLVersion, EBMLReadVersion,
EBMLMaxIDLength, EBMLMaxSizeLength, DocType, DocTypeVersion, DocTypeReadVersion };
public final byte[] id;
public final Class<? extends EbmlBase> clazz;
private String _name;
private MKVType(String name, byte[] id, Class<? extends EbmlBase> clazz) {
this._name = name;
this.id = id;
this.clazz = clazz;
_values.add(this);
}
public String name() {
return this._name;
}
public String toString() {
return this._name;
}
public static MKVType[] values() {
return _values.<MKVType>toArray(new MKVType[0]);
}
public static <T extends EbmlBase> T createByType(MKVType g) {
try {
T elem = Platform.<T>newInstance((Class<T>)g.clazz, new Object[] { g.id });
elem.type = g;
return elem;
} catch (Exception e) {
e.printStackTrace();
return (T)new EbmlBin(g.id);
}
}
public static <T extends EbmlBase> T createById(byte[] id, long offset) {
MKVType[] values = values();
for (int i = 0; i < values.length; i++) {
MKVType t = values[i];
if (Platform.arrayEqualsByte(t.id, id))
return createByType(t);
}
System.err.println("WARNING: unspecified ebml ID (" + EbmlUtil.toHexString(id) + ") encountered at position 0x" +
Long.toHexString(offset).toUpperCase());
EbmlVoid ebmlVoid = new EbmlVoid(id);
ebmlVoid.type = Void;
return (T)ebmlVoid;
}
public static boolean isHeaderFirstByte(byte b) {
MKVType[] values = values();
for (int i = 0; i < values.length; i++) {
MKVType t = values[i];
if (t.id[0] == b)
return true;
}
return false;
}
public static boolean isSpecifiedHeader(byte[] b) {
MKVType[] values = values();
for (int i = 0; i < values.length; i++) {
MKVType firstLevelHeader = values[i];
if (Platform.arrayEqualsByte(firstLevelHeader.id, b))
return true;
}
return false;
}
public static boolean isFirstLevelHeader(byte[] b) {
for (MKVType firstLevelHeader : firstLevelHeaders) {
if (Platform.arrayEqualsByte(firstLevelHeader.id, b))
return true;
}
return false;
}
public static final Map<MKVType, Set<MKVType>> children = new HashMap<>();
static {
children.put(EBML, new HashSet<>(Arrays.asList(EBMLVersion, EBMLReadVersion, EBMLMaxIDLength, EBMLMaxSizeLength, DocType, DocTypeVersion, DocTypeReadVersion)));
children.put(Segment, new HashSet<>(Arrays.asList(SeekHead, Info, Cluster, Tracks, Cues, Attachments, Chapters, Tags)));
children.put(SeekHead, new HashSet<>(Arrays.asList(Seek)));
children.put(Seek, new HashSet<>(Arrays.asList(SeekID, SeekPosition)));
children.put(Info, new HashSet<>(Arrays.asList(SegmentUID, SegmentFilename, PrevUID, PrevFilename, NextUID, NextFilenam, SegmentFamily, ChapterTranslate, TimecodeScale, Duration, DateUTC, Title, MuxingApp, WritingApp)));
children.put(ChapterTranslate, new HashSet<>(Arrays.asList(ChapterTranslateEditionUID, ChapterTranslateCodec, ChapterTranslateID)));
children.put(Cluster, new HashSet<>(Arrays.asList(Timecode, SilentTracks, Position, PrevSize, SimpleBlock, BlockGroup)));
children.put(SilentTracks, new HashSet<>(Arrays.asList(SilentTrackNumber)));
children.put(BlockGroup, new HashSet<>(Arrays.asList(Block, BlockAdditions, BlockDuration, ReferencePriority, ReferenceBlock, CodecState, Slices)));
children.put(BlockAdditions, new HashSet<>(Arrays.asList(BlockMore)));
children.put(BlockMore, new HashSet<>(Arrays.asList(BlockAddID, BlockAdditional)));
children.put(Slices, new HashSet<>(Arrays.asList(TimeSlice)));
children.put(TimeSlice, new HashSet<>(Arrays.asList(LaceNumber)));
children.put(Tracks, new HashSet<>(Arrays.asList(TrackEntry)));
children.put(TrackEntry, new HashSet<>(Arrays.asList(TrackNumber, TrackUID, TrackType, TrackType, FlagDefault, FlagForced, FlagLacing, MinCache, MaxCache, DefaultDuration, MaxBlockAdditionID, Name, Language, CodecID, CodecPrivate, CodecName, AttachmentLink, CodecDecodeAll, TrackOverlay, TrackTranslate, Video, Audio, TrackOperation, ContentEncodings)));
children.put(TrackTranslate, new HashSet<>(Arrays.asList(TrackTranslateEditionUID, TrackTranslateCodec, TrackTranslateTrackID)));
children.put(Video, new HashSet<>(Arrays.asList(FlagInterlaced, StereoMode, AlphaMode, PixelWidth, PixelHeight, PixelCropBottom, PixelCropTop, PixelCropLeft, PixelCropRight, DisplayWidth, DisplayHeight, DisplayUnit, AspectRatioType, ColourSpace)));
children.put(Audio, new HashSet<>(Arrays.asList(SamplingFrequency, OutputSamplingFrequency, Channels, BitDepth)));
children.put(TrackOperation, new HashSet<>(Arrays.asList(TrackCombinePlanes, TrackJoinBlocks)));
children.put(TrackCombinePlanes, new HashSet<>(Arrays.asList(TrackPlane)));
children.put(TrackPlane, new HashSet<>(Arrays.asList(TrackPlaneUID, TrackPlaneType)));
children.put(TrackJoinBlocks, new HashSet<>(Arrays.asList(TrackJoinUID)));
children.put(ContentEncodings, new HashSet<>(Arrays.asList(ContentEncoding)));
children.put(ContentEncoding, new HashSet<>(Arrays.asList(ContentEncodingOrder, ContentEncodingScope, ContentEncodingType, ContentCompression, ContentEncryption)));
children.put(ContentCompression, new HashSet<>(Arrays.asList(ContentCompAlgo, ContentCompSettings)));
children.put(ContentEncryption, new HashSet<>(Arrays.asList(ContentEncAlgo, ContentEncKeyID, ContentSignature, ContentSigKeyID, ContentSigAlgo, ContentSigHashAlgo)));
children.put(Cues, new HashSet<>(Arrays.asList(CuePoint)));
children.put(CuePoint, new HashSet<>(Arrays.asList(CueTime, CueTrackPositions)));
children.put(CueTrackPositions, new HashSet<>(Arrays.asList(CueTrack, CueClusterPosition, CueRelativePosition, CueDuration, CueBlockNumber, CueCodecState, CueReference)));
children.put(CueReference, new HashSet<>(Arrays.asList(CueRefTime)));
children.put(Attachments, new HashSet<>(Arrays.asList(AttachedFile)));
children.put(AttachedFile, new HashSet<>(Arrays.asList(FileDescription, FileName, FileMimeType, FileData, FileUID)));
children.put(Chapters, new HashSet<>(Arrays.asList(EditionEntry)));
children.put(EditionEntry, new HashSet<>(Arrays.asList(EditionUID, EditionFlagHidden, EditionFlagDefault, EditionFlagOrdered, ChapterAtom)));
children.put(ChapterAtom, new HashSet<>(Arrays.asList(ChapterUID, ChapterStringUID, ChapterTimeStart, ChapterTimeEnd, ChapterFlagHidden, ChapterFlagEnabled, ChapterSegmentUID, ChapterSegmentEditionUID, ChapterPhysicalEquiv, ChapterTrack, ChapterDisplay, ChapProcess)));
children.put(ChapterTrack, new HashSet<>(Arrays.asList(ChapterTrackNumber)));
children.put(ChapterDisplay, new HashSet<>(Arrays.asList(ChapString, ChapLanguage, ChapCountry)));
children.put(ChapProcess, new HashSet<>(Arrays.asList(ChapProcessCodecID, ChapProcessPrivate, ChapProcessCommand)));
children.put(ChapProcessCommand, new HashSet<>(Arrays.asList(ChapProcessTime, ChapProcessData)));
children.put(Tags, new HashSet<>(Arrays.asList(Tag)));
children.put(Tag, new HashSet<>(Arrays.asList(Targets, SimpleTag)));
children.put(Targets, new HashSet<>(Arrays.asList(TargetTypeValue, TargetType, TagTrackUID, TagEditionUID, TagChapterUID, TagAttachmentUID)));
children.put(SimpleTag, new HashSet<>(Arrays.asList(TagName, TagLanguage, TagDefault, TagString, TagBinary)));
}
public static MKVType getParent(MKVType t) {
for (Map.Entry<MKVType, Set<MKVType>> ent : children.entrySet()) {
if (ent.getValue().contains(t))
return ent.getKey();
}
return null;
}
public static boolean possibleChild(EbmlMaster parent, EbmlBase child) {
if (parent == null) {
if (child.type == EBML || child.type == Segment)
return true;
return false;
}
if (Platform.arrayEqualsByte(child.id, Void.id) || Platform.arrayEqualsByte(child.id, CRC32.id))
return (child.offset != parent.dataOffset + (long)parent.dataLen);
if (child.type == Void || child.type == CRC32)
return true;
Set<MKVType> candidates = children.get(parent.type);
return (candidates != null && candidates.contains(child.type));
}
public static boolean possibleChildById(EbmlMaster parent, byte[] typeId) {
if (parent == null && (Platform.arrayEqualsByte(EBML.id, typeId) || Platform.arrayEqualsByte(Segment.id, typeId)))
return true;
if (parent == null)
return false;
if (Platform.arrayEqualsByte(Void.id, typeId) || Platform.arrayEqualsByte(CRC32.id, typeId))
return true;
for (MKVType aCandidate : children.get(parent.type)) {
if (Platform.arrayEqualsByte(aCandidate.id, typeId))
return true;
}
return false;
}
public static EbmlBase findFirst(EbmlBase master, MKVType[] path) {
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
return findFirstSub(master, tlist);
}
public static <T> T findFirstTree(List<? extends EbmlBase> tree, MKVType[] path) {
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
for (EbmlBase e : tree) {
EbmlBase z = findFirstSub(e, tlist);
if (z != null)
return (T)z;
}
return null;
}
private static EbmlBase findFirstSub(EbmlBase elem, List<MKVType> path) {
if (path.size() == 0)
return null;
if (!elem.type.equals(path.get(0)))
return null;
if (path.size() == 1)
return elem;
MKVType head = (MKVType)path.remove(0);
EbmlBase result = null;
if (elem instanceof EbmlMaster) {
Iterator<EbmlBase> iter = ((EbmlMaster)elem).children.iterator();
while (iter.hasNext() && result == null)
result = findFirstSub(iter.next(), path);
}
path.add(0, head);
return result;
}
public static <T> List<T> findList(List<? extends EbmlBase> tree, Class<T> class1, MKVType[] path) {
List<T> result = new LinkedList<>();
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
if (tlist.size() > 0)
for (EbmlBase node : tree) {
MKVType head = (MKVType)tlist.remove(0);
if (head == null || head.equals(node.type))
findSubList(node, tlist, result);
tlist.add(0, head);
}
return result;
}
private static <T> void findSubList(EbmlBase element, List<MKVType> path, Collection<T> result) {
if (path.size() > 0) {
MKVType head = (MKVType)path.remove(0);
if (element instanceof EbmlMaster) {
EbmlMaster nb = (EbmlMaster)element;
for (EbmlBase candidate : nb.children) {
if (head == null || head.equals(candidate.type))
findSubList(candidate, path, result);
}
}
path.add(0, head);
} else {
result.add((T)element);
}
}
public static <T> T[] findAllTree(List<? extends EbmlBase> tree, Class<T> class1, MKVType[] path) {
List<EbmlBase> result = new LinkedList<>();
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
if (tlist.size() > 0)
for (EbmlBase node : tree) {
MKVType head = (MKVType)tlist.remove(0);
if (head == null || head.equals(node.type))
findSub(node, tlist, result);
tlist.add(0, head);
}
return result.toArray((T[])Array.newInstance(class1, 0));
}
public static <T> T[] findAll(EbmlBase master, Class<T> class1, boolean ga, MKVType[] path) {
List<EbmlBase> result = new LinkedList<>();
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
if (!master.type.equals(tlist.get(0)))
return result.toArray((T[])Array.newInstance(class1, 0));
tlist.remove(0);
findSub(master, tlist, result);
return result.toArray((T[])Array.newInstance(class1, 0));
}
private static void findSub(EbmlBase master, List<MKVType> path, Collection<EbmlBase> result) {
if (path.size() > 0) {
MKVType head = (MKVType)path.remove(0);
if (master instanceof EbmlMaster) {
EbmlMaster nb = (EbmlMaster)master;
for (EbmlBase candidate : nb.children) {
if (head == null || head.equals(candidate.type))
findSub(candidate, path, result);
}
}
path.add(0, head);
} else {
result.add(master);
}
}
}

View file

@ -0,0 +1,103 @@
package org.jcodec.containers.mkv;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.containers.mkv.boxes.EbmlBase;
import org.jcodec.containers.mkv.boxes.EbmlBin;
import org.jcodec.containers.mkv.boxes.EbmlMaster;
import org.jcodec.containers.mkv.boxes.EbmlUint;
import org.jcodec.containers.mkv.util.EbmlUtil;
public class SeekHeadFactory {
List<SeekMock> a;
long currentDataOffset = 0L;
public SeekHeadFactory() {
this.a = new ArrayList<>();
}
public void add(EbmlBase e) {
SeekMock z = SeekMock.make(e);
z.dataOffset = this.currentDataOffset;
z.seekPointerSize = EbmlUint.calculatePayloadSize(z.dataOffset);
this.currentDataOffset += (long)z.size;
this.a.add(z);
}
public EbmlMaster indexSeekHead() {
int seekHeadSize = computeSeekHeadSize();
EbmlMaster seekHead = MKVType.<EbmlMaster>createByType(MKVType.SeekHead);
for (SeekMock z : this.a) {
EbmlMaster seek = MKVType.<EbmlMaster>createByType(MKVType.Seek);
EbmlBin seekId = MKVType.<EbmlBin>createByType(MKVType.SeekID);
seekId.setBuf(ByteBuffer.wrap(z.id));
seek.add(seekId);
EbmlUint seekPosition = MKVType.<EbmlUint>createByType(MKVType.SeekPosition);
seekPosition.setUint(z.dataOffset + (long)seekHeadSize);
if (seekPosition.data.limit() != z.seekPointerSize)
System.err.println("estimated size of seekPosition differs from the one actually used. ElementId: " + EbmlUtil.toHexString(z.id) + " " + seekPosition.getData().limit() + " vs " + z.seekPointerSize);
seek.add(seekPosition);
seekHead.add(seek);
}
ByteBuffer mux = seekHead.getData();
if (mux.limit() != seekHeadSize)
System.err.println("estimated size of seekHead differs from the one actually used. " + mux.limit() + " vs " + seekHeadSize);
return seekHead;
}
public int computeSeekHeadSize() {
int seekHeadSize = estimateSize();
boolean reindex = false;
while (true) {
reindex = false;
for (SeekMock z : this.a) {
int minSize = EbmlUint.calculatePayloadSize(z.dataOffset + (long)seekHeadSize);
if (minSize > z.seekPointerSize) {
System.out.println("Size " + seekHeadSize + " seems too small for element " + EbmlUtil.toHexString(z.id) + " increasing size by one.");
z.seekPointerSize++;
seekHeadSize++;
reindex = true;
break;
}
if (minSize < z.seekPointerSize)
throw new RuntimeException("Downsizing the index is not well thought through.");
}
if (!reindex)
return seekHeadSize;
}
}
int estimateSize() {
int s = MKVType.SeekHead.id.length + 1;
s += estimeteSeekSize(((SeekMock)this.a.get(0)).id.length, 1);
for (int i = 1; i < this.a.size(); i++)
s += estimeteSeekSize(((SeekMock)this.a.get(i)).id.length, ((SeekMock)this.a.get(i)).seekPointerSize);
return s;
}
public static int estimeteSeekSize(int idLength, int offsetSizeInBytes) {
int seekIdSize = MKVType.SeekID.id.length + EbmlUtil.ebmlLength((long)idLength) + idLength;
int seekPositionSize = MKVType.SeekPosition.id.length + EbmlUtil.ebmlLength((long)offsetSizeInBytes) + offsetSizeInBytes;
int seekSize = MKVType.Seek.id.length + EbmlUtil.ebmlLength((long)(seekIdSize + seekPositionSize)) + seekIdSize + seekPositionSize;
return seekSize;
}
public static class SeekMock {
public long dataOffset;
byte[] id;
int size;
int seekPointerSize;
public static SeekMock make(EbmlBase e) {
SeekMock z = new SeekMock();
z.id = e.id;
z.size = (int)e.size();
return z;
}
}
}

View file

@ -0,0 +1,45 @@
package org.jcodec.containers.mkv.boxes;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.common.UsedViaReflection;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.containers.mkv.MKVType;
import org.jcodec.containers.mkv.util.EbmlUtil;
import org.jcodec.platform.Platform;
public abstract class EbmlBase {
protected EbmlMaster parent;
public MKVType type;
public byte[] id;
public int dataLen = 0;
public long offset;
public long dataOffset;
public int typeSizeLength;
@UsedViaReflection
public EbmlBase(byte[] id) {
this.id = id;
}
public boolean equalId(byte[] typeId) {
return Platform.arrayEqualsByte(this.id, typeId);
}
public abstract ByteBuffer getData();
public long size() {
return (long)(this.dataLen + EbmlUtil.ebmlLength((long)this.dataLen) + this.id.length);
}
public long mux(SeekableByteChannel os) throws IOException {
ByteBuffer bb = getData();
return (long)os.write(bb);
}
}

View file

@ -0,0 +1,62 @@
package org.jcodec.containers.mkv.boxes;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.containers.mkv.util.EbmlUtil;
public class EbmlBin extends EbmlBase {
public ByteBuffer data;
protected boolean dataRead = false;
public EbmlBin(byte[] id) {
super(id);
}
public void readChannel(SeekableByteChannel is) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(this.dataLen);
is.read(bb);
bb.flip();
read(bb);
}
public void read(ByteBuffer source) {
this.data = source.slice();
this.data.limit(this.dataLen);
this.dataRead = true;
}
public void skip(ByteBuffer source) {
if (!this.dataRead) {
source.position((int)(this.dataOffset + (long)this.dataLen));
this.dataRead = true;
}
}
public long size() {
if (this.data == null || this.data.limit() == 0)
return super.size();
long totalSize = (long)this.data.limit();
totalSize += (long)EbmlUtil.ebmlLength((long)this.data.limit());
totalSize += (long)this.id.length;
return totalSize;
}
public void setBuf(ByteBuffer data) {
this.data = data.slice();
this.dataLen = this.data.limit();
}
public ByteBuffer getData() {
int sizeSize = EbmlUtil.ebmlLength((long)this.data.limit());
byte[] size = EbmlUtil.ebmlEncodeLen((long)this.data.limit(), sizeSize);
ByteBuffer bb = ByteBuffer.allocate(this.id.length + sizeSize + this.data.limit());
bb.put(this.id);
bb.put(size);
bb.put(this.data);
bb.flip();
this.data.flip();
return bb;
}
}

View file

@ -0,0 +1,38 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
import java.util.Date;
public class EbmlDate extends EbmlSint {
private static final int NANOSECONDS_IN_A_SECOND = 1000000000;
private static final int MILISECONDS_IN_A_SECOND = 1000;
private static final int NANOSECONDS_IN_A_MILISECOND = 1000000;
public static long MILISECONDS_SINCE_UNIX_EPOCH_START = 978307200L;
public EbmlDate(byte[] id) {
super(id);
}
public void setDate(Date value) {
setMiliseconds(value.getTime());
}
public Date getDate() {
long val = getLong();
val = val / 1000000L + MILISECONDS_SINCE_UNIX_EPOCH_START;
return new Date(val);
}
private void setMiliseconds(long milliseconds) {
setLong((milliseconds - MILISECONDS_SINCE_UNIX_EPOCH_START) * 1000000L);
}
public void setLong(long value) {
this.data = ByteBuffer.allocate(8);
this.data.putLong(value);
this.data.flip();
}
}

View file

@ -0,0 +1,29 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
public class EbmlFloat extends EbmlBin {
public EbmlFloat(byte[] id) {
super(id);
}
public void setDouble(double value) {
if (value < Float.MAX_VALUE) {
ByteBuffer bb = ByteBuffer.allocate(4);
bb.putFloat((float)value);
bb.flip();
this.data = bb;
} else if (value < Double.MAX_VALUE) {
ByteBuffer bb = ByteBuffer.allocate(8);
bb.putDouble(value);
bb.flip();
this.data = bb;
}
}
public double getDouble() {
if (this.data.limit() == 4)
return (double)this.data.duplicate().getFloat();
return this.data.duplicate().getDouble();
}
}

View file

@ -0,0 +1,55 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.jcodec.containers.mkv.util.EbmlUtil;
public class EbmlMaster extends EbmlBase {
protected long usedSize;
public final ArrayList<EbmlBase> children;
public static final byte[] CLUSTER_ID = new byte[] { 31, 67, -74, 117 };
public EbmlMaster(byte[] id) {
super(id);
this.children = new ArrayList<>();
this.id = id;
}
public void add(EbmlBase elem) {
if (elem == null)
return;
elem.parent = this;
this.children.add(elem);
}
public ByteBuffer getData() {
long size = getDataLen();
if (size > Integer.MAX_VALUE)
System.out.println("EbmlMaster.getData: id.length " + this.id.length + " EbmlUtil.ebmlLength(" + size + "): " + EbmlUtil.ebmlLength(size) + " size: " + size);
ByteBuffer bb = ByteBuffer.allocate((int)((long)(this.id.length + EbmlUtil.ebmlLength(size)) + size));
bb.put(this.id);
bb.put(EbmlUtil.ebmlEncode(size));
for (int i = 0; i < this.children.size(); i++)
bb.put(this.children.get(i).getData());
bb.flip();
return bb;
}
protected long getDataLen() {
if (this.children == null || this.children.isEmpty())
return (long)this.dataLen;
long dataLength = 0L;
for (EbmlBase e : this.children)
dataLength += e.size();
return dataLength;
}
public long size() {
long size = getDataLen();
size += (long)EbmlUtil.ebmlLength(size);
size += (long)this.id.length;
return size;
}
}

View file

@ -0,0 +1,50 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
import org.jcodec.containers.mkv.util.EbmlUtil;
public class EbmlSint extends EbmlBin {
public EbmlSint(byte[] id) {
super(id);
}
public void setLong(long value) {
this.data = ByteBuffer.wrap(convertToBytes(value));
}
public long getLong() {
if (this.data.limit() - this.data.position() == 8)
return this.data.duplicate().getLong();
byte[] b = this.data.array();
long l = 0L;
for (int i = b.length - 1; i >= 0; i--)
l |= ((long)b[i] & 0xFFL) << 8 * (b.length - 1 - i);
return l;
}
public static int ebmlSignedLength(long val) {
if (val <= 64L && val >= -63L)
return 1;
if (val <= 8192L && val >= -8191L)
return 2;
if (val <= 1048576L && val >= -1048575L)
return 3;
if (val <= 134217728L && val >= -134217727L)
return 4;
if (val <= 17179869184L && val >= -17179869183L)
return 5;
if (val <= 2199023255552L && val >= -2199023255551L)
return 6;
if (val <= 281474976710656L && val >= -281474976710655L)
return 7;
return 8;
}
public static final long[] signedComplement = new long[] { 0L, 63L, 8191L, 1048575L, 134217727L, 17179869183L, 2199023255551L, 281474976710655L, 36028797018963967L };
public static byte[] convertToBytes(long val) {
int num = ebmlSignedLength(val);
val += signedComplement[num];
return EbmlUtil.ebmlEncodeLen(val, num);
}
}

View file

@ -0,0 +1,35 @@
package org.jcodec.containers.mkv.boxes;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
public class EbmlString extends EbmlBin {
public String charset = "UTF-8";
public EbmlString(byte[] id) {
super(id);
}
public static EbmlString createEbmlString(byte[] id, String value) {
EbmlString e = new EbmlString(id);
e.setString(value);
return e;
}
public String getString() {
try {
return new String(this.data.array(), this.charset);
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
return "";
}
}
public void setString(String value) {
try {
this.data = ByteBuffer.wrap(value.getBytes(this.charset));
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
}
}

View file

@ -0,0 +1,46 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
public class EbmlUint extends EbmlBin {
public EbmlUint(byte[] id) {
super(id);
}
public static EbmlUint createEbmlUint(byte[] id, long value) {
EbmlUint e = new EbmlUint(id);
e.setUint(value);
return e;
}
public void setUint(long value) {
this.data = ByteBuffer.wrap(longToBytes(value));
this.dataLen = this.data.limit();
}
public long getUint() {
long l = 0L;
long tmp = 0L;
for (int i = 0; i < this.data.limit(); i++) {
tmp = (long)this.data.get(this.data.limit() - 1 - i) << 56L;
tmp >>>= 56 - i * 8;
l |= tmp;
}
return l;
}
public static byte[] longToBytes(long value) {
byte[] b = new byte[calculatePayloadSize(value)];
for (int i = b.length - 1; i >= 0; i--)
b[i] = (byte)(int)(value >>> 8 * (b.length - i - 1));
return b;
}
public static int calculatePayloadSize(long value) {
if (value == 0L)
return 1;
if (value <= Integer.MAX_VALUE)
return 4 - (Integer.numberOfLeadingZeros((int)value) >> 3);
return 8 - (Long.numberOfLeadingZeros(value) >> 3);
}
}

View file

@ -0,0 +1,25 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
public class EbmlUlong extends EbmlBin {
public EbmlUlong(byte[] id) {
super(id);
this.data = ByteBuffer.allocate(8);
}
public static EbmlUlong createEbmlUlong(byte[] id, long value) {
EbmlUlong e = new EbmlUlong(id);
e.setUlong(value);
return e;
}
public void setUlong(long value) {
this.data.putLong(value);
this.data.flip();
}
public long getUlong() {
return this.data.duplicate().getLong();
}
}

View file

@ -0,0 +1,19 @@
package org.jcodec.containers.mkv.boxes;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.common.io.SeekableByteChannel;
public class EbmlVoid extends EbmlBase {
public EbmlVoid(byte[] id) {
super(id);
}
public ByteBuffer getData() {
return null;
}
public void skip(SeekableByteChannel is) throws IOException {
is.setPosition(this.dataOffset + (long)this.dataLen);
}
}

View file

@ -0,0 +1,338 @@
package org.jcodec.containers.mkv.boxes;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import org.jcodec.common.ByteArrayList;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.containers.mkv.util.EbmlUtil;
import org.jcodec.platform.Platform;
public class MkvBlock extends EbmlBin {
private static final String XIPH = "Xiph";
private static final String EBML = "EBML";
private static final String FIXED = "Fixed";
private static final int MAX_BLOCK_HEADER_SIZE = 512;
public int[] frameOffsets;
public int[] frameSizes;
public long trackNumber;
public int timecode;
public long absoluteTimecode;
public boolean _keyFrame;
public int headerSize;
public String lacing;
public boolean discardable;
public boolean lacingPresent;
public ByteBuffer[] frames;
public static final byte[] BLOCK_ID = new byte[] { -95 };
public static final byte[] SIMPLEBLOCK_ID = new byte[] { -93 };
public static MkvBlock copy(MkvBlock old) {
MkvBlock be = new MkvBlock(old.id);
be.trackNumber = old.trackNumber;
be.timecode = old.timecode;
be.absoluteTimecode = old.absoluteTimecode;
be._keyFrame = old._keyFrame;
be.headerSize = old.headerSize;
be.lacing = old.lacing;
be.discardable = old.discardable;
be.lacingPresent = old.lacingPresent;
be.frameOffsets = new int[old.frameOffsets.length];
be.frameSizes = new int[old.frameSizes.length];
be.dataOffset = old.dataOffset;
be.offset = old.offset;
be.type = old.type;
System.arraycopy(old.frameOffsets, 0, be.frameOffsets, 0, be.frameOffsets.length);
System.arraycopy(old.frameSizes, 0, be.frameSizes, 0, be.frameSizes.length);
return be;
}
public static MkvBlock keyFrame(long trackNumber, int timecode, ByteBuffer frame) {
MkvBlock be = new MkvBlock(SIMPLEBLOCK_ID);
be.frames = new ByteBuffer[] { frame };
be.frameSizes = new int[] { frame.limit() };
be._keyFrame = true;
be.trackNumber = trackNumber;
be.timecode = timecode;
return be;
}
public MkvBlock(byte[] type) {
super(type);
if (!Platform.arrayEqualsByte(SIMPLEBLOCK_ID, type) && !Platform.arrayEqualsByte(BLOCK_ID, type))
throw new IllegalArgumentException("Block initiated with invalid id: " + EbmlUtil.toHexString(type));
}
public void readChannel(SeekableByteChannel is) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(100);
is.read(bb);
bb.flip();
read(bb);
is.setPosition(this.dataOffset + (long)this.dataLen);
}
public void read(ByteBuffer source) {
ByteBuffer bb = source.slice();
this.trackNumber = ebmlDecode(bb);
int tcPart1 = bb.get() & 0xFF;
int tcPart2 = bb.get() & 0xFF;
this.timecode = (short)((short)tcPart1 << 8 | (short)tcPart2);
int flags = bb.get() & 0xFF;
this._keyFrame = ((flags & 0x80) > 0);
this.discardable = ((flags & 0x1) > 0);
int laceFlags = flags & 0x6;
this.lacingPresent = (laceFlags != 0);
if (this.lacingPresent) {
int lacesCount = bb.get() & 0xFF;
this.frameSizes = new int[lacesCount + 1];
if (laceFlags == 2) {
this.lacing = "Xiph";
this.headerSize = readXiphLaceSizes(bb, this.frameSizes, this.dataLen, bb.position());
} else if (laceFlags == 6) {
this.lacing = "EBML";
this.headerSize = readEBMLLaceSizes(bb, this.frameSizes, this.dataLen, bb.position());
} else if (laceFlags == 4) {
this.lacing = "Fixed";
this.headerSize = bb.position();
int aLaceSize = (this.dataLen - this.headerSize) / (lacesCount + 1);
Arrays.fill(this.frameSizes, aLaceSize);
} else {
throw new RuntimeException("Unsupported lacing type flag.");
}
turnSizesToFrameOffsets(this.frameSizes);
} else {
this.lacing = "";
int frameOffset = bb.position();
this.frameOffsets = new int[1];
this.frameOffsets[0] = frameOffset;
this.headerSize = bb.position();
this.frameSizes = new int[1];
this.frameSizes[0] = this.dataLen - this.headerSize;
}
}
private void turnSizesToFrameOffsets(int[] sizes) {
this.frameOffsets = new int[sizes.length];
this.frameOffsets[0] = this.headerSize;
for (int i = 1; i < sizes.length; i++)
this.frameOffsets[i] = this.frameOffsets[i - 1] + sizes[i - 1];
}
public static int readXiphLaceSizes(ByteBuffer bb, int[] sizes, int size, int preLacingHeaderSize) {
int startPos = bb.position();
int lastIndex = sizes.length - 1;
sizes[lastIndex] = size;
for (int l = 0; l < lastIndex; l++) {
int laceSize = 255;
while (laceSize == 255) {
laceSize = bb.get() & 0xFF;
sizes[l] = sizes[l] + laceSize;
}
sizes[lastIndex] = sizes[lastIndex] - sizes[l];
}
int headerSize = bb.position() - startPos + preLacingHeaderSize;
sizes[lastIndex] = sizes[lastIndex] - headerSize;
return headerSize;
}
public static int readEBMLLaceSizes(ByteBuffer source, int[] sizes, int size, int preLacingHeaderSize) {
int lastIndex = sizes.length - 1;
sizes[lastIndex] = size;
int startPos = source.position();
sizes[0] = (int)ebmlDecode(source);
sizes[lastIndex] = sizes[lastIndex] - sizes[0];
int laceSize = sizes[0];
long laceSizeDiff = 0L;
for (int l = 1; l < lastIndex; l++) {
laceSizeDiff = ebmlDecodeSigned(source);
laceSize = (int)((long)laceSize + laceSizeDiff);
sizes[l] = laceSize;
sizes[lastIndex] = sizes[lastIndex] - sizes[l];
}
int headerSize = source.position() - startPos + preLacingHeaderSize;
sizes[lastIndex] = sizes[lastIndex] - headerSize;
return headerSize;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{dataOffset: ").append(this.dataOffset);
sb.append(", trackNumber: ").append(this.trackNumber);
sb.append(", timecode: ").append(this.timecode);
sb.append(", keyFrame: ").append(this._keyFrame);
sb.append(", headerSize: ").append(this.headerSize);
sb.append(", lacing: ").append(this.lacing);
for (int i = 0; i < this.frameSizes.length; i++)
sb.append(", frame[").append(i).append("] offset ").append(this.frameOffsets[i]).append(" size ").append(this.frameSizes[i]);
sb.append(" }");
return sb.toString();
}
public ByteBuffer[] getFrames(ByteBuffer source) throws IOException {
ByteBuffer[] frames = new ByteBuffer[this.frameSizes.length];
for (int i = 0; i < this.frameSizes.length; i++) {
if (this.frameOffsets[i] > source.limit())
System.err.println("frame offset: " + this.frameOffsets[i] + " limit: " + source.limit());
source.position(this.frameOffsets[i]);
ByteBuffer bb = source.slice();
bb.limit(this.frameSizes[i]);
frames[i] = bb;
}
return frames;
}
public void readFrames(ByteBuffer source) throws IOException {
this.frames = getFrames(source);
}
public ByteBuffer getData() {
int dataSize = getDataSize();
ByteBuffer bb = ByteBuffer.allocate(dataSize + EbmlUtil.ebmlLength((long)dataSize) + this.id.length);
bb.put(this.id);
bb.put(EbmlUtil.ebmlEncode((long)dataSize));
bb.put(EbmlUtil.ebmlEncode(this.trackNumber));
bb.put((byte)(this.timecode >>> 8 & 0xFF));
bb.put((byte)(this.timecode & 0xFF));
byte flags = 0;
if ("Xiph".equals(this.lacing)) {
flags = 2;
} else if ("EBML".equals(this.lacing)) {
flags = 6;
} else if ("Fixed".equals(this.lacing)) {
flags = 4;
}
if (this.discardable)
flags = (byte)(flags | 0x1);
if (this._keyFrame)
flags = (byte)(flags | 0x80);
bb.put(flags);
if ((flags & 0x6) != 0) {
bb.put((byte)(this.frames.length - 1 & 0xFF));
bb.put(muxLacingInfo());
}
for (int i = 0; i < this.frames.length; i++) {
ByteBuffer frame = this.frames[i];
bb.put(frame);
}
bb.flip();
return bb;
}
public void seekAndReadContent(FileChannel source) throws IOException {
this.data = ByteBuffer.allocate(this.dataLen);
source.position(this.dataOffset);
source.read(this.data);
this.data.flip();
}
public long size() {
long size = (long)getDataSize();
size += (long)EbmlUtil.ebmlLength(size);
size += (long)this.id.length;
return size;
}
public int getDataSize() {
int size = 0;
int arrayOfInt[], i;
int j;
for (arrayOfInt = this.frameSizes, i = arrayOfInt.length, j = 0; j < i; ) {
long fsize = (long)arrayOfInt[j];
size = (int)((long)size + fsize);
j++;
}
if (this.lacingPresent) {
size += (muxLacingInfo()).length;
size++;
}
size += 3;
size += EbmlUtil.ebmlLength(this.trackNumber);
return size;
}
private byte[] muxLacingInfo() {
if ("EBML".equals(this.lacing))
return muxEbmlLacing(this.frameSizes);
if ("Xiph".equals(this.lacing))
return muxXiphLacing(this.frameSizes);
if ("Fixed".equals(this.lacing))
return new byte[0];
return null;
}
public static long ebmlDecode(ByteBuffer bb) {
byte firstByte = bb.get();
int length = EbmlUtil.computeLength(firstByte);
if (length == 0)
throw new RuntimeException("Invalid ebml integer size.");
long value = (long)(firstByte & 255 >>> length);
length--;
while (length > 0) {
value = value << 8L | (long)(bb.get() & 0xFF);
length--;
}
return value;
}
public static long ebmlDecodeSigned(ByteBuffer source) {
byte firstByte = source.get();
int size = EbmlUtil.computeLength(firstByte);
if (size == 0)
throw new RuntimeException("Invalid ebml integer size.");
long value = (long)(firstByte & 255 >>> size);
int remaining = size - 1;
while (remaining > 0) {
value = value << 8L | (long)(source.get() & 0xFF);
remaining--;
}
return value - EbmlSint.signedComplement[size];
}
public static long[] calcEbmlLacingDiffs(int[] laceSizes) {
int lacesCount = laceSizes.length - 1;
long[] out = new long[lacesCount];
out[0] = (long)laceSizes[0];
for (int i = 1; i < lacesCount; i++)
out[i] = (long)(laceSizes[i] - laceSizes[i - 1]);
return out;
}
public static byte[] muxEbmlLacing(int[] laceSizes) {
ByteArrayList bytes = ByteArrayList.createByteArrayList();
long[] laceSizeDiffs = calcEbmlLacingDiffs(laceSizes);
bytes.addAll(EbmlUtil.ebmlEncode(laceSizeDiffs[0]));
for (int i = 1; i < laceSizeDiffs.length; i++)
bytes.addAll(EbmlSint.convertToBytes(laceSizeDiffs[i]));
return bytes.toArray();
}
public static byte[] muxXiphLacing(int[] laceSizes) {
ByteArrayList bytes = ByteArrayList.createByteArrayList();
for (int i = 0; i < laceSizes.length - 1; i++) {
long laceSize = (long)laceSizes[i];
while (laceSize >= 255L) {
bytes.add((byte)-1);
laceSize -= 255L;
}
bytes.add((byte)(int)laceSize);
}
return bytes.toArray();
}
}

View file

@ -0,0 +1,48 @@
package org.jcodec.containers.mkv.boxes;
import java.nio.ByteBuffer;
import org.jcodec.containers.mkv.util.EbmlUtil;
import org.jcodec.platform.Platform;
public class MkvSegment extends EbmlMaster {
int headerSize = 0;
public static final byte[] SEGMENT_ID = new byte[] { 24, 83, Byte.MIN_VALUE, 103 };
public MkvSegment(byte[] id) {
super(id);
}
public static MkvSegment createMkvSegment() {
return new MkvSegment(SEGMENT_ID);
}
public ByteBuffer getHeader() {
long headerSize = getHeaderSize();
if (headerSize > Integer.MAX_VALUE)
System.out.println("MkvSegment.getHeader: id.length " + this.id.length + " Element.getEbmlSize(" + this.dataLen + "): " + EbmlUtil.ebmlLength((long)this.dataLen) + " size: " + this.dataLen);
ByteBuffer bb = ByteBuffer.allocate((int)headerSize);
bb.put(this.id);
bb.put(EbmlUtil.ebmlEncode(getDataLen()));
if (this.children != null && !this.children.isEmpty())
for (EbmlBase e : this.children) {
if (Platform.arrayEqualsByte(CLUSTER_ID, e.type.id))
continue;
bb.put(e.getData());
}
bb.flip();
return bb;
}
public long getHeaderSize() {
long returnValue = (long)this.id.length;
returnValue += (long)EbmlUtil.ebmlLength(getDataLen());
if (this.children != null && !this.children.isEmpty())
for (EbmlBase e : this.children) {
if (Platform.arrayEqualsByte(CLUSTER_ID, e.type.id))
continue;
returnValue += e.size();
}
return returnValue;
}
}

View file

@ -0,0 +1,489 @@
package org.jcodec.containers.mkv.demuxer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.Codec;
import org.jcodec.common.Demuxer;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.TrackType;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Size;
import org.jcodec.common.model.TapeTimecode;
import org.jcodec.containers.mkv.MKVParser;
import org.jcodec.containers.mkv.MKVType;
import org.jcodec.containers.mkv.boxes.EbmlBase;
import org.jcodec.containers.mkv.boxes.EbmlBin;
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;
public final class MKVDemuxer implements Demuxer {
private VideoTrack vTrack = null;
private List<AudioTrack> aTracks;
private List<SubtitlesTrack> subsTracks;
private List<EbmlMaster> t;
private SeekableByteChannel channel;
int timescale = 1;
int pictureWidth;
int pictureHeight;
private static Map<String, Codec> codecMapping = new HashMap<>();
static {
codecMapping.put("V_VP8", Codec.VP8);
codecMapping.put("V_VP9", Codec.VP9);
codecMapping.put("V_MPEG4/ISO/AVC", Codec.H264);
}
public MKVDemuxer(SeekableByteChannel fileChannelWrapper) throws IOException {
this.channel = fileChannelWrapper;
this.aTracks = new ArrayList<>();
this.subsTracks = new ArrayList<>();
MKVParser parser = new MKVParser(this.channel);
this.t = parser.parse();
demux();
}
private void demux() {
MKVType[] path = { MKVType.Segment, MKVType.Info, MKVType.TimecodeScale };
EbmlUint ts = MKVType.<EbmlUint>findFirstTree(this.t, path);
if (ts != null)
this.timescale = (int)ts.getUint();
MKVType[] path9 = { MKVType.Segment, MKVType.Tracks, MKVType.TrackEntry };
for (EbmlMaster aTrack : MKVType.<EbmlMaster>findList(this.t, EbmlMaster.class, path9)) {
MKVType[] path1 = { MKVType.TrackEntry, MKVType.TrackType };
long type = ((EbmlUint)MKVType.findFirst(aTrack, path1)).getUint();
MKVType[] arrayOfMKVType1 = { MKVType.TrackEntry, MKVType.TrackNumber };
long id = ((EbmlUint)MKVType.findFirst(aTrack, arrayOfMKVType1)).getUint();
if (type == 1L) {
if (this.vTrack != null)
throw new RuntimeException("More then 1 video track, can not compute...");
MKVType[] path3 = { MKVType.TrackEntry, MKVType.CodecPrivate };
MKVType[] path10 = { MKVType.TrackEntry, MKVType.CodecID };
EbmlString codecId = (EbmlString)MKVType.findFirst(aTrack, path10);
Codec codec = codecMapping.get(codecId.getString());
EbmlBin videoCodecState = (EbmlBin)MKVType.findFirst(aTrack, path3);
ByteBuffer state = null;
if (videoCodecState != null)
state = videoCodecState.data;
MKVType[] path4 = { MKVType.TrackEntry, MKVType.Video, MKVType.PixelWidth };
EbmlUint width = (EbmlUint)MKVType.findFirst(aTrack, path4);
MKVType[] path5 = { MKVType.TrackEntry, MKVType.Video, MKVType.PixelHeight };
EbmlUint height = (EbmlUint)MKVType.findFirst(aTrack, path5);
MKVType[] path6 = { MKVType.TrackEntry, MKVType.Video, MKVType.DisplayWidth };
EbmlUint dwidth = (EbmlUint)MKVType.findFirst(aTrack, path6);
MKVType[] path7 = { MKVType.TrackEntry, MKVType.Video, MKVType.DisplayHeight };
EbmlUint dheight = (EbmlUint)MKVType.findFirst(aTrack, path7);
MKVType[] path8 = { MKVType.TrackEntry, MKVType.Video, MKVType.DisplayUnit };
EbmlUint unit = (EbmlUint)MKVType.findFirst(aTrack, path8);
if (width != null && height != null) {
this.pictureWidth = (int)width.getUint();
this.pictureHeight = (int)height.getUint();
} else if (dwidth != null && dheight != null) {
if (unit == null || unit.getUint() == 0L) {
this.pictureHeight = (int)dheight.getUint();
this.pictureWidth = (int)dwidth.getUint();
} else {
throw new RuntimeException("DisplayUnits other then 0 are not implemented yet");
}
}
this.vTrack = new VideoTrack(this, (int)id, state, codec);
continue;
}
if (type == 2L) {
AudioTrack audioTrack = new AudioTrack((int)id, this);
MKVType[] path3 = { MKVType.TrackEntry, MKVType.Audio, MKVType.SamplingFrequency };
EbmlFloat sf = (EbmlFloat)MKVType.findFirst(aTrack, path3);
if (sf != null)
audioTrack.samplingFrequency = sf.getDouble();
this.aTracks.add(audioTrack);
continue;
}
if (type == 17L) {
SubtitlesTrack subsTrack = new SubtitlesTrack((int)id, this);
this.subsTracks.add(subsTrack);
}
}
MKVType[] path2 = { MKVType.Segment, MKVType.Cluster };
for (EbmlMaster aCluster : MKVType.<EbmlMaster>findList(this.t, EbmlMaster.class, path2)) {
MKVType[] path1 = { MKVType.Cluster, MKVType.Timecode };
long baseTimecode = ((EbmlUint)MKVType.findFirst(aCluster, path1)).getUint();
for (EbmlBase child : aCluster.children) {
if (MKVType.SimpleBlock.equals(child.type)) {
MkvBlock b = (MkvBlock)child;
b.absoluteTimecode = (long)b.timecode + baseTimecode;
putIntoRightBasket(b);
continue;
}
if (MKVType.BlockGroup.equals(child.type)) {
EbmlMaster group = (EbmlMaster)child;
for (EbmlBase grandChild : group.children) {
if (grandChild.type == MKVType.Block) {
MkvBlock b = (MkvBlock)grandChild;
b.absoluteTimecode = (long)b.timecode + baseTimecode;
putIntoRightBasket(b);
}
}
}
}
}
}
private void putIntoRightBasket(MkvBlock b) {
if (this.vTrack != null && b.trackNumber == (long)this.vTrack.trackNo) {
this.vTrack.blocks.add(b);
} else {
for (int j = 0; j < this.aTracks.size(); j++) {
AudioTrack audio = this.aTracks.get(j);
if (b.trackNumber == (long)audio.trackNo) {
audio.blocks.add(IndexedBlock.make(audio.framesCount, b));
audio.framesCount += b.frameSizes.length;
}
}
for (int i = 0; i < this.subsTracks.size(); i++) {
SubtitlesTrack subs = this.subsTracks.get(i);
if (b.trackNumber == (long)subs.trackNo) {
subs.blocks.add(IndexedBlock.make(subs.framesCount, b));
subs.framesCount += b.frameSizes.length;
}
}
}
}
public static class VideoTrack implements SeekableDemuxerTrack {
private ByteBuffer state;
public final int trackNo;
private int frameIdx = 0;
List<MkvBlock> blocks;
private MKVDemuxer demuxer;
private Codec codec;
private AvcCBox avcC;
public VideoTrack(MKVDemuxer demuxer, int trackNo, ByteBuffer state, Codec codec) {
this.blocks = new ArrayList<>();
this.demuxer = demuxer;
this.trackNo = trackNo;
this.codec = codec;
if (codec == Codec.H264) {
this.avcC = H264Utils.parseAVCCFromBuffer(state);
this.state = H264Utils.avcCToAnnexB(this.avcC);
} else {
this.state = state;
}
}
public Packet nextFrame() throws IOException {
if (this.frameIdx >= this.blocks.size())
return null;
MkvBlock b = this.blocks.get(this.frameIdx);
if (b == null)
throw new RuntimeException("Something somewhere went wrong.");
this.frameIdx++;
this.demuxer.channel.setPosition(b.dataOffset);
ByteBuffer data = ByteBuffer.allocate(b.dataLen);
this.demuxer.channel.read(data);
data.flip();
b.readFrames(data.duplicate());
long duration = 1L;
if (this.frameIdx < this.blocks.size())
duration = ((MkvBlock)this.blocks.get(this.frameIdx)).absoluteTimecode - b.absoluteTimecode;
ByteBuffer result = b.frames[0].duplicate();
if (this.codec == Codec.H264)
result = H264Utils.decodeMOVPacket(result, this.avcC);
return Packet.createPacket(result, b.absoluteTimecode, this.demuxer.timescale, duration, (long)(this.frameIdx - 1),
b._keyFrame ? Packet.FrameType.KEY : Packet.FrameType.INTER, TapeTimecode.ZERO_TAPE_TIMECODE);
}
public boolean gotoFrame(long i) {
if (i > Integer.MAX_VALUE)
return false;
if (i > (long)this.blocks.size())
return false;
this.frameIdx = (int)i;
return true;
}
public long getCurFrame() {
return (long)this.frameIdx;
}
public void seek(double second) {
throw new RuntimeException("Not implemented yet");
}
public int getFrameCount() {
return this.blocks.size();
}
public ByteBuffer getCodecState() {
return this.state;
}
public DemuxerTrackMeta getMeta() {
return new DemuxerTrackMeta(TrackType.VIDEO, this.codec, 0.0D, null, 0, this.state,
VideoCodecMeta.createSimpleVideoCodecMeta(new Size(this.demuxer.pictureWidth, this.demuxer.pictureHeight), ColorSpace.YUV420), null);
}
public boolean gotoSyncFrame(long i) {
throw new RuntimeException("Unsupported");
}
}
public static class IndexedBlock {
public int firstFrameNo;
public MkvBlock block;
public static IndexedBlock make(int no, MkvBlock b) {
IndexedBlock ib = new IndexedBlock();
ib.firstFrameNo = no;
ib.block = b;
return ib;
}
}
public static class SubtitlesTrack extends MkvTrack {
SubtitlesTrack(int trackNo, MKVDemuxer demuxer) {
super(trackNo, demuxer);
}
}
private static class MkvBlockData {
final MkvBlock block;
final ByteBuffer data;
final int count;
MkvBlockData(MkvBlock block, ByteBuffer data, int count) {
this.block = block;
this.data = data;
this.count = count;
}
}
public static class MkvTrack implements SeekableDemuxerTrack {
public final int trackNo;
List<MKVDemuxer.IndexedBlock> blocks;
int framesCount = 0;
private int frameIdx = 0;
private int blockIdx = 0;
private int frameInBlockIdx = 0;
private MKVDemuxer demuxer;
public MkvTrack(int trackNo, MKVDemuxer demuxer) {
this.blocks = new ArrayList<>();
this.trackNo = trackNo;
this.demuxer = demuxer;
}
public Packet nextFrame() throws IOException {
MKVDemuxer.MkvBlockData bd = nextBlock();
if (bd == null)
return null;
return Packet.createPacket(bd.data, bd.block.absoluteTimecode, this.demuxer.timescale, 1L, (long)(this.frameIdx - 1), Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
}
protected MKVDemuxer.MkvBlockData nextBlock() throws IOException {
if (this.frameIdx >= this.blocks.size() || this.blockIdx >= this.blocks.size())
return null;
MkvBlock b = ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).block;
if (b == null)
throw new RuntimeException("Something somewhere went wrong.");
if (b.frames == null || b.frames.length == 0) {
this.demuxer.channel.setPosition(b.dataOffset);
ByteBuffer byteBuffer = ByteBuffer.allocate(b.dataLen);
this.demuxer.channel.read(byteBuffer);
b.readFrames(byteBuffer);
}
ByteBuffer data = b.frames[this.frameInBlockIdx].duplicate();
this.frameInBlockIdx++;
this.frameIdx++;
if (this.frameInBlockIdx >= b.frames.length) {
this.blockIdx++;
this.frameInBlockIdx = 0;
}
return new MKVDemuxer.MkvBlockData(b, data, 1);
}
public boolean gotoFrame(long i) {
if (i > Integer.MAX_VALUE)
return false;
if (i > (long)this.framesCount)
return false;
int frameBlockIdx = findBlockIndex(i);
if (frameBlockIdx == -1)
return false;
this.frameIdx = (int)i;
this.blockIdx = frameBlockIdx;
this.frameInBlockIdx = (int)i - ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).firstFrameNo;
return true;
}
private int findBlockIndex(long i) {
for (int blockIndex = 0; blockIndex < this.blocks.size(); blockIndex++) {
if (i < (long)((MKVDemuxer.IndexedBlock)this.blocks.get(blockIndex)).block.frameSizes.length)
return blockIndex;
i -= (long)((MKVDemuxer.IndexedBlock)this.blocks.get(blockIndex)).block.frameSizes.length;
}
return -1;
}
public long getCurFrame() {
return (long)this.frameIdx;
}
public void seek(double second) {
throw new RuntimeException("Not implemented yet");
}
public Packet getFrames(int count) {
MKVDemuxer.MkvBlockData frameBlock = getFrameBlock(count);
if (frameBlock == null)
return null;
return Packet.createPacket(frameBlock.data, frameBlock.block.absoluteTimecode, this.demuxer.timescale, (long)frameBlock.count, 0L, Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
}
MKVDemuxer.MkvBlockData getFrameBlock(int count) {
if (count + this.frameIdx >= this.framesCount)
return null;
List<ByteBuffer> packetFrames = new ArrayList<>();
MkvBlock firstBlockInAPacket = ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).block;
while (count > 0) {
MkvBlock b = ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).block;
if (b.frames == null || b.frames.length == 0)
try {
this.demuxer.channel.setPosition(b.dataOffset);
ByteBuffer byteBuffer = ByteBuffer.allocate(b.dataLen);
this.demuxer.channel.read(byteBuffer);
b.readFrames(byteBuffer);
} catch (IOException ioe) {
throw new RuntimeException("while reading frames of a Block at offset 0x" +
Long.toHexString(b.dataOffset).toUpperCase() + ")", ioe);
}
packetFrames.add(b.frames[this.frameInBlockIdx].duplicate());
this.frameIdx++;
this.frameInBlockIdx++;
if (this.frameInBlockIdx >= b.frames.length) {
this.frameInBlockIdx = 0;
this.blockIdx++;
}
count--;
}
int size = 0;
for (ByteBuffer aFrame : packetFrames)
size += aFrame.limit();
ByteBuffer data = ByteBuffer.allocate(size);
for (ByteBuffer aFrame : packetFrames)
data.put(aFrame);
return new MKVDemuxer.MkvBlockData(firstBlockInAPacket, data, packetFrames.size());
}
public DemuxerTrackMeta getMeta() {
return null;
}
public boolean gotoSyncFrame(long frame) {
return gotoFrame(frame);
}
}
public static class AudioTrack extends MkvTrack {
public double samplingFrequency;
public AudioTrack(int trackNo, MKVDemuxer demuxer) {
super(trackNo, demuxer);
}
public Packet nextFrame() throws IOException {
MKVDemuxer.MkvBlockData b = nextBlock();
if (b == null)
return null;
return Packet.createPacket(b.data, b.block.absoluteTimecode, (int)Math.round(this.samplingFrequency), 1L, 0L, Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
}
public Packet getFrames(int count) {
MKVDemuxer.MkvBlockData frameBlock = getFrameBlock(count);
if (frameBlock == null)
return null;
return Packet.createPacket(frameBlock.data, frameBlock.block.absoluteTimecode, (int)Math.round(this.samplingFrequency), (long)frameBlock.count, 0L, Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
}
public DemuxerTrackMeta getMeta() {
return null;
}
public boolean gotoSyncFrame(long frame) {
return gotoFrame(frame);
}
}
public int getPictureWidth() {
return this.pictureWidth;
}
public int getPictureHeight() {
return this.pictureHeight;
}
public List<DemuxerTrack> getAudioTracks() {
return (List)this.aTracks;
}
public List<DemuxerTrack> getTracks() {
ArrayList<DemuxerTrack> tracks = new ArrayList<>(this.aTracks);
tracks.add(this.vTrack);
tracks.addAll(this.subsTracks);
return tracks;
}
public List<DemuxerTrack> getVideoTracks() {
ArrayList<DemuxerTrack> tracks = new ArrayList<>();
tracks.add(this.vTrack);
return tracks;
}
public List<DemuxerTrack> getSubtitleTracks() {
return (List)this.subsTracks;
}
public List<? extends EbmlBase> getTree() {
return this.t;
}
public void close() throws IOException {
this.channel.close();
}
}

View file

@ -0,0 +1,221 @@
package org.jcodec.containers.mkv.muxer;
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;
public class MKVMuxer implements Muxer {
private List<MKVMuxerTrack> tracks;
private MKVMuxerTrack audioTrack;
private MKVMuxerTrack videoTrack;
private EbmlMaster mkvInfo;
private EbmlMaster mkvTracks;
private EbmlMaster mkvCues;
private EbmlMaster mkvSeekHead;
private List<EbmlMaster> clusterList;
private SeekableByteChannel sink;
private static Map<Codec, String> 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 (this.videoTrack == null) {
this.videoTrack = new MKVMuxerTrack();
this.tracks.add(this.videoTrack);
this.videoTrack.codecId = codecId;
this.videoTrack.videoMeta = meta;
this.videoTrack.trackNo = this.tracks.size();
}
return this.videoTrack;
}
public void finish() throws IOException {
List<EbmlMaster> mkvFile = new ArrayList<>();
EbmlMaster ebmlHeader = defaultEbmlHeader();
mkvFile.add(ebmlHeader);
EbmlMaster segmentElem = MKVType.<EbmlMaster>createByType(MKVType.Segment);
this.mkvInfo = muxInfo();
this.mkvTracks = muxTracks();
this.mkvCues = MKVType.<EbmlMaster>createByType(MKVType.Cues);
this.mkvSeekHead = muxSeekHead();
muxCues();
segmentElem.add(this.mkvSeekHead);
segmentElem.add(this.mkvInfo);
segmentElem.add(this.mkvTracks);
segmentElem.add(this.mkvCues);
for (EbmlMaster aCluster : this.clusterList)
segmentElem.add(aCluster);
mkvFile.add(segmentElem);
for (EbmlMaster el : mkvFile)
el.mux(this.sink);
}
private EbmlMaster defaultEbmlHeader() {
EbmlMaster master = MKVType.<EbmlMaster>createByType(MKVType.EBML);
createLong(master, MKVType.EBMLVersion, 1L);
createLong(master, MKVType.EBMLReadVersion, 1L);
createLong(master, MKVType.EBMLMaxIDLength, 4L);
createLong(master, MKVType.EBMLMaxSizeLength, 8L);
createString(master, MKVType.DocType, "webm");
createLong(master, MKVType.DocTypeVersion, 2L);
createLong(master, MKVType.DocTypeReadVersion, 2L);
return master;
}
private EbmlMaster muxInfo() {
EbmlMaster master = MKVType.<EbmlMaster>createByType(MKVType.Info);
int frameDurationInNanoseconds = 40000000;
createLong(master, MKVType.TimecodeScale, (long)frameDurationInNanoseconds);
createString(master, MKVType.WritingApp, "JCodec");
createString(master, MKVType.MuxingApp, "JCodec");
List<MKVMuxerTrack> tracks2 = this.tracks;
long max = 0L;
for (MKVMuxerTrack track : tracks2) {
MkvBlock lastBlock = track.trackBlocks.get(track.trackBlocks.size() - 1);
if (lastBlock.absoluteTimecode > max)
max = lastBlock.absoluteTimecode;
}
createDouble(master, MKVType.Duration, (double)((max + 1L) * (long)frameDurationInNanoseconds) * 1.0D);
createDate(master, MKVType.DateUTC, new Date());
return master;
}
private EbmlMaster muxTracks() {
EbmlMaster master = MKVType.<EbmlMaster>createByType(MKVType.Tracks);
for (int i = 0; i < this.tracks.size(); i++) {
MKVMuxerTrack track = this.tracks.get(i);
EbmlMaster trackEntryElem = MKVType.<EbmlMaster>createByType(MKVType.TrackEntry);
createLong(trackEntryElem, MKVType.TrackNumber, (long)track.trackNo);
createLong(trackEntryElem, MKVType.TrackUID, (long)track.trackNo);
if (MKVMuxerTrack.MKVMuxerTrackType.VIDEO.equals(track.type)) {
createLong(trackEntryElem, MKVType.TrackType, 1L);
createString(trackEntryElem, MKVType.Name, "Track " + i + 1 + " Video");
createString(trackEntryElem, MKVType.CodecID, track.codecId);
EbmlMaster trackVideoElem = MKVType.<EbmlMaster>createByType(MKVType.Video);
createLong(trackVideoElem, MKVType.PixelWidth, (long)track.videoMeta.getSize().getWidth());
createLong(trackVideoElem, MKVType.PixelHeight, (long)track.videoMeta.getSize().getHeight());
trackEntryElem.add(trackVideoElem);
} else {
createLong(trackEntryElem, MKVType.TrackType, 2L);
createString(trackEntryElem, MKVType.Name, "Track " + i + 1 + " Audio");
createString(trackEntryElem, MKVType.CodecID, track.codecId);
}
master.add(trackEntryElem);
}
return master;
}
private void muxCues() {
CuesFactory cf = new CuesFactory(this.mkvSeekHead.size() + this.mkvInfo.size() + this.mkvTracks.size(), (long)this.videoTrack.trackNo);
for (MkvBlock aBlock : this.videoTrack.trackBlocks) {
EbmlMaster mkvCluster = singleBlockedCluster(aBlock);
this.clusterList.add(mkvCluster);
cf.add(CuesFactory.CuePointMock.make(mkvCluster));
}
EbmlMaster indexedCues = cf.createCues();
for (EbmlBase aCuePoint : indexedCues.children)
this.mkvCues.add(aCuePoint);
}
private EbmlMaster singleBlockedCluster(MkvBlock aBlock) {
EbmlMaster mkvCluster = MKVType.<EbmlMaster>createByType(MKVType.Cluster);
createLong(mkvCluster, MKVType.Timecode, aBlock.absoluteTimecode - (long)aBlock.timecode);
mkvCluster.add(aBlock);
return mkvCluster;
}
private EbmlMaster muxSeekHead() {
SeekHeadFactory shi = new SeekHeadFactory();
shi.add(this.mkvInfo);
shi.add(this.mkvTracks);
shi.add(this.mkvCues);
return shi.indexSeekHead();
}
public static void createLong(EbmlMaster parent, MKVType type, long value) {
EbmlUint se = MKVType.<EbmlUint>createByType(type);
se.setUint(value);
parent.add(se);
}
public static void createString(EbmlMaster parent, MKVType type, String value) {
EbmlString se = MKVType.<EbmlString>createByType(type);
se.setString(value);
parent.add(se);
}
public static void createDate(EbmlMaster parent, MKVType type, Date value) {
EbmlDate se = MKVType.<EbmlDate>createByType(type);
se.setDate(value);
parent.add(se);
}
public static void createBuffer(EbmlMaster parent, MKVType type, ByteBuffer value) {
EbmlBin se = MKVType.<EbmlBin>createByType(type);
se.setBuf(value);
parent.add(se);
}
public static void createDouble(EbmlMaster parent, MKVType type, double value) {
try {
EbmlFloat se = MKVType.<EbmlFloat>createByType(type);
se.setDouble(value);
parent.add(se);
} catch (ClassCastException cce) {
throw new RuntimeException("Element of type " + String.valueOf(type) + " can't be cast to EbmlFloat", cce);
}
}
public MuxerTrack addVideoTrack(Codec codec, VideoCodecMeta meta) {
return createVideoTrack(meta, codec2mkv.get(codec));
}
public MuxerTrack addAudioTrack(Codec codec, AudioCodecMeta meta) {
this.audioTrack = new MKVMuxerTrack();
this.tracks.add(this.audioTrack);
this.audioTrack.codecId = codec2mkv.get(codec);
this.audioTrack.trackNo = this.tracks.size();
return this.audioTrack;
}
}

View file

@ -0,0 +1,51 @@
package org.jcodec.containers.mkv.muxer;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.common.MuxerTrack;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.model.Packet;
import org.jcodec.containers.mkv.boxes.MkvBlock;
public class MKVMuxerTrack implements MuxerTrack {
public MKVMuxerTrackType type;
public VideoCodecMeta videoMeta;
public String codecId;
public int trackNo;
private int frameDuration;
List<MkvBlock> trackBlocks;
static final int DEFAULT_TIMESCALE = 1000000000;
static final int NANOSECONDS_IN_A_MILISECOND = 1000000;
static final int MULTIPLIER = 1000;
public enum MKVMuxerTrackType {
VIDEO;
}
public MKVMuxerTrack() {
this.trackBlocks = new ArrayList<>();
this.type = MKVMuxerTrackType.VIDEO;
}
public int getTimescale() {
return 1000000;
}
public void addFrame(Packet outPacket) {
MkvBlock frame = MkvBlock.keyFrame((long)this.trackNo, 0, outPacket.getData());
frame.absoluteTimecode = outPacket.getPts() - 1L;
this.trackBlocks.add(frame);
}
public long getTrackNo() {
return (long)this.trackNo;
}
}

View file

@ -0,0 +1,60 @@
package org.jcodec.containers.mkv.util;
public class EbmlUtil {
public static byte[] ebmlEncodeLen(long value, int length) {
byte[] b = new byte[length];
for (int idx = 0; idx < length; idx++)
b[length - idx - 1] = (byte)(int)(value >>> 8 * idx & 0xFFL);
b[0] = (byte)(b[0] | 128 >>> length - 1);
return b;
}
public static byte[] ebmlEncode(long value) {
return ebmlEncodeLen(value, ebmlLength(value));
}
public static final byte[] lengthOptions = new byte[] { 0, Byte.MIN_VALUE, 64, 32, 16, 8, 4, 2, 1 };
public static final long one = 127L;
public static final long two = 16256L;
public static final long three = 2080768L;
public static final long four = 266338304L;
public static final long five = 34091302912L;
public static final long six = 4363686772736L;
public static final long seven = 558551906910208L;
public static final long eight = 71494644084506624L;
public static int computeLength(byte b) {
if (b == 0)
throw new RuntimeException("Invalid head element for ebml sequence");
int i = 1;
while ((b & lengthOptions[i]) == 0)
i++;
return i;
}
public static final long[] ebmlLengthMasks = new long[] { 0L, 127L, 16256L, 2080768L, 266338304L, 34091302912L, 4363686772736L, 558551906910208L, 71494644084506624L };
public static int ebmlLength(long v) {
if (v == 0L)
return 1;
int length = 8;
while (length > 0 && (v & ebmlLengthMasks[length]) == 0L)
length--;
return length;
}
public static String toHexString(byte[] a) {
StringBuilder sb = new StringBuilder();
for (byte b : a)
sb.append(String.format("0x%02x ", Integer.valueOf(b & 0xFF)));
return sb.toString();
}
}