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

com.googlecode.mp4parser.AbstractBox Maven / Gradle / Ivy

Go to download

A generic parser and writer for all ISO 14496 based files (MP4, Quicktime, DCF, PDCF, ...)

There is a newer version: 1.1.22
Show newest version
/*  
 * Copyright 2012 Sebastian Annies, Hamburg
 *
 * Licensed under the Apache License, Version 2.0 (the License); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an AS IS BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

package com.googlecode.mp4parser;

import com.coremedia.iso.BoxParser;
import com.coremedia.iso.Hex;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeWriter;
import com.coremedia.iso.boxes.Box;
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.UserBox;
import com.googlecode.mp4parser.annotations.DoNotParseDetail;
import com.googlecode.mp4parser.util.Logger;
import com.googlecode.mp4parser.util.Path;

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

import static com.googlecode.mp4parser.util.CastUtils.l2i;

/**
 * A basic on-demand parsing box. Requires the implementation of three methods to become a fully working box:
 * 
    *
  1. {@link #_parseDetails(java.nio.ByteBuffer)}
  2. *
  3. {@link #getContent(java.nio.ByteBuffer)}
  4. *
  5. {@link #getContentSize()}
  6. *
* additionally this new box has to be put into the isoparser-default.properties file so that * it is accessible by the PropertyBoxParserImpl */ public abstract class AbstractBox implements Box { private static Logger LOG = Logger.getLogger(AbstractBox.class); protected String type; private byte[] userType; private Container parent; boolean isParsed; boolean isRead; private ByteBuffer content; long contentStartPosition; long offset; long memMapSize = -1; DataSource dataSource; private synchronized void readContent() { if (!isRead) { try { LOG.logDebug("mem mapping " + this.getType()); content = dataSource.map( contentStartPosition, memMapSize); } catch (IOException e) { throw new RuntimeException(e); } isRead = true; } } private ByteBuffer deadBytes = null; public long getOffset() { return offset; } protected AbstractBox(String type) { this.type = type; isRead = true; isParsed = true; } protected AbstractBox(String type, byte[] userType) { this.type = type; this.userType = userType; isRead = true; isParsed = true; } /** * Get the box's content size without its header. This must be the exact number of bytes * that getContent(ByteBuffer) writes. * * @return Gets the box's content size in bytes * @see #getContent(java.nio.ByteBuffer) */ protected abstract long getContentSize(); /** * Write the box's content into the given ByteBuffer. This must include flags * and version in case of a full box. byteBuffer has been initialized with * getSize() bytes. * * @param byteBuffer the sink for the box's content */ protected abstract void getContent(ByteBuffer byteBuffer); /** * Parse the box's fields and child boxes if any. * * @param content the box's raw content beginning after the 4-cc field. */ protected abstract void _parseDetails(ByteBuffer content); /** * {@inheritDoc} */ @DoNotParseDetail public void parse(DataSource dataSource, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException { this.contentStartPosition = dataSource.position(); this.offset = contentStartPosition - header.remaining(); this.memMapSize = contentSize; this.dataSource = dataSource; dataSource.position(dataSource.position() + contentSize); isRead = false; isParsed = false; } public void getBox(WritableByteChannel os) throws IOException { if (isRead) { if (isParsed) { ByteBuffer bb = ByteBuffer.allocate(l2i(getSize())); getHeader(bb); getContent(bb); if (deadBytes != null) { deadBytes.rewind(); while (deadBytes.remaining() > 0) { bb.put(deadBytes); } } os.write((ByteBuffer) bb.rewind()); } else { ByteBuffer header = ByteBuffer.allocate((isSmallBox() ? 8 : 16) + (UserBox.TYPE.equals(getType()) ? 16 : 0)); getHeader(header); os.write((ByteBuffer) header.rewind()); os.write((ByteBuffer) content.position(0)); } } else { ByteBuffer header = ByteBuffer.allocate((isSmallBox() ? 8 : 16) + (UserBox.TYPE.equals(getType()) ? 16 : 0)); getHeader(header); os.write((ByteBuffer) header.rewind()); dataSource.transferTo(contentStartPosition, memMapSize, os); } } /** * Parses the raw content of the box. It surrounds the actual parsing * which is done */ public synchronized final void parseDetails() { readContent(); LOG.logDebug("parsing details of " + this.getType()); if (content != null) { ByteBuffer content = this.content; isParsed = true; content.rewind(); _parseDetails(content); if (content.remaining() > 0) { deadBytes = content.slice(); } this.content = null; assert verify(content); } } /** * Sets the 'dead' bytes. These bytes are left if the content of the box * has been parsed but not all bytes have been used up. * * @param newDeadBytes the unused bytes with no meaning but required for bytewise reconstruction */ protected void setDeadBytes(ByteBuffer newDeadBytes) { deadBytes = newDeadBytes; } /** * Gets the full size of the box including header and content. * * @return the box's size */ public long getSize() { long size = (isRead ? (isParsed ? getContentSize() : (content != null ? content.limit() : 0)) : memMapSize); size += (8 + // size|type (size >= ((1L << 32) - 8) ? 8 : 0) + // 32bit - 8 byte size and type (UserBox.TYPE.equals(getType()) ? 16 : 0)); size += (deadBytes == null ? 0 : deadBytes.limit()); return size; } @DoNotParseDetail public String getType() { return type; } @DoNotParseDetail public byte[] getUserType() { return userType; } @DoNotParseDetail public Container getParent() { return parent; } @DoNotParseDetail public void setParent(Container parent) { this.parent = parent; } /** * Check if details are parsed. * * @return true whenever the content ByteBuffer is not null */ public boolean isParsed() { return isParsed; } /** * Verifies that a box can be reconstructed byte-exact after parsing. * * @param content the raw content of the box * @return true if raw content exactly matches the reconstructed content */ private boolean verify(ByteBuffer content) { ByteBuffer bb = ByteBuffer.allocate(l2i(getContentSize() + (deadBytes != null ? deadBytes.limit() : 0))); getContent(bb); if (deadBytes != null) { deadBytes.rewind(); while (deadBytes.remaining() > 0) { bb.put(deadBytes); } } content.rewind(); bb.rewind(); if (content.remaining() != bb.remaining()) { System.err.print(this.getType() + ": remaining differs " + content.remaining() + " vs. " + bb.remaining()); LOG.logError(this.getType() + ": remaining differs " + content.remaining() + " vs. " + bb.remaining()); return false; } int p = content.position(); for (int i = content.limit() - 1, j = bb.limit() - 1; i >= p; i--, j--) { byte v1 = content.get(i); byte v2 = bb.get(j); if (v1 != v2) { LOG.logError(String.format("%s: buffers differ at %d: %2X/%2X", this.getType(), i, v1, v2)); byte[] b1 = new byte[content.remaining()]; byte[] b2 = new byte[bb.remaining()]; content.get(b1); bb.get(b2); System.err.println("original : " + Hex.encodeHex(b1, 4)); System.err.println("reconstructed : " + Hex.encodeHex(b2, 4)); return false; } } return true; } private boolean isSmallBox() { int baseSize = 8; if (UserBox.TYPE.equals(getType())) { baseSize += 16; } if (isRead) { if (isParsed) { return (getContentSize() + (deadBytes != null ? deadBytes.limit() : 0) + baseSize) < (1L << 32); } else { return content.limit() + baseSize < (1L << 32); } } else { return memMapSize + baseSize < (1L << 32); } } private void getHeader(ByteBuffer byteBuffer) { if (isSmallBox()) { IsoTypeWriter.writeUInt32(byteBuffer, this.getSize()); byteBuffer.put(IsoFile.fourCCtoBytes(getType())); } else { IsoTypeWriter.writeUInt32(byteBuffer, 1); byteBuffer.put(IsoFile.fourCCtoBytes(getType())); IsoTypeWriter.writeUInt64(byteBuffer, getSize()); } if (UserBox.TYPE.equals(getType())) { byteBuffer.put(getUserType()); } } @DoNotParseDetail public String getPath() { return Path.createPath(this); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy