/* * Copyright 2008 CoreMedia AG, 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.coremedia.iso.boxes; import com.coremedia.iso.BoxParser; import com.coremedia.iso.IsoBufferWrapper; import com.coremedia.iso.IsoFile; import com.coremedia.iso.IsoOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.UUID; /** * A basic ISO box. No full box. */ public abstract class AbstractBox implements Box { public long offset; private List<WriteListener> writeListeners = null; protected boolean parsed; /** * Adds a Listener that will be called right before writing the box. * * @param writeListener the new Listener. */ public void addWriteListener(WriteListener writeListener) { if (writeListeners == null) { writeListeners = new LinkedList<WriteListener>(); } writeListeners.add(writeListener); } public long getSize() { return getContentSize() + getHeaderSize() + (deadBytes == null ? 0 : deadBytes.size()); } protected long getHeaderSize() { return 4 + // size 4 + // type (getContentSize() >= 4294967296L ? 8 : 0) + (Arrays.equals(getType(), IsoFile.fourCCtoBytes(UserBox.TYPE)) ? 16 : 0); } /** * Gets the box's content size without header size. * * @return Gets the box's content size in bytes */ protected abstract long getContentSize(); private byte[] type; private byte[] userType; private ContainerBox parent; protected AbstractBox(String type) { this.type = IsoFile.fourCCtoBytes(type); } protected AbstractBox(byte[] type) { this.type = type; } public byte[] getType() { return type; } public byte[] getUserType() { return userType; } public void setUserType(byte[] userType) { this.userType = userType; } public ContainerBox getParent() { return parent; } public boolean isParsed() { return parsed; } public void setParsed(boolean parsed) { this.parsed = parsed; } public long getOffset() { return offset; } public void setParent(ContainerBox parent) { this.parent = parent; } public IsoFile getIsoFile() { return parent.getIsoFile(); } /** * Pareses the given IsoBufferWrapper and returns the remaining bytes. * * @param in the (part of the) iso file to parse * @param size expected size of the box * @param boxParser creates inner boxes * @param lastMovieFragmentBox latest of previously found moof boxes * @throws IOException in case of an I/O error. */ public abstract void parse(IsoBufferWrapper in, long size, BoxParser boxParser, Box lastMovieFragmentBox) throws IOException; protected IsoBufferWrapper deadBytes = null; public IsoBufferWrapper getDeadBytes() { return deadBytes; } public void setDeadBytes(IsoBufferWrapper newDeadBytes) { deadBytes = newDeadBytes; } public byte[] getHeader() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); IsoOutputStream ios = new IsoOutputStream(baos); if (isSmallBox()) { ios.writeUInt32((int) this.getContentSize() + 8); ios.write(getType()); } else { ios.writeUInt32(1); ios.write(getType()); ios.writeUInt64(getContentSize() + 16); } if (Arrays.equals(getType(), IsoFile.fourCCtoBytes(UserBox.TYPE))) { ios.write(userType); } assert baos.size() == getHeaderSize() : "written header size differs from calculated size: " + baos.size() + " vs. " + getHeaderSize(); return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } protected boolean isSmallBox() { return (getContentSize() + 8) < 4294967296L; } public void getBox(IsoOutputStream os) throws IOException { long sp = os.getStreamPosition(); if (writeListeners != null) { for (WriteListener writeListener : writeListeners) { writeListener.beforeWrite(sp); } } os.write(getHeader()); getContent(os); if (deadBytes != null) { deadBytes.position(0); while (deadBytes.remaining() > 0) { os.write(deadBytes.readByte()); } } long writtenBytes = os.getStreamPosition() - sp; String uuid; if (getUserType() != null && getUserType().length == 16) { ByteBuffer b = ByteBuffer.wrap(getUserType()); b.order(ByteOrder.BIG_ENDIAN); uuid = new UUID(b.getLong(), b.getLong()).toString(); } else { uuid = "--"; } assert writtenBytes == getSize() : " getHeader + getContent + getDeadBytes (" + writtenBytes + ") of " + IsoFile.bytesToFourCC(getType()) + " userType: " + uuid + " doesn't match getSize (" + getSize() + ")"; } /** * Writes the box's content into the given <code>IsoOutputStream</code>. This MUST NOT include * any header bytes. * * @param os the box's content-sink. * @throws IOException in case of an exception in the underlying <code>OutputStream</code>. */ protected abstract void getContent(IsoOutputStream os) throws IOException; public static int utf8StringLengthInBytes(String utf8) { try { if (utf8 != null) { return utf8.getBytes("UTF-8").length; } else { return 0; } } catch (UnsupportedEncodingException e) { throw new RuntimeException(); } } public long calculateOffset() { long offsetFromParentBoxStart = parent.getNumOfBytesToFirstChild(); for (Box box : parent.getBoxes()) { if (box.equals(this)) { return parent.calculateOffset() + offsetFromParentBoxStart; } offsetFromParentBoxStart += box.getSize(); } throw new InternalError("this box is not in the list of its parent's children"); } }