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