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

org.jcodec.containers.mkv.Reader Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
/**
 * JEBML - Java library to read/write EBML/Matroska elements.
 * Copyright (C) 2004 Jory Stone 
 * Based on Javatroska (C) 2002 John Cannon 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.jcodec.containers.mkv;

import static java.lang.Integer.toHexString;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import org.jcodec.containers.mkv.ebml.Element;

/**
 * EBMLReader.java
 *
 * Created on November 18, 2002, 4:03 PM
 *
 * @version 1.0
 */

/**
 * 

JEBML Intro

*
*

* The following are the basic steps of how reading in JEBML works. *

*
    *
  • 1. The EbmlReader class reads the element header (id+size) and looks it * up in the supplied DocType class. *
  • 2. The correct element type (Binary, UInteger, String, etc) is created * using the DocType data, BinaryElement is default element type for unknown * elements. *
  • 3. The MatroskaDocType has the ids of all the elements staticly declared. *
    * So to easily find out what an element is, you can use some code like the * following code *

    * * Element level1;
    * // ... fill level1
    * if (level1.equals(MatroskaDocType.SegmentInfo_Id)) {
    * // Your Code Here
    * }
    *
    *

    *
  • 4. To get the actual data for an Element you call the readData method, if * you just want to skip it use skipData(). *
  • 5. MasterElements are special, they have the readNextChild() method which * returns the next child element, returning null when all the children have * been read (it keeps track of the current inputstream position). *
  • The usage method for JEBML is very close to libebml/libmatroska. *
*
* * Reads EBML elements from a DataSource and looks them up in the * provided DocType. * * @author (c) 2002 John Cannon * @author (c) 2004 Jory Stone */ public class Reader { protected FileChannel ds; /** * Creates a new EBMLReader reading from the DataSource * source. The DocType doc is used to validate the * document. * * @param source * DataSource to read from * @param doc * DocType to use to validate the docment */ public Reader(FileChannel source) { this.ds = source; } public long getPos() throws IOException { return 0; } public long getAvailable() throws IOException { return 0; } public static String bytesToHex(byte[] bts) { StringBuilder sb = new StringBuilder(); if (bts == null) return ""; for (byte b : bts) { sb.append(" 0x").append(toHexString(b & 0xFF).toUpperCase()); } return sb.toString(); } public static String printAsHex(byte[] a){ StringBuilder sb = new StringBuilder(); for(byte b: a) sb.append(String.format("0x%02x ", b&0xff)); return sb.toString(); } public Element readNextElement() throws IOException { // Read the type. long offset = ds.position(); byte[] typeId = getRawEbmlBytes(ds); if (typeId == null) // Failed to read type id // one byte was read from source (this should be reflected in // 'usedSize' of a master element) return null; // Read the size. byte[] ebmlCodedElementSize = getEbmlBytes(ds); long size = bytesToLong(ebmlCodedElementSize); // Zero sized element is valid if (size == 0) ; // according to MatroskaDocType.createElement return value is never // 'null' Element elem = Type.createElementById(typeId); elem.offset = offset; // Set it's size elem.size = size; // if (ebmlCodedElementSize == null) { // elem.setHeaderSize(1); // if data array is null, only one byte was read; // } else { // elem.setHeaderSize(ebmlCodedElementSize.length); // } return elem; } /** * Reads an (Unsigned) EBML code from the DataSource and encodes it into a * long. This size should be cast into an int for actual use as Java only * allows upto 32-bit file I/O operations. * * @return ebml size * @throws IOException */ static public long getEbmlVInt(FileChannel source) throws IOException { // Begin loop with byte set to newly read byte. ByteBuffer bufferForFirstByte = ByteBuffer.allocate(1); source.read(bufferForFirstByte); bufferForFirstByte.flip(); byte firstByte = bufferForFirstByte.get(); int numBytes = getEbmlSizeByFirstByte(firstByte); if (numBytes == 0) // Invalid size return 0; if (numBytes == 1) return firstByte & ((0xFF >>> (numBytes))); // Setup space to store the bits ByteBuffer bb = ByteBuffer.allocate(numBytes); // Clear the 1 at the front of this byte, all the way to the beginning // of the size bb.put((byte) (firstByte & ((0xFF >>> (numBytes))))); // Read the rest of the size. source.read(bb); // Put this into a long return bytesToLong(bb.array()); } /** * Reads an Signed EBML code from the DataSource and encodes it into a long. * This size should be cast into an int for actual use as Java only allows * upto 32-bit file I/O operations. * * @return ebml size * @throws IOException */ static public long getSignedEbmlVInt(FileChannel is) throws IOException { // Begin loop with byte set to newly read byte. byte[] vInt = getEbmlBytes(is); long uInt = bytesToLong(vInt); return uInt - signedComplement[vInt.length]; } public static long bytesToLong(ByteBuffer data) { if (data == null) return 0; long value = 0; while(data.position() < data.limit()) value = (value << 8) | (data.get() & 0xff); return value; } public static long bytesToLong(byte[] data) { if (data == null) return 0; long value = 0; for (int i = 0; i < data.length; i++) value = (value << 8) | (data[i] & 0xff); return value; } static public int getEbmlSizeByFirstByte(byte b){ int numBytes = 0; // Begin by counting the bits unset before the first '1'. long mask = 0x0080; for (int i = 0; i < 8; i++) { // Start at left, shift to right. if ((b & mask) == mask) { // One found // Set number of bytes in size = // i+1 ( we must count the 1 too) numBytes = i + 1; // exit loop by pushing i out of the limit i = 8; } mask >>>= 1; } return numBytes; } /** * Reads an (Unsigned) EBML code from the DataSource and encodes it into a * long. This size should be cast into an int for actual use as Java only * allows upto 32-bit file I/O operations. * * @return ebml size */ static public long getEbmlVInt(ByteBuffer bb) { // Begin loop with byte set to newly read byte. byte firstByte = bb.get(); int numBytes = getEbmlSizeByFirstByte(firstByte); if (numBytes == 0) // Invalid size return 0; if (numBytes == 1) return firstByte & ((0xFF >>> (numBytes))); // Setup space to store the bits byte[] data = new byte[numBytes]; // Clear the 1 at the front of this byte, all the way to the beginning // of the size data[0] = (byte) (firstByte & ((0xFF >>> (numBytes)))); // Read the rest of the size. bb.get(data, 1, data.length - 1); long longValue = bytesToLong(data); // Put this into a long return longValue; } /** * Reads an (Unsigned) EBML code from the DataSource and encodes it into a * long. This size should be cast into an int for actual use as Java only * allows upto 32-bit file I/O operations. * * @return ebml size */ static public long getEbmlVInt(ByteBuffer source, int offset) { // Begin loop with byte set to newly read byte. byte firstByte = source.get(offset); int numBytes = getEbmlSizeByFirstByte(firstByte); if (numBytes == 0) // Invalid size return 0; if (numBytes == 1) return firstByte & ((0xFF >>> (numBytes))); // Setup space to store the bits byte[] data = new byte[numBytes]; // Clear the 1 at the front of this byte, all the way to the beginning // of the size data[0] = (byte) (firstByte & ((0xFF >>> (numBytes)))); // Read the rest of the size. System.arraycopy(source.array(), offset + 1, data, 1, numBytes - 1); long longValue = bytesToLong(data); // Put this into a long return longValue; } public static final long[] signedComplement = {0, 0x3F, 0x1FFF, 0x0FFFFF, 0x07FFFFFF, 0x03FFFFFFFFL, 0x01FFFFFFFFFFL, 0x00FFFFFFFFFFFFL, 0x007FFFFFFFFFFFFFL}; public static String asHex(int i){ return "0x"+printAsHex(new byte[]{(byte)i}); } /** * Reads an Signed EBML code from the DataSource and encodes it into a long. * This size should be cast into an int for actual use as Java only allows * upto 32-bit file I/O operations. * * @return ebml size */ /** * Reads an Signed EBML code from the DataSource and encodes it into a long. * This size should be cast into an int for actual use as Java only allows * upto 32-bit file I/O operations. * * @return ebml size */ static public long getSignedEbmlVInt(ByteBuffer source) { // Begin loop with byte set to newly read byte. byte[] bytes = getVIntEbmlBytes(source); if (bytes == null) throw new RuntimeException("Can't convert byte 0x"+Integer.toHexString(source.get(source.position()-1) & 0xFF).toUpperCase()+" to first ebml byte."); return bytesToLong(bytes) - signedComplement[bytes.length]; } // /** // * Reads an Signed EBML code from the DataSource and encodes it into a long. // * This size should be cast into an int for actual use as Java only allows // * upto 32-bit file I/O operations. // * // * @return ebml size // * @throws IOException // */ // static public long readSignedEBMLCode(FileChannel source) throws IOException { // // // Begin loop with byte set to newly read byte. // ByteBuffer bufferForFirstByte = ByteBuffer.allocate(1); // source.read(bufferForFirstByte); // bufferForFirstByte.flip(); // byte firstByte = bufferForFirstByte.get(); // int numBytes = 0; // // // Begin by counting the bits unset before the first '1'. // long mask = 0x0080; // for (int i = 0; i < 8; i++) { // // Start at left, shift to right. // if ((firstByte & mask) == mask) { // One found // // Set number of bytes in size = i+1 ( we must count the 1 too) // numBytes = i + 1; // // exit loop by pushing i out of the limit // i = 8; // } // mask >>>= 1; // } // if (numBytes == 0) // // Invalid size // return 0; // // // Setup space to store the bits // byte[] data = new byte[numBytes]; // // // Clear the 1 at the front of this byte, all the way to the beginning // // of the size // data[0] = (byte) (firstByte & ((0xFF >>> (numBytes)))); // // if (numBytes > 1) { // // Read the rest of the size. // source.read(data, 1, numBytes - 1); // } // // // Put this into a long // long size = 0; // long n = 0; // for (int i = 0; i < numBytes; i++) { // n = ((long) data[numBytes - 1 - i] << 56) >>> 56; // size = size | (n << (8 * i)); // } // // // Sign it ;) // if (numBytes == 1) { // size -= 63; // // } else if (numBytes == 2) { // size -= 8191; // // } else if (numBytes == 3) { // size -= 1048575; // // } else if (numBytes == 4) { // size -= 134217727; // } // // return size; // } static public byte[] getVIntEbmlBytes(ByteBuffer source) { // Begin loop with byte set to newly read byte. byte firstByte = source.get(); int numBytes = getEbmlSizeByFirstByte(firstByte); if (numBytes == 0) // Invalid size return null; // Setup space to store the bits ByteBuffer data = ByteBuffer.allocate(numBytes); // Clear the 1 at the front of this byte, all the way to the beginning of the size data.put((byte) (firstByte & ((0xFF >>> (numBytes))))); // Read the rest of the size. int remaining = numBytes-1; while(remaining > 0){ data.put(source.get()); remaining--; } return data.array(); } static public byte[] getEbmlBytes(FileChannel source) throws IOException { // Begin loop with byte set to newly read byte. ByteBuffer bufferForFirstByte = ByteBuffer.allocate(1); source.read(bufferForFirstByte); bufferForFirstByte.flip(); byte firstByte = (byte) bufferForFirstByte.get(); int numBytes = getEbmlSizeByFirstByte(firstByte); if (numBytes == 0) // Invalid size return null; if (numBytes == 1) return new byte[]{(byte) (firstByte & ((0xFF >>> (numBytes))))}; // Setup space to store the bits ByteBuffer data = ByteBuffer.allocate(numBytes); // Clear the 1 at the front of this byte, all the way to the beginning // of the size data.put((byte) (firstByte & ((0xFF >>> (numBytes))))); // Read the rest of the size. source.read(data); return data.array(); } /** * Reads an EBML id from the DataSource. EBML ids have length encoded inside * of them For instance, all one-byte ids have first byte set to '1', like * 0xA3, 0xE7, whereas the two-byte ids have first byte set to '0' and * second byte set to '1', thus: 0x4286 (ebml version) or 0x42F7 (ebml read * version) * * @return byte array filled with the ebml size, (size bits included) * @throws IOException */ static public byte[] getRawEbmlBytes(FileChannel source) throws IOException { // Begin loop with byte set to newly read byte. if (source.position() == source.size()) return null; ByteBuffer bufferForFirstByte = ByteBuffer.allocate(1); source.read(bufferForFirstByte); bufferForFirstByte.flip(); byte firstByte = bufferForFirstByte.get(); int numBytes = getEbmlSizeByFirstByte(firstByte); if (numBytes == 0) // Invalid element return null; if (numBytes == 1) return bufferForFirstByte.array(); // Setup space to store the bits ByteBuffer data = ByteBuffer.allocate(numBytes); data.put(firstByte); // Clear the 1 at the front of this byte, all the way to the beginning // of the size // Read the rest of the size. source.read(data); return data.array(); } public Element readNextFirstLevelElement() throws IOException { if (ds.position() == ds.size()) return null; long offset = ds.position(); byte[] typeId = getRawEbmlBytes(ds); while (typeId == null || !Type.isFirstLevelHeader(typeId)){ offset++; ds.position(offset); typeId = getRawEbmlBytes(ds); } byte[] data = getEbmlBytes(ds); long elementSize = bytesToLong(data); Element elem = Type.createElementById(typeId); elem.size = elementSize; elem.offset = offset; return elem; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy