Home | History | Annotate | Download | only in mdat
      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 }