1 /*
2  * Copyright (C) 2016 The Android Open Source Project
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 package org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.mp4;
17 
18 import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes.getMimeTypeFromMp4ObjectType;
19 
20 import android.util.Pair;
21 import androidx.annotation.Nullable;
22 import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
23 import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
24 import org.mozilla.thirdparty.com.google.android.exoplayer2.ParserException;
25 import org.mozilla.thirdparty.com.google.android.exoplayer2.audio.Ac3Util;
26 import org.mozilla.thirdparty.com.google.android.exoplayer2.audio.Ac4Util;
27 import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmInitData;
28 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.GaplessInfoHolder;
29 import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.Metadata;
30 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
31 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.CodecSpecificDataUtil;
32 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
33 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
34 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
35 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
36 import org.mozilla.thirdparty.com.google.android.exoplayer2.video.AvcConfig;
37 import org.mozilla.thirdparty.com.google.android.exoplayer2.video.DolbyVisionConfig;
38 import org.mozilla.thirdparty.com.google.android.exoplayer2.video.HevcConfig;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.List;
43 
44 /** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
45 @SuppressWarnings({"ConstantField"})
46 /* package */ final class AtomParsers {
47 
48   private static final String TAG = "AtomParsers";
49 
50   @SuppressWarnings("ConstantCaseForConstants")
51   private static final int TYPE_vide = 0x76696465;
52 
53   @SuppressWarnings("ConstantCaseForConstants")
54   private static final int TYPE_soun = 0x736f756e;
55 
56   @SuppressWarnings("ConstantCaseForConstants")
57   private static final int TYPE_text = 0x74657874;
58 
59   @SuppressWarnings("ConstantCaseForConstants")
60   private static final int TYPE_sbtl = 0x7362746c;
61 
62   @SuppressWarnings("ConstantCaseForConstants")
63   private static final int TYPE_subt = 0x73756274;
64 
65   @SuppressWarnings("ConstantCaseForConstants")
66   private static final int TYPE_clcp = 0x636c6370;
67 
68   @SuppressWarnings("ConstantCaseForConstants")
69   private static final int TYPE_meta = 0x6d657461;
70 
71   @SuppressWarnings("ConstantCaseForConstants")
72   private static final int TYPE_mdta = 0x6d647461;
73 
74   /**
75    * The threshold number of samples to trim from the start/end of an audio track when applying an
76    * edit below which gapless info can be used (rather than removing samples from the sample table).
77    */
78   private static final int MAX_GAPLESS_TRIM_SIZE_SAMPLES = 4;
79 
80   /** The magic signature for an Opus Identification header, as defined in RFC-7845. */
81   private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead");
82 
83   /**
84    * Parses a trak atom (defined in 14496-12).
85    *
86    * @param trak Atom to decode.
87    * @param mvhd Movie header atom, used to get the timescale.
88    * @param duration The duration in units of the timescale declared in the mvhd atom, or
89    *     {@link C#TIME_UNSET} if the duration should be parsed from the tkhd atom.
90    * @param drmInitData {@link DrmInitData} to be included in the format.
91    * @param ignoreEditLists Whether to ignore any edit lists in the trak box.
92    * @param isQuickTime True for QuickTime media. False otherwise.
93    * @return A {@link Track} instance, or {@code null} if the track's type isn't supported.
94    */
parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime)95   public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration,
96       DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime)
97       throws ParserException {
98     Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
99     int trackType = getTrackTypeForHdlr(parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data));
100     if (trackType == C.TRACK_TYPE_UNKNOWN) {
101       return null;
102     }
103 
104     TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
105     if (duration == C.TIME_UNSET) {
106       duration = tkhdData.duration;
107     }
108     long movieTimescale = parseMvhd(mvhd.data);
109     long durationUs;
110     if (duration == C.TIME_UNSET) {
111       durationUs = C.TIME_UNSET;
112     } else {
113       durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, movieTimescale);
114     }
115     Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
116         .getContainerAtomOfType(Atom.TYPE_stbl);
117 
118     Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
119     StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
120         tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime);
121     long[] editListDurations = null;
122     long[] editListMediaTimes = null;
123     if (!ignoreEditLists) {
124       Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
125       editListDurations = edtsData.first;
126       editListMediaTimes = edtsData.second;
127     }
128     return stsdData.format == null ? null
129         : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
130             stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes,
131             stsdData.nalUnitLengthFieldLength, editListDurations, editListMediaTimes);
132   }
133 
134   /**
135    * Parses an stbl atom (defined in 14496-12).
136    *
137    * @param track Track to which this sample table corresponds.
138    * @param stblAtom stbl (sample table) atom to decode.
139    * @param gaplessInfoHolder Holder to populate with gapless playback information.
140    * @return Sample table described by the stbl atom.
141    * @throws ParserException Thrown if the stbl atom can't be parsed.
142    */
parseStbl( Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder)143   public static TrackSampleTable parseStbl(
144       Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder)
145       throws ParserException {
146     SampleSizeBox sampleSizeBox;
147     Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz);
148     if (stszAtom != null) {
149       sampleSizeBox = new StszSampleSizeBox(stszAtom);
150     } else {
151       Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2);
152       if (stz2Atom == null) {
153         throw new ParserException("Track has no sample table size information");
154       }
155       sampleSizeBox = new Stz2SampleSizeBox(stz2Atom);
156     }
157 
158     int sampleCount = sampleSizeBox.getSampleCount();
159     if (sampleCount == 0) {
160       return new TrackSampleTable(
161           track,
162           /* offsets= */ new long[0],
163           /* sizes= */ new int[0],
164           /* maximumSize= */ 0,
165           /* timestampsUs= */ new long[0],
166           /* flags= */ new int[0],
167           /* durationUs= */ C.TIME_UNSET);
168     }
169 
170     // Entries are byte offsets of chunks.
171     boolean chunkOffsetsAreLongs = false;
172     Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco);
173     if (chunkOffsetsAtom == null) {
174       chunkOffsetsAreLongs = true;
175       chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64);
176     }
177     ParsableByteArray chunkOffsets = chunkOffsetsAtom.data;
178     // Entries are (chunk number, number of samples per chunk, sample description index).
179     ParsableByteArray stsc = stblAtom.getLeafAtomOfType(Atom.TYPE_stsc).data;
180     // Entries are (number of samples, timestamp delta between those samples).
181     ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data;
182     // Entries are the indices of samples that are synchronization samples.
183     Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss);
184     ParsableByteArray stss = stssAtom != null ? stssAtom.data : null;
185     // Entries are (number of samples, timestamp offset).
186     Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts);
187     ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
188 
189     // Prepare to read chunk information.
190     ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs);
191 
192     // Prepare to read sample timestamps.
193     stts.setPosition(Atom.FULL_HEADER_SIZE);
194     int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1;
195     int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
196     int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt();
197 
198     // Prepare to read sample timestamp offsets, if ctts is present.
199     int remainingSamplesAtTimestampOffset = 0;
200     int remainingTimestampOffsetChanges = 0;
201     int timestampOffset = 0;
202     if (ctts != null) {
203       ctts.setPosition(Atom.FULL_HEADER_SIZE);
204       remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt();
205     }
206 
207     int nextSynchronizationSampleIndex = C.INDEX_UNSET;
208     int remainingSynchronizationSamples = 0;
209     if (stss != null) {
210       stss.setPosition(Atom.FULL_HEADER_SIZE);
211       remainingSynchronizationSamples = stss.readUnsignedIntToInt();
212       if (remainingSynchronizationSamples > 0) {
213         nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1;
214       } else {
215         // Ignore empty stss boxes, which causes all samples to be treated as sync samples.
216         stss = null;
217       }
218     }
219 
220     // Fixed sample size raw audio may need to be rechunked.
221     boolean isFixedSampleSizeRawAudio =
222         sampleSizeBox.isFixedSampleSize()
223             && MimeTypes.AUDIO_RAW.equals(track.format.sampleMimeType)
224             && remainingTimestampDeltaChanges == 0
225             && remainingTimestampOffsetChanges == 0
226             && remainingSynchronizationSamples == 0;
227 
228     long[] offsets;
229     int[] sizes;
230     int maximumSize = 0;
231     long[] timestamps;
232     int[] flags;
233     long timestampTimeUnits = 0;
234     long duration;
235 
236     if (!isFixedSampleSizeRawAudio) {
237       offsets = new long[sampleCount];
238       sizes = new int[sampleCount];
239       timestamps = new long[sampleCount];
240       flags = new int[sampleCount];
241       long offset = 0;
242       int remainingSamplesInChunk = 0;
243 
244       for (int i = 0; i < sampleCount; i++) {
245         // Advance to the next chunk if necessary.
246         boolean chunkDataComplete = true;
247         while (remainingSamplesInChunk == 0 && (chunkDataComplete = chunkIterator.moveNext())) {
248           offset = chunkIterator.offset;
249           remainingSamplesInChunk = chunkIterator.numSamples;
250         }
251         if (!chunkDataComplete) {
252           Log.w(TAG, "Unexpected end of chunk data");
253           sampleCount = i;
254           offsets = Arrays.copyOf(offsets, sampleCount);
255           sizes = Arrays.copyOf(sizes, sampleCount);
256           timestamps = Arrays.copyOf(timestamps, sampleCount);
257           flags = Arrays.copyOf(flags, sampleCount);
258           break;
259         }
260 
261         // Add on the timestamp offset if ctts is present.
262         if (ctts != null) {
263           while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) {
264             remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt();
265             // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers
266             // in version 0 ctts boxes, however some streams violate the spec and use signed
267             // integers instead. It's safe to always decode sample offsets as signed integers here,
268             // because unsigned integers will still be parsed correctly (unless their top bit is
269             // set, which is never true in practice because sample offsets are always small).
270             timestampOffset = ctts.readInt();
271             remainingTimestampOffsetChanges--;
272           }
273           remainingSamplesAtTimestampOffset--;
274         }
275 
276         offsets[i] = offset;
277         sizes[i] = sampleSizeBox.readNextSampleSize();
278         if (sizes[i] > maximumSize) {
279           maximumSize = sizes[i];
280         }
281         timestamps[i] = timestampTimeUnits + timestampOffset;
282 
283         // All samples are synchronization samples if the stss is not present.
284         flags[i] = stss == null ? C.BUFFER_FLAG_KEY_FRAME : 0;
285         if (i == nextSynchronizationSampleIndex) {
286           flags[i] = C.BUFFER_FLAG_KEY_FRAME;
287           remainingSynchronizationSamples--;
288           if (remainingSynchronizationSamples > 0) {
289             nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1;
290           }
291         }
292 
293         // Add on the duration of this sample.
294         timestampTimeUnits += timestampDeltaInTimeUnits;
295         remainingSamplesAtTimestampDelta--;
296         if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) {
297           remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
298           // The BMFF spec (ISO 14496-12) states that sample deltas should be unsigned integers
299           // in stts boxes, however some streams violate the spec and use signed integers instead.
300           // See https://github.com/google/ExoPlayer/issues/3384. It's safe to always decode sample
301           // deltas as signed integers here, because unsigned integers will still be parsed
302           // correctly (unless their top bit is set, which is never true in practice because sample
303           // deltas are always small).
304           timestampDeltaInTimeUnits = stts.readInt();
305           remainingTimestampDeltaChanges--;
306         }
307 
308         offset += sizes[i];
309         remainingSamplesInChunk--;
310       }
311       duration = timestampTimeUnits + timestampOffset;
312 
313       // If the stbl's child boxes are not consistent the container is malformed, but the stream may
314       // still be playable.
315       boolean isCttsValid = true;
316       while (remainingTimestampOffsetChanges > 0) {
317         if (ctts.readUnsignedIntToInt() != 0) {
318           isCttsValid = false;
319           break;
320         }
321         ctts.readInt(); // Ignore offset.
322         remainingTimestampOffsetChanges--;
323       }
324       if (remainingSynchronizationSamples != 0
325           || remainingSamplesAtTimestampDelta != 0
326           || remainingSamplesInChunk != 0
327           || remainingTimestampDeltaChanges != 0
328           || remainingSamplesAtTimestampOffset != 0
329           || !isCttsValid) {
330         Log.w(
331             TAG,
332             "Inconsistent stbl box for track "
333                 + track.id
334                 + ": remainingSynchronizationSamples "
335                 + remainingSynchronizationSamples
336                 + ", remainingSamplesAtTimestampDelta "
337                 + remainingSamplesAtTimestampDelta
338                 + ", remainingSamplesInChunk "
339                 + remainingSamplesInChunk
340                 + ", remainingTimestampDeltaChanges "
341                 + remainingTimestampDeltaChanges
342                 + ", remainingSamplesAtTimestampOffset "
343                 + remainingSamplesAtTimestampOffset
344                 + (!isCttsValid ? ", ctts invalid" : ""));
345       }
346     } else {
347       long[] chunkOffsetsBytes = new long[chunkIterator.length];
348       int[] chunkSampleCounts = new int[chunkIterator.length];
349       while (chunkIterator.moveNext()) {
350         chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset;
351         chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples;
352       }
353       int fixedSampleSize =
354           Util.getPcmFrameSize(track.format.pcmEncoding, track.format.channelCount);
355       FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk(
356           fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits);
357       offsets = rechunkedResults.offsets;
358       sizes = rechunkedResults.sizes;
359       maximumSize = rechunkedResults.maximumSize;
360       timestamps = rechunkedResults.timestamps;
361       flags = rechunkedResults.flags;
362       duration = rechunkedResults.duration;
363     }
364     long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale);
365 
366     if (track.editListDurations == null) {
367       Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
368       return new TrackSampleTable(
369           track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
370     }
371 
372     // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a
373     // sync sample after reordering are not supported. Partial audio sample truncation is only
374     // supported in edit lists with one edit that removes less than MAX_GAPLESS_TRIM_SIZE_SAMPLES
375     // samples from the start/end of the track. This implementation handles simple
376     // discarding/delaying of samples. The extractor may place further restrictions on what edited
377     // streams are playable.
378 
379     if (track.editListDurations.length == 1
380         && track.type == C.TRACK_TYPE_AUDIO
381         && timestamps.length >= 2) {
382       long editStartTime = track.editListMediaTimes[0];
383       long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0],
384           track.timescale, track.movieTimescale);
385       if (canApplyEditWithGaplessInfo(timestamps, duration, editStartTime, editEndTime)) {
386         long paddingTimeUnits = duration - editEndTime;
387         long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0],
388             track.format.sampleRate, track.timescale);
389         long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits,
390             track.format.sampleRate, track.timescale);
391         if ((encoderDelay != 0 || encoderPadding != 0) && encoderDelay <= Integer.MAX_VALUE
392             && encoderPadding <= Integer.MAX_VALUE) {
393           gaplessInfoHolder.encoderDelay = (int) encoderDelay;
394           gaplessInfoHolder.encoderPadding = (int) encoderPadding;
395           Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
396           long editedDurationUs =
397               Util.scaleLargeTimestamp(
398                   track.editListDurations[0], C.MICROS_PER_SECOND, track.movieTimescale);
399           return new TrackSampleTable(
400               track, offsets, sizes, maximumSize, timestamps, flags, editedDurationUs);
401         }
402       }
403     }
404 
405     if (track.editListDurations.length == 1 && track.editListDurations[0] == 0) {
406       // The current version of the spec leaves handling of an edit with zero segment_duration in
407       // unfragmented files open to interpretation. We handle this as a special case and include all
408       // samples in the edit.
409       long editStartTime = track.editListMediaTimes[0];
410       for (int i = 0; i < timestamps.length; i++) {
411         timestamps[i] =
412             Util.scaleLargeTimestamp(
413                 timestamps[i] - editStartTime, C.MICROS_PER_SECOND, track.timescale);
414       }
415       durationUs =
416           Util.scaleLargeTimestamp(duration - editStartTime, C.MICROS_PER_SECOND, track.timescale);
417       return new TrackSampleTable(
418           track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
419     }
420 
421     // Omit any sample at the end point of an edit for audio tracks.
422     boolean omitClippedSample = track.type == C.TRACK_TYPE_AUDIO;
423 
424     // Count the number of samples after applying edits.
425     int editedSampleCount = 0;
426     int nextSampleIndex = 0;
427     boolean copyMetadata = false;
428     int[] startIndices = new int[track.editListDurations.length];
429     int[] endIndices = new int[track.editListDurations.length];
430     for (int i = 0; i < track.editListDurations.length; i++) {
431       long editMediaTime = track.editListMediaTimes[i];
432       if (editMediaTime != -1) {
433         long editDuration =
434             Util.scaleLargeTimestamp(
435                 track.editListDurations[i], track.timescale, track.movieTimescale);
436         startIndices[i] =
437             Util.binarySearchFloor(
438                 timestamps, editMediaTime, /* inclusive= */ true, /* stayInBounds= */ true);
439         endIndices[i] =
440             Util.binarySearchCeil(
441                 timestamps,
442                 editMediaTime + editDuration,
443                 /* inclusive= */ omitClippedSample,
444                 /* stayInBounds= */ false);
445         while (startIndices[i] < endIndices[i]
446             && (flags[startIndices[i]] & C.BUFFER_FLAG_KEY_FRAME) == 0) {
447           // Applying the edit correctly would require prerolling from the previous sync sample. In
448           // the current implementation we advance to the next sync sample instead. Only other
449           // tracks (i.e. audio) will be rendered until the time of the first sync sample.
450           // See https://github.com/google/ExoPlayer/issues/1659.
451           startIndices[i]++;
452         }
453         editedSampleCount += endIndices[i] - startIndices[i];
454         copyMetadata |= nextSampleIndex != startIndices[i];
455         nextSampleIndex = endIndices[i];
456       }
457     }
458     copyMetadata |= editedSampleCount != sampleCount;
459 
460     // Calculate edited sample timestamps and update the corresponding metadata arrays.
461     long[] editedOffsets = copyMetadata ? new long[editedSampleCount] : offsets;
462     int[] editedSizes = copyMetadata ? new int[editedSampleCount] : sizes;
463     int editedMaximumSize = copyMetadata ? 0 : maximumSize;
464     int[] editedFlags = copyMetadata ? new int[editedSampleCount] : flags;
465     long[] editedTimestamps = new long[editedSampleCount];
466     long pts = 0;
467     int sampleIndex = 0;
468     for (int i = 0; i < track.editListDurations.length; i++) {
469       long editMediaTime = track.editListMediaTimes[i];
470       int startIndex = startIndices[i];
471       int endIndex = endIndices[i];
472       if (copyMetadata) {
473         int count = endIndex - startIndex;
474         System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count);
475         System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count);
476         System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count);
477       }
478       for (int j = startIndex; j < endIndex; j++) {
479         long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
480         long timeInSegmentUs =
481             Util.scaleLargeTimestamp(
482                 Math.max(0, timestamps[j] - editMediaTime), C.MICROS_PER_SECOND, track.timescale);
483         editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs;
484         if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) {
485           editedMaximumSize = sizes[j];
486         }
487         sampleIndex++;
488       }
489       pts += track.editListDurations[i];
490     }
491     long editedDurationUs =
492         Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
493     return new TrackSampleTable(
494         track,
495         editedOffsets,
496         editedSizes,
497         editedMaximumSize,
498         editedTimestamps,
499         editedFlags,
500         editedDurationUs);
501   }
502 
503   /**
504    * Parses a udta atom.
505    *
506    * @param udtaAtom The udta (user data) atom to decode.
507    * @param isQuickTime True for QuickTime media. False otherwise.
508    * @return Parsed metadata, or null.
509    */
510   @Nullable
parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime)511   public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) {
512     if (isQuickTime) {
513       // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
514       // decode one.
515       return null;
516     }
517     ParsableByteArray udtaData = udtaAtom.data;
518     udtaData.setPosition(Atom.HEADER_SIZE);
519     while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) {
520       int atomPosition = udtaData.getPosition();
521       int atomSize = udtaData.readInt();
522       int atomType = udtaData.readInt();
523       if (atomType == Atom.TYPE_meta) {
524         udtaData.setPosition(atomPosition);
525         return parseUdtaMeta(udtaData, atomPosition + atomSize);
526       }
527       udtaData.setPosition(atomPosition + atomSize);
528     }
529     return null;
530   }
531 
532   /**
533    * Parses a metadata meta atom if it contains metadata with handler 'mdta'.
534    *
535    * @param meta The metadata atom to decode.
536    * @return Parsed metadata, or null.
537    */
538   @Nullable
parseMdtaFromMeta(Atom.ContainerAtom meta)539   public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) {
540     Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr);
541     Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys);
542     Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst);
543     if (hdlrAtom == null
544         || keysAtom == null
545         || ilstAtom == null
546         || AtomParsers.parseHdlr(hdlrAtom.data) != TYPE_mdta) {
547       // There isn't enough information to parse the metadata, or the handler type is unexpected.
548       return null;
549     }
550 
551     // Parse metadata keys.
552     ParsableByteArray keys = keysAtom.data;
553     keys.setPosition(Atom.FULL_HEADER_SIZE);
554     int entryCount = keys.readInt();
555     String[] keyNames = new String[entryCount];
556     for (int i = 0; i < entryCount; i++) {
557       int entrySize = keys.readInt();
558       keys.skipBytes(4); // keyNamespace
559       int keySize = entrySize - 8;
560       keyNames[i] = keys.readString(keySize);
561     }
562 
563     // Parse metadata items.
564     ParsableByteArray ilst = ilstAtom.data;
565     ilst.setPosition(Atom.HEADER_SIZE);
566     ArrayList<Metadata.Entry> entries = new ArrayList<>();
567     while (ilst.bytesLeft() > Atom.HEADER_SIZE) {
568       int atomPosition = ilst.getPosition();
569       int atomSize = ilst.readInt();
570       int keyIndex = ilst.readInt() - 1;
571       if (keyIndex >= 0 && keyIndex < keyNames.length) {
572         String key = keyNames[keyIndex];
573         Metadata.Entry entry =
574             MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key);
575         if (entry != null) {
576           entries.add(entry);
577         }
578       } else {
579         Log.w(TAG, "Skipped metadata with unknown key index: " + keyIndex);
580       }
581       ilst.setPosition(atomPosition + atomSize);
582     }
583     return entries.isEmpty() ? null : new Metadata(entries);
584   }
585 
586   @Nullable
parseUdtaMeta(ParsableByteArray meta, int limit)587   private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) {
588     meta.skipBytes(Atom.FULL_HEADER_SIZE);
589     while (meta.getPosition() < limit) {
590       int atomPosition = meta.getPosition();
591       int atomSize = meta.readInt();
592       int atomType = meta.readInt();
593       if (atomType == Atom.TYPE_ilst) {
594         meta.setPosition(atomPosition);
595         return parseIlst(meta, atomPosition + atomSize);
596       }
597       meta.setPosition(atomPosition + atomSize);
598     }
599     return null;
600   }
601 
602   @Nullable
parseIlst(ParsableByteArray ilst, int limit)603   private static Metadata parseIlst(ParsableByteArray ilst, int limit) {
604     ilst.skipBytes(Atom.HEADER_SIZE);
605     ArrayList<Metadata.Entry> entries = new ArrayList<>();
606     while (ilst.getPosition() < limit) {
607       Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst);
608       if (entry != null) {
609         entries.add(entry);
610       }
611     }
612     return entries.isEmpty() ? null : new Metadata(entries);
613   }
614 
615   /**
616    * Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie.
617    *
618    * @param mvhd Contents of the mvhd atom to be parsed.
619    * @return Timescale for the movie.
620    */
parseMvhd(ParsableByteArray mvhd)621   private static long parseMvhd(ParsableByteArray mvhd) {
622     mvhd.setPosition(Atom.HEADER_SIZE);
623     int fullAtom = mvhd.readInt();
624     int version = Atom.parseFullAtomVersion(fullAtom);
625     mvhd.skipBytes(version == 0 ? 8 : 16);
626     return mvhd.readUnsignedInt();
627   }
628 
629   /**
630    * Parses a tkhd atom (defined in 14496-12).
631    *
632    * @return An object containing the parsed data.
633    */
parseTkhd(ParsableByteArray tkhd)634   private static TkhdData parseTkhd(ParsableByteArray tkhd) {
635     tkhd.setPosition(Atom.HEADER_SIZE);
636     int fullAtom = tkhd.readInt();
637     int version = Atom.parseFullAtomVersion(fullAtom);
638 
639     tkhd.skipBytes(version == 0 ? 8 : 16);
640     int trackId = tkhd.readInt();
641 
642     tkhd.skipBytes(4);
643     boolean durationUnknown = true;
644     int durationPosition = tkhd.getPosition();
645     int durationByteCount = version == 0 ? 4 : 8;
646     for (int i = 0; i < durationByteCount; i++) {
647       if (tkhd.data[durationPosition + i] != -1) {
648         durationUnknown = false;
649         break;
650       }
651     }
652     long duration;
653     if (durationUnknown) {
654       tkhd.skipBytes(durationByteCount);
655       duration = C.TIME_UNSET;
656     } else {
657       duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
658       if (duration == 0) {
659         // 0 duration normally indicates that the file is fully fragmented (i.e. all of the media
660         // samples are in fragments). Treat as unknown.
661         duration = C.TIME_UNSET;
662       }
663     }
664 
665     tkhd.skipBytes(16);
666     int a00 = tkhd.readInt();
667     int a01 = tkhd.readInt();
668     tkhd.skipBytes(4);
669     int a10 = tkhd.readInt();
670     int a11 = tkhd.readInt();
671 
672     int rotationDegrees;
673     int fixedOne = 65536;
674     if (a00 == 0 && a01 == fixedOne && a10 == -fixedOne && a11 == 0) {
675       rotationDegrees = 90;
676     } else if (a00 == 0 && a01 == -fixedOne && a10 == fixedOne && a11 == 0) {
677       rotationDegrees = 270;
678     } else if (a00 == -fixedOne && a01 == 0 && a10 == 0 && a11 == -fixedOne) {
679       rotationDegrees = 180;
680     } else {
681       // Only 0, 90, 180 and 270 are supported. Treat anything else as 0.
682       rotationDegrees = 0;
683     }
684 
685     return new TkhdData(trackId, duration, rotationDegrees);
686   }
687 
688   /**
689    * Parses an hdlr atom.
690    *
691    * @param hdlr The hdlr atom to decode.
692    * @return The handler value.
693    */
parseHdlr(ParsableByteArray hdlr)694   private static int parseHdlr(ParsableByteArray hdlr) {
695     hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4);
696     return hdlr.readInt();
697   }
698 
699   /** Returns the track type for a given handler value. */
getTrackTypeForHdlr(int hdlr)700   private static int getTrackTypeForHdlr(int hdlr) {
701     if (hdlr == TYPE_soun) {
702       return C.TRACK_TYPE_AUDIO;
703     } else if (hdlr == TYPE_vide) {
704       return C.TRACK_TYPE_VIDEO;
705     } else if (hdlr == TYPE_text || hdlr == TYPE_sbtl || hdlr == TYPE_subt || hdlr == TYPE_clcp) {
706       return C.TRACK_TYPE_TEXT;
707     } else if (hdlr == TYPE_meta) {
708       return C.TRACK_TYPE_METADATA;
709     } else {
710       return C.TRACK_TYPE_UNKNOWN;
711     }
712   }
713 
714   /**
715    * Parses an mdhd atom (defined in 14496-12).
716    *
717    * @param mdhd The mdhd atom to decode.
718    * @return A pair consisting of the media timescale defined as the number of time units that pass
719    * in one second, and the language code.
720    */
parseMdhd(ParsableByteArray mdhd)721   private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) {
722     mdhd.setPosition(Atom.HEADER_SIZE);
723     int fullAtom = mdhd.readInt();
724     int version = Atom.parseFullAtomVersion(fullAtom);
725     mdhd.skipBytes(version == 0 ? 8 : 16);
726     long timescale = mdhd.readUnsignedInt();
727     mdhd.skipBytes(version == 0 ? 4 : 8);
728     int languageCode = mdhd.readUnsignedShort();
729     String language =
730         ""
731             + (char) (((languageCode >> 10) & 0x1F) + 0x60)
732             + (char) (((languageCode >> 5) & 0x1F) + 0x60)
733             + (char) ((languageCode & 0x1F) + 0x60);
734     return Pair.create(timescale, language);
735   }
736 
737   /**
738    * Parses a stsd atom (defined in 14496-12).
739    *
740    * @param stsd The stsd atom to decode.
741    * @param trackId The track's identifier in its container.
742    * @param rotationDegrees The rotation of the track in degrees.
743    * @param language The language of the track.
744    * @param drmInitData {@link DrmInitData} to be included in the format.
745    * @param isQuickTime True for QuickTime media. False otherwise.
746    * @return An object containing the parsed data.
747    */
parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees, String language, DrmInitData drmInitData, boolean isQuickTime)748   private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees,
749       String language, DrmInitData drmInitData, boolean isQuickTime) throws ParserException {
750     stsd.setPosition(Atom.FULL_HEADER_SIZE);
751     int numberOfEntries = stsd.readInt();
752     StsdData out = new StsdData(numberOfEntries);
753     for (int i = 0; i < numberOfEntries; i++) {
754       int childStartPosition = stsd.getPosition();
755       int childAtomSize = stsd.readInt();
756       Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
757       int childAtomType = stsd.readInt();
758       if (childAtomType == Atom.TYPE_avc1
759           || childAtomType == Atom.TYPE_avc3
760           || childAtomType == Atom.TYPE_encv
761           || childAtomType == Atom.TYPE_mp4v
762           || childAtomType == Atom.TYPE_hvc1
763           || childAtomType == Atom.TYPE_hev1
764           || childAtomType == Atom.TYPE_s263
765           || childAtomType == Atom.TYPE_vp08
766           || childAtomType == Atom.TYPE_vp09
767           || childAtomType == Atom.TYPE_av01
768           || childAtomType == Atom.TYPE_dvav
769           || childAtomType == Atom.TYPE_dva1
770           || childAtomType == Atom.TYPE_dvhe
771           || childAtomType == Atom.TYPE_dvh1) {
772         parseVideoSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
773             rotationDegrees, drmInitData, out, i);
774       } else if (childAtomType == Atom.TYPE_mp4a
775           || childAtomType == Atom.TYPE_enca
776           || childAtomType == Atom.TYPE_ac_3
777           || childAtomType == Atom.TYPE_ec_3
778           || childAtomType == Atom.TYPE_ac_4
779           || childAtomType == Atom.TYPE_dtsc
780           || childAtomType == Atom.TYPE_dtse
781           || childAtomType == Atom.TYPE_dtsh
782           || childAtomType == Atom.TYPE_dtsl
783           || childAtomType == Atom.TYPE_samr
784           || childAtomType == Atom.TYPE_sawb
785           || childAtomType == Atom.TYPE_lpcm
786           || childAtomType == Atom.TYPE_sowt
787           || childAtomType == Atom.TYPE_twos
788           || childAtomType == Atom.TYPE__mp3
789           || childAtomType == Atom.TYPE_alac
790           || childAtomType == Atom.TYPE_alaw
791           || childAtomType == Atom.TYPE_ulaw
792           || childAtomType == Atom.TYPE_Opus
793           || childAtomType == Atom.TYPE_fLaC) {
794         parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
795             language, isQuickTime, drmInitData, out, i);
796       } else if (childAtomType == Atom.TYPE_TTML || childAtomType == Atom.TYPE_tx3g
797           || childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp
798           || childAtomType == Atom.TYPE_c608) {
799         parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
800             language, out);
801       } else if (childAtomType == Atom.TYPE_camm) {
802         out.format = Format.createSampleFormat(Integer.toString(trackId),
803             MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, null);
804       }
805       stsd.setPosition(childStartPosition + childAtomSize);
806     }
807     return out;
808   }
809 
parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, int atomSize, int trackId, String language, StsdData out)810   private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position,
811       int atomSize, int trackId, String language, StsdData out) throws ParserException {
812     parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
813 
814     // Default values.
815     List<byte[]> initializationData = null;
816     long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE;
817 
818     String mimeType;
819     if (atomType == Atom.TYPE_TTML) {
820       mimeType = MimeTypes.APPLICATION_TTML;
821     } else if (atomType == Atom.TYPE_tx3g) {
822       mimeType = MimeTypes.APPLICATION_TX3G;
823       int sampleDescriptionLength = atomSize - Atom.HEADER_SIZE - 8;
824       byte[] sampleDescriptionData = new byte[sampleDescriptionLength];
825       parent.readBytes(sampleDescriptionData, 0, sampleDescriptionLength);
826       initializationData = Collections.singletonList(sampleDescriptionData);
827     } else if (atomType == Atom.TYPE_wvtt) {
828       mimeType = MimeTypes.APPLICATION_MP4VTT;
829     } else if (atomType == Atom.TYPE_stpp) {
830       mimeType = MimeTypes.APPLICATION_TTML;
831       subsampleOffsetUs = 0; // Subsample timing is absolute.
832     } else if (atomType == Atom.TYPE_c608) {
833       // Defined by the QuickTime File Format specification.
834       mimeType = MimeTypes.APPLICATION_MP4CEA608;
835       out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
836     } else {
837       // Never happens.
838       throw new IllegalStateException();
839     }
840 
841     out.format =
842         Format.createTextSampleFormat(
843             Integer.toString(trackId),
844             mimeType,
845             /* codecs= */ null,
846             /* bitrate= */ Format.NO_VALUE,
847             /* selectionFlags= */ 0,
848             language,
849             /* accessibilityChannel= */ Format.NO_VALUE,
850             /* drmInitData= */ null,
851             subsampleOffsetUs,
852             initializationData);
853   }
854 
parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, int entryIndex)855   private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position,
856       int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out,
857       int entryIndex) throws ParserException {
858     parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
859 
860     parent.skipBytes(16);
861     int width = parent.readUnsignedShort();
862     int height = parent.readUnsignedShort();
863     boolean pixelWidthHeightRatioFromPasp = false;
864     float pixelWidthHeightRatio = 1;
865     parent.skipBytes(50);
866 
867     int childPosition = parent.getPosition();
868     if (atomType == Atom.TYPE_encv) {
869       Pair<Integer, TrackEncryptionBox> sampleEntryEncryptionData = parseSampleEntryEncryptionData(
870           parent, position, size);
871       if (sampleEntryEncryptionData != null) {
872         atomType = sampleEntryEncryptionData.first;
873         drmInitData = drmInitData == null ? null
874             : drmInitData.copyWithSchemeType(sampleEntryEncryptionData.second.schemeType);
875         out.trackEncryptionBoxes[entryIndex] = sampleEntryEncryptionData.second;
876       }
877       parent.setPosition(childPosition);
878     }
879     // TODO: Uncomment when [Internal: b/63092960] is fixed.
880     // else {
881     //   drmInitData = null;
882     // }
883 
884     List<byte[]> initializationData = null;
885     String mimeType = null;
886     String codecs = null;
887     byte[] projectionData = null;
888     @C.StereoMode
889     int stereoMode = Format.NO_VALUE;
890     while (childPosition - position < size) {
891       parent.setPosition(childPosition);
892       int childStartPosition = parent.getPosition();
893       int childAtomSize = parent.readInt();
894       if (childAtomSize == 0 && parent.getPosition() - position == size) {
895         // Handle optional terminating four zero bytes in MOV files.
896         break;
897       }
898       Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
899       int childAtomType = parent.readInt();
900       if (childAtomType == Atom.TYPE_avcC) {
901         Assertions.checkState(mimeType == null);
902         mimeType = MimeTypes.VIDEO_H264;
903         parent.setPosition(childStartPosition + Atom.HEADER_SIZE);
904         AvcConfig avcConfig = AvcConfig.parse(parent);
905         initializationData = avcConfig.initializationData;
906         out.nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength;
907         if (!pixelWidthHeightRatioFromPasp) {
908           pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio;
909         }
910       } else if (childAtomType == Atom.TYPE_hvcC) {
911         Assertions.checkState(mimeType == null);
912         mimeType = MimeTypes.VIDEO_H265;
913         parent.setPosition(childStartPosition + Atom.HEADER_SIZE);
914         HevcConfig hevcConfig = HevcConfig.parse(parent);
915         initializationData = hevcConfig.initializationData;
916         out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength;
917       } else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) {
918         DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent);
919         if (dolbyVisionConfig != null) {
920           codecs = dolbyVisionConfig.codecs;
921           mimeType = MimeTypes.VIDEO_DOLBY_VISION;
922         }
923       } else if (childAtomType == Atom.TYPE_vpcC) {
924         Assertions.checkState(mimeType == null);
925         mimeType = (atomType == Atom.TYPE_vp08) ? MimeTypes.VIDEO_VP8 : MimeTypes.VIDEO_VP9;
926       } else if (childAtomType == Atom.TYPE_av1C) {
927         Assertions.checkState(mimeType == null);
928         mimeType = MimeTypes.VIDEO_AV1;
929       } else if (childAtomType == Atom.TYPE_d263) {
930         Assertions.checkState(mimeType == null);
931         mimeType = MimeTypes.VIDEO_H263;
932       } else if (childAtomType == Atom.TYPE_esds) {
933         Assertions.checkState(mimeType == null);
934         Pair<String, byte[]> mimeTypeAndInitializationData =
935             parseEsdsFromParent(parent, childStartPosition);
936         mimeType = mimeTypeAndInitializationData.first;
937         initializationData = Collections.singletonList(mimeTypeAndInitializationData.second);
938       } else if (childAtomType == Atom.TYPE_pasp) {
939         pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
940         pixelWidthHeightRatioFromPasp = true;
941       } else if (childAtomType == Atom.TYPE_sv3d) {
942         projectionData = parseProjFromParent(parent, childStartPosition, childAtomSize);
943       } else if (childAtomType == Atom.TYPE_st3d) {
944         int version = parent.readUnsignedByte();
945         parent.skipBytes(3); // Flags.
946         if (version == 0) {
947           int layout = parent.readUnsignedByte();
948           switch (layout) {
949             case 0:
950               stereoMode = C.STEREO_MODE_MONO;
951               break;
952             case 1:
953               stereoMode = C.STEREO_MODE_TOP_BOTTOM;
954               break;
955             case 2:
956               stereoMode = C.STEREO_MODE_LEFT_RIGHT;
957               break;
958             case 3:
959               stereoMode = C.STEREO_MODE_STEREO_MESH;
960               break;
961             default:
962               break;
963           }
964         }
965       }
966       childPosition += childAtomSize;
967     }
968 
969     // If the media type was not recognized, ignore the track.
970     if (mimeType == null) {
971       return;
972     }
973 
974     out.format =
975         Format.createVideoSampleFormat(
976             Integer.toString(trackId),
977             mimeType,
978             codecs,
979             /* bitrate= */ Format.NO_VALUE,
980             /* maxInputSize= */ Format.NO_VALUE,
981             width,
982             height,
983             /* frameRate= */ Format.NO_VALUE,
984             initializationData,
985             rotationDegrees,
986             pixelWidthHeightRatio,
987             projectionData,
988             stereoMode,
989             /* colorInfo= */ null,
990             drmInitData);
991   }
992 
993   /**
994    * Parses the edts atom (defined in 14496-12 subsection 8.6.5).
995    *
996    * @param edtsAtom edts (edit box) atom to decode.
997    * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are
998    *     not present.
999    */
parseEdts(Atom.ContainerAtom edtsAtom)1000   private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) {
1001     Atom.LeafAtom elst;
1002     if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) {
1003       return Pair.create(null, null);
1004     }
1005     ParsableByteArray elstData = elst.data;
1006     elstData.setPosition(Atom.HEADER_SIZE);
1007     int fullAtom = elstData.readInt();
1008     int version = Atom.parseFullAtomVersion(fullAtom);
1009     int entryCount = elstData.readUnsignedIntToInt();
1010     long[] editListDurations = new long[entryCount];
1011     long[] editListMediaTimes = new long[entryCount];
1012     for (int i = 0; i < entryCount; i++) {
1013       editListDurations[i] =
1014           version == 1 ? elstData.readUnsignedLongToLong() : elstData.readUnsignedInt();
1015       editListMediaTimes[i] = version == 1 ? elstData.readLong() : elstData.readInt();
1016       int mediaRateInteger = elstData.readShort();
1017       if (mediaRateInteger != 1) {
1018         // The extractor does not handle dwell edits (mediaRateInteger == 0).
1019         throw new IllegalArgumentException("Unsupported media rate.");
1020       }
1021       elstData.skipBytes(2);
1022     }
1023     return Pair.create(editListDurations, editListMediaTimes);
1024   }
1025 
parsePaspFromParent(ParsableByteArray parent, int position)1026   private static float parsePaspFromParent(ParsableByteArray parent, int position) {
1027     parent.setPosition(position + Atom.HEADER_SIZE);
1028     int hSpacing = parent.readUnsignedIntToInt();
1029     int vSpacing = parent.readUnsignedIntToInt();
1030     return (float) hSpacing / vSpacing;
1031   }
1032 
parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData, StsdData out, int entryIndex)1033   private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
1034       int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData,
1035       StsdData out, int entryIndex) throws ParserException {
1036     parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
1037 
1038     int quickTimeSoundDescriptionVersion = 0;
1039     if (isQuickTime) {
1040       quickTimeSoundDescriptionVersion = parent.readUnsignedShort();
1041       parent.skipBytes(6);
1042     } else {
1043       parent.skipBytes(8);
1044     }
1045 
1046     int channelCount;
1047     int sampleRate;
1048     @C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
1049 
1050     if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
1051       channelCount = parent.readUnsignedShort();
1052       parent.skipBytes(6);  // sampleSize, compressionId, packetSize.
1053       sampleRate = parent.readUnsignedFixedPoint1616();
1054 
1055       if (quickTimeSoundDescriptionVersion == 1) {
1056         parent.skipBytes(16);
1057       }
1058     } else if (quickTimeSoundDescriptionVersion == 2) {
1059       parent.skipBytes(16);  // always[3,16,Minus2,0,65536], sizeOfStructOnly
1060 
1061       sampleRate = (int) Math.round(parent.readDouble());
1062       channelCount = parent.readUnsignedIntToInt();
1063 
1064       // Skip always7F000000, sampleSize, formatSpecificFlags, constBytesPerAudioPacket,
1065       // constLPCMFramesPerAudioPacket.
1066       parent.skipBytes(20);
1067     } else {
1068       // Unsupported version.
1069       return;
1070     }
1071 
1072     int childPosition = parent.getPosition();
1073     if (atomType == Atom.TYPE_enca) {
1074       Pair<Integer, TrackEncryptionBox> sampleEntryEncryptionData = parseSampleEntryEncryptionData(
1075           parent, position, size);
1076       if (sampleEntryEncryptionData != null) {
1077         atomType = sampleEntryEncryptionData.first;
1078         drmInitData = drmInitData == null ? null
1079             : drmInitData.copyWithSchemeType(sampleEntryEncryptionData.second.schemeType);
1080         out.trackEncryptionBoxes[entryIndex] = sampleEntryEncryptionData.second;
1081       }
1082       parent.setPosition(childPosition);
1083     }
1084     // TODO: Uncomment when [Internal: b/63092960] is fixed.
1085     // else {
1086     //   drmInitData = null;
1087     // }
1088 
1089     // If the atom type determines a MIME type, set it immediately.
1090     String mimeType = null;
1091     if (atomType == Atom.TYPE_ac_3) {
1092       mimeType = MimeTypes.AUDIO_AC3;
1093     } else if (atomType == Atom.TYPE_ec_3) {
1094       mimeType = MimeTypes.AUDIO_E_AC3;
1095     } else if (atomType == Atom.TYPE_ac_4) {
1096       mimeType = MimeTypes.AUDIO_AC4;
1097     } else if (atomType == Atom.TYPE_dtsc) {
1098       mimeType = MimeTypes.AUDIO_DTS;
1099     } else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) {
1100       mimeType = MimeTypes.AUDIO_DTS_HD;
1101     } else if (atomType == Atom.TYPE_dtse) {
1102       mimeType = MimeTypes.AUDIO_DTS_EXPRESS;
1103     } else if (atomType == Atom.TYPE_samr) {
1104       mimeType = MimeTypes.AUDIO_AMR_NB;
1105     } else if (atomType == Atom.TYPE_sawb) {
1106       mimeType = MimeTypes.AUDIO_AMR_WB;
1107     } else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) {
1108       mimeType = MimeTypes.AUDIO_RAW;
1109       pcmEncoding = C.ENCODING_PCM_16BIT;
1110     } else if (atomType == Atom.TYPE_twos) {
1111       mimeType = MimeTypes.AUDIO_RAW;
1112       pcmEncoding = C.ENCODING_PCM_16BIT_BIG_ENDIAN;
1113     } else if (atomType == Atom.TYPE__mp3) {
1114       mimeType = MimeTypes.AUDIO_MPEG;
1115     } else if (atomType == Atom.TYPE_alac) {
1116       mimeType = MimeTypes.AUDIO_ALAC;
1117     } else if (atomType == Atom.TYPE_alaw) {
1118       mimeType = MimeTypes.AUDIO_ALAW;
1119     } else if (atomType == Atom.TYPE_ulaw) {
1120       mimeType = MimeTypes.AUDIO_MLAW;
1121     } else if (atomType == Atom.TYPE_Opus) {
1122       mimeType = MimeTypes.AUDIO_OPUS;
1123     } else if (atomType == Atom.TYPE_fLaC) {
1124       mimeType = MimeTypes.AUDIO_FLAC;
1125     }
1126 
1127     byte[] initializationData = null;
1128     while (childPosition - position < size) {
1129       parent.setPosition(childPosition);
1130       int childAtomSize = parent.readInt();
1131       Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
1132       int childAtomType = parent.readInt();
1133       if (childAtomType == Atom.TYPE_esds || (isQuickTime && childAtomType == Atom.TYPE_wave)) {
1134         int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition
1135             : findEsdsPosition(parent, childPosition, childAtomSize);
1136         if (esdsAtomPosition != C.POSITION_UNSET) {
1137           Pair<String, byte[]> mimeTypeAndInitializationData =
1138               parseEsdsFromParent(parent, esdsAtomPosition);
1139           mimeType = mimeTypeAndInitializationData.first;
1140           initializationData = mimeTypeAndInitializationData.second;
1141           if (MimeTypes.AUDIO_AAC.equals(mimeType)) {
1142             // Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
1143             // which is more reliable. See [Internal: b/10903778].
1144             Pair<Integer, Integer> audioSpecificConfig =
1145                 CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData);
1146             sampleRate = audioSpecificConfig.first;
1147             channelCount = audioSpecificConfig.second;
1148           }
1149         }
1150       } else if (childAtomType == Atom.TYPE_dac3) {
1151         parent.setPosition(Atom.HEADER_SIZE + childPosition);
1152         out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language,
1153             drmInitData);
1154       } else if (childAtomType == Atom.TYPE_dec3) {
1155         parent.setPosition(Atom.HEADER_SIZE + childPosition);
1156         out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language,
1157             drmInitData);
1158       } else if (childAtomType == Atom.TYPE_dac4) {
1159         parent.setPosition(Atom.HEADER_SIZE + childPosition);
1160         out.format =
1161             Ac4Util.parseAc4AnnexEFormat(parent, Integer.toString(trackId), language, drmInitData);
1162       } else if (childAtomType == Atom.TYPE_ddts) {
1163         out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
1164             Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,
1165             language);
1166       } else if (childAtomType == Atom.TYPE_dOps) {
1167         // Build an Opus Identification Header (defined in RFC-7845) by concatenating the Opus Magic
1168         // Signature and the body of the dOps atom.
1169         int childAtomBodySize = childAtomSize - Atom.HEADER_SIZE;
1170         initializationData = new byte[opusMagic.length + childAtomBodySize];
1171         System.arraycopy(opusMagic, 0, initializationData, 0, opusMagic.length);
1172         parent.setPosition(childPosition + Atom.HEADER_SIZE);
1173         parent.readBytes(initializationData, opusMagic.length, childAtomBodySize);
1174       } else if (childAtomType == Atom.TYPE_dfLa) {
1175         int childAtomBodySize = childAtomSize - Atom.FULL_HEADER_SIZE;
1176         initializationData = new byte[4 + childAtomBodySize];
1177         initializationData[0] = 0x66; // f
1178         initializationData[1] = 0x4C; // L
1179         initializationData[2] = 0x61; // a
1180         initializationData[3] = 0x43; // C
1181         parent.setPosition(childPosition + Atom.FULL_HEADER_SIZE);
1182         parent.readBytes(initializationData, /* offset= */ 4, childAtomBodySize);
1183       } else if (childAtomType == Atom.TYPE_alac) {
1184         int childAtomBodySize = childAtomSize - Atom.FULL_HEADER_SIZE;
1185         initializationData = new byte[childAtomBodySize];
1186         parent.setPosition(childPosition + Atom.FULL_HEADER_SIZE);
1187         parent.readBytes(initializationData, /* offset= */ 0, childAtomBodySize);
1188         // Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
1189         // which is more reliable. See https://github.com/google/ExoPlayer/pull/6629.
1190         Pair<Integer, Integer> audioSpecificConfig =
1191             CodecSpecificDataUtil.parseAlacAudioSpecificConfig(initializationData);
1192         sampleRate = audioSpecificConfig.first;
1193         channelCount = audioSpecificConfig.second;
1194       }
1195       childPosition += childAtomSize;
1196     }
1197 
1198     if (out.format == null && mimeType != null) {
1199       out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
1200           Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
1201           initializationData == null ? null : Collections.singletonList(initializationData),
1202           drmInitData, 0, language);
1203     }
1204   }
1205 
1206   /**
1207    * Returns the position of the esds box within a parent, or {@link C#POSITION_UNSET} if no esds
1208    * box is found
1209    */
findEsdsPosition(ParsableByteArray parent, int position, int size)1210   private static int findEsdsPosition(ParsableByteArray parent, int position, int size) {
1211     int childAtomPosition = parent.getPosition();
1212     while (childAtomPosition - position < size) {
1213       parent.setPosition(childAtomPosition);
1214       int childAtomSize = parent.readInt();
1215       Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
1216       int childType = parent.readInt();
1217       if (childType == Atom.TYPE_esds) {
1218         return childAtomPosition;
1219       }
1220       childAtomPosition += childAtomSize;
1221     }
1222     return C.POSITION_UNSET;
1223   }
1224 
1225   /**
1226    * Returns codec-specific initialization data contained in an esds box.
1227    */
parseEsdsFromParent(ParsableByteArray parent, int position)1228   private static Pair<String, byte[]> parseEsdsFromParent(ParsableByteArray parent, int position) {
1229     parent.setPosition(position + Atom.HEADER_SIZE + 4);
1230     // Start of the ES_Descriptor (defined in 14496-1)
1231     parent.skipBytes(1); // ES_Descriptor tag
1232     parseExpandableClassSize(parent);
1233     parent.skipBytes(2); // ES_ID
1234 
1235     int flags = parent.readUnsignedByte();
1236     if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
1237       parent.skipBytes(2);
1238     }
1239     if ((flags & 0x40 /* URL_Flag */) != 0) {
1240       parent.skipBytes(parent.readUnsignedShort());
1241     }
1242     if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
1243       parent.skipBytes(2);
1244     }
1245 
1246     // Start of the DecoderConfigDescriptor (defined in 14496-1)
1247     parent.skipBytes(1); // DecoderConfigDescriptor tag
1248     parseExpandableClassSize(parent);
1249 
1250     // Set the MIME type based on the object type indication (14496-1 table 5).
1251     int objectTypeIndication = parent.readUnsignedByte();
1252     String mimeType = getMimeTypeFromMp4ObjectType(objectTypeIndication);
1253     if (MimeTypes.AUDIO_MPEG.equals(mimeType)
1254         || MimeTypes.AUDIO_DTS.equals(mimeType)
1255         || MimeTypes.AUDIO_DTS_HD.equals(mimeType)) {
1256       return Pair.create(mimeType, null);
1257     }
1258 
1259     parent.skipBytes(12);
1260 
1261     // Start of the DecoderSpecificInfo.
1262     parent.skipBytes(1); // DecoderSpecificInfo tag
1263     int initializationDataSize = parseExpandableClassSize(parent);
1264     byte[] initializationData = new byte[initializationDataSize];
1265     parent.readBytes(initializationData, 0, initializationDataSize);
1266     return Pair.create(mimeType, initializationData);
1267   }
1268 
1269   /**
1270    * Parses encryption data from an audio/video sample entry, returning a pair consisting of the
1271    * unencrypted atom type and a {@link TrackEncryptionBox}. Null is returned if no common
1272    * encryption sinf atom was present.
1273    */
parseSampleEntryEncryptionData( ParsableByteArray parent, int position, int size)1274   private static Pair<Integer, TrackEncryptionBox> parseSampleEntryEncryptionData(
1275       ParsableByteArray parent, int position, int size) {
1276     int childPosition = parent.getPosition();
1277     while (childPosition - position < size) {
1278       parent.setPosition(childPosition);
1279       int childAtomSize = parent.readInt();
1280       Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
1281       int childAtomType = parent.readInt();
1282       if (childAtomType == Atom.TYPE_sinf) {
1283         Pair<Integer, TrackEncryptionBox> result = parseCommonEncryptionSinfFromParent(parent,
1284             childPosition, childAtomSize);
1285         if (result != null) {
1286           return result;
1287         }
1288       }
1289       childPosition += childAtomSize;
1290     }
1291     return null;
1292   }
1293 
parseCommonEncryptionSinfFromParent( ParsableByteArray parent, int position, int size)1294   /* package */ static Pair<Integer, TrackEncryptionBox> parseCommonEncryptionSinfFromParent(
1295       ParsableByteArray parent, int position, int size) {
1296     int childPosition = position + Atom.HEADER_SIZE;
1297     int schemeInformationBoxPosition = C.POSITION_UNSET;
1298     int schemeInformationBoxSize = 0;
1299     String schemeType = null;
1300     Integer dataFormat = null;
1301     while (childPosition - position < size) {
1302       parent.setPosition(childPosition);
1303       int childAtomSize = parent.readInt();
1304       int childAtomType = parent.readInt();
1305       if (childAtomType == Atom.TYPE_frma) {
1306         dataFormat = parent.readInt();
1307       } else if (childAtomType == Atom.TYPE_schm) {
1308         parent.skipBytes(4);
1309         // Common encryption scheme_type values are defined in ISO/IEC 23001-7:2016, section 4.1.
1310         schemeType = parent.readString(4);
1311       } else if (childAtomType == Atom.TYPE_schi) {
1312         schemeInformationBoxPosition = childPosition;
1313         schemeInformationBoxSize = childAtomSize;
1314       }
1315       childPosition += childAtomSize;
1316     }
1317 
1318     if (C.CENC_TYPE_cenc.equals(schemeType) || C.CENC_TYPE_cbc1.equals(schemeType)
1319         || C.CENC_TYPE_cens.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType)) {
1320       Assertions.checkArgument(dataFormat != null, "frma atom is mandatory");
1321       Assertions.checkArgument(schemeInformationBoxPosition != C.POSITION_UNSET,
1322           "schi atom is mandatory");
1323       TrackEncryptionBox encryptionBox = parseSchiFromParent(parent, schemeInformationBoxPosition,
1324           schemeInformationBoxSize, schemeType);
1325       Assertions.checkArgument(encryptionBox != null, "tenc atom is mandatory");
1326       return Pair.create(dataFormat, encryptionBox);
1327     } else {
1328       return null;
1329     }
1330   }
1331 
parseSchiFromParent(ParsableByteArray parent, int position, int size, String schemeType)1332   private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
1333       int size, String schemeType) {
1334     int childPosition = position + Atom.HEADER_SIZE;
1335     while (childPosition - position < size) {
1336       parent.setPosition(childPosition);
1337       int childAtomSize = parent.readInt();
1338       int childAtomType = parent.readInt();
1339       if (childAtomType == Atom.TYPE_tenc) {
1340         int fullAtom = parent.readInt();
1341         int version = Atom.parseFullAtomVersion(fullAtom);
1342         parent.skipBytes(1); // reserved = 0.
1343         int defaultCryptByteBlock = 0;
1344         int defaultSkipByteBlock = 0;
1345         if (version == 0) {
1346           parent.skipBytes(1); // reserved = 0.
1347         } else /* version 1 or greater */ {
1348           int patternByte = parent.readUnsignedByte();
1349           defaultCryptByteBlock = (patternByte & 0xF0) >> 4;
1350           defaultSkipByteBlock = patternByte & 0x0F;
1351         }
1352         boolean defaultIsProtected = parent.readUnsignedByte() == 1;
1353         int defaultPerSampleIvSize = parent.readUnsignedByte();
1354         byte[] defaultKeyId = new byte[16];
1355         parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
1356         byte[] constantIv = null;
1357         if (defaultIsProtected && defaultPerSampleIvSize == 0) {
1358           int constantIvSize = parent.readUnsignedByte();
1359           constantIv = new byte[constantIvSize];
1360           parent.readBytes(constantIv, 0, constantIvSize);
1361         }
1362         return new TrackEncryptionBox(defaultIsProtected, schemeType, defaultPerSampleIvSize,
1363             defaultKeyId, defaultCryptByteBlock, defaultSkipByteBlock, constantIv);
1364       }
1365       childPosition += childAtomSize;
1366     }
1367     return null;
1368   }
1369 
1370   /**
1371    * Parses the proj box from sv3d box, as specified by https://github.com/google/spatial-media.
1372    */
parseProjFromParent(ParsableByteArray parent, int position, int size)1373   private static byte[] parseProjFromParent(ParsableByteArray parent, int position, int size) {
1374     int childPosition = position + Atom.HEADER_SIZE;
1375     while (childPosition - position < size) {
1376       parent.setPosition(childPosition);
1377       int childAtomSize = parent.readInt();
1378       int childAtomType = parent.readInt();
1379       if (childAtomType == Atom.TYPE_proj) {
1380         return Arrays.copyOfRange(parent.data, childPosition, childPosition + childAtomSize);
1381       }
1382       childPosition += childAtomSize;
1383     }
1384     return null;
1385   }
1386 
1387   /**
1388    * Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3.
1389    */
parseExpandableClassSize(ParsableByteArray data)1390   private static int parseExpandableClassSize(ParsableByteArray data) {
1391     int currentByte = data.readUnsignedByte();
1392     int size = currentByte & 0x7F;
1393     while ((currentByte & 0x80) == 0x80) {
1394       currentByte = data.readUnsignedByte();
1395       size = (size << 7) | (currentByte & 0x7F);
1396     }
1397     return size;
1398   }
1399 
1400   /** Returns whether it's possible to apply the specified edit using gapless playback info. */
canApplyEditWithGaplessInfo( long[] timestamps, long duration, long editStartTime, long editEndTime)1401   private static boolean canApplyEditWithGaplessInfo(
1402       long[] timestamps, long duration, long editStartTime, long editEndTime) {
1403     int lastIndex = timestamps.length - 1;
1404     int latestDelayIndex = Util.constrainValue(MAX_GAPLESS_TRIM_SIZE_SAMPLES, 0, lastIndex);
1405     int earliestPaddingIndex =
1406         Util.constrainValue(timestamps.length - MAX_GAPLESS_TRIM_SIZE_SAMPLES, 0, lastIndex);
1407     return timestamps[0] <= editStartTime
1408         && editStartTime < timestamps[latestDelayIndex]
1409         && timestamps[earliestPaddingIndex] < editEndTime
1410         && editEndTime <= duration;
1411   }
1412 
AtomParsers()1413   private AtomParsers() {
1414     // Prevent instantiation.
1415   }
1416 
1417   private static final class ChunkIterator {
1418 
1419     public final int length;
1420 
1421     public int index;
1422     public int numSamples;
1423     public long offset;
1424 
1425     private final boolean chunkOffsetsAreLongs;
1426     private final ParsableByteArray chunkOffsets;
1427     private final ParsableByteArray stsc;
1428 
1429     private int nextSamplesPerChunkChangeIndex;
1430     private int remainingSamplesPerChunkChanges;
1431 
ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, boolean chunkOffsetsAreLongs)1432     public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets,
1433         boolean chunkOffsetsAreLongs) {
1434       this.stsc = stsc;
1435       this.chunkOffsets = chunkOffsets;
1436       this.chunkOffsetsAreLongs = chunkOffsetsAreLongs;
1437       chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE);
1438       length = chunkOffsets.readUnsignedIntToInt();
1439       stsc.setPosition(Atom.FULL_HEADER_SIZE);
1440       remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt();
1441       Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1");
1442       index = -1;
1443     }
1444 
moveNext()1445     public boolean moveNext() {
1446       if (++index == length) {
1447         return false;
1448       }
1449       offset = chunkOffsetsAreLongs ? chunkOffsets.readUnsignedLongToLong()
1450           : chunkOffsets.readUnsignedInt();
1451       if (index == nextSamplesPerChunkChangeIndex) {
1452         numSamples = stsc.readUnsignedIntToInt();
1453         stsc.skipBytes(4); // Skip sample_description_index
1454         nextSamplesPerChunkChangeIndex = --remainingSamplesPerChunkChanges > 0
1455             ? (stsc.readUnsignedIntToInt() - 1) : C.INDEX_UNSET;
1456       }
1457       return true;
1458     }
1459 
1460   }
1461 
1462   /**
1463    * Holds data parsed from a tkhd atom.
1464    */
1465   private static final class TkhdData {
1466 
1467     private final int id;
1468     private final long duration;
1469     private final int rotationDegrees;
1470 
TkhdData(int id, long duration, int rotationDegrees)1471     public TkhdData(int id, long duration, int rotationDegrees) {
1472       this.id = id;
1473       this.duration = duration;
1474       this.rotationDegrees = rotationDegrees;
1475     }
1476 
1477   }
1478 
1479   /**
1480    * Holds data parsed from an stsd atom and its children.
1481    */
1482   private static final class StsdData {
1483 
1484     public static final int STSD_HEADER_SIZE = 8;
1485 
1486     public final TrackEncryptionBox[] trackEncryptionBoxes;
1487 
1488     public Format format;
1489     public int nalUnitLengthFieldLength;
1490     @Track.Transformation
1491     public int requiredSampleTransformation;
1492 
StsdData(int numberOfEntries)1493     public StsdData(int numberOfEntries) {
1494       trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
1495       requiredSampleTransformation = Track.TRANSFORMATION_NONE;
1496     }
1497 
1498   }
1499 
1500   /**
1501    * A box containing sample sizes (e.g. stsz, stz2).
1502    */
1503   private interface SampleSizeBox {
1504 
1505     /**
1506      * Returns the number of samples.
1507      */
getSampleCount()1508     int getSampleCount();
1509 
1510     /**
1511      * Returns the size for the next sample.
1512      */
readNextSampleSize()1513     int readNextSampleSize();
1514 
1515     /**
1516      * Returns whether samples have a fixed size.
1517      */
isFixedSampleSize()1518     boolean isFixedSampleSize();
1519 
1520   }
1521 
1522   /**
1523    * An stsz sample size box.
1524    */
1525   /* package */ static final class StszSampleSizeBox implements SampleSizeBox {
1526 
1527     private final int fixedSampleSize;
1528     private final int sampleCount;
1529     private final ParsableByteArray data;
1530 
StszSampleSizeBox(Atom.LeafAtom stszAtom)1531     public StszSampleSizeBox(Atom.LeafAtom stszAtom) {
1532       data = stszAtom.data;
1533       data.setPosition(Atom.FULL_HEADER_SIZE);
1534       fixedSampleSize = data.readUnsignedIntToInt();
1535       sampleCount = data.readUnsignedIntToInt();
1536     }
1537 
1538     @Override
getSampleCount()1539     public int getSampleCount() {
1540       return sampleCount;
1541     }
1542 
1543     @Override
readNextSampleSize()1544     public int readNextSampleSize() {
1545       return fixedSampleSize == 0 ? data.readUnsignedIntToInt() : fixedSampleSize;
1546     }
1547 
1548     @Override
isFixedSampleSize()1549     public boolean isFixedSampleSize() {
1550       return fixedSampleSize != 0;
1551     }
1552 
1553   }
1554 
1555   /**
1556    * An stz2 sample size box.
1557    */
1558   /* package */ static final class Stz2SampleSizeBox implements SampleSizeBox {
1559 
1560     private final ParsableByteArray data;
1561     private final int sampleCount;
1562     private final int fieldSize; // Can be 4, 8, or 16.
1563 
1564     // Used only if fieldSize == 4.
1565     private int sampleIndex;
1566     private int currentByte;
1567 
Stz2SampleSizeBox(Atom.LeafAtom stz2Atom)1568     public Stz2SampleSizeBox(Atom.LeafAtom stz2Atom) {
1569       data = stz2Atom.data;
1570       data.setPosition(Atom.FULL_HEADER_SIZE);
1571       fieldSize = data.readUnsignedIntToInt() & 0x000000FF;
1572       sampleCount = data.readUnsignedIntToInt();
1573     }
1574 
1575     @Override
getSampleCount()1576     public int getSampleCount() {
1577       return sampleCount;
1578     }
1579 
1580     @Override
readNextSampleSize()1581     public int readNextSampleSize() {
1582       if (fieldSize == 8) {
1583         return data.readUnsignedByte();
1584       } else if (fieldSize == 16) {
1585         return data.readUnsignedShort();
1586       } else {
1587         // fieldSize == 4.
1588         if ((sampleIndex++ % 2) == 0) {
1589           // Read the next byte into our cached byte when we are reading the upper bits.
1590           currentByte = data.readUnsignedByte();
1591           // Read the upper bits from the byte and shift them to the lower 4 bits.
1592           return (currentByte & 0xF0) >> 4;
1593         } else {
1594           // Mask out the upper 4 bits of the last byte we read.
1595           return currentByte & 0x0F;
1596         }
1597       }
1598     }
1599 
1600     @Override
isFixedSampleSize()1601     public boolean isFixedSampleSize() {
1602       return false;
1603     }
1604 
1605   }
1606 
1607 }
1608