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