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; /** * <pre> * 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; * } * } * } * </pre> * <p/> * 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. * <p/> * 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<Entry> entries = Collections.emptyList(); public CompositionTimeToSample() { super(TYPE); } protected long getContentSize() { return 8 + 8 * entries.size(); } public List<Entry> getEntries() { return entries; } public void setEntries(List<Entry> entries) { this.entries = entries; } @Override public void _parseDetails(ByteBuffer content) { parseVersionAndFlags(content); int numberOfEntries = l2i(IsoTypeReader.readUInt32(content)); entries = new ArrayList<Entry>(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<CompositionTimeToSample.Entry> 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; } }