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