diff options
Diffstat (limited to 'isoparser/src/main/java/com/coremedia/iso/boxes/mdat/MediaDataBox.java')
-rw-r--r-- | isoparser/src/main/java/com/coremedia/iso/boxes/mdat/MediaDataBox.java | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/isoparser/src/main/java/com/coremedia/iso/boxes/mdat/MediaDataBox.java b/isoparser/src/main/java/com/coremedia/iso/boxes/mdat/MediaDataBox.java new file mode 100644 index 0000000..5075a15 --- /dev/null +++ b/isoparser/src/main/java/com/coremedia/iso/boxes/mdat/MediaDataBox.java @@ -0,0 +1,189 @@ +/* + * 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.mdat; + +import com.coremedia.iso.BoxParser; +import com.coremedia.iso.ChannelHelper; +import com.coremedia.iso.boxes.Box; +import com.coremedia.iso.boxes.ContainerBox; +import com.googlecode.mp4parser.AbstractBox; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import static com.googlecode.mp4parser.util.CastUtils.l2i; + +/** + * This box contains the media data. In video tracks, this box would contain video frames. A presentation may + * contain zero or more Media Data Boxes. The actual media data follows the type field; its structure is described + * by the metadata (see {@link com.coremedia.iso.boxes.SampleTableBox}).<br> + * In large presentations, it may be desirable to have more data in this box than a 32-bit size would permit. In this + * case, the large variant of the size field is used.<br> + * There may be any number of these boxes in the file (including zero, if all the media data is in other files). The + * metadata refers to media data by its absolute offset within the file (see {@link com.coremedia.iso.boxes.StaticChunkOffsetBox}); + * so Media Data Box headers and free space may easily be skipped, and files without any box structure may + * also be referenced and used. + */ +public final class MediaDataBox implements Box { + private static Logger LOG = Logger.getLogger(MediaDataBox.class.getName()); + + public static final String TYPE = "mdat"; + public static final int BUFFER_SIZE = 10 * 1024 * 1024; + ContainerBox parent; + + ByteBuffer header; + + // These fields are for the special case of a FileChannel as input. + private FileChannel fileChannel; + private long startPosition; + private long contentSize; + + + private Map<Long, Reference<ByteBuffer>> cache = new HashMap<Long, Reference<ByteBuffer>>(); + + + /** + * If the whole content is just in one mapped buffer keep a strong reference to it so it is + * not evicted from the cache. + */ + private ByteBuffer content; + + public ContainerBox getParent() { + return parent; + } + + public void setParent(ContainerBox parent) { + this.parent = parent; + } + + public String getType() { + return TYPE; + } + + private static void transfer(FileChannel from, long position, long count, WritableByteChannel to) throws IOException { + long maxCount = (64 * 1024 * 1024) - (32 * 1024); + // Transfer data in chunks a bit less than 64MB + // People state that this is a kind of magic number on Windows. + // I don't care. The size seems reasonable. + long offset = 0; + while (offset < count) { + offset += from.transferTo(position + offset, Math.min(maxCount, count - offset), to); + } + } + + public void getBox(WritableByteChannel writableByteChannel) throws IOException { + if (fileChannel != null) { + assert checkStillOk(); + transfer(fileChannel, startPosition - header.limit(), contentSize + header.limit(), writableByteChannel); + } else { + header.rewind(); + writableByteChannel.write(header); + writableByteChannel.write(content); + } + } + + /** + * If someone use the same file as source and sink it could the case that + * inserting a few bytes before the mdat results in overwrting data we still + * need to write this mdat here. This method just makes sure that we haven't already + * overwritten the mdat contents. + * + * @return true if ok + */ + private boolean checkStillOk() { + try { + fileChannel.position(startPosition - header.limit()); + ByteBuffer h2 = ByteBuffer.allocate(header.limit()); + fileChannel.read(h2); + header.rewind(); + h2.rewind(); + assert h2.equals(header) : "It seems that the content I want to read has already been overwritten."; + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } + + + public long getSize() { + long size = header.limit(); + size += contentSize; + return size; + } + + public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException { + this.header = header; + this.contentSize = contentSize; + + if (readableByteChannel instanceof FileChannel && (contentSize > AbstractBox.MEM_MAP_THRESHOLD)) { + this.fileChannel = ((FileChannel) readableByteChannel); + this.startPosition = ((FileChannel) readableByteChannel).position(); + ((FileChannel) readableByteChannel).position(((FileChannel) readableByteChannel).position() + contentSize); + } else { + content = ChannelHelper.readFully(readableByteChannel, l2i(contentSize)); + cache.put(0l, new SoftReference<ByteBuffer>(content)); + } + } + + public synchronized ByteBuffer getContent(long offset, int length) { + + for (Long chacheEntryOffset : cache.keySet()) { + if (chacheEntryOffset <= offset && offset <= chacheEntryOffset + BUFFER_SIZE) { + ByteBuffer cacheEntry = cache.get(chacheEntryOffset).get(); + if ((cacheEntry != null) && ((chacheEntryOffset + cacheEntry.limit()) >= (offset + length))) { + // CACHE HIT + cacheEntry.position((int) (offset - chacheEntryOffset)); + ByteBuffer cachedSample = cacheEntry.slice(); + cachedSample.limit(length); + return cachedSample; + } + } + } + // CACHE MISS + ByteBuffer cacheEntry; + try { + // Just mapping 10MB at a time. Seems reasonable. + cacheEntry = fileChannel.map(FileChannel.MapMode.READ_ONLY, startPosition + offset, Math.min(BUFFER_SIZE, contentSize - offset)); + } catch (IOException e1) { + LOG.fine("Even mapping just 10MB of the source file into the memory failed. " + e1); + throw new RuntimeException( + "Delayed reading of mdat content failed. Make sure not to close " + + "the FileChannel that has been used to create the IsoFile!", e1); + } + cache.put(offset, new SoftReference<ByteBuffer>(cacheEntry)); + cacheEntry.position(0); + ByteBuffer cachedSample = cacheEntry.slice(); + cachedSample.limit(length); + return cachedSample; + } + + + public ByteBuffer getHeader() { + return header; + } + +} |