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 android.util.Pair;
19 import android.util.SparseArray;
20 import androidx.annotation.IntDef;
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.Ac4Util;
26 import com.google.android.exoplayer2.drm.DrmInitData;
27 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
28 import com.google.android.exoplayer2.extractor.CeaUtil;
29 import com.google.android.exoplayer2.extractor.ChunkIndex;
30 import com.google.android.exoplayer2.extractor.Extractor;
31 import com.google.android.exoplayer2.extractor.ExtractorInput;
32 import com.google.android.exoplayer2.extractor.ExtractorOutput;
33 import com.google.android.exoplayer2.extractor.ExtractorsFactory;
34 import com.google.android.exoplayer2.extractor.PositionHolder;
35 import com.google.android.exoplayer2.extractor.SeekMap;
36 import com.google.android.exoplayer2.extractor.TrackOutput;
37 import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
38 import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
39 import com.google.android.exoplayer2.metadata.emsg.EventMessage;
40 import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
41 import com.google.android.exoplayer2.util.Assertions;
42 import com.google.android.exoplayer2.util.Log;
43 import com.google.android.exoplayer2.util.MimeTypes;
44 import com.google.android.exoplayer2.util.NalUnitUtil;
45 import com.google.android.exoplayer2.util.ParsableByteArray;
46 import com.google.android.exoplayer2.util.TimestampAdjuster;
47 import com.google.android.exoplayer2.util.Util;
48 import java.io.IOException;
49 import java.lang.annotation.Documented;
50 import java.lang.annotation.Retention;
51 import java.lang.annotation.RetentionPolicy;
52 import java.util.ArrayDeque;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.UUID;
58 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
59 
60 /** Extracts data from the FMP4 container format. */
61 @SuppressWarnings("ConstantField")
62 public class FragmentedMp4Extractor implements Extractor {
63 
64   /** Factory for {@link FragmentedMp4Extractor} instances. */
65   public static final ExtractorsFactory FACTORY =
66       () -> new Extractor[] {new FragmentedMp4Extractor()};
67 
68   /**
69    * Flags controlling the behavior of the extractor. Possible flag values are {@link
70    * #FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME}, {@link #FLAG_WORKAROUND_IGNORE_TFDT_BOX},
71    * {@link #FLAG_ENABLE_EMSG_TRACK}, {@link #FLAG_SIDELOADED} and {@link
72    * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}.
73    */
74   @Documented
75   @Retention(RetentionPolicy.SOURCE)
76   @IntDef(
77       flag = true,
78       value = {
79         FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
80         FLAG_WORKAROUND_IGNORE_TFDT_BOX,
81         FLAG_ENABLE_EMSG_TRACK,
82         FLAG_SIDELOADED,
83         FLAG_WORKAROUND_IGNORE_EDIT_LISTS
84       })
85   public @interface Flags {}
86   /**
87    * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
88    * The workaround overrides the sync frame flags in the stream, forcing them to false except for
89    * the first sample in each segment.
90    * <p>
91    * This flag does nothing if the stream is not a video stream.
92    */
93   public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
94   /** Flag to ignore any tfdt boxes in the stream. */
95   public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 1 << 1; // 2
96   /**
97    * Flag to indicate that the extractor should output an event message metadata track. Any event
98    * messages in the stream will be delivered as samples to this track.
99    */
100   public static final int FLAG_ENABLE_EMSG_TRACK = 1 << 2; // 4
101   /**
102    * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
103    * container.
104    */
105   private static final int FLAG_SIDELOADED = 1 << 3; // 8
106   /** Flag to ignore any edit lists in the stream. */
107   public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1 << 4; // 16
108 
109   private static final String TAG = "FragmentedMp4Extractor";
110 
111   @SuppressWarnings("ConstantCaseForConstants")
112   private static final int SAMPLE_GROUP_TYPE_seig = 0x73656967;
113 
114   private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
115       new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
116   private static final Format EMSG_FORMAT =
117       new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_EMSG).build();
118 
119   // Parser states.
120   private static final int STATE_READING_ATOM_HEADER = 0;
121   private static final int STATE_READING_ATOM_PAYLOAD = 1;
122   private static final int STATE_READING_ENCRYPTION_DATA = 2;
123   private static final int STATE_READING_SAMPLE_START = 3;
124   private static final int STATE_READING_SAMPLE_CONTINUE = 4;
125 
126   // Workarounds.
127   @Flags private final int flags;
128   @Nullable private final Track sideloadedTrack;
129 
130   // Sideloaded data.
131   private final List<Format> closedCaptionFormats;
132 
133   // Track-linked data bundle, accessible as a whole through trackID.
134   private final SparseArray<TrackBundle> trackBundles;
135 
136   // Temporary arrays.
137   private final ParsableByteArray nalStartCode;
138   private final ParsableByteArray nalPrefix;
139   private final ParsableByteArray nalBuffer;
140   private final byte[] scratchBytes;
141   private final ParsableByteArray scratch;
142 
143   // Adjusts sample timestamps.
144   @Nullable private final TimestampAdjuster timestampAdjuster;
145 
146   private final EventMessageEncoder eventMessageEncoder;
147 
148   // Parser state.
149   private final ParsableByteArray atomHeader;
150   private final ArrayDeque<ContainerAtom> containerAtoms;
151   private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
152   @Nullable private final TrackOutput additionalEmsgTrackOutput;
153 
154   private int parserState;
155   private int atomType;
156   private long atomSize;
157   private int atomHeaderBytesRead;
158   @Nullable private ParsableByteArray atomData;
159   private long endOfMdatPosition;
160   private int pendingMetadataSampleBytes;
161   private long pendingSeekTimeUs;
162 
163   private long durationUs;
164   private long segmentIndexEarliestPresentationTimeUs;
165   @Nullable private TrackBundle currentTrackBundle;
166   private int sampleSize;
167   private int sampleBytesWritten;
168   private int sampleCurrentNalBytesRemaining;
169   private boolean processSeiNalUnitPayload;
170 
171   // Extractor output.
172   private @MonotonicNonNull ExtractorOutput extractorOutput;
173   private TrackOutput[] emsgTrackOutputs;
174   private TrackOutput[] cea608TrackOutputs;
175 
176   // Whether extractorOutput.seekMap has been called.
177   private boolean haveOutputSeekMap;
178 
FragmentedMp4Extractor()179   public FragmentedMp4Extractor() {
180     this(0);
181   }
182 
183   /**
184    * @param flags Flags that control the extractor's behavior.
185    */
FragmentedMp4Extractor(@lags int flags)186   public FragmentedMp4Extractor(@Flags int flags) {
187     this(flags, /* timestampAdjuster= */ null);
188   }
189 
190   /**
191    * @param flags Flags that control the extractor's behavior.
192    * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
193    */
FragmentedMp4Extractor(@lags int flags, @Nullable TimestampAdjuster timestampAdjuster)194   public FragmentedMp4Extractor(@Flags int flags, @Nullable TimestampAdjuster timestampAdjuster) {
195     this(flags, timestampAdjuster, /* sideloadedTrack= */ null, Collections.emptyList());
196   }
197 
198   /**
199    * @param flags Flags that control the extractor's behavior.
200    * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
201    * @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
202    *     receive a moov box in the input data. Null if a moov box is expected.
203    */
FragmentedMp4Extractor( @lags int flags, @Nullable TimestampAdjuster timestampAdjuster, @Nullable Track sideloadedTrack)204   public FragmentedMp4Extractor(
205       @Flags int flags,
206       @Nullable TimestampAdjuster timestampAdjuster,
207       @Nullable Track sideloadedTrack) {
208     this(flags, timestampAdjuster, sideloadedTrack, Collections.emptyList());
209   }
210 
211   /**
212    * @param flags Flags that control the extractor's behavior.
213    * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
214    * @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
215    *     receive a moov box in the input data. Null if a moov box is expected.
216    * @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
217    *     caption channels to expose.
218    */
FragmentedMp4Extractor( @lags int flags, @Nullable TimestampAdjuster timestampAdjuster, @Nullable Track sideloadedTrack, List<Format> closedCaptionFormats)219   public FragmentedMp4Extractor(
220       @Flags int flags,
221       @Nullable TimestampAdjuster timestampAdjuster,
222       @Nullable Track sideloadedTrack,
223       List<Format> closedCaptionFormats) {
224     this(
225         flags,
226         timestampAdjuster,
227         sideloadedTrack,
228         closedCaptionFormats,
229         /* additionalEmsgTrackOutput= */ null);
230   }
231 
232   /**
233    * @param flags Flags that control the extractor's behavior.
234    * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
235    * @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
236    *     receive a moov box in the input data. Null if a moov box is expected.
237    * @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
238    *     caption channels to expose.
239    * @param additionalEmsgTrackOutput An extra track output that will receive all emsg messages
240    *     targeting the player, even if {@link #FLAG_ENABLE_EMSG_TRACK} is not set. Null if special
241    *     handling of emsg messages for players is not required.
242    */
FragmentedMp4Extractor( @lags int flags, @Nullable TimestampAdjuster timestampAdjuster, @Nullable Track sideloadedTrack, List<Format> closedCaptionFormats, @Nullable TrackOutput additionalEmsgTrackOutput)243   public FragmentedMp4Extractor(
244       @Flags int flags,
245       @Nullable TimestampAdjuster timestampAdjuster,
246       @Nullable Track sideloadedTrack,
247       List<Format> closedCaptionFormats,
248       @Nullable TrackOutput additionalEmsgTrackOutput) {
249     this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
250     this.timestampAdjuster = timestampAdjuster;
251     this.sideloadedTrack = sideloadedTrack;
252     this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
253     this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
254     eventMessageEncoder = new EventMessageEncoder();
255     atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
256     nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
257     nalPrefix = new ParsableByteArray(5);
258     nalBuffer = new ParsableByteArray();
259     scratchBytes = new byte[16];
260     scratch = new ParsableByteArray(scratchBytes);
261     containerAtoms = new ArrayDeque<>();
262     pendingMetadataSampleInfos = new ArrayDeque<>();
263     trackBundles = new SparseArray<>();
264     durationUs = C.TIME_UNSET;
265     pendingSeekTimeUs = C.TIME_UNSET;
266     segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
267     enterReadingAtomHeaderState();
268   }
269 
270   @Override
sniff(ExtractorInput input)271   public boolean sniff(ExtractorInput input) throws IOException {
272     return Sniffer.sniffFragmented(input);
273   }
274 
275   @Override
init(ExtractorOutput output)276   public void init(ExtractorOutput output) {
277     extractorOutput = output;
278     if (sideloadedTrack != null) {
279       TrackBundle bundle = new TrackBundle(output.track(0, sideloadedTrack.type));
280       bundle.init(sideloadedTrack, new DefaultSampleValues(0, 0, 0, 0));
281       trackBundles.put(0, bundle);
282       maybeInitExtraTracks();
283       extractorOutput.endTracks();
284     }
285   }
286 
287   @Override
seek(long position, long timeUs)288   public void seek(long position, long timeUs) {
289     int trackCount = trackBundles.size();
290     for (int i = 0; i < trackCount; i++) {
291       trackBundles.valueAt(i).reset();
292     }
293     pendingMetadataSampleInfos.clear();
294     pendingMetadataSampleBytes = 0;
295     pendingSeekTimeUs = timeUs;
296     containerAtoms.clear();
297     enterReadingAtomHeaderState();
298   }
299 
300   @Override
release()301   public void release() {
302     // Do nothing
303   }
304 
305   @Override
read(ExtractorInput input, PositionHolder seekPosition)306   public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
307     while (true) {
308       switch (parserState) {
309         case STATE_READING_ATOM_HEADER:
310           if (!readAtomHeader(input)) {
311             return Extractor.RESULT_END_OF_INPUT;
312           }
313           break;
314         case STATE_READING_ATOM_PAYLOAD:
315           readAtomPayload(input);
316           break;
317         case STATE_READING_ENCRYPTION_DATA:
318           readEncryptionData(input);
319           break;
320         default:
321           if (readSample(input)) {
322             return RESULT_CONTINUE;
323           }
324       }
325     }
326   }
327 
enterReadingAtomHeaderState()328   private void enterReadingAtomHeaderState() {
329     parserState = STATE_READING_ATOM_HEADER;
330     atomHeaderBytesRead = 0;
331   }
332 
readAtomHeader(ExtractorInput input)333   private boolean readAtomHeader(ExtractorInput input) throws IOException {
334     if (atomHeaderBytesRead == 0) {
335       // Read the standard length atom header.
336       if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
337         return false;
338       }
339       atomHeaderBytesRead = Atom.HEADER_SIZE;
340       atomHeader.setPosition(0);
341       atomSize = atomHeader.readUnsignedInt();
342       atomType = atomHeader.readInt();
343     }
344 
345     if (atomSize == Atom.DEFINES_LARGE_SIZE) {
346       // Read the large size.
347       int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
348       input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
349       atomHeaderBytesRead += headerBytesRemaining;
350       atomSize = atomHeader.readUnsignedLongToLong();
351     } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) {
352       // The atom extends to the end of the file. Note that if the atom is within a container we can
353       // work out its size even if the input length is unknown.
354       long endPosition = input.getLength();
355       if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) {
356         endPosition = containerAtoms.peek().endPosition;
357       }
358       if (endPosition != C.LENGTH_UNSET) {
359         atomSize = endPosition - input.getPosition() + atomHeaderBytesRead;
360       }
361     }
362 
363     if (atomSize < atomHeaderBytesRead) {
364       throw new ParserException("Atom size less than header length (unsupported).");
365     }
366 
367     long atomPosition = input.getPosition() - atomHeaderBytesRead;
368     if (atomType == Atom.TYPE_moof) {
369       // The data positions may be updated when parsing the tfhd/trun.
370       int trackCount = trackBundles.size();
371       for (int i = 0; i < trackCount; i++) {
372         TrackFragment fragment = trackBundles.valueAt(i).fragment;
373         fragment.atomPosition = atomPosition;
374         fragment.auxiliaryDataPosition = atomPosition;
375         fragment.dataPosition = atomPosition;
376       }
377     }
378 
379     if (atomType == Atom.TYPE_mdat) {
380       currentTrackBundle = null;
381       endOfMdatPosition = atomPosition + atomSize;
382       if (!haveOutputSeekMap) {
383         // This must be the first mdat in the stream.
384         extractorOutput.seekMap(new SeekMap.Unseekable(durationUs, atomPosition));
385         haveOutputSeekMap = true;
386       }
387       parserState = STATE_READING_ENCRYPTION_DATA;
388       return true;
389     }
390 
391     if (shouldParseContainerAtom(atomType)) {
392       long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE;
393       containerAtoms.push(new ContainerAtom(atomType, endPosition));
394       if (atomSize == atomHeaderBytesRead) {
395         processAtomEnded(endPosition);
396       } else {
397         // Start reading the first child atom.
398         enterReadingAtomHeaderState();
399       }
400     } else if (shouldParseLeafAtom(atomType)) {
401       if (atomHeaderBytesRead != Atom.HEADER_SIZE) {
402         throw new ParserException("Leaf atom defines extended atom size (unsupported).");
403       }
404       if (atomSize > Integer.MAX_VALUE) {
405         throw new ParserException("Leaf atom with length > 2147483647 (unsupported).");
406       }
407       atomData = new ParsableByteArray((int) atomSize);
408       System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE);
409       parserState = STATE_READING_ATOM_PAYLOAD;
410     } else {
411       if (atomSize > Integer.MAX_VALUE) {
412         throw new ParserException("Skipping atom with length > 2147483647 (unsupported).");
413       }
414       atomData = null;
415       parserState = STATE_READING_ATOM_PAYLOAD;
416     }
417 
418     return true;
419   }
420 
readAtomPayload(ExtractorInput input)421   private void readAtomPayload(ExtractorInput input) throws IOException {
422     int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
423     if (atomData != null) {
424       input.readFully(atomData.data, Atom.HEADER_SIZE, atomPayloadSize);
425       onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
426     } else {
427       input.skipFully(atomPayloadSize);
428     }
429     processAtomEnded(input.getPosition());
430   }
431 
processAtomEnded(long atomEndPosition)432   private void processAtomEnded(long atomEndPosition) throws ParserException {
433     while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) {
434       onContainerAtomRead(containerAtoms.pop());
435     }
436     enterReadingAtomHeaderState();
437   }
438 
onLeafAtomRead(LeafAtom leaf, long inputPosition)439   private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException {
440     if (!containerAtoms.isEmpty()) {
441       containerAtoms.peek().add(leaf);
442     } else if (leaf.type == Atom.TYPE_sidx) {
443       Pair<Long, ChunkIndex> result = parseSidx(leaf.data, inputPosition);
444       segmentIndexEarliestPresentationTimeUs = result.first;
445       extractorOutput.seekMap(result.second);
446       haveOutputSeekMap = true;
447     } else if (leaf.type == Atom.TYPE_emsg) {
448       onEmsgLeafAtomRead(leaf.data);
449     }
450   }
451 
onContainerAtomRead(ContainerAtom container)452   private void onContainerAtomRead(ContainerAtom container) throws ParserException {
453     if (container.type == Atom.TYPE_moov) {
454       onMoovContainerAtomRead(container);
455     } else if (container.type == Atom.TYPE_moof) {
456       onMoofContainerAtomRead(container);
457     } else if (!containerAtoms.isEmpty()) {
458       containerAtoms.peek().add(container);
459     }
460   }
461 
onMoovContainerAtomRead(ContainerAtom moov)462   private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
463     Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
464 
465     @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
466 
467     // Read declaration of track fragments in the Moov box.
468     ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
469     SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
470     long duration = C.TIME_UNSET;
471     int mvexChildrenSize = mvex.leafChildren.size();
472     for (int i = 0; i < mvexChildrenSize; i++) {
473       Atom.LeafAtom atom = mvex.leafChildren.get(i);
474       if (atom.type == Atom.TYPE_trex) {
475         Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data);
476         defaultSampleValuesArray.put(trexData.first, trexData.second);
477       } else if (atom.type == Atom.TYPE_mehd) {
478         duration = parseMehd(atom.data);
479       }
480     }
481 
482     // Construction of tracks.
483     SparseArray<Track> tracks = new SparseArray<>();
484     int moovContainerChildrenSize = moov.containerChildren.size();
485     for (int i = 0; i < moovContainerChildrenSize; i++) {
486       Atom.ContainerAtom atom = moov.containerChildren.get(i);
487       if (atom.type == Atom.TYPE_trak) {
488         @Nullable
489         Track track =
490             modifyTrack(
491                 AtomParsers.parseTrak(
492                     atom,
493                     moov.getLeafAtomOfType(Atom.TYPE_mvhd),
494                     duration,
495                     drmInitData,
496                     (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0,
497                     false));
498         if (track != null) {
499           tracks.put(track.id, track);
500         }
501       }
502     }
503 
504     int trackCount = tracks.size();
505     if (trackBundles.size() == 0) {
506       // We need to create the track bundles.
507       for (int i = 0; i < trackCount; i++) {
508         Track track = tracks.valueAt(i);
509         TrackBundle trackBundle = new TrackBundle(extractorOutput.track(i, track.type));
510         trackBundle.init(track, getDefaultSampleValues(defaultSampleValuesArray, track.id));
511         trackBundles.put(track.id, trackBundle);
512         durationUs = Math.max(durationUs, track.durationUs);
513       }
514       maybeInitExtraTracks();
515       extractorOutput.endTracks();
516     } else {
517       Assertions.checkState(trackBundles.size() == trackCount);
518       for (int i = 0; i < trackCount; i++) {
519         Track track = tracks.valueAt(i);
520         trackBundles
521             .get(track.id)
522             .init(track, getDefaultSampleValues(defaultSampleValuesArray, track.id));
523       }
524     }
525   }
526 
527   @Nullable
modifyTrack(@ullable Track track)528   protected Track modifyTrack(@Nullable Track track) {
529     return track;
530   }
531 
getDefaultSampleValues( SparseArray<DefaultSampleValues> defaultSampleValuesArray, int trackId)532   private DefaultSampleValues getDefaultSampleValues(
533       SparseArray<DefaultSampleValues> defaultSampleValuesArray, int trackId) {
534     if (defaultSampleValuesArray.size() == 1) {
535       // Ignore track id if there is only one track to cope with non-matching track indices.
536       // See https://github.com/google/ExoPlayer/issues/4477.
537       return defaultSampleValuesArray.valueAt(/* index= */ 0);
538     }
539     return Assertions.checkNotNull(defaultSampleValuesArray.get(trackId));
540   }
541 
onMoofContainerAtomRead(ContainerAtom moof)542   private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
543     parseMoof(moof, trackBundles, flags, scratchBytes);
544 
545     @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
546     if (drmInitData != null) {
547       int trackCount = trackBundles.size();
548       for (int i = 0; i < trackCount; i++) {
549         trackBundles.valueAt(i).updateDrmInitData(drmInitData);
550       }
551     }
552     // If we have a pending seek, advance tracks to their preceding sync frames.
553     if (pendingSeekTimeUs != C.TIME_UNSET) {
554       int trackCount = trackBundles.size();
555       for (int i = 0; i < trackCount; i++) {
556         trackBundles.valueAt(i).seek(pendingSeekTimeUs);
557       }
558       pendingSeekTimeUs = C.TIME_UNSET;
559     }
560   }
561 
maybeInitExtraTracks()562   private void maybeInitExtraTracks() {
563     if (emsgTrackOutputs == null) {
564       emsgTrackOutputs = new TrackOutput[2];
565       int emsgTrackOutputCount = 0;
566       if (additionalEmsgTrackOutput != null) {
567         emsgTrackOutputs[emsgTrackOutputCount++] = additionalEmsgTrackOutput;
568       }
569       if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0) {
570         emsgTrackOutputs[emsgTrackOutputCount++] =
571             extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA);
572       }
573       emsgTrackOutputs = Arrays.copyOf(emsgTrackOutputs, emsgTrackOutputCount);
574 
575       for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) {
576         eventMessageTrackOutput.format(EMSG_FORMAT);
577       }
578     }
579     if (cea608TrackOutputs == null) {
580       cea608TrackOutputs = new TrackOutput[closedCaptionFormats.size()];
581       for (int i = 0; i < cea608TrackOutputs.length; i++) {
582         TrackOutput output = extractorOutput.track(trackBundles.size() + 1 + i, C.TRACK_TYPE_TEXT);
583         output.format(closedCaptionFormats.get(i));
584         cea608TrackOutputs[i] = output;
585       }
586     }
587   }
588 
589   /** Handles an emsg atom (defined in 23009-1). */
onEmsgLeafAtomRead(ParsableByteArray atom)590   private void onEmsgLeafAtomRead(ParsableByteArray atom) {
591     if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) {
592       return;
593     }
594     atom.setPosition(Atom.HEADER_SIZE);
595     int fullAtom = atom.readInt();
596     int version = Atom.parseFullAtomVersion(fullAtom);
597     String schemeIdUri;
598     String value;
599     long timescale;
600     long presentationTimeDeltaUs = C.TIME_UNSET; // Only set if version == 0
601     long sampleTimeUs = C.TIME_UNSET;
602     long durationMs;
603     long id;
604     switch (version) {
605       case 0:
606         schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
607         value = Assertions.checkNotNull(atom.readNullTerminatedString());
608         timescale = atom.readUnsignedInt();
609         presentationTimeDeltaUs =
610             Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
611         if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
612           sampleTimeUs = segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs;
613         }
614         durationMs =
615             Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
616         id = atom.readUnsignedInt();
617         break;
618       case 1:
619         timescale = atom.readUnsignedInt();
620         sampleTimeUs =
621             Util.scaleLargeTimestamp(atom.readUnsignedLongToLong(), C.MICROS_PER_SECOND, timescale);
622         durationMs =
623             Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
624         id = atom.readUnsignedInt();
625         schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
626         value = Assertions.checkNotNull(atom.readNullTerminatedString());
627         break;
628       default:
629         Log.w(TAG, "Skipping unsupported emsg version: " + version);
630         return;
631     }
632 
633     byte[] messageData = new byte[atom.bytesLeft()];
634     atom.readBytes(messageData, /*offset=*/ 0, atom.bytesLeft());
635     EventMessage eventMessage = new EventMessage(schemeIdUri, value, durationMs, id, messageData);
636     ParsableByteArray encodedEventMessage =
637         new ParsableByteArray(eventMessageEncoder.encode(eventMessage));
638     int sampleSize = encodedEventMessage.bytesLeft();
639 
640     // Output the sample data.
641     for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
642       encodedEventMessage.setPosition(0);
643       emsgTrackOutput.sampleData(encodedEventMessage, sampleSize);
644     }
645 
646     // Output the sample metadata. This is made a little complicated because emsg-v0 atoms
647     // have presentation time *delta* while v1 atoms have absolute presentation time.
648     if (sampleTimeUs == C.TIME_UNSET) {
649       // We need the first sample timestamp in the segment before we can output the metadata.
650       pendingMetadataSampleInfos.addLast(
651           new MetadataSampleInfo(presentationTimeDeltaUs, sampleSize));
652       pendingMetadataSampleBytes += sampleSize;
653     } else {
654       if (timestampAdjuster != null) {
655         sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
656       }
657       for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
658         emsgTrackOutput.sampleMetadata(
659             sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, /* offset= */ 0, null);
660       }
661     }
662   }
663 
664   /** Parses a trex atom (defined in 14496-12). */
parseTrex(ParsableByteArray trex)665   private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
666     trex.setPosition(Atom.FULL_HEADER_SIZE);
667     int trackId = trex.readInt();
668     int defaultSampleDescriptionIndex = trex.readInt() - 1;
669     int defaultSampleDuration = trex.readInt();
670     int defaultSampleSize = trex.readInt();
671     int defaultSampleFlags = trex.readInt();
672 
673     return Pair.create(trackId, new DefaultSampleValues(defaultSampleDescriptionIndex,
674         defaultSampleDuration, defaultSampleSize, defaultSampleFlags));
675   }
676 
677   /**
678    * Parses an mehd atom (defined in 14496-12).
679    */
parseMehd(ParsableByteArray mehd)680   private static long parseMehd(ParsableByteArray mehd) {
681     mehd.setPosition(Atom.HEADER_SIZE);
682     int fullAtom = mehd.readInt();
683     int version = Atom.parseFullAtomVersion(fullAtom);
684     return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong();
685   }
686 
parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch)687   private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
688       @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
689     int moofContainerChildrenSize = moof.containerChildren.size();
690     for (int i = 0; i < moofContainerChildrenSize; i++) {
691       Atom.ContainerAtom child = moof.containerChildren.get(i);
692       // TODO: Support multiple traf boxes per track in a single moof.
693       if (child.type == Atom.TYPE_traf) {
694         parseTraf(child, trackBundleArray, flags, extendedTypeScratch);
695       }
696     }
697   }
698 
699   /**
700    * Parses a traf atom (defined in 14496-12).
701    */
parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch)702   private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
703       @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
704     LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
705     @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray);
706     if (trackBundle == null) {
707       return;
708     }
709 
710     TrackFragment fragment = trackBundle.fragment;
711     long decodeTime = fragment.nextFragmentDecodeTime;
712     trackBundle.reset();
713 
714     @Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
715     if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
716       decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
717     }
718 
719     parseTruns(traf, trackBundle, decodeTime, flags);
720 
721     @Nullable
722     TrackEncryptionBox encryptionBox =
723         trackBundle.track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex);
724 
725     @Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
726     if (saiz != null) {
727       parseSaiz(encryptionBox, saiz.data, fragment);
728     }
729 
730     @Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
731     if (saio != null) {
732       parseSaio(saio.data, fragment);
733     }
734 
735     @Nullable LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
736     if (senc != null) {
737       parseSenc(senc.data, fragment);
738     }
739 
740     @Nullable LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp);
741     @Nullable LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd);
742     if (sbgp != null && sgpd != null) {
743       parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null,
744           fragment);
745     }
746 
747     int leafChildrenSize = traf.leafChildren.size();
748     for (int i = 0; i < leafChildrenSize; i++) {
749       LeafAtom atom = traf.leafChildren.get(i);
750       if (atom.type == Atom.TYPE_uuid) {
751         parseUuid(atom.data, fragment, extendedTypeScratch);
752       }
753     }
754   }
755 
parseTruns( ContainerAtom traf, TrackBundle trackBundle, long decodeTime, @Flags int flags)756   private static void parseTruns(
757       ContainerAtom traf, TrackBundle trackBundle, long decodeTime, @Flags int flags)
758       throws ParserException {
759     int trunCount = 0;
760     int totalSampleCount = 0;
761     List<LeafAtom> leafChildren = traf.leafChildren;
762     int leafChildrenSize = leafChildren.size();
763     for (int i = 0; i < leafChildrenSize; i++) {
764       LeafAtom atom = leafChildren.get(i);
765       if (atom.type == Atom.TYPE_trun) {
766         ParsableByteArray trunData = atom.data;
767         trunData.setPosition(Atom.FULL_HEADER_SIZE);
768         int trunSampleCount = trunData.readUnsignedIntToInt();
769         if (trunSampleCount > 0) {
770           totalSampleCount += trunSampleCount;
771           trunCount++;
772         }
773       }
774     }
775     trackBundle.currentTrackRunIndex = 0;
776     trackBundle.currentSampleInTrackRun = 0;
777     trackBundle.currentSampleIndex = 0;
778     trackBundle.fragment.initTables(trunCount, totalSampleCount);
779 
780     int trunIndex = 0;
781     int trunStartPosition = 0;
782     for (int i = 0; i < leafChildrenSize; i++) {
783       LeafAtom trun = leafChildren.get(i);
784       if (trun.type == Atom.TYPE_trun) {
785         trunStartPosition = parseTrun(trackBundle, trunIndex++, decodeTime, flags, trun.data,
786             trunStartPosition);
787       }
788     }
789   }
790 
parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out)791   private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
792       TrackFragment out) throws ParserException {
793     int vectorSize = encryptionBox.perSampleIvSize;
794     saiz.setPosition(Atom.HEADER_SIZE);
795     int fullAtom = saiz.readInt();
796     int flags = Atom.parseFullAtomFlags(fullAtom);
797     if ((flags & 0x01) == 1) {
798       saiz.skipBytes(8);
799     }
800     int defaultSampleInfoSize = saiz.readUnsignedByte();
801 
802     int sampleCount = saiz.readUnsignedIntToInt();
803     if (sampleCount != out.sampleCount) {
804       throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
805     }
806 
807     int totalSize = 0;
808     if (defaultSampleInfoSize == 0) {
809       boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable;
810       for (int i = 0; i < sampleCount; i++) {
811         int sampleInfoSize = saiz.readUnsignedByte();
812         totalSize += sampleInfoSize;
813         sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize;
814       }
815     } else {
816       boolean subsampleEncryption = defaultSampleInfoSize > vectorSize;
817       totalSize += defaultSampleInfoSize * sampleCount;
818       Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
819     }
820     out.initEncryptionData(totalSize);
821   }
822 
823   /**
824    * Parses a saio atom (defined in 14496-12).
825    *
826    * @param saio The saio atom to decode.
827    * @param out The {@link TrackFragment} to populate with data from the saio atom.
828    */
parseSaio(ParsableByteArray saio, TrackFragment out)829   private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
830     saio.setPosition(Atom.HEADER_SIZE);
831     int fullAtom = saio.readInt();
832     int flags = Atom.parseFullAtomFlags(fullAtom);
833     if ((flags & 0x01) == 1) {
834       saio.skipBytes(8);
835     }
836 
837     int entryCount = saio.readUnsignedIntToInt();
838     if (entryCount != 1) {
839       // We only support one trun element currently, so always expect one entry.
840       throw new ParserException("Unexpected saio entry count: " + entryCount);
841     }
842 
843     int version = Atom.parseFullAtomVersion(fullAtom);
844     out.auxiliaryDataPosition +=
845         version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong();
846   }
847 
848   /**
849    * Parses a tfhd atom (defined in 14496-12), updates the corresponding {@link TrackFragment} and
850    * returns the {@link TrackBundle} of the corresponding {@link Track}. If the tfhd does not refer
851    * to any {@link TrackBundle}, {@code null} is returned and no changes are made.
852    *
853    * @param tfhd The tfhd atom to decode.
854    * @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed.
855    * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd
856    *     does not refer to any {@link TrackBundle}.
857    */
858   @Nullable
parseTfhd( ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles)859   private static TrackBundle parseTfhd(
860       ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles) {
861     tfhd.setPosition(Atom.HEADER_SIZE);
862     int fullAtom = tfhd.readInt();
863     int atomFlags = Atom.parseFullAtomFlags(fullAtom);
864     int trackId = tfhd.readInt();
865     @Nullable TrackBundle trackBundle = getTrackBundle(trackBundles, trackId);
866     if (trackBundle == null) {
867       return null;
868     }
869     if ((atomFlags & 0x01 /* base_data_offset_present */) != 0) {
870       long baseDataPosition = tfhd.readUnsignedLongToLong();
871       trackBundle.fragment.dataPosition = baseDataPosition;
872       trackBundle.fragment.auxiliaryDataPosition = baseDataPosition;
873     }
874 
875     DefaultSampleValues defaultSampleValues = trackBundle.defaultSampleValues;
876     int defaultSampleDescriptionIndex =
877         ((atomFlags & 0x02 /* default_sample_description_index_present */) != 0)
878             ? tfhd.readInt() - 1
879             : defaultSampleValues.sampleDescriptionIndex;
880     int defaultSampleDuration =
881         ((atomFlags & 0x08 /* default_sample_duration_present */) != 0)
882             ? tfhd.readInt()
883             : defaultSampleValues.duration;
884     int defaultSampleSize =
885         ((atomFlags & 0x10 /* default_sample_size_present */) != 0)
886             ? tfhd.readInt()
887             : defaultSampleValues.size;
888     int defaultSampleFlags =
889         ((atomFlags & 0x20 /* default_sample_flags_present */) != 0)
890             ? tfhd.readInt()
891             : defaultSampleValues.flags;
892     trackBundle.fragment.header = new DefaultSampleValues(defaultSampleDescriptionIndex,
893         defaultSampleDuration, defaultSampleSize, defaultSampleFlags);
894     return trackBundle;
895   }
896 
getTrackBundle( SparseArray<TrackBundle> trackBundles, int trackId)897   private static @Nullable TrackBundle getTrackBundle(
898       SparseArray<TrackBundle> trackBundles, int trackId) {
899     if (trackBundles.size() == 1) {
900       // Ignore track id if there is only one track. This is either because we have a side-loaded
901       // track (flag FLAG_SIDELOADED) or to cope with non-matching track indices (see
902       // https://github.com/google/ExoPlayer/issues/4083).
903       return trackBundles.valueAt(/* index= */ 0);
904     }
905     return trackBundles.get(trackId);
906   }
907 
908   /**
909    * Parses a tfdt atom (defined in 14496-12).
910    *
911    * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
912    *     media, expressed in the media's timescale.
913    */
parseTfdt(ParsableByteArray tfdt)914   private static long parseTfdt(ParsableByteArray tfdt) {
915     tfdt.setPosition(Atom.HEADER_SIZE);
916     int fullAtom = tfdt.readInt();
917     int version = Atom.parseFullAtomVersion(fullAtom);
918     return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
919   }
920 
921   /**
922    * Parses a trun atom (defined in 14496-12).
923    *
924    * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into which
925    *     parsed data should be placed.
926    * @param index Index of the track run in the fragment.
927    * @param decodeTime The decode time of the first sample in the fragment run.
928    * @param flags Flags to allow any required workaround to be executed.
929    * @param trun The trun atom to decode.
930    * @return The starting position of samples for the next run.
931    */
parseTrun( TrackBundle trackBundle, int index, long decodeTime, @Flags int flags, ParsableByteArray trun, int trackRunStart)932   private static int parseTrun(
933       TrackBundle trackBundle,
934       int index,
935       long decodeTime,
936       @Flags int flags,
937       ParsableByteArray trun,
938       int trackRunStart)
939       throws ParserException {
940     trun.setPosition(Atom.HEADER_SIZE);
941     int fullAtom = trun.readInt();
942     int atomFlags = Atom.parseFullAtomFlags(fullAtom);
943 
944     Track track = trackBundle.track;
945     TrackFragment fragment = trackBundle.fragment;
946     DefaultSampleValues defaultSampleValues = fragment.header;
947 
948     fragment.trunLength[index] = trun.readUnsignedIntToInt();
949     fragment.trunDataPosition[index] = fragment.dataPosition;
950     if ((atomFlags & 0x01 /* data_offset_present */) != 0) {
951       fragment.trunDataPosition[index] += trun.readInt();
952     }
953 
954     boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0;
955     int firstSampleFlags = defaultSampleValues.flags;
956     if (firstSampleFlagsPresent) {
957       firstSampleFlags = trun.readInt();
958     }
959 
960     boolean sampleDurationsPresent = (atomFlags & 0x100 /* sample_duration_present */) != 0;
961     boolean sampleSizesPresent = (atomFlags & 0x200 /* sample_size_present */) != 0;
962     boolean sampleFlagsPresent = (atomFlags & 0x400 /* sample_flags_present */) != 0;
963     boolean sampleCompositionTimeOffsetsPresent =
964         (atomFlags & 0x800 /* sample_composition_time_offsets_present */) != 0;
965 
966     // Offset to the entire video timeline. In the presence of B-frames this is usually used to
967     // ensure that the first frame's presentation timestamp is zero.
968     long edtsOffsetUs = 0;
969 
970     // Currently we only support a single edit that moves the entire media timeline (indicated by
971     // duration == 0). Other uses of edit lists are uncommon and unsupported.
972     if (track.editListDurations != null && track.editListDurations.length == 1
973         && track.editListDurations[0] == 0) {
974       edtsOffsetUs =
975           Util.scaleLargeTimestamp(
976               track.editListMediaTimes[0], C.MICROS_PER_SECOND, track.timescale);
977     }
978 
979     int[] sampleSizeTable = fragment.sampleSizeTable;
980     int[] sampleCompositionTimeOffsetUsTable = fragment.sampleCompositionTimeOffsetUsTable;
981     long[] sampleDecodingTimeUsTable = fragment.sampleDecodingTimeUsTable;
982     boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable;
983 
984     boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO
985         && (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
986 
987     int trackRunEnd = trackRunStart + fragment.trunLength[index];
988     long timescale = track.timescale;
989     long cumulativeTime = index > 0 ? fragment.nextFragmentDecodeTime : decodeTime;
990     for (int i = trackRunStart; i < trackRunEnd; i++) {
991       // Use trun values if present, otherwise tfhd, otherwise trex.
992       int sampleDuration =
993           checkNonNegative(sampleDurationsPresent ? trun.readInt() : defaultSampleValues.duration);
994       int sampleSize =
995           checkNonNegative(sampleSizesPresent ? trun.readInt() : defaultSampleValues.size);
996       int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags
997           : sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags;
998       if (sampleCompositionTimeOffsetsPresent) {
999         // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
1000         // version 0 trun boxes, however a significant number of streams violate the spec and use
1001         // signed integers instead. It's safe to always decode sample offsets as signed integers
1002         // here, because unsigned integers will still be parsed correctly (unless their top bit is
1003         // set, which is never true in practice because sample offsets are always small).
1004         int sampleOffset = trun.readInt();
1005         sampleCompositionTimeOffsetUsTable[i] =
1006             (int) ((sampleOffset * C.MICROS_PER_SECOND) / timescale);
1007       } else {
1008         sampleCompositionTimeOffsetUsTable[i] = 0;
1009       }
1010       sampleDecodingTimeUsTable[i] =
1011           Util.scaleLargeTimestamp(cumulativeTime, C.MICROS_PER_SECOND, timescale) - edtsOffsetUs;
1012       sampleSizeTable[i] = sampleSize;
1013       sampleIsSyncFrameTable[i] = ((sampleFlags >> 16) & 0x1) == 0
1014           && (!workaroundEveryVideoFrameIsSyncFrame || i == 0);
1015       cumulativeTime += sampleDuration;
1016     }
1017     fragment.nextFragmentDecodeTime = cumulativeTime;
1018     return trackRunEnd;
1019   }
1020 
checkNonNegative(int value)1021   private static int checkNonNegative(int value) throws ParserException {
1022     if (value < 0) {
1023       throw new ParserException("Unexpected negtive value: " + value);
1024     }
1025     return value;
1026   }
1027 
parseUuid(ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch)1028   private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
1029       byte[] extendedTypeScratch) throws ParserException {
1030     uuid.setPosition(Atom.HEADER_SIZE);
1031     uuid.readBytes(extendedTypeScratch, 0, 16);
1032 
1033     // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
1034     if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) {
1035       return;
1036     }
1037 
1038     // Except for the extended type, this box is identical to a SENC box. See "Portable encoding of
1039     // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al,
1040     // Section 5.3.2.1."
1041     parseSenc(uuid, 16, out);
1042   }
1043 
parseSenc(ParsableByteArray senc, TrackFragment out)1044   private static void parseSenc(ParsableByteArray senc, TrackFragment out) throws ParserException {
1045     parseSenc(senc, 0, out);
1046   }
1047 
parseSenc(ParsableByteArray senc, int offset, TrackFragment out)1048   private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out)
1049       throws ParserException {
1050     senc.setPosition(Atom.HEADER_SIZE + offset);
1051     int fullAtom = senc.readInt();
1052     int flags = Atom.parseFullAtomFlags(fullAtom);
1053 
1054     if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
1055       // TODO: Implement this.
1056       throw new ParserException("Overriding TrackEncryptionBox parameters is unsupported.");
1057     }
1058 
1059     boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
1060     int sampleCount = senc.readUnsignedIntToInt();
1061     if (sampleCount != out.sampleCount) {
1062       throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
1063     }
1064 
1065     Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
1066     out.initEncryptionData(senc.bytesLeft());
1067     out.fillEncryptionData(senc);
1068   }
1069 
parseSgpd( ParsableByteArray sbgp, ParsableByteArray sgpd, @Nullable String schemeType, TrackFragment out)1070   private static void parseSgpd(
1071       ParsableByteArray sbgp,
1072       ParsableByteArray sgpd,
1073       @Nullable String schemeType,
1074       TrackFragment out)
1075       throws ParserException {
1076     sbgp.setPosition(Atom.HEADER_SIZE);
1077     int sbgpFullAtom = sbgp.readInt();
1078     if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) {
1079       // Only seig grouping type is supported.
1080       return;
1081     }
1082     if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) {
1083       sbgp.skipBytes(4); // default_length.
1084     }
1085     if (sbgp.readInt() != 1) { // entry_count.
1086       throw new ParserException("Entry count in sbgp != 1 (unsupported).");
1087     }
1088 
1089     sgpd.setPosition(Atom.HEADER_SIZE);
1090     int sgpdFullAtom = sgpd.readInt();
1091     if (sgpd.readInt() != SAMPLE_GROUP_TYPE_seig) {
1092       // Only seig grouping type is supported.
1093       return;
1094     }
1095     int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom);
1096     if (sgpdVersion == 1) {
1097       if (sgpd.readUnsignedInt() == 0) {
1098         throw new ParserException("Variable length description in sgpd found (unsupported)");
1099       }
1100     } else if (sgpdVersion >= 2) {
1101       sgpd.skipBytes(4); // default_sample_description_index.
1102     }
1103     if (sgpd.readUnsignedInt() != 1) { // entry_count.
1104       throw new ParserException("Entry count in sgpd != 1 (unsupported).");
1105     }
1106     // CencSampleEncryptionInformationGroupEntry
1107     sgpd.skipBytes(1); // reserved = 0.
1108     int patternByte = sgpd.readUnsignedByte();
1109     int cryptByteBlock = (patternByte & 0xF0) >> 4;
1110     int skipByteBlock = patternByte & 0x0F;
1111     boolean isProtected = sgpd.readUnsignedByte() == 1;
1112     if (!isProtected) {
1113       return;
1114     }
1115     int perSampleIvSize = sgpd.readUnsignedByte();
1116     byte[] keyId = new byte[16];
1117     sgpd.readBytes(keyId, 0, keyId.length);
1118     byte[] constantIv = null;
1119     if (perSampleIvSize == 0) {
1120       int constantIvSize = sgpd.readUnsignedByte();
1121       constantIv = new byte[constantIvSize];
1122       sgpd.readBytes(constantIv, 0, constantIvSize);
1123     }
1124     out.definesEncryptionData = true;
1125     out.trackEncryptionBox = new TrackEncryptionBox(isProtected, schemeType, perSampleIvSize, keyId,
1126         cryptByteBlock, skipByteBlock, constantIv);
1127   }
1128 
1129   /**
1130    * Parses a sidx atom (defined in 14496-12).
1131    *
1132    * @param atom The atom data.
1133    * @param inputPosition The input position of the first byte after the atom.
1134    * @return A pair consisting of the earliest presentation time in microseconds, and the parsed
1135    *     {@link ChunkIndex}.
1136    */
parseSidx(ParsableByteArray atom, long inputPosition)1137   private static Pair<Long, ChunkIndex> parseSidx(ParsableByteArray atom, long inputPosition)
1138       throws ParserException {
1139     atom.setPosition(Atom.HEADER_SIZE);
1140     int fullAtom = atom.readInt();
1141     int version = Atom.parseFullAtomVersion(fullAtom);
1142 
1143     atom.skipBytes(4);
1144     long timescale = atom.readUnsignedInt();
1145     long earliestPresentationTime;
1146     long offset = inputPosition;
1147     if (version == 0) {
1148       earliestPresentationTime = atom.readUnsignedInt();
1149       offset += atom.readUnsignedInt();
1150     } else {
1151       earliestPresentationTime = atom.readUnsignedLongToLong();
1152       offset += atom.readUnsignedLongToLong();
1153     }
1154     long earliestPresentationTimeUs = Util.scaleLargeTimestamp(earliestPresentationTime,
1155         C.MICROS_PER_SECOND, timescale);
1156 
1157     atom.skipBytes(2);
1158 
1159     int referenceCount = atom.readUnsignedShort();
1160     int[] sizes = new int[referenceCount];
1161     long[] offsets = new long[referenceCount];
1162     long[] durationsUs = new long[referenceCount];
1163     long[] timesUs = new long[referenceCount];
1164 
1165     long time = earliestPresentationTime;
1166     long timeUs = earliestPresentationTimeUs;
1167     for (int i = 0; i < referenceCount; i++) {
1168       int firstInt = atom.readInt();
1169 
1170       int type = 0x80000000 & firstInt;
1171       if (type != 0) {
1172         throw new ParserException("Unhandled indirect reference");
1173       }
1174       long referenceDuration = atom.readUnsignedInt();
1175 
1176       sizes[i] = 0x7FFFFFFF & firstInt;
1177       offsets[i] = offset;
1178 
1179       // Calculate time and duration values such that any rounding errors are consistent. i.e. That
1180       // timesUs[i] + durationsUs[i] == timesUs[i + 1].
1181       timesUs[i] = timeUs;
1182       time += referenceDuration;
1183       timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale);
1184       durationsUs[i] = timeUs - timesUs[i];
1185 
1186       atom.skipBytes(4);
1187       offset += sizes[i];
1188     }
1189 
1190     return Pair.create(earliestPresentationTimeUs,
1191         new ChunkIndex(sizes, offsets, durationsUs, timesUs));
1192   }
1193 
readEncryptionData(ExtractorInput input)1194   private void readEncryptionData(ExtractorInput input) throws IOException {
1195     TrackBundle nextTrackBundle = null;
1196     long nextDataOffset = Long.MAX_VALUE;
1197     int trackBundlesSize = trackBundles.size();
1198     for (int i = 0; i < trackBundlesSize; i++) {
1199       TrackFragment trackFragment = trackBundles.valueAt(i).fragment;
1200       if (trackFragment.sampleEncryptionDataNeedsFill
1201           && trackFragment.auxiliaryDataPosition < nextDataOffset) {
1202         nextDataOffset = trackFragment.auxiliaryDataPosition;
1203         nextTrackBundle = trackBundles.valueAt(i);
1204       }
1205     }
1206     if (nextTrackBundle == null) {
1207       parserState = STATE_READING_SAMPLE_START;
1208       return;
1209     }
1210     int bytesToSkip = (int) (nextDataOffset - input.getPosition());
1211     if (bytesToSkip < 0) {
1212       throw new ParserException("Offset to encryption data was negative.");
1213     }
1214     input.skipFully(bytesToSkip);
1215     nextTrackBundle.fragment.fillEncryptionData(input);
1216   }
1217 
1218   /**
1219    * Attempts to read the next sample in the current mdat atom. The read sample may be output or
1220    * skipped.
1221    *
1222    * <p>If there are no more samples in the current mdat atom then the parser state is transitioned
1223    * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
1224    *
1225    * <p>It is possible for a sample to be partially read in the case that an exception is thrown. In
1226    * this case the method can be called again to read the remainder of the sample.
1227    *
1228    * @param input The {@link ExtractorInput} from which to read data.
1229    * @return Whether a sample was read. The read sample may have been output or skipped. False
1230    *     indicates that there are no samples left to read in the current mdat.
1231    * @throws IOException If an error occurs reading from the input.
1232    */
readSample(ExtractorInput input)1233   private boolean readSample(ExtractorInput input) throws IOException {
1234     if (parserState == STATE_READING_SAMPLE_START) {
1235       if (currentTrackBundle == null) {
1236         @Nullable TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles);
1237         if (currentTrackBundle == null) {
1238           // We've run out of samples in the current mdat. Discard any trailing data and prepare to
1239           // read the header of the next atom.
1240           int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
1241           if (bytesToSkip < 0) {
1242             throw new ParserException("Offset to end of mdat was negative.");
1243           }
1244           input.skipFully(bytesToSkip);
1245           enterReadingAtomHeaderState();
1246           return false;
1247         }
1248 
1249         long nextDataPosition = currentTrackBundle.fragment
1250             .trunDataPosition[currentTrackBundle.currentTrackRunIndex];
1251         // We skip bytes preceding the next sample to read.
1252         int bytesToSkip = (int) (nextDataPosition - input.getPosition());
1253         if (bytesToSkip < 0) {
1254           // Assume the sample data must be contiguous in the mdat with no preceding data.
1255           Log.w(TAG, "Ignoring negative offset to sample data.");
1256           bytesToSkip = 0;
1257         }
1258         input.skipFully(bytesToSkip);
1259         this.currentTrackBundle = currentTrackBundle;
1260       }
1261 
1262       sampleSize = currentTrackBundle.fragment
1263           .sampleSizeTable[currentTrackBundle.currentSampleIndex];
1264 
1265       if (currentTrackBundle.currentSampleIndex < currentTrackBundle.firstSampleToOutputIndex) {
1266         input.skipFully(sampleSize);
1267         currentTrackBundle.skipSampleEncryptionData();
1268         if (!currentTrackBundle.next()) {
1269           currentTrackBundle = null;
1270         }
1271         parserState = STATE_READING_SAMPLE_START;
1272         return true;
1273       }
1274 
1275       if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
1276         sampleSize -= Atom.HEADER_SIZE;
1277         input.skipFully(Atom.HEADER_SIZE);
1278       }
1279 
1280       if (MimeTypes.AUDIO_AC4.equals(currentTrackBundle.track.format.sampleMimeType)) {
1281         // AC4 samples need to be prefixed with a clear sample header.
1282         sampleBytesWritten =
1283             currentTrackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
1284         Ac4Util.getAc4SampleHeader(sampleSize, scratch);
1285         currentTrackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
1286         sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
1287       } else {
1288         sampleBytesWritten =
1289             currentTrackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0);
1290       }
1291       sampleSize += sampleBytesWritten;
1292       parserState = STATE_READING_SAMPLE_CONTINUE;
1293       sampleCurrentNalBytesRemaining = 0;
1294     }
1295 
1296     TrackFragment fragment = currentTrackBundle.fragment;
1297     Track track = currentTrackBundle.track;
1298     TrackOutput output = currentTrackBundle.output;
1299     int sampleIndex = currentTrackBundle.currentSampleIndex;
1300     long sampleTimeUs = fragment.getSamplePresentationTimeUs(sampleIndex);
1301     if (timestampAdjuster != null) {
1302       sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
1303     }
1304     if (track.nalUnitLengthFieldLength != 0) {
1305       // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
1306       // they're only 1 or 2 bytes long.
1307       byte[] nalPrefixData = nalPrefix.data;
1308       nalPrefixData[0] = 0;
1309       nalPrefixData[1] = 0;
1310       nalPrefixData[2] = 0;
1311       int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
1312       int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
1313       // NAL units are length delimited, but the decoder requires start code delimited units.
1314       // Loop until we've written the sample to the track output, replacing length delimiters with
1315       // start codes as we encounter them.
1316       while (sampleBytesWritten < sampleSize) {
1317         if (sampleCurrentNalBytesRemaining == 0) {
1318           // Read the NAL length so that we know where we find the next one, and its type.
1319           input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
1320           nalPrefix.setPosition(0);
1321           int nalLengthInt = nalPrefix.readInt();
1322           if (nalLengthInt < 1) {
1323             throw new ParserException("Invalid NAL length");
1324           }
1325           sampleCurrentNalBytesRemaining = nalLengthInt - 1;
1326           // Write a start code for the current NAL unit.
1327           nalStartCode.setPosition(0);
1328           output.sampleData(nalStartCode, 4);
1329           // Write the NAL unit type byte.
1330           output.sampleData(nalPrefix, 1);
1331           processSeiNalUnitPayload = cea608TrackOutputs.length > 0
1332               && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
1333           sampleBytesWritten += 5;
1334           sampleSize += nalUnitLengthFieldLengthDiff;
1335         } else {
1336           int writtenBytes;
1337           if (processSeiNalUnitPayload) {
1338             // Read and write the payload of the SEI NAL unit.
1339             nalBuffer.reset(sampleCurrentNalBytesRemaining);
1340             input.readFully(nalBuffer.data, 0, sampleCurrentNalBytesRemaining);
1341             output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
1342             writtenBytes = sampleCurrentNalBytesRemaining;
1343             // Unescape and process the SEI NAL unit.
1344             int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.data, nalBuffer.limit());
1345             // If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte.
1346             nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
1347             nalBuffer.setLimit(unescapedLength);
1348             CeaUtil.consume(sampleTimeUs, nalBuffer, cea608TrackOutputs);
1349           } else {
1350             // Write the payload of the NAL unit.
1351             writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
1352           }
1353           sampleBytesWritten += writtenBytes;
1354           sampleCurrentNalBytesRemaining -= writtenBytes;
1355         }
1356       }
1357     } else {
1358       while (sampleBytesWritten < sampleSize) {
1359         int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
1360         sampleBytesWritten += writtenBytes;
1361       }
1362     }
1363 
1364     @C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex]
1365         ? C.BUFFER_FLAG_KEY_FRAME : 0;
1366 
1367     // Encryption data.
1368     TrackOutput.CryptoData cryptoData = null;
1369     TrackEncryptionBox encryptionBox = currentTrackBundle.getEncryptionBoxIfEncrypted();
1370     if (encryptionBox != null) {
1371       sampleFlags |= C.BUFFER_FLAG_ENCRYPTED;
1372       cryptoData = encryptionBox.cryptoData;
1373     }
1374 
1375     output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData);
1376 
1377     // After we have the sampleTimeUs, we can commit all the pending metadata samples
1378     outputPendingMetadataSamples(sampleTimeUs);
1379     if (!currentTrackBundle.next()) {
1380       currentTrackBundle = null;
1381     }
1382     parserState = STATE_READING_SAMPLE_START;
1383     return true;
1384   }
1385 
outputPendingMetadataSamples(long sampleTimeUs)1386   private void outputPendingMetadataSamples(long sampleTimeUs) {
1387     while (!pendingMetadataSampleInfos.isEmpty()) {
1388       MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst();
1389       pendingMetadataSampleBytes -= sampleInfo.size;
1390       long metadataTimeUs = sampleTimeUs + sampleInfo.presentationTimeDeltaUs;
1391       if (timestampAdjuster != null) {
1392         metadataTimeUs = timestampAdjuster.adjustSampleTimestamp(metadataTimeUs);
1393       }
1394       for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
1395         emsgTrackOutput.sampleMetadata(
1396             metadataTimeUs,
1397             C.BUFFER_FLAG_KEY_FRAME,
1398             sampleInfo.size,
1399             pendingMetadataSampleBytes,
1400             null);
1401       }
1402     }
1403   }
1404 
1405   /**
1406    * Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those
1407    * yet to be consumed, or null if all have been consumed.
1408    */
1409   @Nullable
getNextFragmentRun(SparseArray<TrackBundle> trackBundles)1410   private static TrackBundle getNextFragmentRun(SparseArray<TrackBundle> trackBundles) {
1411     TrackBundle nextTrackBundle = null;
1412     long nextTrackRunOffset = Long.MAX_VALUE;
1413 
1414     int trackBundlesSize = trackBundles.size();
1415     for (int i = 0; i < trackBundlesSize; i++) {
1416       TrackBundle trackBundle = trackBundles.valueAt(i);
1417       if (trackBundle.currentTrackRunIndex == trackBundle.fragment.trunCount) {
1418         // This track fragment contains no more runs in the next mdat box.
1419       } else {
1420         long trunOffset = trackBundle.fragment.trunDataPosition[trackBundle.currentTrackRunIndex];
1421         if (trunOffset < nextTrackRunOffset) {
1422           nextTrackBundle = trackBundle;
1423           nextTrackRunOffset = trunOffset;
1424         }
1425       }
1426     }
1427     return nextTrackBundle;
1428   }
1429 
1430   /** Returns DrmInitData from leaf atoms. */
1431   @Nullable
getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren)1432   private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
1433     @Nullable ArrayList<SchemeData> schemeDatas = null;
1434     int leafChildrenSize = leafChildren.size();
1435     for (int i = 0; i < leafChildrenSize; i++) {
1436       LeafAtom child = leafChildren.get(i);
1437       if (child.type == Atom.TYPE_pssh) {
1438         if (schemeDatas == null) {
1439           schemeDatas = new ArrayList<>();
1440         }
1441         byte[] psshData = child.data.data;
1442         @Nullable UUID uuid = PsshAtomUtil.parseUuid(psshData);
1443         if (uuid == null) {
1444           Log.w(TAG, "Skipped pssh atom (failed to extract uuid)");
1445         } else {
1446           schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData));
1447         }
1448       }
1449     }
1450     return schemeDatas == null ? null : new DrmInitData(schemeDatas);
1451   }
1452 
1453   /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */
shouldParseLeafAtom(int atom)1454   private static boolean shouldParseLeafAtom(int atom) {
1455     return atom == Atom.TYPE_hdlr || atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd
1456         || atom == Atom.TYPE_sidx || atom == Atom.TYPE_stsd || atom == Atom.TYPE_tfdt
1457         || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex
1458         || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz
1459         || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid
1460         || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst
1461         || atom == Atom.TYPE_mehd || atom == Atom.TYPE_emsg;
1462   }
1463 
1464   /** Returns whether the extractor should decode a container atom with type {@code atom}. */
shouldParseContainerAtom(int atom)1465   private static boolean shouldParseContainerAtom(int atom) {
1466     return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia
1467         || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_moof
1468         || atom == Atom.TYPE_traf || atom == Atom.TYPE_mvex || atom == Atom.TYPE_edts;
1469   }
1470 
1471   /**
1472    * Holds data corresponding to a metadata sample.
1473    */
1474   private static final class MetadataSampleInfo {
1475 
1476     public final long presentationTimeDeltaUs;
1477     public final int size;
1478 
MetadataSampleInfo(long presentationTimeDeltaUs, int size)1479     public MetadataSampleInfo(long presentationTimeDeltaUs, int size) {
1480       this.presentationTimeDeltaUs = presentationTimeDeltaUs;
1481       this.size = size;
1482     }
1483 
1484   }
1485 
1486   /**
1487    * Holds data corresponding to a single track.
1488    */
1489   private static final class TrackBundle {
1490 
1491     private static final int SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH = 8;
1492 
1493     public final TrackOutput output;
1494     public final TrackFragment fragment;
1495     public final ParsableByteArray scratch;
1496 
1497     public Track track;
1498     public DefaultSampleValues defaultSampleValues;
1499     public int currentSampleIndex;
1500     public int currentSampleInTrackRun;
1501     public int currentTrackRunIndex;
1502     public int firstSampleToOutputIndex;
1503 
1504     private final ParsableByteArray encryptionSignalByte;
1505     private final ParsableByteArray defaultInitializationVector;
1506 
TrackBundle(TrackOutput output)1507     public TrackBundle(TrackOutput output) {
1508       this.output = output;
1509       fragment = new TrackFragment();
1510       scratch = new ParsableByteArray();
1511       encryptionSignalByte = new ParsableByteArray(1);
1512       defaultInitializationVector = new ParsableByteArray();
1513     }
1514 
init(Track track, DefaultSampleValues defaultSampleValues)1515     public void init(Track track, DefaultSampleValues defaultSampleValues) {
1516       this.track = Assertions.checkNotNull(track);
1517       this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues);
1518       output.format(track.format);
1519       reset();
1520     }
1521 
updateDrmInitData(DrmInitData drmInitData)1522     public void updateDrmInitData(DrmInitData drmInitData) {
1523       @Nullable
1524       TrackEncryptionBox encryptionBox =
1525           track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex);
1526       @Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null;
1527       DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType);
1528       Format format = track.format.buildUpon().setDrmInitData(updatedDrmInitData).build();
1529       output.format(format);
1530     }
1531 
1532     /** Resets the current fragment and sample indices. */
reset()1533     public void reset() {
1534       fragment.reset();
1535       currentSampleIndex = 0;
1536       currentTrackRunIndex = 0;
1537       currentSampleInTrackRun = 0;
1538       firstSampleToOutputIndex = 0;
1539     }
1540 
1541     /**
1542      * Advances {@link #firstSampleToOutputIndex} to point to the sync sample before the specified
1543      * seek time in the current fragment.
1544      *
1545      * @param timeUs The seek time, in microseconds.
1546      */
seek(long timeUs)1547     public void seek(long timeUs) {
1548       int searchIndex = currentSampleIndex;
1549       while (searchIndex < fragment.sampleCount
1550           && fragment.getSamplePresentationTimeUs(searchIndex) < timeUs) {
1551         if (fragment.sampleIsSyncFrameTable[searchIndex]) {
1552           firstSampleToOutputIndex = searchIndex;
1553         }
1554         searchIndex++;
1555       }
1556     }
1557 
1558     /**
1559      * Advances the indices in the bundle to point to the next sample in the current fragment. If
1560      * the current sample is the last one in the current fragment, then the advanced state will be
1561      * {@code currentSampleIndex == fragment.sampleCount}, {@code currentTrackRunIndex ==
1562      * fragment.trunCount} and {@code #currentSampleInTrackRun == 0}.
1563      *
1564      * @return Whether the next sample is in the same track run as the previous one.
1565      */
next()1566     public boolean next() {
1567       currentSampleIndex++;
1568       currentSampleInTrackRun++;
1569       if (currentSampleInTrackRun == fragment.trunLength[currentTrackRunIndex]) {
1570         currentTrackRunIndex++;
1571         currentSampleInTrackRun = 0;
1572         return false;
1573       }
1574       return true;
1575     }
1576 
1577     /**
1578      * Outputs the encryption data for the current sample.
1579      *
1580      * @param sampleSize The size of the current sample in bytes, excluding any additional clear
1581      *     header that will be prefixed to the sample by the extractor.
1582      * @param clearHeaderSize The size of a clear header that will be prefixed to the sample by the
1583      *     extractor, or 0.
1584      * @return The number of written bytes.
1585      */
outputSampleEncryptionData(int sampleSize, int clearHeaderSize)1586     public int outputSampleEncryptionData(int sampleSize, int clearHeaderSize) {
1587       TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
1588       if (encryptionBox == null) {
1589         return 0;
1590       }
1591 
1592       ParsableByteArray initializationVectorData;
1593       int vectorSize;
1594       if (encryptionBox.perSampleIvSize != 0) {
1595         initializationVectorData = fragment.sampleEncryptionData;
1596         vectorSize = encryptionBox.perSampleIvSize;
1597       } else {
1598         // The default initialization vector should be used.
1599         byte[] initVectorData = encryptionBox.defaultInitializationVector;
1600         defaultInitializationVector.reset(initVectorData, initVectorData.length);
1601         initializationVectorData = defaultInitializationVector;
1602         vectorSize = initVectorData.length;
1603       }
1604 
1605       boolean haveSubsampleEncryptionTable =
1606           fragment.sampleHasSubsampleEncryptionTable(currentSampleIndex);
1607       boolean writeSubsampleEncryptionData = haveSubsampleEncryptionTable || clearHeaderSize != 0;
1608 
1609       // Write the signal byte, containing the vector size and the subsample encryption flag.
1610       encryptionSignalByte.data[0] =
1611           (byte) (vectorSize | (writeSubsampleEncryptionData ? 0x80 : 0));
1612       encryptionSignalByte.setPosition(0);
1613       output.sampleData(encryptionSignalByte, 1, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
1614       // Write the vector.
1615       output.sampleData(
1616           initializationVectorData, vectorSize, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
1617 
1618       if (!writeSubsampleEncryptionData) {
1619         return 1 + vectorSize;
1620       }
1621 
1622       if (!haveSubsampleEncryptionTable) {
1623         // The sample is fully encrypted, except for the additional clear header that the extractor
1624         // is going to prefix. We need to synthesize subsample encryption data that takes the header
1625         // into account.
1626         scratch.reset(SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH);
1627         // subsampleCount = 1 (unsigned short)
1628         scratch.data[0] = (byte) 0;
1629         scratch.data[1] = (byte) 1;
1630         // clearDataSize = clearHeaderSize (unsigned short)
1631         scratch.data[2] = (byte) ((clearHeaderSize >> 8) & 0xFF);
1632         scratch.data[3] = (byte) (clearHeaderSize & 0xFF);
1633         // encryptedDataSize = sampleSize (unsigned short)
1634         scratch.data[4] = (byte) ((sampleSize >> 24) & 0xFF);
1635         scratch.data[5] = (byte) ((sampleSize >> 16) & 0xFF);
1636         scratch.data[6] = (byte) ((sampleSize >> 8) & 0xFF);
1637         scratch.data[7] = (byte) (sampleSize & 0xFF);
1638         output.sampleData(
1639             scratch,
1640             SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH,
1641             TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
1642         return 1 + vectorSize + SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH;
1643       }
1644 
1645       ParsableByteArray subsampleEncryptionData = fragment.sampleEncryptionData;
1646       int subsampleCount = subsampleEncryptionData.readUnsignedShort();
1647       subsampleEncryptionData.skipBytes(-2);
1648       int subsampleDataLength = 2 + 6 * subsampleCount;
1649 
1650       if (clearHeaderSize != 0) {
1651         // We need to account for the additional clear header by adding clearHeaderSize to
1652         // clearDataSize for the first subsample specified in the subsample encryption data.
1653         scratch.reset(subsampleDataLength);
1654         scratch.readBytes(subsampleEncryptionData.data, /* offset= */ 0, subsampleDataLength);
1655         subsampleEncryptionData.skipBytes(subsampleDataLength);
1656 
1657         int clearDataSize = (scratch.data[2] & 0xFF) << 8 | (scratch.data[3] & 0xFF);
1658         int adjustedClearDataSize = clearDataSize + clearHeaderSize;
1659         scratch.data[2] = (byte) ((adjustedClearDataSize >> 8) & 0xFF);
1660         scratch.data[3] = (byte) (adjustedClearDataSize & 0xFF);
1661         subsampleEncryptionData = scratch;
1662       }
1663 
1664       output.sampleData(
1665           subsampleEncryptionData, subsampleDataLength, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
1666       return 1 + vectorSize + subsampleDataLength;
1667     }
1668 
1669     /** Skips the encryption data for the current sample. */
skipSampleEncryptionData()1670     private void skipSampleEncryptionData() {
1671       @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
1672       if (encryptionBox == null) {
1673         return;
1674       }
1675 
1676       ParsableByteArray sampleEncryptionData = fragment.sampleEncryptionData;
1677       if (encryptionBox.perSampleIvSize != 0) {
1678         sampleEncryptionData.skipBytes(encryptionBox.perSampleIvSize);
1679       }
1680       if (fragment.sampleHasSubsampleEncryptionTable(currentSampleIndex)) {
1681         sampleEncryptionData.skipBytes(6 * sampleEncryptionData.readUnsignedShort());
1682       }
1683     }
1684 
1685     @Nullable
getEncryptionBoxIfEncrypted()1686     private TrackEncryptionBox getEncryptionBoxIfEncrypted() {
1687       int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
1688       @Nullable
1689       TrackEncryptionBox encryptionBox =
1690           fragment.trackEncryptionBox != null
1691               ? fragment.trackEncryptionBox
1692               : track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex);
1693       return encryptionBox != null && encryptionBox.isEncrypted ? encryptionBox : null;
1694     }
1695 
1696   }
1697 
1698 }
1699