• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2008 CoreMedia AG, Hamburg
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.coremedia.iso.boxes.mdat;
18 
19 import com.coremedia.iso.BoxParser;
20 import com.coremedia.iso.ChannelHelper;
21 import com.coremedia.iso.boxes.Box;
22 import com.coremedia.iso.boxes.ContainerBox;
23 import com.googlecode.mp4parser.AbstractBox;
24 
25 import java.io.IOException;
26 import java.lang.ref.Reference;
27 import java.lang.ref.SoftReference;
28 import java.nio.ByteBuffer;
29 import java.nio.channels.FileChannel;
30 import java.nio.channels.ReadableByteChannel;
31 import java.nio.channels.WritableByteChannel;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.logging.Logger;
35 
36 import static com.googlecode.mp4parser.util.CastUtils.l2i;
37 
38 /**
39  * This box contains the media data. In video tracks, this box would contain video frames. A presentation may
40  * contain zero or more Media Data Boxes. The actual media data follows the type field; its structure is described
41  * by the metadata (see {@link com.coremedia.iso.boxes.SampleTableBox}).<br>
42  * In large presentations, it may be desirable to have more data in this box than a 32-bit size would permit. In this
43  * case, the large variant of the size field is used.<br>
44  * There may be any number of these boxes in the file (including zero, if all the media data is in other files). The
45  * metadata refers to media data by its absolute offset within the file (see {@link com.coremedia.iso.boxes.StaticChunkOffsetBox});
46  * so Media Data Box headers and free space may easily be skipped, and files without any box structure may
47  * also be referenced and used.
48  */
49 public final class MediaDataBox implements Box {
50     private static Logger LOG = Logger.getLogger(MediaDataBox.class.getName());
51 
52     public static final String TYPE = "mdat";
53     public static final int BUFFER_SIZE = 10 * 1024 * 1024;
54     ContainerBox parent;
55 
56     ByteBuffer header;
57 
58     // These fields are for the special case of a FileChannel as input.
59     private FileChannel fileChannel;
60     private long startPosition;
61     private long contentSize;
62 
63 
64     private Map<Long, Reference<ByteBuffer>> cache = new HashMap<Long, Reference<ByteBuffer>>();
65 
66 
67     /**
68      * If the whole content is just in one mapped buffer keep a strong reference to it so it is
69      * not evicted from the cache.
70      */
71     private ByteBuffer content;
72 
getParent()73     public ContainerBox getParent() {
74         return parent;
75     }
76 
setParent(ContainerBox parent)77     public void setParent(ContainerBox parent) {
78         this.parent = parent;
79     }
80 
getType()81     public String getType() {
82         return TYPE;
83     }
84 
transfer(FileChannel from, long position, long count, WritableByteChannel to)85     private static void transfer(FileChannel from, long position, long count, WritableByteChannel to) throws IOException {
86         long maxCount = (64 * 1024 * 1024) - (32 * 1024);
87         // Transfer data in chunks a bit less than 64MB
88         // People state that this is a kind of magic number on Windows.
89         // I don't care. The size seems reasonable.
90         long offset = 0;
91         while (offset < count) {
92             offset += from.transferTo(position + offset, Math.min(maxCount, count - offset), to);
93         }
94     }
95 
getBox(WritableByteChannel writableByteChannel)96     public void getBox(WritableByteChannel writableByteChannel) throws IOException {
97         if (fileChannel != null) {
98             assert checkStillOk();
99             transfer(fileChannel, startPosition - header.limit(), contentSize + header.limit(), writableByteChannel);
100         } else {
101             header.rewind();
102             writableByteChannel.write(header);
103             writableByteChannel.write(content);
104         }
105     }
106 
107     /**
108      * If someone use the same file as source and sink it could the case that
109      * inserting a few bytes before the mdat results in overwrting data we still
110      * need to write this mdat here. This method just makes sure that we haven't already
111      * overwritten the mdat contents.
112      *
113      * @return true if ok
114      */
checkStillOk()115     private boolean checkStillOk() {
116         try {
117             fileChannel.position(startPosition - header.limit());
118             ByteBuffer h2 = ByteBuffer.allocate(header.limit());
119             fileChannel.read(h2);
120             header.rewind();
121             h2.rewind();
122             assert h2.equals(header) : "It seems that the content I want to read has already been overwritten.";
123             return true;
124         } catch (IOException e) {
125             e.printStackTrace();
126             return false;
127         }
128 
129     }
130 
131 
getSize()132     public long getSize() {
133         long size = header.limit();
134         size += contentSize;
135         return size;
136     }
137 
parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser)138     public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
139         this.header = header;
140         this.contentSize = contentSize;
141 
142         if (readableByteChannel instanceof FileChannel && (contentSize > AbstractBox.MEM_MAP_THRESHOLD)) {
143             this.fileChannel = ((FileChannel) readableByteChannel);
144             this.startPosition = ((FileChannel) readableByteChannel).position();
145             ((FileChannel) readableByteChannel).position(((FileChannel) readableByteChannel).position() + contentSize);
146         } else {
147             content = ChannelHelper.readFully(readableByteChannel, l2i(contentSize));
148             cache.put(0l, new SoftReference<ByteBuffer>(content));
149         }
150     }
151 
getContent(long offset, int length)152     public synchronized ByteBuffer getContent(long offset, int length) {
153 
154         for (Long chacheEntryOffset : cache.keySet()) {
155             if (chacheEntryOffset <= offset && offset <= chacheEntryOffset + BUFFER_SIZE) {
156                 ByteBuffer cacheEntry = cache.get(chacheEntryOffset).get();
157                 if ((cacheEntry != null) && ((chacheEntryOffset + cacheEntry.limit()) >= (offset + length))) {
158                     // CACHE HIT
159                     cacheEntry.position((int) (offset - chacheEntryOffset));
160                     ByteBuffer cachedSample = cacheEntry.slice();
161                     cachedSample.limit(length);
162                     return cachedSample;
163                 }
164             }
165         }
166         // CACHE MISS
167         ByteBuffer cacheEntry;
168         try {
169             // Just mapping 10MB at a time. Seems reasonable.
170             cacheEntry = fileChannel.map(FileChannel.MapMode.READ_ONLY, startPosition + offset, Math.min(BUFFER_SIZE, contentSize - offset));
171         } catch (IOException e1) {
172             LOG.fine("Even mapping just 10MB of the source file into the memory failed. " + e1);
173             throw new RuntimeException(
174                     "Delayed reading of mdat content failed. Make sure not to close " +
175                             "the FileChannel that has been used to create the IsoFile!", e1);
176         }
177         cache.put(offset, new SoftReference<ByteBuffer>(cacheEntry));
178         cacheEntry.position(0);
179         ByteBuffer cachedSample = cacheEntry.slice();
180         cachedSample.limit(length);
181         return cachedSample;
182     }
183 
184 
getHeader()185     public ByteBuffer getHeader() {
186         return header;
187     }
188 
189 }
190