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