156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks/* 256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * Copyright 2008 CoreMedia AG, Hamburg 356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * 456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * Licensed under the Apache License, Version 2.0 (the License); 556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * you may not use this file except in compliance with the License. 656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * You may obtain a copy of the License at 756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * 856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * http://www.apache.org/licenses/LICENSE-2.0 956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * 107df30109963092559d3760c0661a020f9daf1030The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 117df30109963092559d3760c0661a020f9daf1030The Android Open Source Project * distributed under the License is distributed on an AS IS BASIS, 127df30109963092559d3760c0661a020f9daf1030The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137df30109963092559d3760c0661a020f9daf1030The Android Open Source Project * See the License for the specific language governing permissions and 147df30109963092559d3760c0661a020f9daf1030The Android Open Source Project * limitations under the License. 157df30109963092559d3760c0661a020f9daf1030The Android Open Source Project */ 167df30109963092559d3760c0661a020f9daf1030The Android Open Source Project 177df30109963092559d3760c0661a020f9daf1030The Android Open Source Projectpackage com.coremedia.iso.boxes.mdat; 187df30109963092559d3760c0661a020f9daf1030The Android Open Source Project 197df30109963092559d3760c0661a020f9daf1030The Android Open Source Projectimport com.coremedia.iso.BoxParser; 207df30109963092559d3760c0661a020f9daf1030The Android Open Source Projectimport com.coremedia.iso.ChannelHelper; 217df30109963092559d3760c0661a020f9daf1030The Android Open Source Projectimport com.coremedia.iso.boxes.Box; 2256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport com.coremedia.iso.boxes.ContainerBox; 2356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport com.googlecode.mp4parser.AbstractBox; 2456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 2556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.io.IOException; 2656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.lang.ref.Reference; 2756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.lang.ref.SoftReference; 2856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.nio.ByteBuffer; 2956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.nio.channels.FileChannel; 3056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.nio.channels.ReadableByteChannel; 3156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.nio.channels.WritableByteChannel; 3256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.util.HashMap; 3356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.util.Map; 3456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport java.util.logging.Logger; 3556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 3656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparksimport static com.googlecode.mp4parser.util.CastUtils.l2i; 3756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 3856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks/** 3956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * This box contains the media data. In video tracks, this box would contain video frames. A presentation may 4056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * contain zero or more Media Data Boxes. The actual media data follows the type field; its structure is described 4156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * by the metadata (see {@link com.coremedia.iso.boxes.SampleTableBox}).<br> 4256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * In large presentations, it may be desirable to have more data in this box than a 32-bit size would permit. In this 4356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * case, the large variant of the size field is used.<br> 4456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * There may be any number of these boxes in the file (including zero, if all the media data is in other files). The 4556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * metadata refers to media data by its absolute offset within the file (see {@link com.coremedia.iso.boxes.StaticChunkOffsetBox}); 4656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * so Media Data Box headers and free space may easily be skipped, and files without any box structure may 4756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * also be referenced and used. 4856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks */ 4956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparkspublic final class MediaDataBox implements Box { 5056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private static Logger LOG = Logger.getLogger(MediaDataBox.class.getName()); 5156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 5256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public static final String TYPE = "mdat"; 5356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public static final int BUFFER_SIZE = 10 * 1024 * 1024; 5456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ContainerBox parent; 5556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 5656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ByteBuffer header; 5756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 5856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // These fields are for the special case of a FileChannel as input. 5956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private FileChannel fileChannel; 6056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private long startPosition; 6156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private long contentSize; 6256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 6356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 6456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private Map<Long, Reference<ByteBuffer>> cache = new HashMap<Long, Reference<ByteBuffer>>(); 6556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 6656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 6756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks /** 6856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * If the whole content is just in one mapped buffer keep a strong reference to it so it is 6956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * not evicted from the cache. 7056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks */ 7156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private ByteBuffer content; 7256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 7356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public ContainerBox getParent() { 7456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return parent; 7556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 7656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 7756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public void setParent(ContainerBox parent) { 7856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks this.parent = parent; 7956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 8056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 8156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public String getType() { 8256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return TYPE; 8356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 8456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 8556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private static void transfer(FileChannel from, long position, long count, WritableByteChannel to) throws IOException { 8656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks long maxCount = (64 * 1024 * 1024) - (32 * 1024); 8756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // Transfer data in chunks a bit less than 64MB 8856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // People state that this is a kind of magic number on Windows. 8956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // I don't care. The size seems reasonable. 9056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks long offset = 0; 9156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks while (offset < count) { 9256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks offset += from.transferTo(position + offset, Math.min(maxCount, count - offset), to); 9356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 9456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 9556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 9656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public void getBox(WritableByteChannel writableByteChannel) throws IOException { 9756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks if (fileChannel != null) { 9856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks assert checkStillOk(); 9956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks transfer(fileChannel, startPosition - header.limit(), contentSize + header.limit(), writableByteChannel); 10056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } else { 10156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks header.rewind(); 10256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks writableByteChannel.write(header); 10356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks writableByteChannel.write(content); 10456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 10556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 10656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 10756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks /** 10856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * If someone use the same file as source and sink it could the case that 10956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * inserting a few bytes before the mdat results in overwrting data we still 11056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * need to write this mdat here. This method just makes sure that we haven't already 11156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * overwritten the mdat contents. 11256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * 11356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks * @return true if ok 11456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks */ 11556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks private boolean checkStillOk() { 11656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks try { 11756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks fileChannel.position(startPosition - header.limit()); 11856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ByteBuffer h2 = ByteBuffer.allocate(header.limit()); 11956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks fileChannel.read(h2); 12056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks header.rewind(); 12156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks h2.rewind(); 12256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks assert h2.equals(header) : "It seems that the content I want to read has already been overwritten."; 12356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return true; 12456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } catch (IOException e) { 12556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks e.printStackTrace(); 12656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return false; 12756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 12856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 12956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 13056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 13156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 13256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public long getSize() { 13356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks long size = header.limit(); 13456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks size += contentSize; 13556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return size; 13656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 13756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 13856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException { 13956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks this.header = header; 14056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks this.contentSize = contentSize; 14156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 14256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks if (readableByteChannel instanceof FileChannel && (contentSize > AbstractBox.MEM_MAP_THRESHOLD)) { 14356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks this.fileChannel = ((FileChannel) readableByteChannel); 14456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks this.startPosition = ((FileChannel) readableByteChannel).position(); 14556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ((FileChannel) readableByteChannel).position(((FileChannel) readableByteChannel).position() + contentSize); 14656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } else { 14756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks content = ChannelHelper.readFully(readableByteChannel, l2i(contentSize)); 14856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cache.put(0l, new SoftReference<ByteBuffer>(content)); 14956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 15056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 15156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 15256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public synchronized ByteBuffer getContent(long offset, int length) { 15356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 15456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks for (Long chacheEntryOffset : cache.keySet()) { 15556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks if (chacheEntryOffset <= offset && offset <= chacheEntryOffset + BUFFER_SIZE) { 15656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ByteBuffer cacheEntry = cache.get(chacheEntryOffset).get(); 15756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks if ((cacheEntry != null) && ((chacheEntryOffset + cacheEntry.limit()) >= (offset + length))) { 15856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // CACHE HIT 15956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cacheEntry.position((int) (offset - chacheEntryOffset)); 16056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ByteBuffer cachedSample = cacheEntry.slice(); 16156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cachedSample.limit(length); 16256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return cachedSample; 16356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 16456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 16556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 16656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // CACHE MISS 16756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ByteBuffer cacheEntry; 16856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks try { 16956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks // Just mapping 10MB at a time. Seems reasonable. 17056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cacheEntry = fileChannel.map(FileChannel.MapMode.READ_ONLY, startPosition + offset, Math.min(BUFFER_SIZE, contentSize - offset)); 17156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } catch (IOException e1) { 17256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks LOG.fine("Even mapping just 10MB of the source file into the memory failed. " + e1); 17356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks throw new RuntimeException( 17456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks "Delayed reading of mdat content failed. Make sure not to close " + 17556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks "the FileChannel that has been used to create the IsoFile!", e1); 17656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 17756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cache.put(offset, new SoftReference<ByteBuffer>(cacheEntry)); 17856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cacheEntry.position(0); 17956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks ByteBuffer cachedSample = cacheEntry.slice(); 18056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks cachedSample.limit(length); 18156c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return cachedSample; 18256c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 18356c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 18456c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 18556c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks public ByteBuffer getHeader() { 18656c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks return header; 18756c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks } 18856c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks 18956c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks} 19056c99cd2c2c1e6ab038dac5fced5b92ccf11ff6cDave Sparks