summaryrefslogtreecommitdiff
path: root/isoparser/src/main/java/com/coremedia/iso/boxes/mdat/.svn/text-base/MediaDataBox.java.svn-base
blob: 5075a159fe0b68eec657e532b328eaa3b26ffebe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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;
    }

}