package com.googlecode.mp4parser.authoring.samples;
import com.coremedia.iso.boxes.*;
import com.googlecode.mp4parser.authoring.Sample;
import com.googlecode.mp4parser.authoring.SampleImpl;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.AbstractList;
import java.util.List;
import static com.googlecode.mp4parser.util.CastUtils.l2i;
/**
* Created by sannies on 25.05.13.
*/
public class DefaultMp4SampleList extends AbstractList<Sample> {
Container topLevel;
TrackBox trackBox = null;
SoftReference<ByteBuffer>[] cache = null;
int[] chunkNumsStartSampleNum;
long[] chunkOffsets;
int[] chunkSizes;
SampleSizeBox ssb;
public DefaultMp4SampleList(long track, Container topLevel) {
this.topLevel = topLevel;
MovieBox movieBox = topLevel.getBoxes(MovieBox.class).get(0);
List<TrackBox> trackBoxes = movieBox.getBoxes(TrackBox.class);
for (TrackBox tb : trackBoxes) {
if (tb.getTrackHeaderBox().getTrackId() == track) {
trackBox = tb;
}
}
if (trackBox == null) {
throw new RuntimeException("This MP4 does not contain track " + track);
}
chunkOffsets = trackBox.getSampleTableBox().getChunkOffsetBox().getChunkOffsets();
chunkSizes = new int[chunkOffsets.length];
cache = (SoftReference<ByteBuffer>[]) Array.newInstance(SoftReference.class, chunkOffsets.length );
ssb = trackBox.getSampleTableBox().getSampleSizeBox();
List<SampleToChunkBox.Entry> s2chunkEntries = trackBox.getSampleTableBox().getSampleToChunkBox().getEntries();
SampleToChunkBox.Entry[] entries = s2chunkEntries.toArray(new SampleToChunkBox.Entry[s2chunkEntries.size()]);
int s2cIndex = 0;
SampleToChunkBox.Entry next = entries[s2cIndex++];
int currentChunkNo = 0;
int currentSamplePerChunk = 0;
long nextFirstChunk = next.getFirstChunk();
int nextSamplePerChunk = l2i(next.getSamplesPerChunk());
int currentSampleNo = 1;
int lastSampleNo = size();
do {
currentChunkNo++;
if (currentChunkNo == nextFirstChunk) {
currentSamplePerChunk = nextSamplePerChunk;
if (entries.length > s2cIndex) {
next = entries[s2cIndex++];
nextSamplePerChunk = l2i(next.getSamplesPerChunk());
nextFirstChunk = next.getFirstChunk();
} else {
nextSamplePerChunk = -1;
nextFirstChunk = Long.MAX_VALUE;
}
}
} while ((currentSampleNo += currentSamplePerChunk) <= lastSampleNo);
chunkNumsStartSampleNum = new int[currentChunkNo + 1];
// reset of algorithm
s2cIndex = 0;
next = entries[s2cIndex++];
currentChunkNo = 0;
currentSamplePerChunk = 0;
nextFirstChunk = next.getFirstChunk();
nextSamplePerChunk = l2i(next.getSamplesPerChunk());
currentSampleNo = 1;
do {
chunkNumsStartSampleNum[currentChunkNo++] = currentSampleNo;
if (currentChunkNo == nextFirstChunk) {
currentSamplePerChunk = nextSamplePerChunk;
if (entries.length > s2cIndex) {
next = entries[s2cIndex++];
nextSamplePerChunk = l2i(next.getSamplesPerChunk());
nextFirstChunk = next.getFirstChunk();
} else {
nextSamplePerChunk = -1;
nextFirstChunk = Long.MAX_VALUE;
}
}
} while ((currentSampleNo += currentSamplePerChunk) <= lastSampleNo);
chunkNumsStartSampleNum[currentChunkNo] = Integer.MAX_VALUE;
currentChunkNo=0;
for (int i = 1; i<= ssb.getSampleCount(); i++) {
if (i == chunkNumsStartSampleNum[currentChunkNo]) {
currentChunkNo++;
}
chunkSizes[currentChunkNo-1] += ssb.getSampleSizeAtIndex(i-1);
}
}
int lastChunk = 0;
synchronized int getChunkForSample(int index) {
int sampleNum = index + 1;
// we always look for the next chunk in the last one to make linear access fast
if (sampleNum >= chunkNumsStartSampleNum[lastChunk] && sampleNum < chunkNumsStartSampleNum[lastChunk + 1]) {
return lastChunk;
} else if (sampleNum < chunkNumsStartSampleNum[lastChunk]) {
// we could search backwards but i don't believe there is much backward linear access
// I'd then rather suspect a start from scratch
lastChunk = 0;
while (chunkNumsStartSampleNum[lastChunk + 1] <= sampleNum) {
lastChunk++;
}
return lastChunk;
} else {
lastChunk += 1;
while (chunkNumsStartSampleNum[lastChunk + 1] <= sampleNum) {
lastChunk++;
}
return lastChunk;
}
}
@Override
public Sample get(int index) {
if (index >= ssb.getSampleCount()) {
throw new IndexOutOfBoundsException();
}
int currentChunkNoZeroBased = getChunkForSample(index);
int currentSampleNo = chunkNumsStartSampleNum[currentChunkNoZeroBased];
long offset = chunkOffsets[l2i(currentChunkNoZeroBased)];
ByteBuffer chunk = cache[l2i(currentChunkNoZeroBased)]!=null?cache[l2i(currentChunkNoZeroBased)].get():null;
if (chunk == null) {
try {
chunk = topLevel.getByteBuffer(offset, chunkSizes[l2i(currentChunkNoZeroBased)] );
cache[l2i(currentChunkNoZeroBased)] = new SoftReference<ByteBuffer>(chunk);
} catch (IOException e) {
throw new IndexOutOfBoundsException(e.getMessage());
}
}
int offsetWithinChunk = 0;
while (currentSampleNo < index + 1) {
offsetWithinChunk += ssb.getSampleSizeAtIndex((currentSampleNo++) - 1);
}
final long sampleSize = ssb.getSampleSizeAtIndex(currentSampleNo - 1);
return new SampleImpl(offsetWithinChunk, sampleSize,
(ByteBuffer) ((ByteBuffer)chunk.position(offsetWithinChunk)).slice().limit(l2i(sampleSize)));
}
@Override
public int size() {
return l2i(trackBox.getSampleTableBox().getSampleSizeBox().getSampleCount());
}
}