1 package com.coremedia.iso.boxes; 2 3 import com.coremedia.iso.IsoTypeReader; 4 import com.coremedia.iso.IsoTypeWriter; 5 import com.googlecode.mp4parser.AbstractFullBox; 6 7 import java.nio.ByteBuffer; 8 import java.util.ArrayList; 9 import java.util.Collections; 10 import java.util.List; 11 12 import static com.googlecode.mp4parser.util.CastUtils.l2i; 13 14 /** 15 * <pre> 16 * aligned(8) class CompositionOffsetBox 17 * extends FullBox(‘ctts’, version = 0, 0) { 18 * unsigned int(32) entry_count; 19 * int i; 20 * if (version==0) { 21 * for (i=0; i < entry_count; i++) { 22 * unsigned int(32) sample_count; 23 * unsigned int(32) sample_offset; 24 * } 25 * } 26 * else if (version == 1) { 27 * for (i=0; i < entry_count; i++) { 28 * unsigned int(32) sample_count; 29 * signed int(32) sample_offset; 30 * } 31 * } 32 * } 33 * </pre> 34 * <p/> 35 * This box provides the offset between decoding time and composition time. 36 * In version 0 of this box the decoding time must be less than the composition time, and 37 * the offsets are expressed as unsigned numbers such that 38 * CT(n) = DT(n) + CTTS(n) where CTTS(n) is the (uncompressed) table entry for sample n. 39 * <p/> 40 * In version 1 of this box, the composition timeline and the decoding timeline are 41 * still derived from each other, but the offsets are signed. 42 * It is recommended that for the computed composition timestamps, there is 43 * exactly one with the value 0 (zero). 44 */ 45 public class CompositionTimeToSample extends AbstractFullBox { 46 public static final String TYPE = "ctts"; 47 48 List<Entry> entries = Collections.emptyList(); 49 CompositionTimeToSample()50 public CompositionTimeToSample() { 51 super(TYPE); 52 } 53 getContentSize()54 protected long getContentSize() { 55 return 8 + 8 * entries.size(); 56 } 57 getEntries()58 public List<Entry> getEntries() { 59 return entries; 60 } 61 setEntries(List<Entry> entries)62 public void setEntries(List<Entry> entries) { 63 this.entries = entries; 64 } 65 66 @Override _parseDetails(ByteBuffer content)67 public void _parseDetails(ByteBuffer content) { 68 parseVersionAndFlags(content); 69 int numberOfEntries = l2i(IsoTypeReader.readUInt32(content)); 70 entries = new ArrayList<Entry>(numberOfEntries); 71 for (int i = 0; i < numberOfEntries; i++) { 72 Entry e = new Entry(l2i(IsoTypeReader.readUInt32(content)), content.getInt()); 73 entries.add(e); 74 } 75 } 76 77 @Override getContent(ByteBuffer byteBuffer)78 protected void getContent(ByteBuffer byteBuffer) { 79 writeVersionAndFlags(byteBuffer); 80 IsoTypeWriter.writeUInt32(byteBuffer, entries.size()); 81 82 for (Entry entry : entries) { 83 IsoTypeWriter.writeUInt32(byteBuffer, entry.getCount()); 84 byteBuffer.putInt(entry.getOffset()); 85 } 86 87 } 88 89 90 public static class Entry { 91 int count; 92 int offset; 93 Entry(int count, int offset)94 public Entry(int count, int offset) { 95 this.count = count; 96 this.offset = offset; 97 } 98 getCount()99 public int getCount() { 100 return count; 101 } 102 getOffset()103 public int getOffset() { 104 return offset; 105 } 106 setCount(int count)107 public void setCount(int count) { 108 this.count = count; 109 } 110 setOffset(int offset)111 public void setOffset(int offset) { 112 this.offset = offset; 113 } 114 115 @Override toString()116 public String toString() { 117 return "Entry{" + 118 "count=" + count + 119 ", offset=" + offset + 120 '}'; 121 } 122 } 123 124 125 /** 126 * Decompresses the list of entries and returns the list of composition times. 127 * 128 * @return decoding time per sample 129 */ blowupCompositionTimes(List<CompositionTimeToSample.Entry> entries)130 public static int[] blowupCompositionTimes(List<CompositionTimeToSample.Entry> entries) { 131 long numOfSamples = 0; 132 for (CompositionTimeToSample.Entry entry : entries) { 133 numOfSamples += entry.getCount(); 134 } 135 assert numOfSamples <= Integer.MAX_VALUE; 136 int[] decodingTime = new int[(int) numOfSamples]; 137 138 int current = 0; 139 140 141 for (CompositionTimeToSample.Entry entry : entries) { 142 for (int i = 0; i < entry.getCount(); i++) { 143 decodingTime[current++] = entry.getOffset(); 144 } 145 } 146 147 return decodingTime; 148 } 149 150 } 151