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