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