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.support.annotation.IntDef;
19 import android.util.Log;
20 import android.util.Pair;
21 import android.util.SparseArray;
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.drm.DrmInitData;
26 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
27 import com.google.android.exoplayer2.extractor.ChunkIndex;
28 import com.google.android.exoplayer2.extractor.Extractor;
29 import com.google.android.exoplayer2.extractor.ExtractorInput;
30 import com.google.android.exoplayer2.extractor.ExtractorOutput;
31 import com.google.android.exoplayer2.extractor.ExtractorsFactory;
32 import com.google.android.exoplayer2.extractor.PositionHolder;
33 import com.google.android.exoplayer2.extractor.SeekMap;
34 import com.google.android.exoplayer2.extractor.TrackOutput;
35 import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
36 import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
37 import com.google.android.exoplayer2.text.cea.CeaUtil;
38 import com.google.android.exoplayer2.util.Assertions;
39 import com.google.android.exoplayer2.util.MimeTypes;
40 import com.google.android.exoplayer2.util.NalUnitUtil;
41 import com.google.android.exoplayer2.util.ParsableByteArray;
42 import com.google.android.exoplayer2.util.TimestampAdjuster;
43 import com.google.android.exoplayer2.util.Util;
44 import java.io.IOException;
45 import java.lang.annotation.Retention;
46 import java.lang.annotation.RetentionPolicy;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Stack;
52 import java.util.UUID;
53 
54 /**
55  * Facilitates the extraction of data from the fragmented mp4 container format.
56  */
57 public final class FragmentedMp4Extractor implements Extractor {
58 
59   /**
60    * Factory for {@link FragmentedMp4Extractor} instances.
61    */
62   public static final ExtractorsFactory FACTORY = new ExtractorsFactory() {
63 
64     @Override
65     public Extractor[] createExtractors() {
66       return new Extractor[] {new FragmentedMp4Extractor()};
67     }
68 
69   };
70 
71   /**
72    * Flags controlling the behavior of the extractor.
73    */
74   @Retention(RetentionPolicy.SOURCE)
75   @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
76       FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_ENABLE_CEA608_TRACK,
77       FLAG_SIDELOADED})
78   public @interface Flags {}
79   /**
80    * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
81    * The workaround overrides the sync frame flags in the stream, forcing them to false except for
82    * the first sample in each segment.
83    * <p>
84    * This flag does nothing if the stream is not a video stream.
85    */
86   public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
87   /**
88    * Flag to ignore any tfdt boxes in the stream.
89    */
90   public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
91   /**
92    * Flag to indicate that the extractor should output an event message metadata track. Any event
93    * messages in the stream will be delivered as samples to this track.
94    */
95   public static final int FLAG_ENABLE_EMSG_TRACK = 4;
96   /**
97    * Flag to indicate that the extractor should output a CEA-608 text track. Any CEA-608 messages
98    * contained within SEI NAL units in the stream will be delivered as samples to this track.
99    */
100   public static final int FLAG_ENABLE_CEA608_TRACK = 8;
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 = 16;
106 
107   private static final String TAG = "FragmentedMp4Extractor";
108   private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
109   private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
110       new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
111 
112   // Parser states.
113   private static final int STATE_READING_ATOM_HEADER = 0;
114   private static final int STATE_READING_ATOM_PAYLOAD = 1;
115   private static final int STATE_READING_ENCRYPTION_DATA = 2;
116   private static final int STATE_READING_SAMPLE_START = 3;
117   private static final int STATE_READING_SAMPLE_CONTINUE = 4;
118 
119   // Workarounds.
120   @Flags private final int flags;
121   private final Track sideloadedTrack;
122 
123   // Track-linked data bundle, accessible as a whole through trackID.
124   private final SparseArray<TrackBundle> trackBundles;
125 
126   // Temporary arrays.
127   private final ParsableByteArray nalStartCode;
128   private final ParsableByteArray nalPrefix;
129   private final ParsableByteArray nalBuffer;
130   private final ParsableByteArray encryptionSignalByte;
131 
132   // Adjusts sample timestamps.
133   private final TimestampAdjuster timestampAdjuster;
134 
135   // Parser state.
136   private final ParsableByteArray atomHeader;
137   private final byte[] extendedTypeScratch;
138   private final Stack<ContainerAtom> containerAtoms;
139   private final LinkedList<MetadataSampleInfo> pendingMetadataSampleInfos;
140 
141   private int parserState;
142   private int atomType;
143   private long atomSize;
144   private int atomHeaderBytesRead;
145   private ParsableByteArray atomData;
146   private long endOfMdatPosition;
147   private int pendingMetadataSampleBytes;
148 
149   private long durationUs;
150   private long segmentIndexEarliestPresentationTimeUs;
151   private TrackBundle currentTrackBundle;
152   private int sampleSize;
153   private int sampleBytesWritten;
154   private int sampleCurrentNalBytesRemaining;
155   private boolean processSeiNalUnitPayload;
156 
157   // Extractor output.
158   private ExtractorOutput extractorOutput;
159   private TrackOutput eventMessageTrackOutput;
160   private TrackOutput[] cea608TrackOutputs;
161 
162   // Whether extractorOutput.seekMap has been called.
163   private boolean haveOutputSeekMap;
164 
FragmentedMp4Extractor()165   public FragmentedMp4Extractor() {
166     this(0);
167   }
168 
169   /**
170    * @param flags Flags that control the extractor's behavior.
171    */
FragmentedMp4Extractor(@lags int flags)172   public FragmentedMp4Extractor(@Flags int flags) {
173     this(flags, null);
174   }
175 
176   /**
177    * @param flags Flags that control the extractor's behavior.
178    * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
179    */
FragmentedMp4Extractor(@lags int flags, TimestampAdjuster timestampAdjuster)180   public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) {
181     this(flags, timestampAdjuster, null);
182   }
183 
184   /**
185    * @param flags Flags that control the extractor's behavior.
186    * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
187    * @param sideloadedTrack Sideloaded track information, in the case that the extractor
188    *     will not receive a moov box in the input data.
189    */
FragmentedMp4Extractor(@lags int flags, TimestampAdjuster timestampAdjuster, Track sideloadedTrack)190   public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster,
191       Track sideloadedTrack) {
192     this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
193     this.timestampAdjuster = timestampAdjuster;
194     this.sideloadedTrack = sideloadedTrack;
195     atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
196     nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
197     nalPrefix = new ParsableByteArray(5);
198     nalBuffer = new ParsableByteArray();
199     encryptionSignalByte = new ParsableByteArray(1);
200     extendedTypeScratch = new byte[16];
201     containerAtoms = new Stack<>();
202     pendingMetadataSampleInfos = new LinkedList<>();
203     trackBundles = new SparseArray<>();
204     durationUs = C.TIME_UNSET;
205     segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
206     enterReadingAtomHeaderState();
207   }
208 
209   @Override
sniff(ExtractorInput input)210   public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
211     return Sniffer.sniffFragmented(input);
212   }
213 
214   @Override
init(ExtractorOutput output)215   public void init(ExtractorOutput output) {
216     extractorOutput = output;
217     if (sideloadedTrack != null) {
218       TrackBundle bundle = new TrackBundle(output.track(0, sideloadedTrack.type));
219       bundle.init(sideloadedTrack, new DefaultSampleValues(0, 0, 0, 0));
220       trackBundles.put(0, bundle);
221       maybeInitExtraTracks();
222       extractorOutput.endTracks();
223     }
224   }
225 
226   @Override
seek(long position, long timeUs)227   public void seek(long position, long timeUs) {
228     int trackCount = trackBundles.size();
229     for (int i = 0; i < trackCount; i++) {
230       trackBundles.valueAt(i).reset();
231     }
232     pendingMetadataSampleInfos.clear();
233     pendingMetadataSampleBytes = 0;
234     containerAtoms.clear();
235     enterReadingAtomHeaderState();
236   }
237 
238   @Override
release()239   public void release() {
240     // Do nothing
241   }
242 
243   @Override
read(ExtractorInput input, PositionHolder seekPosition)244   public int read(ExtractorInput input, PositionHolder seekPosition)
245       throws IOException, InterruptedException {
246     while (true) {
247       switch (parserState) {
248         case STATE_READING_ATOM_HEADER:
249           if (!readAtomHeader(input)) {
250             return Extractor.RESULT_END_OF_INPUT;
251           }
252           break;
253         case STATE_READING_ATOM_PAYLOAD:
254           readAtomPayload(input);
255           break;
256         case STATE_READING_ENCRYPTION_DATA:
257           readEncryptionData(input);
258           break;
259         default:
260           if (readSample(input)) {
261             return RESULT_CONTINUE;
262           }
263       }
264     }
265   }
266 
enterReadingAtomHeaderState()267   private void enterReadingAtomHeaderState() {
268     parserState = STATE_READING_ATOM_HEADER;
269     atomHeaderBytesRead = 0;
270   }
271 
readAtomHeader(ExtractorInput input)272   private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException {
273     if (atomHeaderBytesRead == 0) {
274       // Read the standard length atom header.
275       if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
276         return false;
277       }
278       atomHeaderBytesRead = Atom.HEADER_SIZE;
279       atomHeader.setPosition(0);
280       atomSize = atomHeader.readUnsignedInt();
281       atomType = atomHeader.readInt();
282     }
283 
284     if (atomSize == Atom.LONG_SIZE_PREFIX) {
285       // Read the extended atom size.
286       int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
287       input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
288       atomHeaderBytesRead += headerBytesRemaining;
289       atomSize = atomHeader.readUnsignedLongToLong();
290     }
291 
292     if (atomSize < atomHeaderBytesRead) {
293       throw new ParserException("Atom size less than header length (unsupported).");
294     }
295 
296     long atomPosition = input.getPosition() - atomHeaderBytesRead;
297     if (atomType == Atom.TYPE_moof) {
298       // The data positions may be updated when parsing the tfhd/trun.
299       int trackCount = trackBundles.size();
300       for (int i = 0; i < trackCount; i++) {
301         TrackFragment fragment = trackBundles.valueAt(i).fragment;
302         fragment.atomPosition = atomPosition;
303         fragment.auxiliaryDataPosition = atomPosition;
304         fragment.dataPosition = atomPosition;
305       }
306     }
307 
308     if (atomType == Atom.TYPE_mdat) {
309       currentTrackBundle = null;
310       endOfMdatPosition = atomPosition + atomSize;
311       if (!haveOutputSeekMap) {
312         extractorOutput.seekMap(new SeekMap.Unseekable(durationUs));
313         haveOutputSeekMap = true;
314       }
315       parserState = STATE_READING_ENCRYPTION_DATA;
316       return true;
317     }
318 
319     if (shouldParseContainerAtom(atomType)) {
320       long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE;
321       containerAtoms.add(new ContainerAtom(atomType, endPosition));
322       if (atomSize == atomHeaderBytesRead) {
323         processAtomEnded(endPosition);
324       } else {
325         // Start reading the first child atom.
326         enterReadingAtomHeaderState();
327       }
328     } else if (shouldParseLeafAtom(atomType)) {
329       if (atomHeaderBytesRead != Atom.HEADER_SIZE) {
330         throw new ParserException("Leaf atom defines extended atom size (unsupported).");
331       }
332       if (atomSize > Integer.MAX_VALUE) {
333         throw new ParserException("Leaf atom with length > 2147483647 (unsupported).");
334       }
335       atomData = new ParsableByteArray((int) atomSize);
336       System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE);
337       parserState = STATE_READING_ATOM_PAYLOAD;
338     } else {
339       if (atomSize > Integer.MAX_VALUE) {
340         throw new ParserException("Skipping atom with length > 2147483647 (unsupported).");
341       }
342       atomData = null;
343       parserState = STATE_READING_ATOM_PAYLOAD;
344     }
345 
346     return true;
347   }
348 
readAtomPayload(ExtractorInput input)349   private void readAtomPayload(ExtractorInput input) throws IOException, InterruptedException {
350     int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
351     if (atomData != null) {
352       input.readFully(atomData.data, Atom.HEADER_SIZE, atomPayloadSize);
353       onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
354     } else {
355       input.skipFully(atomPayloadSize);
356     }
357     processAtomEnded(input.getPosition());
358   }
359 
processAtomEnded(long atomEndPosition)360   private void processAtomEnded(long atomEndPosition) throws ParserException {
361     while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) {
362       onContainerAtomRead(containerAtoms.pop());
363     }
364     enterReadingAtomHeaderState();
365   }
366 
onLeafAtomRead(LeafAtom leaf, long inputPosition)367   private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException {
368     if (!containerAtoms.isEmpty()) {
369       containerAtoms.peek().add(leaf);
370     } else if (leaf.type == Atom.TYPE_sidx) {
371       Pair<Long, ChunkIndex> result = parseSidx(leaf.data, inputPosition);
372       segmentIndexEarliestPresentationTimeUs = result.first;
373       extractorOutput.seekMap(result.second);
374       haveOutputSeekMap = true;
375     } else if (leaf.type == Atom.TYPE_emsg) {
376       onEmsgLeafAtomRead(leaf.data);
377     }
378   }
379 
onContainerAtomRead(ContainerAtom container)380   private void onContainerAtomRead(ContainerAtom container) throws ParserException {
381     if (container.type == Atom.TYPE_moov) {
382       onMoovContainerAtomRead(container);
383     } else if (container.type == Atom.TYPE_moof) {
384       onMoofContainerAtomRead(container);
385     } else if (!containerAtoms.isEmpty()) {
386       containerAtoms.peek().add(container);
387     }
388   }
389 
onMoovContainerAtomRead(ContainerAtom moov)390   private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
391     Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
392 
393     DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
394 
395     // Read declaration of track fragments in the Moov box.
396     ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
397     SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
398     long duration = C.TIME_UNSET;
399     int mvexChildrenSize = mvex.leafChildren.size();
400     for (int i = 0; i < mvexChildrenSize; i++) {
401       Atom.LeafAtom atom = mvex.leafChildren.get(i);
402       if (atom.type == Atom.TYPE_trex) {
403         Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data);
404         defaultSampleValuesArray.put(trexData.first, trexData.second);
405       } else if (atom.type == Atom.TYPE_mehd) {
406         duration = parseMehd(atom.data);
407       }
408     }
409 
410     // Construction of tracks.
411     SparseArray<Track> tracks = new SparseArray<>();
412     int moovContainerChildrenSize = moov.containerChildren.size();
413     for (int i = 0; i < moovContainerChildrenSize; i++) {
414       Atom.ContainerAtom atom = moov.containerChildren.get(i);
415       if (atom.type == Atom.TYPE_trak) {
416         Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration,
417             drmInitData, false);
418         if (track != null) {
419           tracks.put(track.id, track);
420         }
421       }
422     }
423 
424     int trackCount = tracks.size();
425     if (trackBundles.size() == 0) {
426       // We need to create the track bundles.
427       for (int i = 0; i < trackCount; i++) {
428         Track track = tracks.valueAt(i);
429         TrackBundle trackBundle = new TrackBundle(extractorOutput.track(i, track.type));
430         trackBundle.init(track, defaultSampleValuesArray.get(track.id));
431         trackBundles.put(track.id, trackBundle);
432         durationUs = Math.max(durationUs, track.durationUs);
433       }
434       maybeInitExtraTracks();
435       extractorOutput.endTracks();
436     } else {
437       Assertions.checkState(trackBundles.size() == trackCount);
438       for (int i = 0; i < trackCount; i++) {
439         Track track = tracks.valueAt(i);
440         trackBundles.get(track.id).init(track, defaultSampleValuesArray.get(track.id));
441       }
442     }
443   }
444 
onMoofContainerAtomRead(ContainerAtom moof)445   private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
446     parseMoof(moof, trackBundles, flags, extendedTypeScratch);
447     DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
448     if (drmInitData != null) {
449       int trackCount = trackBundles.size();
450       for (int i = 0; i < trackCount; i++) {
451         trackBundles.valueAt(i).updateDrmInitData(drmInitData);
452       }
453     }
454   }
455 
maybeInitExtraTracks()456   private void maybeInitExtraTracks() {
457     if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0 && eventMessageTrackOutput == null) {
458       eventMessageTrackOutput = extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA);
459       eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG,
460           Format.OFFSET_SAMPLE_RELATIVE));
461     }
462     if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutputs == null) {
463       TrackOutput cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1,
464           C.TRACK_TYPE_TEXT);
465       cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
466           null, Format.NO_VALUE, 0, null, null));
467       cea608TrackOutputs = new TrackOutput[] {cea608TrackOutput};
468     }
469   }
470 
471   /**
472    * Handles an emsg atom (defined in 23009-1).
473    */
onEmsgLeafAtomRead(ParsableByteArray atom)474   private void onEmsgLeafAtomRead(ParsableByteArray atom) {
475     if (eventMessageTrackOutput == null) {
476       return;
477     }
478     // Parse the event's presentation time delta.
479     atom.setPosition(Atom.FULL_HEADER_SIZE);
480     atom.readNullTerminatedString(); // schemeIdUri
481     atom.readNullTerminatedString(); // value
482     long timescale = atom.readUnsignedInt();
483     long presentationTimeDeltaUs =
484         Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
485     // Output the sample data.
486     atom.setPosition(Atom.FULL_HEADER_SIZE);
487     int sampleSize = atom.bytesLeft();
488     eventMessageTrackOutput.sampleData(atom, sampleSize);
489     // Output the sample metadata.
490     if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
491       // We can output the sample metadata immediately.
492       eventMessageTrackOutput.sampleMetadata(
493           segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs,
494           C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0 /* offset */, null);
495     } else {
496       // We need the first sample timestamp in the segment before we can output the metadata.
497       pendingMetadataSampleInfos.addLast(
498           new MetadataSampleInfo(presentationTimeDeltaUs, sampleSize));
499       pendingMetadataSampleBytes += sampleSize;
500     }
501   }
502 
503   /**
504    * Parses a trex atom (defined in 14496-12).
505    */
parseTrex(ParsableByteArray trex)506   private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
507     trex.setPosition(Atom.FULL_HEADER_SIZE);
508     int trackId = trex.readInt();
509     int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
510     int defaultSampleDuration = trex.readUnsignedIntToInt();
511     int defaultSampleSize = trex.readUnsignedIntToInt();
512     int defaultSampleFlags = trex.readInt();
513 
514     return Pair.create(trackId, new DefaultSampleValues(defaultSampleDescriptionIndex,
515         defaultSampleDuration, defaultSampleSize, defaultSampleFlags));
516   }
517 
518   /**
519    * Parses an mehd atom (defined in 14496-12).
520    */
parseMehd(ParsableByteArray mehd)521   private static long parseMehd(ParsableByteArray mehd) {
522     mehd.setPosition(Atom.HEADER_SIZE);
523     int fullAtom = mehd.readInt();
524     int version = Atom.parseFullAtomVersion(fullAtom);
525     return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong();
526   }
527 
parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch)528   private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
529       @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
530     int moofContainerChildrenSize = moof.containerChildren.size();
531     for (int i = 0; i < moofContainerChildrenSize; i++) {
532       Atom.ContainerAtom child = moof.containerChildren.get(i);
533       // TODO: Support multiple traf boxes per track in a single moof.
534       if (child.type == Atom.TYPE_traf) {
535         parseTraf(child, trackBundleArray, flags, extendedTypeScratch);
536       }
537     }
538   }
539 
540   /**
541    * Parses a traf atom (defined in 14496-12).
542    */
parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch)543   private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
544       @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
545     LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
546     TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
547     if (trackBundle == null) {
548       return;
549     }
550 
551     TrackFragment fragment = trackBundle.fragment;
552     long decodeTime = fragment.nextFragmentDecodeTime;
553     trackBundle.reset();
554 
555     LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
556     if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
557       decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
558     }
559 
560     parseTruns(traf, trackBundle, decodeTime, flags);
561 
562     LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
563     if (saiz != null) {
564       TrackEncryptionBox trackEncryptionBox = trackBundle.track
565           .sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex];
566       parseSaiz(trackEncryptionBox, saiz.data, fragment);
567     }
568 
569     LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
570     if (saio != null) {
571       parseSaio(saio.data, fragment);
572     }
573 
574     LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
575     if (senc != null) {
576       parseSenc(senc.data, fragment);
577     }
578 
579     LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp);
580     LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd);
581     if (sbgp != null && sgpd != null) {
582       parseSgpd(sbgp.data, sgpd.data, fragment);
583     }
584 
585     int leafChildrenSize = traf.leafChildren.size();
586     for (int i = 0; i < leafChildrenSize; i++) {
587       LeafAtom atom = traf.leafChildren.get(i);
588       if (atom.type == Atom.TYPE_uuid) {
589         parseUuid(atom.data, fragment, extendedTypeScratch);
590       }
591     }
592   }
593 
parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, @Flags int flags)594   private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
595       @Flags int flags) {
596     int trunCount = 0;
597     int totalSampleCount = 0;
598     List<LeafAtom> leafChildren = traf.leafChildren;
599     int leafChildrenSize = leafChildren.size();
600     for (int i = 0; i < leafChildrenSize; i++) {
601       LeafAtom atom = leafChildren.get(i);
602       if (atom.type == Atom.TYPE_trun) {
603         ParsableByteArray trunData = atom.data;
604         trunData.setPosition(Atom.FULL_HEADER_SIZE);
605         int trunSampleCount = trunData.readUnsignedIntToInt();
606         if (trunSampleCount > 0) {
607           totalSampleCount += trunSampleCount;
608           trunCount++;
609         }
610       }
611     }
612     trackBundle.currentTrackRunIndex = 0;
613     trackBundle.currentSampleInTrackRun = 0;
614     trackBundle.currentSampleIndex = 0;
615     trackBundle.fragment.initTables(trunCount, totalSampleCount);
616 
617     int trunIndex = 0;
618     int trunStartPosition = 0;
619     for (int i = 0; i < leafChildrenSize; i++) {
620       LeafAtom trun = leafChildren.get(i);
621       if (trun.type == Atom.TYPE_trun) {
622         trunStartPosition = parseTrun(trackBundle, trunIndex++, decodeTime, flags, trun.data,
623             trunStartPosition);
624       }
625     }
626   }
627 
parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out)628   private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
629       TrackFragment out) throws ParserException {
630     int vectorSize = encryptionBox.initializationVectorSize;
631     saiz.setPosition(Atom.HEADER_SIZE);
632     int fullAtom = saiz.readInt();
633     int flags = Atom.parseFullAtomFlags(fullAtom);
634     if ((flags & 0x01) == 1) {
635       saiz.skipBytes(8);
636     }
637     int defaultSampleInfoSize = saiz.readUnsignedByte();
638 
639     int sampleCount = saiz.readUnsignedIntToInt();
640     if (sampleCount != out.sampleCount) {
641       throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
642     }
643 
644     int totalSize = 0;
645     if (defaultSampleInfoSize == 0) {
646       boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable;
647       for (int i = 0; i < sampleCount; i++) {
648         int sampleInfoSize = saiz.readUnsignedByte();
649         totalSize += sampleInfoSize;
650         sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize;
651       }
652     } else {
653       boolean subsampleEncryption = defaultSampleInfoSize > vectorSize;
654       totalSize += defaultSampleInfoSize * sampleCount;
655       Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
656     }
657     out.initEncryptionData(totalSize);
658   }
659 
660   /**
661    * Parses a saio atom (defined in 14496-12).
662    *
663    * @param saio The saio atom to decode.
664    * @param out The {@link TrackFragment} to populate with data from the saio atom.
665    */
parseSaio(ParsableByteArray saio, TrackFragment out)666   private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
667     saio.setPosition(Atom.HEADER_SIZE);
668     int fullAtom = saio.readInt();
669     int flags = Atom.parseFullAtomFlags(fullAtom);
670     if ((flags & 0x01) == 1) {
671       saio.skipBytes(8);
672     }
673 
674     int entryCount = saio.readUnsignedIntToInt();
675     if (entryCount != 1) {
676       // We only support one trun element currently, so always expect one entry.
677       throw new ParserException("Unexpected saio entry count: " + entryCount);
678     }
679 
680     int version = Atom.parseFullAtomVersion(fullAtom);
681     out.auxiliaryDataPosition +=
682         version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong();
683   }
684 
685   /**
686    * Parses a tfhd atom (defined in 14496-12), updates the corresponding {@link TrackFragment} and
687    * returns the {@link TrackBundle} of the corresponding {@link Track}. If the tfhd does not refer
688    * to any {@link TrackBundle}, {@code null} is returned and no changes are made.
689    *
690    * @param tfhd The tfhd atom to decode.
691    * @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed.
692    * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd
693    *     does not refer to any {@link TrackBundle}.
694    */
parseTfhd(ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles, int flags)695   private static TrackBundle parseTfhd(ParsableByteArray tfhd,
696       SparseArray<TrackBundle> trackBundles, int flags) {
697     tfhd.setPosition(Atom.HEADER_SIZE);
698     int fullAtom = tfhd.readInt();
699     int atomFlags = Atom.parseFullAtomFlags(fullAtom);
700     int trackId = tfhd.readInt();
701     TrackBundle trackBundle = trackBundles.get((flags & FLAG_SIDELOADED) == 0 ? trackId : 0);
702     if (trackBundle == null) {
703       return null;
704     }
705     if ((atomFlags & 0x01 /* base_data_offset_present */) != 0) {
706       long baseDataPosition = tfhd.readUnsignedLongToLong();
707       trackBundle.fragment.dataPosition = baseDataPosition;
708       trackBundle.fragment.auxiliaryDataPosition = baseDataPosition;
709     }
710 
711     DefaultSampleValues defaultSampleValues = trackBundle.defaultSampleValues;
712     int defaultSampleDescriptionIndex =
713         ((atomFlags & 0x02 /* default_sample_description_index_present */) != 0)
714             ? tfhd.readUnsignedIntToInt() - 1 : defaultSampleValues.sampleDescriptionIndex;
715     int defaultSampleDuration = ((atomFlags & 0x08 /* default_sample_duration_present */) != 0)
716         ? tfhd.readUnsignedIntToInt() : defaultSampleValues.duration;
717     int defaultSampleSize = ((atomFlags & 0x10 /* default_sample_size_present */) != 0)
718         ? tfhd.readUnsignedIntToInt() : defaultSampleValues.size;
719     int defaultSampleFlags = ((atomFlags & 0x20 /* default_sample_flags_present */) != 0)
720         ? tfhd.readUnsignedIntToInt() : defaultSampleValues.flags;
721     trackBundle.fragment.header = new DefaultSampleValues(defaultSampleDescriptionIndex,
722         defaultSampleDuration, defaultSampleSize, defaultSampleFlags);
723     return trackBundle;
724   }
725 
726   /**
727    * Parses a tfdt atom (defined in 14496-12).
728    *
729    * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
730    *     media, expressed in the media's timescale.
731    */
parseTfdt(ParsableByteArray tfdt)732   private static long parseTfdt(ParsableByteArray tfdt) {
733     tfdt.setPosition(Atom.HEADER_SIZE);
734     int fullAtom = tfdt.readInt();
735     int version = Atom.parseFullAtomVersion(fullAtom);
736     return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
737   }
738 
739   /**
740    * Parses a trun atom (defined in 14496-12).
741    *
742    * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into
743    *     which parsed data should be placed.
744    * @param index Index of the track run in the fragment.
745    * @param decodeTime The decode time of the first sample in the fragment run.
746    * @param flags Flags to allow any required workaround to be executed.
747    * @param trun The trun atom to decode.
748    * @return The starting position of samples for the next run.
749    */
parseTrun(TrackBundle trackBundle, int index, long decodeTime, @Flags int flags, ParsableByteArray trun, int trackRunStart)750   private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
751       @Flags int flags, ParsableByteArray trun, int trackRunStart) {
752     trun.setPosition(Atom.HEADER_SIZE);
753     int fullAtom = trun.readInt();
754     int atomFlags = Atom.parseFullAtomFlags(fullAtom);
755 
756     Track track = trackBundle.track;
757     TrackFragment fragment = trackBundle.fragment;
758     DefaultSampleValues defaultSampleValues = fragment.header;
759 
760     fragment.trunLength[index] = trun.readUnsignedIntToInt();
761     fragment.trunDataPosition[index] = fragment.dataPosition;
762     if ((atomFlags & 0x01 /* data_offset_present */) != 0) {
763       fragment.trunDataPosition[index] += trun.readInt();
764     }
765 
766     boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0;
767     int firstSampleFlags = defaultSampleValues.flags;
768     if (firstSampleFlagsPresent) {
769       firstSampleFlags = trun.readUnsignedIntToInt();
770     }
771 
772     boolean sampleDurationsPresent = (atomFlags & 0x100 /* sample_duration_present */) != 0;
773     boolean sampleSizesPresent = (atomFlags & 0x200 /* sample_size_present */) != 0;
774     boolean sampleFlagsPresent = (atomFlags & 0x400 /* sample_flags_present */) != 0;
775     boolean sampleCompositionTimeOffsetsPresent =
776         (atomFlags & 0x800 /* sample_composition_time_offsets_present */) != 0;
777 
778     // Offset to the entire video timeline. In the presence of B-frames this is usually used to
779     // ensure that the first frame's presentation timestamp is zero.
780     long edtsOffset = 0;
781 
782     // Currently we only support a single edit that moves the entire media timeline (indicated by
783     // duration == 0). Other uses of edit lists are uncommon and unsupported.
784     if (track.editListDurations != null && track.editListDurations.length == 1
785         && track.editListDurations[0] == 0) {
786       edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale);
787     }
788 
789     int[] sampleSizeTable = fragment.sampleSizeTable;
790     int[] sampleCompositionTimeOffsetTable = fragment.sampleCompositionTimeOffsetTable;
791     long[] sampleDecodingTimeTable = fragment.sampleDecodingTimeTable;
792     boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable;
793 
794     boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO
795         && (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
796 
797     int trackRunEnd = trackRunStart + fragment.trunLength[index];
798     long timescale = track.timescale;
799     long cumulativeTime = index > 0 ? fragment.nextFragmentDecodeTime : decodeTime;
800     for (int i = trackRunStart; i < trackRunEnd; i++) {
801       // Use trun values if present, otherwise tfhd, otherwise trex.
802       int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
803           : defaultSampleValues.duration;
804       int sampleSize = sampleSizesPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.size;
805       int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags
806           : sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags;
807       if (sampleCompositionTimeOffsetsPresent) {
808         // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
809         // version 0 trun boxes, however a significant number of streams violate the spec and use
810         // signed integers instead. It's safe to always decode sample offsets as signed integers
811         // here, because unsigned integers will still be parsed correctly (unless their top bit is
812         // set, which is never true in practice because sample offsets are always small).
813         int sampleOffset = trun.readInt();
814         sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale);
815       } else {
816         sampleCompositionTimeOffsetTable[i] = 0;
817       }
818       sampleDecodingTimeTable[i] =
819           Util.scaleLargeTimestamp(cumulativeTime, 1000, timescale) - edtsOffset;
820       sampleSizeTable[i] = sampleSize;
821       sampleIsSyncFrameTable[i] = ((sampleFlags >> 16) & 0x1) == 0
822           && (!workaroundEveryVideoFrameIsSyncFrame || i == 0);
823       cumulativeTime += sampleDuration;
824     }
825     fragment.nextFragmentDecodeTime = cumulativeTime;
826     return trackRunEnd;
827   }
828 
parseUuid(ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch)829   private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
830       byte[] extendedTypeScratch) throws ParserException {
831     uuid.setPosition(Atom.HEADER_SIZE);
832     uuid.readBytes(extendedTypeScratch, 0, 16);
833 
834     // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
835     if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) {
836       return;
837     }
838 
839     // Except for the extended type, this box is identical to a SENC box. See "Portable encoding of
840     // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al,
841     // Section 5.3.2.1."
842     parseSenc(uuid, 16, out);
843   }
844 
parseSenc(ParsableByteArray senc, TrackFragment out)845   private static void parseSenc(ParsableByteArray senc, TrackFragment out) throws ParserException {
846     parseSenc(senc, 0, out);
847   }
848 
parseSenc(ParsableByteArray senc, int offset, TrackFragment out)849   private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out)
850       throws ParserException {
851     senc.setPosition(Atom.HEADER_SIZE + offset);
852     int fullAtom = senc.readInt();
853     int flags = Atom.parseFullAtomFlags(fullAtom);
854 
855     if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
856       // TODO: Implement this.
857       throw new ParserException("Overriding TrackEncryptionBox parameters is unsupported.");
858     }
859 
860     boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
861     int sampleCount = senc.readUnsignedIntToInt();
862     if (sampleCount != out.sampleCount) {
863       throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
864     }
865 
866     Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
867     out.initEncryptionData(senc.bytesLeft());
868     out.fillEncryptionData(senc);
869   }
870 
parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, TrackFragment out)871   private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, TrackFragment out)
872       throws ParserException {
873     sbgp.setPosition(Atom.HEADER_SIZE);
874     int sbgpFullAtom = sbgp.readInt();
875     if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) {
876       // Only seig grouping type is supported.
877       return;
878     }
879     if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) {
880       sbgp.skipBytes(4);
881     }
882     if (sbgp.readInt() != 1) {
883       throw new ParserException("Entry count in sbgp != 1 (unsupported).");
884     }
885 
886     sgpd.setPosition(Atom.HEADER_SIZE);
887     int sgpdFullAtom = sgpd.readInt();
888     if (sgpd.readInt() != SAMPLE_GROUP_TYPE_seig) {
889       // Only seig grouping type is supported.
890       return;
891     }
892     int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom);
893     if (sgpdVersion == 1) {
894       if (sgpd.readUnsignedInt() == 0) {
895         throw new ParserException("Variable length decription in sgpd found (unsupported)");
896       }
897     } else if (sgpdVersion >= 2) {
898       sgpd.skipBytes(4);
899     }
900     if (sgpd.readUnsignedInt() != 1) {
901       throw new ParserException("Entry count in sgpd != 1 (unsupported).");
902     }
903     // CencSampleEncryptionInformationGroupEntry
904     sgpd.skipBytes(2);
905     boolean isProtected = sgpd.readUnsignedByte() == 1;
906     if (!isProtected) {
907       return;
908     }
909     int initVectorSize = sgpd.readUnsignedByte();
910     byte[] keyId = new byte[16];
911     sgpd.readBytes(keyId, 0, keyId.length);
912     out.definesEncryptionData = true;
913     out.trackEncryptionBox = new TrackEncryptionBox(isProtected, initVectorSize, keyId);
914   }
915 
916   /**
917    * Parses a sidx atom (defined in 14496-12).
918    *
919    * @param atom The atom data.
920    * @param inputPosition The input position of the first byte after the atom.
921    * @return A pair consisting of the earliest presentation time in microseconds, and the parsed
922    *     {@link ChunkIndex}.
923    */
parseSidx(ParsableByteArray atom, long inputPosition)924   private static Pair<Long, ChunkIndex> parseSidx(ParsableByteArray atom, long inputPosition)
925       throws ParserException {
926     atom.setPosition(Atom.HEADER_SIZE);
927     int fullAtom = atom.readInt();
928     int version = Atom.parseFullAtomVersion(fullAtom);
929 
930     atom.skipBytes(4);
931     long timescale = atom.readUnsignedInt();
932     long earliestPresentationTime;
933     long offset = inputPosition;
934     if (version == 0) {
935       earliestPresentationTime = atom.readUnsignedInt();
936       offset += atom.readUnsignedInt();
937     } else {
938       earliestPresentationTime = atom.readUnsignedLongToLong();
939       offset += atom.readUnsignedLongToLong();
940     }
941     long earliestPresentationTimeUs = Util.scaleLargeTimestamp(earliestPresentationTime,
942         C.MICROS_PER_SECOND, timescale);
943 
944     atom.skipBytes(2);
945 
946     int referenceCount = atom.readUnsignedShort();
947     int[] sizes = new int[referenceCount];
948     long[] offsets = new long[referenceCount];
949     long[] durationsUs = new long[referenceCount];
950     long[] timesUs = new long[referenceCount];
951 
952     long time = earliestPresentationTime;
953     long timeUs = earliestPresentationTimeUs;
954     for (int i = 0; i < referenceCount; i++) {
955       int firstInt = atom.readInt();
956 
957       int type = 0x80000000 & firstInt;
958       if (type != 0) {
959         throw new ParserException("Unhandled indirect reference");
960       }
961       long referenceDuration = atom.readUnsignedInt();
962 
963       sizes[i] = 0x7FFFFFFF & firstInt;
964       offsets[i] = offset;
965 
966       // Calculate time and duration values such that any rounding errors are consistent. i.e. That
967       // timesUs[i] + durationsUs[i] == timesUs[i + 1].
968       timesUs[i] = timeUs;
969       time += referenceDuration;
970       timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale);
971       durationsUs[i] = timeUs - timesUs[i];
972 
973       atom.skipBytes(4);
974       offset += sizes[i];
975     }
976 
977     return Pair.create(earliestPresentationTimeUs,
978         new ChunkIndex(sizes, offsets, durationsUs, timesUs));
979   }
980 
readEncryptionData(ExtractorInput input)981   private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
982     TrackBundle nextTrackBundle = null;
983     long nextDataOffset = Long.MAX_VALUE;
984     int trackBundlesSize = trackBundles.size();
985     for (int i = 0; i < trackBundlesSize; i++) {
986       TrackFragment trackFragment = trackBundles.valueAt(i).fragment;
987       if (trackFragment.sampleEncryptionDataNeedsFill
988           && trackFragment.auxiliaryDataPosition < nextDataOffset) {
989         nextDataOffset = trackFragment.auxiliaryDataPosition;
990         nextTrackBundle = trackBundles.valueAt(i);
991       }
992     }
993     if (nextTrackBundle == null) {
994       parserState = STATE_READING_SAMPLE_START;
995       return;
996     }
997     int bytesToSkip = (int) (nextDataOffset - input.getPosition());
998     if (bytesToSkip < 0) {
999       throw new ParserException("Offset to encryption data was negative.");
1000     }
1001     input.skipFully(bytesToSkip);
1002     nextTrackBundle.fragment.fillEncryptionData(input);
1003   }
1004 
1005   /**
1006    * Attempts to extract the next sample in the current mdat atom.
1007    * <p>
1008    * If there are no more samples in the current mdat atom then the parser state is transitioned
1009    * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
1010    * <p>
1011    * It is possible for a sample to be extracted in part in the case that an exception is thrown. In
1012    * this case the method can be called again to extract the remainder of the sample.
1013    *
1014    * @param input The {@link ExtractorInput} from which to read data.
1015    * @return Whether a sample was extracted.
1016    * @throws IOException If an error occurs reading from the input.
1017    * @throws InterruptedException If the thread is interrupted.
1018    */
readSample(ExtractorInput input)1019   private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
1020     if (parserState == STATE_READING_SAMPLE_START) {
1021       if (currentTrackBundle == null) {
1022         TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles);
1023         if (currentTrackBundle == null) {
1024           // We've run out of samples in the current mdat. Discard any trailing data and prepare to
1025           // read the header of the next atom.
1026           int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
1027           if (bytesToSkip < 0) {
1028             throw new ParserException("Offset to end of mdat was negative.");
1029           }
1030           input.skipFully(bytesToSkip);
1031           enterReadingAtomHeaderState();
1032           return false;
1033         }
1034 
1035         long nextDataPosition = currentTrackBundle.fragment
1036             .trunDataPosition[currentTrackBundle.currentTrackRunIndex];
1037         // We skip bytes preceding the next sample to read.
1038         int bytesToSkip = (int) (nextDataPosition - input.getPosition());
1039         if (bytesToSkip < 0) {
1040           // Assume the sample data must be contiguous in the mdat with no preceding data.
1041           Log.w(TAG, "Ignoring negative offset to sample data.");
1042           bytesToSkip = 0;
1043         }
1044         input.skipFully(bytesToSkip);
1045         this.currentTrackBundle = currentTrackBundle;
1046       }
1047       sampleSize = currentTrackBundle.fragment
1048           .sampleSizeTable[currentTrackBundle.currentSampleIndex];
1049       if (currentTrackBundle.fragment.definesEncryptionData) {
1050         sampleBytesWritten = appendSampleEncryptionData(currentTrackBundle);
1051         sampleSize += sampleBytesWritten;
1052       } else {
1053         sampleBytesWritten = 0;
1054       }
1055       if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
1056         sampleSize -= Atom.HEADER_SIZE;
1057         input.skipFully(Atom.HEADER_SIZE);
1058       }
1059       parserState = STATE_READING_SAMPLE_CONTINUE;
1060       sampleCurrentNalBytesRemaining = 0;
1061     }
1062 
1063     TrackFragment fragment = currentTrackBundle.fragment;
1064     Track track = currentTrackBundle.track;
1065     TrackOutput output = currentTrackBundle.output;
1066     int sampleIndex = currentTrackBundle.currentSampleIndex;
1067     if (track.nalUnitLengthFieldLength != 0) {
1068       // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
1069       // they're only 1 or 2 bytes long.
1070       byte[] nalPrefixData = nalPrefix.data;
1071       nalPrefixData[0] = 0;
1072       nalPrefixData[1] = 0;
1073       nalPrefixData[2] = 0;
1074       int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
1075       int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
1076       // NAL units are length delimited, but the decoder requires start code delimited units.
1077       // Loop until we've written the sample to the track output, replacing length delimiters with
1078       // start codes as we encounter them.
1079       while (sampleBytesWritten < sampleSize) {
1080         if (sampleCurrentNalBytesRemaining == 0) {
1081           // Read the NAL length so that we know where we find the next one, and its type.
1082           input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
1083           nalPrefix.setPosition(0);
1084           sampleCurrentNalBytesRemaining = nalPrefix.readUnsignedIntToInt() - 1;
1085           // Write a start code for the current NAL unit.
1086           nalStartCode.setPosition(0);
1087           output.sampleData(nalStartCode, 4);
1088           // Write the NAL unit type byte.
1089           output.sampleData(nalPrefix, 1);
1090           processSeiNalUnitPayload = cea608TrackOutputs != null
1091               && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
1092           sampleBytesWritten += 5;
1093           sampleSize += nalUnitLengthFieldLengthDiff;
1094         } else {
1095           int writtenBytes;
1096           if (processSeiNalUnitPayload) {
1097             // Read and write the payload of the SEI NAL unit.
1098             nalBuffer.reset(sampleCurrentNalBytesRemaining);
1099             input.readFully(nalBuffer.data, 0, sampleCurrentNalBytesRemaining);
1100             output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
1101             writtenBytes = sampleCurrentNalBytesRemaining;
1102             // Unescape and process the SEI NAL unit.
1103             int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.data, nalBuffer.limit());
1104             // If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte.
1105             nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
1106             nalBuffer.setLimit(unescapedLength);
1107             CeaUtil.consume(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer,
1108                 cea608TrackOutputs);
1109           } else {
1110             // Write the payload of the NAL unit.
1111             writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
1112           }
1113           sampleBytesWritten += writtenBytes;
1114           sampleCurrentNalBytesRemaining -= writtenBytes;
1115         }
1116       }
1117     } else {
1118       while (sampleBytesWritten < sampleSize) {
1119         int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
1120         sampleBytesWritten += writtenBytes;
1121       }
1122     }
1123 
1124     long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
1125     @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
1126         | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
1127     int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
1128     byte[] encryptionKey = null;
1129     if (fragment.definesEncryptionData) {
1130       encryptionKey = fragment.trackEncryptionBox != null
1131           ? fragment.trackEncryptionBox.keyId
1132           : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId;
1133     }
1134     if (timestampAdjuster != null) {
1135       sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
1136     }
1137     output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
1138 
1139     while (!pendingMetadataSampleInfos.isEmpty()) {
1140       MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst();
1141       pendingMetadataSampleBytes -= sampleInfo.size;
1142       eventMessageTrackOutput.sampleMetadata(
1143           sampleTimeUs + sampleInfo.presentationTimeDeltaUs,
1144           C.BUFFER_FLAG_KEY_FRAME, sampleInfo.size, pendingMetadataSampleBytes, null);
1145     }
1146 
1147     currentTrackBundle.currentSampleIndex++;
1148     currentTrackBundle.currentSampleInTrackRun++;
1149     if (currentTrackBundle.currentSampleInTrackRun
1150         == fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) {
1151       currentTrackBundle.currentTrackRunIndex++;
1152       currentTrackBundle.currentSampleInTrackRun = 0;
1153       currentTrackBundle = null;
1154     }
1155     parserState = STATE_READING_SAMPLE_START;
1156     return true;
1157   }
1158 
1159   /**
1160    * Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those
1161    * yet to be consumed, or null if all have been consumed.
1162    */
getNextFragmentRun(SparseArray<TrackBundle> trackBundles)1163   private static TrackBundle getNextFragmentRun(SparseArray<TrackBundle> trackBundles) {
1164     TrackBundle nextTrackBundle = null;
1165     long nextTrackRunOffset = Long.MAX_VALUE;
1166 
1167     int trackBundlesSize = trackBundles.size();
1168     for (int i = 0; i < trackBundlesSize; i++) {
1169       TrackBundle trackBundle = trackBundles.valueAt(i);
1170       if (trackBundle.currentTrackRunIndex == trackBundle.fragment.trunCount) {
1171         // This track fragment contains no more runs in the next mdat box.
1172       } else {
1173         long trunOffset = trackBundle.fragment.trunDataPosition[trackBundle.currentTrackRunIndex];
1174         if (trunOffset < nextTrackRunOffset) {
1175           nextTrackBundle = trackBundle;
1176           nextTrackRunOffset = trunOffset;
1177         }
1178       }
1179     }
1180     return nextTrackBundle;
1181   }
1182 
1183   /**
1184    * Appends the corresponding encryption data to the {@link TrackOutput} contained in the given
1185    * {@link TrackBundle}.
1186    *
1187    * @param trackBundle The {@link TrackBundle} that contains the {@link Track} for which the
1188    *     Sample encryption data must be output.
1189    * @return The number of written bytes.
1190    */
appendSampleEncryptionData(TrackBundle trackBundle)1191   private int appendSampleEncryptionData(TrackBundle trackBundle) {
1192     TrackFragment trackFragment = trackBundle.fragment;
1193     ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData;
1194     int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex;
1195     TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null
1196         ? trackFragment.trackEncryptionBox
1197         : trackBundle.track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
1198     int vectorSize = encryptionBox.initializationVectorSize;
1199     boolean subsampleEncryption = trackFragment
1200         .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex];
1201 
1202     // Write the signal byte, containing the vector size and the subsample encryption flag.
1203     encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0));
1204     encryptionSignalByte.setPosition(0);
1205     TrackOutput output = trackBundle.output;
1206     output.sampleData(encryptionSignalByte, 1);
1207     // Write the vector.
1208     output.sampleData(sampleEncryptionData, vectorSize);
1209     // If we don't have subsample encryption data, we're done.
1210     if (!subsampleEncryption) {
1211       return 1 + vectorSize;
1212     }
1213     // Write the subsample encryption data.
1214     int subsampleCount = sampleEncryptionData.readUnsignedShort();
1215     sampleEncryptionData.skipBytes(-2);
1216     int subsampleDataLength = 2 + 6 * subsampleCount;
1217     output.sampleData(sampleEncryptionData, subsampleDataLength);
1218     return 1 + vectorSize + subsampleDataLength;
1219   }
1220 
1221 
1222   /** Returns DrmInitData from leaf atoms. */
getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren)1223   private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
1224     ArrayList<SchemeData> schemeDatas = null;
1225     int leafChildrenSize = leafChildren.size();
1226     for (int i = 0; i < leafChildrenSize; i++) {
1227       LeafAtom child = leafChildren.get(i);
1228       if (child.type == Atom.TYPE_pssh) {
1229         if (schemeDatas == null) {
1230           schemeDatas = new ArrayList<>();
1231         }
1232         byte[] psshData = child.data.data;
1233         UUID uuid = PsshAtomUtil.parseUuid(psshData);
1234         if (uuid == null) {
1235           Log.w(TAG, "Skipped pssh atom (failed to extract uuid)");
1236         } else {
1237           schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData));
1238         }
1239       }
1240     }
1241     return schemeDatas == null ? null : new DrmInitData(schemeDatas);
1242   }
1243 
1244   /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */
shouldParseLeafAtom(int atom)1245   private static boolean shouldParseLeafAtom(int atom) {
1246     return atom == Atom.TYPE_hdlr || atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd
1247         || atom == Atom.TYPE_sidx || atom == Atom.TYPE_stsd || atom == Atom.TYPE_tfdt
1248         || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex
1249         || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz
1250         || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid
1251         || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst
1252         || atom == Atom.TYPE_mehd || atom == Atom.TYPE_emsg;
1253   }
1254 
1255   /** Returns whether the extractor should decode a container atom with type {@code atom}. */
shouldParseContainerAtom(int atom)1256   private static boolean shouldParseContainerAtom(int atom) {
1257     return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia
1258         || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_moof
1259         || atom == Atom.TYPE_traf || atom == Atom.TYPE_mvex || atom == Atom.TYPE_edts;
1260   }
1261 
1262   /**
1263    * Holds data corresponding to a metadata sample.
1264    */
1265   private static final class MetadataSampleInfo {
1266 
1267     public final long presentationTimeDeltaUs;
1268     public final int size;
1269 
MetadataSampleInfo(long presentationTimeDeltaUs, int size)1270     public MetadataSampleInfo(long presentationTimeDeltaUs, int size) {
1271       this.presentationTimeDeltaUs = presentationTimeDeltaUs;
1272       this.size = size;
1273     }
1274 
1275   }
1276 
1277   /**
1278    * Holds data corresponding to a single track.
1279    */
1280   private static final class TrackBundle {
1281 
1282     public final TrackFragment fragment;
1283     public final TrackOutput output;
1284 
1285     public Track track;
1286     public DefaultSampleValues defaultSampleValues;
1287     public int currentSampleIndex;
1288     public int currentSampleInTrackRun;
1289     public int currentTrackRunIndex;
1290 
TrackBundle(TrackOutput output)1291     public TrackBundle(TrackOutput output) {
1292       fragment = new TrackFragment();
1293       this.output = output;
1294     }
1295 
init(Track track, DefaultSampleValues defaultSampleValues)1296     public void init(Track track, DefaultSampleValues defaultSampleValues) {
1297       this.track = Assertions.checkNotNull(track);
1298       this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues);
1299       output.format(track.format);
1300       reset();
1301     }
1302 
reset()1303     public void reset() {
1304       fragment.reset();
1305       currentSampleIndex = 0;
1306       currentTrackRunIndex = 0;
1307       currentSampleInTrackRun = 0;
1308     }
1309 
updateDrmInitData(DrmInitData drmInitData)1310     public void updateDrmInitData(DrmInitData drmInitData) {
1311       output.format(track.format.copyWithDrmInitData(drmInitData));
1312     }
1313   }
1314 
1315 }
1316