package mp4.util.atom;
/**
* Sample-to-chunk atom. A chunk contains one or more samples. The chunks
* in a media may have different sizes, and the samples within a chunk may
* have different sizes.
*/
public class StscAtom extends LeafAtom {
// layout of entries in the atom
private static final int ENTRIES_OFFSET = 4;
private static final int TABLE_OFFSET = 8;
// layout of each table entry
private static final int FIRST_CHUNK = 0;
private static final int SAMPLES_PER_CHUNK = 4;
private static final int DESCRIPTION_ID = 8;
// size of the table entry
private static final int ENTRY_SIZE = 12;
/**
* Constructor for the stsc atom
*/
public StscAtom() {
super(new byte[]{'s','t','s','c'});
}
/**
* Copy constructor.
* @param old the atom to copy
*/
public StscAtom(StscAtom old) {
super(old);
}
/**
* Allocate space for the stsc atom data. The actual data is filled in
* later
* @param numEntries the number of entries to add
*/
@Override
public void allocateData(long numEntries) {
long size = TABLE_OFFSET + (numEntries * ENTRY_SIZE);
super.allocateData(size);
}
/**
* Return the number of entries in the table
* @return the number of entries in the table
*/
public long getNumEntries() {
return data.getUnsignedInt(ENTRIES_OFFSET);
}
/**
* Set the number of entries in the stsc atom
* @param numEntries the number of entries
*/
public void setNumEntries(long numEntries) {
data.addUnsignedInt(ENTRIES_OFFSET, numEntries);
}
/**
* Return the first chunk value for the specified entry
* @param index the table index
* @return the first chunk value for the specified entry
*/
public long getFirstChunk(int index) {
return data.getUnsignedInt(TABLE_OFFSET + (index * ENTRY_SIZE) + FIRST_CHUNK);
}
/**
* Set the first chunk value for the specified entry
* @param index the table index
* @param chunk the chunk value for the specified entry
*/
public void setFirstChunk(int index, long chunk) {
data.addUnsignedInt(TABLE_OFFSET + (index * ENTRY_SIZE) + FIRST_CHUNK, chunk);
}
/**
* Return the samples per chunk value for the specified entry
* @param index the table index
* @return the samples per chunk value for the specified entry
*/
public long getSamplesPerChunk(int index) {
return data.getUnsignedInt(TABLE_OFFSET + (index * ENTRY_SIZE) + SAMPLES_PER_CHUNK);
}
/**
* Set the samples per chunk value for the specified entry
* @param index the table index
* @param spc the samples per chunk value for the specified entry
*/
public void setSamplesPerChunk(int index, long spc) {
data.addUnsignedInt(TABLE_OFFSET + (index * ENTRY_SIZE) + SAMPLES_PER_CHUNK, spc);
}
/**
* Return the description id for the specified entry
* @param index the table index
* @return the description id for the specified entry
*/
public long getDescriptionId(int index) {
return data.getUnsignedInt(TABLE_OFFSET + (index * ENTRY_SIZE) + DESCRIPTION_ID);
}
/**
* Set the description Id for the specified entry
* @param index the table index
* @param id the description id for the specified entry
*/
public void setDescriptionId(int index, long id) {
data.addUnsignedInt(TABLE_OFFSET + (index * ENTRY_SIZE) + DESCRIPTION_ID, id);
}
int lowerBoundPos = 0;
int lowerBoundSampleNum = 1;
/**
* Given a sample return the chunk that the sample is located in.
* @param sampleNum the sample number
* @return the chunk number that contains the sample
*/
public long sampleToChunk(long sampleNum) {
long entries = getNumEntries();
if (entries == 0) {
return 0;
}
if (lowerBoundSampleNum > sampleNum) {
lowerBoundSampleNum = 1;
lowerBoundPos = 0;
}
int i;
for (i = lowerBoundPos; i < entries - 1; i++) {
long maxSamplesInChunk = (getFirstChunk(i+1) - getFirstChunk(i)) * getSamplesPerChunk(i);
if (sampleNum < lowerBoundSampleNum + maxSamplesInChunk) {
break;
}
lowerBoundSampleNum += maxSamplesInChunk;
lowerBoundPos = i+1;
}
long chunkNum = ((sampleNum - lowerBoundSampleNum) / getSamplesPerChunk(i)) + getFirstChunk(i);
return chunkNum;
}
/**
* Cut the atom at the specified sample number. The update is done in place
* and the rest of the atom is filled with a free atom.
* @param sampleNum the sample number
* @return the
*/
public StscAtom cut(long sampleNum) {
long numEntries = getNumEntries();
int i;
int sampleNumCounter = 1;
for (i = 0; i < numEntries - 1; i++) {
long maxSamplesInChunk = (getFirstChunk(i+1) - getFirstChunk(i)) * getSamplesPerChunk(i);
if (sampleNum < sampleNumCounter + maxSamplesInChunk) {
// we've found the stsc with the sample, so we can create the new table
break;
}
sampleNumCounter += maxSamplesInChunk;
}
StscAtom cutStsc = new StscAtom();
// create the new table.
long newNumEntries = numEntries - i;
int entryNumber = 0;
long chunkNum = ((sampleNum - sampleNumCounter) / getSamplesPerChunk(i)) + getFirstChunk(i);
// check if the number of samples per chunk has changed
if ((sampleNum - sampleNumCounter) % getSamplesPerChunk(i) > 0) {
// the number of samples per chunk has changed, so we need to split
// the table entry into two entries
boolean split = (i == (numEntries-1)) || (getFirstChunk(i+1) - getFirstChunk(i) > 1);
if (split)
newNumEntries++;
cutStsc.allocateData(newNumEntries);
cutStsc.setNumEntries(newNumEntries);
// the first entry is for the new number of samples per chunk
cutStsc.setFirstChunk(entryNumber, 1);
long spc = getSamplesPerChunk(i) - (sampleNum - sampleNumCounter) % getSamplesPerChunk(i);
cutStsc.setSamplesPerChunk(entryNumber, spc);
cutStsc.setDescriptionId(entryNumber, getDescriptionId(i));
entryNumber++;
if (split) {
// the second entry is for the existing samples per chunk, but the number of samples has changed
cutStsc.setFirstChunk(entryNumber, 2);
cutStsc.setSamplesPerChunk(entryNumber, getSamplesPerChunk(i));
cutStsc.setDescriptionId(entryNumber, getDescriptionId(i));
entryNumber++;
}
i++;
}
else {
cutStsc.allocateData(newNumEntries);
cutStsc.setNumEntries(newNumEntries);
cutStsc.setFirstChunk(entryNumber, 1);
cutStsc.setSamplesPerChunk(entryNumber, getSamplesPerChunk(i));
cutStsc.setDescriptionId(entryNumber, getDescriptionId(i));
entryNumber++;
i++;
}
// copy the rest of the table, and change the chunk numbers
for (; i < numEntries; i++, entryNumber++) {
cutStsc.setFirstChunk(entryNumber, getFirstChunk(i) - chunkNum + 1);
cutStsc.setSamplesPerChunk(entryNumber, getSamplesPerChunk(i));
cutStsc.setDescriptionId(entryNumber, getDescriptionId(i));
}
return cutStsc;
}
@Override
public void accept(AtomVisitor v) throws AtomException {
v.visit(this);
}
}