1 /*
2  * Copyright 2009 castLabs GmbH, Berlin
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.coremedia.iso.boxes.fragment;
18 
19 import com.coremedia.iso.IsoTypeReader;
20 import com.coremedia.iso.IsoTypeWriter;
21 import com.coremedia.iso.boxes.MovieBox;
22 import com.googlecode.mp4parser.AbstractFullBox;
23 
24 import java.nio.ByteBuffer;
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 import static com.googlecode.mp4parser.util.CastUtils.l2i;
29 
30 /**
31  * aligned(8) class TrackRunBox
32  * extends FullBox('trun', 0, tr_flags) {
33  * unsigned int(32) sample_count;
34  * // the following are optional fields
35  * signed int(32) data_offset;
36  * unsigned int(32) first_sample_flags;
37  * // all fields in the following array are optional
38  * {
39  * unsigned int(32) sample_duration;
40  * unsigned int(32) sample_size;
41  * unsigned int(32) sample_flags
42  * unsigned int(32) sample_composition_time_offset;
43  * }[ sample_count ]
44  * }
45  */
46 
47 public class TrackRunBox extends AbstractFullBox {
48     public static final String TYPE = "trun";
49     private int dataOffset;
50     private SampleFlags firstSampleFlags;
51     private List<Entry> entries = new ArrayList<Entry>();
52 
53 
getEntries()54     public List<Entry> getEntries() {
55         return entries;
56     }
57 
58     public static class Entry {
59         private long sampleDuration;
60         private long sampleSize;
61         private SampleFlags sampleFlags;
62         private int sampleCompositionTimeOffset;
63 
Entry()64         public Entry() {
65         }
66 
Entry(long sampleDuration, long sampleSize, SampleFlags sampleFlags, int sampleCompositionTimeOffset)67         public Entry(long sampleDuration, long sampleSize, SampleFlags sampleFlags, int sampleCompositionTimeOffset) {
68             this.sampleDuration = sampleDuration;
69             this.sampleSize = sampleSize;
70             this.sampleFlags = sampleFlags;
71             this.sampleCompositionTimeOffset = sampleCompositionTimeOffset;
72         }
73 
getSampleDuration()74         public long getSampleDuration() {
75             return sampleDuration;
76         }
77 
getSampleSize()78         public long getSampleSize() {
79             return sampleSize;
80         }
81 
getSampleFlags()82         public SampleFlags getSampleFlags() {
83             return sampleFlags;
84         }
85 
getSampleCompositionTimeOffset()86         public int getSampleCompositionTimeOffset() {
87             return sampleCompositionTimeOffset;
88         }
89 
setSampleDuration(long sampleDuration)90         public void setSampleDuration(long sampleDuration) {
91             this.sampleDuration = sampleDuration;
92         }
93 
setSampleSize(long sampleSize)94         public void setSampleSize(long sampleSize) {
95             this.sampleSize = sampleSize;
96         }
97 
setSampleFlags(SampleFlags sampleFlags)98         public void setSampleFlags(SampleFlags sampleFlags) {
99             this.sampleFlags = sampleFlags;
100         }
101 
setSampleCompositionTimeOffset(int sampleCompositionTimeOffset)102         public void setSampleCompositionTimeOffset(int sampleCompositionTimeOffset) {
103             this.sampleCompositionTimeOffset = sampleCompositionTimeOffset;
104         }
105 
106         @Override
toString()107         public String toString() {
108             return "Entry{" +
109                     "sampleDuration=" + sampleDuration +
110                     ", sampleSize=" + sampleSize +
111                     ", sampleFlags=" + sampleFlags +
112                     ", sampleCompositionTimeOffset=" + sampleCompositionTimeOffset +
113                     '}';
114         }
115     }
116 
setDataOffset(int dataOffset)117     public void setDataOffset(int dataOffset) {
118         if (dataOffset == -1) {
119             setFlags(getFlags() & (0xFFFFFF ^ 1));
120         } else {
121             setFlags(getFlags() | 0x1); // turn on dataoffset
122         }
123         this.dataOffset = dataOffset;
124     }
125 
getSampleCompositionTimeOffsets()126     public long[] getSampleCompositionTimeOffsets() {
127         if (isSampleCompositionTimeOffsetPresent()) {
128             long[] result = new long[entries.size()];
129 
130             for (int i = 0; i < result.length; i++) {
131                 result[i] = entries.get(i).getSampleCompositionTimeOffset();
132             }
133             return result;
134         }
135         return null;
136     }
137 
getTrackExtendsBox()138     public TrackExtendsBox getTrackExtendsBox() {
139         final TrackFragmentHeaderBox tfhd = ((TrackFragmentBox) getParent()).getTrackFragmentHeaderBox();
140         final List<MovieBox> movieBoxes = tfhd.getIsoFile().getBoxes(MovieBox.class);
141         if (movieBoxes.size() == 0) {
142             return null;
143         }
144 
145         final List<TrackExtendsBox> trexBoxes = movieBoxes.get(0).getBoxes(TrackExtendsBox.class, true);
146         TrackExtendsBox trex = null;
147         for (TrackExtendsBox aTrex : trexBoxes) {
148             if (aTrex.getTrackId() == tfhd.getTrackId()) {
149                 trex = aTrex;
150             }
151         }
152         return trex;
153     }
154 
TrackRunBox()155     public TrackRunBox() {
156         super(TYPE);
157     }
158 
getContentSize()159     protected long getContentSize() {
160         long size = 8;
161         int flags = getFlags();
162 
163         if ((flags & 0x1) == 0x1) { //dataOffsetPresent
164             size += 4;
165         }
166         if ((flags & 0x4) == 0x4) { //firstSampleFlagsPresent
167             size += 4;
168         }
169 
170         long entrySize = 0;
171         if ((flags & 0x100) == 0x100) { //sampleDurationPresent
172             entrySize += 4;
173         }
174         if ((flags & 0x200) == 0x200) { //sampleSizePresent
175             entrySize += 4;
176         }
177         if ((flags & 0x400) == 0x400) { //sampleFlagsPresent
178             entrySize += 4;
179         }
180         if ((flags & 0x800) == 0x800) { //sampleCompositionTimeOffsetPresent
181             entrySize += 4;
182         }
183         size += entrySize * entries.size();
184         return size;
185     }
186 
getContent(ByteBuffer byteBuffer)187     protected void getContent(ByteBuffer byteBuffer) {
188         writeVersionAndFlags(byteBuffer);
189         IsoTypeWriter.writeUInt32(byteBuffer, entries.size());
190         int flags = getFlags();
191 
192         if ((flags & 0x1) == 1) { //dataOffsetPresent
193             IsoTypeWriter.writeUInt32(byteBuffer, dataOffset);
194         }
195         if ((flags & 0x4) == 0x4) { //firstSampleFlagsPresent
196             firstSampleFlags.getContent(byteBuffer);
197         }
198 
199         for (Entry entry : entries) {
200             if ((flags & 0x100) == 0x100) { //sampleDurationPresent
201                 IsoTypeWriter.writeUInt32(byteBuffer, entry.sampleDuration);
202             }
203             if ((flags & 0x200) == 0x200) { //sampleSizePresent
204                 IsoTypeWriter.writeUInt32(byteBuffer, entry.sampleSize);
205             }
206             if ((flags & 0x400) == 0x400) { //sampleFlagsPresent
207                 entry.sampleFlags.getContent(byteBuffer);
208             }
209             if ((flags & 0x800) == 0x800) { //sampleCompositionTimeOffsetPresent
210                 byteBuffer.putInt(entry.sampleCompositionTimeOffset);
211             }
212         }
213     }
214 
215     @Override
_parseDetails(ByteBuffer content)216     public void _parseDetails(ByteBuffer content) {
217         parseVersionAndFlags(content);
218         long sampleCount = IsoTypeReader.readUInt32(content);
219 
220         if ((getFlags() & 0x1) == 1) { //dataOffsetPresent
221             dataOffset = l2i(IsoTypeReader.readUInt32(content));
222         } else {
223             dataOffset = -1;
224         }
225         if ((getFlags() & 0x4) == 0x4) { //firstSampleFlagsPresent
226             firstSampleFlags = new SampleFlags(content);
227         }
228 
229         for (int i = 0; i < sampleCount; i++) {
230             Entry entry = new Entry();
231             if ((getFlags() & 0x100) == 0x100) { //sampleDurationPresent
232                 entry.sampleDuration = IsoTypeReader.readUInt32(content);
233             }
234             if ((getFlags() & 0x200) == 0x200) { //sampleSizePresent
235                 entry.sampleSize = IsoTypeReader.readUInt32(content);
236             }
237             if ((getFlags() & 0x400) == 0x400) { //sampleFlagsPresent
238                 entry.sampleFlags = new SampleFlags(content);
239             }
240             if ((getFlags() & 0x800) == 0x800) { //sampleCompositionTimeOffsetPresent
241                 entry.sampleCompositionTimeOffset = content.getInt();
242             }
243             entries.add(entry);
244         }
245 
246     }
247 
getSampleCount()248     public long getSampleCount() {
249         return entries.size();
250     }
251 
isDataOffsetPresent()252     public boolean isDataOffsetPresent() {
253         return (getFlags() & 0x1) == 1;
254     }
255 
isFirstSampleFlagsPresent()256     public boolean isFirstSampleFlagsPresent() {
257         return (getFlags() & 0x4) == 0x4;
258     }
259 
260 
isSampleSizePresent()261     public boolean isSampleSizePresent() {
262         return (getFlags() & 0x200) == 0x200;
263     }
264 
isSampleDurationPresent()265     public boolean isSampleDurationPresent() {
266         return (getFlags() & 0x100) == 0x100;
267     }
268 
isSampleFlagsPresent()269     public boolean isSampleFlagsPresent() {
270         return (getFlags() & 0x400) == 0x400;
271     }
272 
isSampleCompositionTimeOffsetPresent()273     public boolean isSampleCompositionTimeOffsetPresent() {
274         return (getFlags() & 0x800) == 0x800;
275     }
276 
setDataOffsetPresent(boolean v)277     public void setDataOffsetPresent(boolean v) {
278         if (v) {
279             setFlags(getFlags() | 0x01);
280         } else {
281             setFlags(getFlags() & (0xFFFFFF ^ 0x1));
282         }
283     }
284 
setSampleSizePresent(boolean v)285     public void setSampleSizePresent(boolean v) {
286         if (v) {
287             setFlags(getFlags() | 0x200);
288         } else {
289             setFlags(getFlags() & (0xFFFFFF ^ 0x200));
290         }
291     }
292 
setSampleDurationPresent(boolean v)293     public void setSampleDurationPresent(boolean v) {
294 
295         if (v) {
296             setFlags(getFlags() | 0x100);
297         } else {
298             setFlags(getFlags() & (0xFFFFFF ^ 0x100));
299         }
300     }
301 
setSampleFlagsPresent(boolean v)302     public void setSampleFlagsPresent(boolean v) {
303         if (v) {
304             setFlags(getFlags() | 0x400);
305         } else {
306             setFlags(getFlags() & (0xFFFFFF ^ 0x400));
307         }
308     }
309 
setSampleCompositionTimeOffsetPresent(boolean v)310     public void setSampleCompositionTimeOffsetPresent(boolean v) {
311         if (v) {
312             setFlags(getFlags() | 0x800);
313         } else {
314             setFlags(getFlags() & (0xFFFFFF ^ 0x800));
315         }
316 
317     }
318 
getDataOffset()319     public int getDataOffset() {
320         return dataOffset;
321     }
322 
getFirstSampleFlags()323     public SampleFlags getFirstSampleFlags() {
324         return firstSampleFlags;
325     }
326 
setFirstSampleFlags(SampleFlags firstSampleFlags)327     public void setFirstSampleFlags(SampleFlags firstSampleFlags) {
328         if (firstSampleFlags == null) {
329             setFlags(getFlags() & (0xFFFFFF ^ 0x4));
330         } else {
331             setFlags(getFlags() | 0x4);
332         }
333         this.firstSampleFlags = firstSampleFlags;
334     }
335 
336     @Override
toString()337     public String toString() {
338         final StringBuilder sb = new StringBuilder();
339         sb.append("TrackRunBox");
340         sb.append("{sampleCount=").append(entries.size());
341         sb.append(", dataOffset=").append(dataOffset);
342         sb.append(", dataOffsetPresent=").append(isDataOffsetPresent());
343         sb.append(", sampleSizePresent=").append(isSampleSizePresent());
344         sb.append(", sampleDurationPresent=").append(isSampleDurationPresent());
345         sb.append(", sampleFlagsPresentPresent=").append(isSampleFlagsPresent());
346         sb.append(", sampleCompositionTimeOffsetPresent=").append(isSampleCompositionTimeOffsetPresent());
347         sb.append(", firstSampleFlags=").append(firstSampleFlags);
348         sb.append('}');
349         return sb.toString();
350     }
351 
setEntries(List<Entry> entries)352     public void setEntries(List<Entry> entries) {
353         this.entries = entries;
354     }
355 }
356