package org.jcodec.containers.mkv; import static java.lang.Long.toHexString; import static org.jcodec.containers.mkv.MKVType.Attachments; import static org.jcodec.containers.mkv.MKVType.Chapters; import static org.jcodec.containers.mkv.MKVType.Cluster; import static org.jcodec.containers.mkv.MKVType.Cues; import static org.jcodec.containers.mkv.MKVType.Info; import static org.jcodec.containers.mkv.MKVType.SeekHead; import static org.jcodec.containers.mkv.MKVType.Tags; import static org.jcodec.containers.mkv.MKVType.Tracks; import static org.jcodec.containers.mkv.MKVType.createById; import static org.jcodec.containers.mkv.util.EbmlUtil.toHexString; 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; import java.io.IOException; import java.lang.System; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed under FreeBSD License * * EBML IO implementation * * @author The JCodec project * */ public class MKVParser { private SeekableByteChannel channel; private LinkedList<EbmlMaster> trace; public MKVParser(SeekableByteChannel channel) { this.channel = channel; this.trace = new LinkedList<EbmlMaster>(); } public List<EbmlMaster> parse() throws IOException { List<EbmlMaster> tree = new ArrayList<EbmlMaster>(); 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(trace.peekFirst(), e)) closeElem(trace.removeFirst(), tree); openElem(e); if (e instanceof EbmlMaster) { trace.push((EbmlMaster) e); } else if (e instanceof EbmlBin) { EbmlBin bin = (EbmlBin) e; EbmlMaster traceTop = trace.peekFirst(); if ((traceTop.dataOffset + traceTop.dataLen) < (e.dataOffset + e.dataLen)) { channel.setPosition((traceTop.dataOffset + traceTop.dataLen)); } else try { bin.readChannel(channel); } catch (OutOfMemoryError oome) { throw new RuntimeException(e.type + " 0x" + toHexString(bin.id) + " size: " + toHexString(bin.dataLen) + " offset: 0x" + toHexString(e.offset), oome); } trace.peekFirst().add(e); } else if (e instanceof EbmlVoid) { ((EbmlVoid) e).skip(channel); } else { throw new RuntimeException("Currently there are no elements that are neither Master nor Binary, should never actually get here"); } } while (trace.peekFirst() != null) closeElem(trace.removeFirst(), tree); return tree; } private boolean possibleChild(EbmlMaster parent, EbmlBase child) { if (parent != null && Cluster.equals(parent.type) && child != null && !Cluster.equals(child.type) && !Info.equals(child.type) && !SeekHead.equals(child.type) && !Tracks.equals(child.type) && !Cues.equals(child.type) && !Attachments.equals(child.type) && !Tags.equals(child.type) && !Chapters.equals(child.type)) return true; return MKVType.possibleChild(parent, child); } private void openElem(EbmlBase e) { /* * Whatever logging you would like to have. Here's just one example */ // System.out.println(e.type.name() + (e instanceof EbmlMaster ? " master " : "") + " id: " + printAsHex(e.id) + " off: 0x" + toHexString(e.offset).toUpperCase() + " data off: 0x" + // toHexString(e.dataOffset).toUpperCase() + " len: 0x" + toHexString(e.dataLen).toUpperCase()); } private void closeElem(EbmlMaster e, List<EbmlMaster> tree) { if (trace.peekFirst() == null) { tree.add(e); } else { trace.peekFirst().add(e); } } private EbmlBase nextElement() throws IOException { long offset = channel.position(); if (offset >= channel.size()) return null; byte[] typeId = MKVParser.readEbmlId(channel); while ((typeId == null && !isKnownType(typeId)) && offset < channel.size()) { offset++; channel.setPosition(offset); typeId = MKVParser.readEbmlId(channel); } long dataLen = MKVParser.readEbmlInt(channel); EbmlBase elem = createById(typeId, offset); elem.offset = offset; elem.typeSizeLength = (int) (channel.position() - offset); elem.dataOffset = channel.position(); elem.dataLen = (int) dataLen; return elem; } public boolean isKnownType(byte[] b) { if (!trace.isEmpty() && Cluster.equals(trace.peekFirst().type)) return true; return MKVType.isSpecifiedHeader(b); } /** * Reads an EBML id from the channel. EBML ids have length encoded inside of them For instance, all one-byte ids have first byte set to '1', like 0xA3 or 0xE7, whereas the two-byte ids have first * byte set to '0' and second byte set to '1', thus: 0x42 0x86 or 0x42 0xF7 * * @return byte array filled with the ebml id * @throws IOException */ static public 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(); } static public long readEbmlInt(SeekableByteChannel source) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(8); buffer.limit(1); source.read(buffer); buffer.flip(); // read the first byte byte firstByte = (byte) buffer.get(); int length = EbmlUtil.computeLength(firstByte); if (length == 0) throw new RuntimeException("Invalid ebml integer size."); // read the reset buffer.limit(length); source.read(buffer); buffer.position(1); // use the first byte long value = firstByte & (0xFF >>> length); length--; // use the reset while(length > 0){ value = (value << 8) | (buffer.get() & 0xff); length--; } return value; } }