package com.coremedia.iso.boxes; import com.coremedia.iso.IsoTypeReader; import com.coremedia.iso.IsoTypeWriter; import com.googlecode.mp4parser.AbstractFullBox; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static com.googlecode.mp4parser.util.CastUtils.l2i; /** *
 * aligned(8) class CompositionOffsetBox
 * extends FullBox(‘ctts’, version = 0, 0) {
 *  unsigned int(32) entry_count;
 *  int i;
 *  if (version==0) {
 *   for (i=0; i < entry_count; i++) {
 *    unsigned int(32) sample_count;
 *    unsigned int(32) sample_offset;
 *   }
 *  }
 *  else if (version == 1) {
 *   for (i=0; i < entry_count; i++) {
 *    unsigned int(32) sample_count;
 *    signed int(32) sample_offset;
 *   }
 *  }
 * }
 * 
*

* This box provides the offset between decoding time and composition time. * In version 0 of this box the decoding time must be less than the composition time, and * the offsets are expressed as unsigned numbers such that * CT(n) = DT(n) + CTTS(n) where CTTS(n) is the (uncompressed) table entry for sample n. *

* In version 1 of this box, the composition timeline and the decoding timeline are * still derived from each other, but the offsets are signed. * It is recommended that for the computed composition timestamps, there is * exactly one with the value 0 (zero). */ public class CompositionTimeToSample extends AbstractFullBox { public static final String TYPE = "ctts"; List entries = Collections.emptyList(); public CompositionTimeToSample() { super(TYPE); } protected long getContentSize() { return 8 + 8 * entries.size(); } public List getEntries() { return entries; } public void setEntries(List entries) { this.entries = entries; } @Override public void _parseDetails(ByteBuffer content) { parseVersionAndFlags(content); int numberOfEntries = l2i(IsoTypeReader.readUInt32(content)); entries = new ArrayList(numberOfEntries); for (int i = 0; i < numberOfEntries; i++) { Entry e = new Entry(l2i(IsoTypeReader.readUInt32(content)), content.getInt()); entries.add(e); } } @Override protected void getContent(ByteBuffer byteBuffer) { writeVersionAndFlags(byteBuffer); IsoTypeWriter.writeUInt32(byteBuffer, entries.size()); for (Entry entry : entries) { IsoTypeWriter.writeUInt32(byteBuffer, entry.getCount()); byteBuffer.putInt(entry.getOffset()); } } public static class Entry { int count; int offset; public Entry(int count, int offset) { this.count = count; this.offset = offset; } public int getCount() { return count; } public int getOffset() { return offset; } public void setCount(int count) { this.count = count; } public void setOffset(int offset) { this.offset = offset; } @Override public String toString() { return "Entry{" + "count=" + count + ", offset=" + offset + '}'; } } /** * Decompresses the list of entries and returns the list of composition times. * * @return decoding time per sample */ public static int[] blowupCompositionTimes(List entries) { long numOfSamples = 0; for (CompositionTimeToSample.Entry entry : entries) { numOfSamples += entry.getCount(); } assert numOfSamples <= Integer.MAX_VALUE; int[] decodingTime = new int[(int) numOfSamples]; int current = 0; for (CompositionTimeToSample.Entry entry : entries) { for (int i = 0; i < entry.getCount(); i++) { decodingTime[current++] = entry.getOffset(); } } return decodingTime; } }