1 package com.coremedia.iso.boxes.mdat; 2 3 import com.coremedia.iso.IsoFile; 4 import com.coremedia.iso.boxes.*; 5 import com.coremedia.iso.boxes.fragment.*; 6 7 import java.nio.ByteBuffer; 8 import java.util.*; 9 10 import static com.googlecode.mp4parser.util.CastUtils.l2i; 11 12 /** 13 * Creates a list of <code>ByteBuffer</code>s that represent the samples of a given track. 14 */ 15 public class SampleList extends AbstractList<ByteBuffer> { 16 17 18 long[] offsets; 19 long[] sizes; 20 21 IsoFile isoFile; 22 HashMap<MediaDataBox, Long> mdatStartCache = new HashMap<MediaDataBox, Long>(); 23 HashMap<MediaDataBox, Long> mdatEndCache = new HashMap<MediaDataBox, Long>(); 24 MediaDataBox[] mdats; 25 26 /** 27 * Gets a sorted random access optimized list of all sample offsets. 28 * Basically it is a map from sample number to sample offset. 29 * 30 * @return the sorted list of sample offsets 31 */ 32 public long[] getOffsetKeys() { 33 return offsets; 34 } 35 36 37 public SampleList(TrackBox trackBox) { 38 initIsoFile(trackBox.getIsoFile()); // where are we? 39 40 // first we get all sample from the 'normal' MP4 part. 41 // if there are none - no problem. 42 SampleSizeBox sampleSizeBox = trackBox.getSampleTableBox().getSampleSizeBox(); 43 ChunkOffsetBox chunkOffsetBox = trackBox.getSampleTableBox().getChunkOffsetBox(); 44 SampleToChunkBox sampleToChunkBox = trackBox.getSampleTableBox().getSampleToChunkBox(); 45 46 47 final long[] chunkOffsets = chunkOffsetBox != null ? chunkOffsetBox.getChunkOffsets() : new long[0]; 48 if (sampleToChunkBox != null && sampleToChunkBox.getEntries().size() > 0 && 49 chunkOffsets.length > 0 && sampleSizeBox != null && sampleSizeBox.getSampleCount() > 0) { 50 long[] numberOfSamplesInChunk = sampleToChunkBox.blowup(chunkOffsets.length); 51 52 int sampleIndex = 0; 53 54 if (sampleSizeBox.getSampleSize() > 0) { 55 sizes = new long[l2i(sampleSizeBox.getSampleCount())]; 56 Arrays.fill(sizes, sampleSizeBox.getSampleSize()); 57 } else { 58 sizes = sampleSizeBox.getSampleSizes(); 59 } 60 offsets = new long[sizes.length]; 61 62 for (int i = 0; i < numberOfSamplesInChunk.length; i++) { 63 long thisChunksNumberOfSamples = numberOfSamplesInChunk[i]; 64 long sampleOffset = chunkOffsets[i]; 65 for (int j = 0; j < thisChunksNumberOfSamples; j++) { 66 long sampleSize = sizes[sampleIndex]; 67 offsets[sampleIndex] = sampleOffset; 68 sampleOffset += sampleSize; 69 sampleIndex++; 70 } 71 } 72 73 } 74 75 // Next we add all samples from the fragments 76 // in most cases - I've never seen it different it's either normal or fragmented. 77 List<MovieExtendsBox> movieExtendsBoxes = trackBox.getParent().getBoxes(MovieExtendsBox.class); 78 79 if (movieExtendsBoxes.size() > 0) { 80 Map<Long, Long> offsets2Sizes = new HashMap<Long, Long>(); 81 List<TrackExtendsBox> trackExtendsBoxes = movieExtendsBoxes.get(0).getBoxes(TrackExtendsBox.class); 82 for (TrackExtendsBox trackExtendsBox : trackExtendsBoxes) { 83 if (trackExtendsBox.getTrackId() == trackBox.getTrackHeaderBox().getTrackId()) { 84 for (MovieFragmentBox movieFragmentBox : trackBox.getIsoFile().getBoxes(MovieFragmentBox.class)) { 85 offsets2Sizes.putAll(getOffsets(movieFragmentBox, trackBox.getTrackHeaderBox().getTrackId(), trackExtendsBox)); 86 } 87 } 88 } 89 90 if (sizes == null || offsets == null) { 91 sizes = new long[0]; 92 offsets = new long[0]; 93 } 94 95 splitToArrays(offsets2Sizes); 96 } 97 98 // We have now a map from all sample offsets to their sizes 99 } 100 101 private void splitToArrays(Map<Long, Long> offsets2Sizes) { 102 List<Long> keys = new ArrayList<Long>(offsets2Sizes.keySet()); 103 Collections.sort(keys); 104 105 long[] nuSizes = new long[sizes.length + keys.size()]; 106 System.arraycopy(sizes, 0, nuSizes, 0, sizes.length); 107 long[] nuOffsets = new long[offsets.length + keys.size()]; 108 System.arraycopy(offsets, 0, nuOffsets, 0, offsets.length); 109 for (int i = 0; i < keys.size(); i++) { 110 nuOffsets[i + offsets.length] = keys.get(i); 111 nuSizes[i + sizes.length] = offsets2Sizes.get(keys.get(i)); 112 } 113 sizes = nuSizes; 114 offsets = nuOffsets; 115 } 116 117 public SampleList(TrackFragmentBox traf) { 118 sizes = new long[0]; 119 offsets = new long[0]; 120 Map<Long, Long> offsets2Sizes = new HashMap<Long, Long>(); 121 initIsoFile(traf.getIsoFile()); 122 123 final List<MovieFragmentBox> movieFragmentBoxList = isoFile.getBoxes(MovieFragmentBox.class); 124 125 final long trackId = traf.getTrackFragmentHeaderBox().getTrackId(); 126 for (MovieFragmentBox moof : movieFragmentBoxList) { 127 final List<TrackFragmentHeaderBox> trackFragmentHeaderBoxes = moof.getTrackFragmentHeaderBoxes(); 128 for (TrackFragmentHeaderBox tfhd : trackFragmentHeaderBoxes) { 129 if (tfhd.getTrackId() == trackId) { 130 offsets2Sizes.putAll(getOffsets(moof, trackId, null)); 131 } 132 } 133 } 134 splitToArrays(offsets2Sizes); 135 } 136 137 private void initIsoFile(IsoFile isoFile) { 138 this.isoFile = isoFile; 139 // find all mdats first to be able to use them later with explicitly looking them up 140 long currentOffset = 0; 141 LinkedList<MediaDataBox> mdats = new LinkedList<MediaDataBox>(); 142 for (Box b : this.isoFile.getBoxes()) { 143 long currentSize = b.getSize(); 144 if ("mdat".equals(b.getType())) { 145 if (b instanceof MediaDataBox) { 146 long contentOffset = currentOffset + ((MediaDataBox) b).getHeader().limit(); 147 mdatStartCache.put((MediaDataBox) b, contentOffset); 148 mdatEndCache.put((MediaDataBox) b, contentOffset + currentSize); 149 mdats.add((MediaDataBox) b); 150 } else { 151 throw new RuntimeException("Sample need to be in mdats and mdats need to be instanceof MediaDataBox"); 152 } 153 } 154 currentOffset += currentSize; 155 } 156 this.mdats = mdats.toArray(new MediaDataBox[mdats.size()]); 157 } 158 159 160 @Override 161 public int size() { 162 return sizes.length; 163 } 164 165 166 @Override 167 public ByteBuffer get(int index) { 168 // it is a two stage lookup: from index to offset to size 169 long offset = offsets[index]; 170 int sampleSize = l2i(sizes[index]); 171 172 for (MediaDataBox mediaDataBox : mdats) { 173 long start = mdatStartCache.get(mediaDataBox); 174 long end = mdatEndCache.get(mediaDataBox); 175 if ((start <= offset) && (offset + sampleSize <= end)) { 176 return mediaDataBox.getContent(offset - start, sampleSize); 177 } 178 } 179 180 throw new RuntimeException("The sample with offset " + offset + " and size " + sampleSize + " is NOT located within an mdat"); 181 } 182 183 Map<Long, Long> getOffsets(MovieFragmentBox moof, long trackId, TrackExtendsBox trex) { 184 Map<Long, Long> offsets2Sizes = new HashMap<Long, Long>(); 185 List<TrackFragmentBox> traf = moof.getBoxes(TrackFragmentBox.class); 186 for (TrackFragmentBox trackFragmentBox : traf) { 187 if (trackFragmentBox.getTrackFragmentHeaderBox().getTrackId() == trackId) { 188 long baseDataOffset; 189 if (trackFragmentBox.getTrackFragmentHeaderBox().hasBaseDataOffset()) { 190 baseDataOffset = trackFragmentBox.getTrackFragmentHeaderBox().getBaseDataOffset(); 191 } else { 192 baseDataOffset = moof.getOffset(); 193 } 194 195 for (TrackRunBox trun : trackFragmentBox.getBoxes(TrackRunBox.class)) { 196 long sampleBaseOffset = baseDataOffset + trun.getDataOffset(); 197 final TrackFragmentHeaderBox tfhd = ((TrackFragmentBox) trun.getParent()).getTrackFragmentHeaderBox(); 198 199 long offset = 0; 200 for (TrackRunBox.Entry entry : trun.getEntries()) { 201 final long sampleSize; 202 if (trun.isSampleSizePresent()) { 203 sampleSize = entry.getSampleSize(); 204 offsets2Sizes.put(offset + sampleBaseOffset, sampleSize); 205 offset += sampleSize; 206 } else { 207 if (tfhd.hasDefaultSampleSize()) { 208 sampleSize = tfhd.getDefaultSampleSize(); 209 offsets2Sizes.put(offset + sampleBaseOffset, sampleSize); 210 offset += sampleSize; 211 } else { 212 if (trex == null) { 213 throw new RuntimeException("File doesn't contain trex box but track fragments aren't fully self contained. Cannot determine sample size."); 214 } 215 sampleSize = trex.getDefaultSampleSize(); 216 offsets2Sizes.put(offset + sampleBaseOffset, sampleSize); 217 offset += sampleSize; 218 } 219 } 220 } 221 } 222 } 223 } 224 return offsets2Sizes; 225 } 226 227 }