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 static com.google.android.exoplayer2.util.MimeTypes.getMimeTypeFromMp4ObjectType; 19 20 import android.util.Pair; 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.AacUtil; 26 import com.google.android.exoplayer2.audio.Ac3Util; 27 import com.google.android.exoplayer2.audio.Ac4Util; 28 import com.google.android.exoplayer2.drm.DrmInitData; 29 import com.google.android.exoplayer2.extractor.GaplessInfoHolder; 30 import com.google.android.exoplayer2.metadata.Metadata; 31 import com.google.android.exoplayer2.util.Assertions; 32 import com.google.android.exoplayer2.util.CodecSpecificDataUtil; 33 import com.google.android.exoplayer2.util.Log; 34 import com.google.android.exoplayer2.util.MimeTypes; 35 import com.google.android.exoplayer2.util.ParsableByteArray; 36 import com.google.android.exoplayer2.util.Util; 37 import com.google.android.exoplayer2.video.AvcConfig; 38 import com.google.android.exoplayer2.video.DolbyVisionConfig; 39 import com.google.android.exoplayer2.video.HevcConfig; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.List; 44 import org.checkerframework.checker.nullness.compatqual.NullableType; 45 46 /** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */ 47 @SuppressWarnings({"ConstantField"}) 48 /* package */ final class AtomParsers { 49 50 private static final String TAG = "AtomParsers"; 51 52 @SuppressWarnings("ConstantCaseForConstants") 53 private static final int TYPE_vide = 0x76696465; 54 55 @SuppressWarnings("ConstantCaseForConstants") 56 private static final int TYPE_soun = 0x736f756e; 57 58 @SuppressWarnings("ConstantCaseForConstants") 59 private static final int TYPE_text = 0x74657874; 60 61 @SuppressWarnings("ConstantCaseForConstants") 62 private static final int TYPE_sbtl = 0x7362746c; 63 64 @SuppressWarnings("ConstantCaseForConstants") 65 private static final int TYPE_subt = 0x73756274; 66 67 @SuppressWarnings("ConstantCaseForConstants") 68 private static final int TYPE_clcp = 0x636c6370; 69 70 @SuppressWarnings("ConstantCaseForConstants") 71 private static final int TYPE_meta = 0x6d657461; 72 73 @SuppressWarnings("ConstantCaseForConstants") 74 private static final int TYPE_mdta = 0x6d647461; 75 76 /** 77 * The threshold number of samples to trim from the start/end of an audio track when applying an 78 * edit below which gapless info can be used (rather than removing samples from the sample table). 79 */ 80 private static final int MAX_GAPLESS_TRIM_SIZE_SAMPLES = 4; 81 82 /** The magic signature for an Opus Identification header, as defined in RFC-7845. */ 83 private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead"); 84 85 /** 86 * Parses a trak atom (defined in 14496-12). 87 * 88 * @param trak Atom to decode. 89 * @param mvhd Movie header atom, used to get the timescale. 90 * @param duration The duration in units of the timescale declared in the mvhd atom, or {@link 91 * C#TIME_UNSET} if the duration should be parsed from the tkhd atom. 92 * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. 93 * @param ignoreEditLists Whether to ignore any edit lists in the trak box. 94 * @param isQuickTime True for QuickTime media. False otherwise. 95 * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. 96 */ 97 @Nullable parseTrak( Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, @Nullable DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime)98 public static Track parseTrak( 99 Atom.ContainerAtom trak, 100 Atom.LeafAtom mvhd, 101 long duration, 102 @Nullable DrmInitData drmInitData, 103 boolean ignoreEditLists, 104 boolean isQuickTime) 105 throws ParserException { 106 Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); 107 int trackType = getTrackTypeForHdlr(parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data)); 108 if (trackType == C.TRACK_TYPE_UNKNOWN) { 109 return null; 110 } 111 112 TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); 113 if (duration == C.TIME_UNSET) { 114 duration = tkhdData.duration; 115 } 116 long movieTimescale = parseMvhd(mvhd.data); 117 long durationUs; 118 if (duration == C.TIME_UNSET) { 119 durationUs = C.TIME_UNSET; 120 } else { 121 durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, movieTimescale); 122 } 123 Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) 124 .getContainerAtomOfType(Atom.TYPE_stbl); 125 126 Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); 127 StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, 128 tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); 129 @Nullable long[] editListDurations = null; 130 @Nullable long[] editListMediaTimes = null; 131 if (!ignoreEditLists) { 132 @Nullable Atom.ContainerAtom edtsAtom = trak.getContainerAtomOfType(Atom.TYPE_edts); 133 if (edtsAtom != null) { 134 @Nullable Pair<long[], long[]> edtsData = parseEdts(edtsAtom); 135 if (edtsData != null) { 136 editListDurations = edtsData.first; 137 editListMediaTimes = edtsData.second; 138 } 139 } 140 } 141 return stsdData.format == null ? null 142 : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, 143 stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes, 144 stsdData.nalUnitLengthFieldLength, editListDurations, editListMediaTimes); 145 } 146 147 /** 148 * Parses an stbl atom (defined in 14496-12). 149 * 150 * @param track Track to which this sample table corresponds. 151 * @param stblAtom stbl (sample table) atom to decode. 152 * @param gaplessInfoHolder Holder to populate with gapless playback information. 153 * @return Sample table described by the stbl atom. 154 * @throws ParserException Thrown if the stbl atom can't be parsed. 155 */ parseStbl( Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder)156 public static TrackSampleTable parseStbl( 157 Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder) 158 throws ParserException { 159 SampleSizeBox sampleSizeBox; 160 @Nullable Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); 161 if (stszAtom != null) { 162 sampleSizeBox = new StszSampleSizeBox(stszAtom); 163 } else { 164 @Nullable Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); 165 if (stz2Atom == null) { 166 throw new ParserException("Track has no sample table size information"); 167 } 168 sampleSizeBox = new Stz2SampleSizeBox(stz2Atom); 169 } 170 171 int sampleCount = sampleSizeBox.getSampleCount(); 172 if (sampleCount == 0) { 173 return new TrackSampleTable( 174 track, 175 /* offsets= */ new long[0], 176 /* sizes= */ new int[0], 177 /* maximumSize= */ 0, 178 /* timestampsUs= */ new long[0], 179 /* flags= */ new int[0], 180 /* durationUs= */ C.TIME_UNSET); 181 } 182 183 // Entries are byte offsets of chunks. 184 boolean chunkOffsetsAreLongs = false; 185 @Nullable Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); 186 if (chunkOffsetsAtom == null) { 187 chunkOffsetsAreLongs = true; 188 chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64); 189 } 190 ParsableByteArray chunkOffsets = chunkOffsetsAtom.data; 191 // Entries are (chunk number, number of samples per chunk, sample description index). 192 ParsableByteArray stsc = stblAtom.getLeafAtomOfType(Atom.TYPE_stsc).data; 193 // Entries are (number of samples, timestamp delta between those samples). 194 ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data; 195 // Entries are the indices of samples that are synchronization samples. 196 @Nullable Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); 197 @Nullable ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; 198 // Entries are (number of samples, timestamp offset). 199 @Nullable Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); 200 @Nullable ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; 201 202 // Prepare to read chunk information. 203 ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs); 204 205 // Prepare to read sample timestamps. 206 stts.setPosition(Atom.FULL_HEADER_SIZE); 207 int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1; 208 int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); 209 int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); 210 211 // Prepare to read sample timestamp offsets, if ctts is present. 212 int remainingSamplesAtTimestampOffset = 0; 213 int remainingTimestampOffsetChanges = 0; 214 int timestampOffset = 0; 215 if (ctts != null) { 216 ctts.setPosition(Atom.FULL_HEADER_SIZE); 217 remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt(); 218 } 219 220 int nextSynchronizationSampleIndex = C.INDEX_UNSET; 221 int remainingSynchronizationSamples = 0; 222 if (stss != null) { 223 stss.setPosition(Atom.FULL_HEADER_SIZE); 224 remainingSynchronizationSamples = stss.readUnsignedIntToInt(); 225 if (remainingSynchronizationSamples > 0) { 226 nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; 227 } else { 228 // Ignore empty stss boxes, which causes all samples to be treated as sync samples. 229 stss = null; 230 } 231 } 232 233 // Fixed sample size raw audio may need to be rechunked. 234 boolean isFixedSampleSizeRawAudio = 235 sampleSizeBox.isFixedSampleSize() 236 && MimeTypes.AUDIO_RAW.equals(track.format.sampleMimeType) 237 && remainingTimestampDeltaChanges == 0 238 && remainingTimestampOffsetChanges == 0 239 && remainingSynchronizationSamples == 0; 240 241 long[] offsets; 242 int[] sizes; 243 int maximumSize = 0; 244 long[] timestamps; 245 int[] flags; 246 long timestampTimeUnits = 0; 247 long duration; 248 249 if (!isFixedSampleSizeRawAudio) { 250 offsets = new long[sampleCount]; 251 sizes = new int[sampleCount]; 252 timestamps = new long[sampleCount]; 253 flags = new int[sampleCount]; 254 long offset = 0; 255 int remainingSamplesInChunk = 0; 256 257 for (int i = 0; i < sampleCount; i++) { 258 // Advance to the next chunk if necessary. 259 boolean chunkDataComplete = true; 260 while (remainingSamplesInChunk == 0 && (chunkDataComplete = chunkIterator.moveNext())) { 261 offset = chunkIterator.offset; 262 remainingSamplesInChunk = chunkIterator.numSamples; 263 } 264 if (!chunkDataComplete) { 265 Log.w(TAG, "Unexpected end of chunk data"); 266 sampleCount = i; 267 offsets = Arrays.copyOf(offsets, sampleCount); 268 sizes = Arrays.copyOf(sizes, sampleCount); 269 timestamps = Arrays.copyOf(timestamps, sampleCount); 270 flags = Arrays.copyOf(flags, sampleCount); 271 break; 272 } 273 274 // Add on the timestamp offset if ctts is present. 275 if (ctts != null) { 276 while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) { 277 remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt(); 278 // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers 279 // in version 0 ctts boxes, however some streams violate the spec and use signed 280 // integers instead. It's safe to always decode sample offsets as signed integers here, 281 // because unsigned integers will still be parsed correctly (unless their top bit is 282 // set, which is never true in practice because sample offsets are always small). 283 timestampOffset = ctts.readInt(); 284 remainingTimestampOffsetChanges--; 285 } 286 remainingSamplesAtTimestampOffset--; 287 } 288 289 offsets[i] = offset; 290 sizes[i] = sampleSizeBox.readNextSampleSize(); 291 if (sizes[i] > maximumSize) { 292 maximumSize = sizes[i]; 293 } 294 timestamps[i] = timestampTimeUnits + timestampOffset; 295 296 // All samples are synchronization samples if the stss is not present. 297 flags[i] = stss == null ? C.BUFFER_FLAG_KEY_FRAME : 0; 298 if (i == nextSynchronizationSampleIndex) { 299 flags[i] = C.BUFFER_FLAG_KEY_FRAME; 300 remainingSynchronizationSamples--; 301 if (remainingSynchronizationSamples > 0) { 302 nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; 303 } 304 } 305 306 // Add on the duration of this sample. 307 timestampTimeUnits += timestampDeltaInTimeUnits; 308 remainingSamplesAtTimestampDelta--; 309 if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) { 310 remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); 311 // The BMFF spec (ISO 14496-12) states that sample deltas should be unsigned integers 312 // in stts boxes, however some streams violate the spec and use signed integers instead. 313 // See https://github.com/google/ExoPlayer/issues/3384. It's safe to always decode sample 314 // deltas as signed integers here, because unsigned integers will still be parsed 315 // correctly (unless their top bit is set, which is never true in practice because sample 316 // deltas are always small). 317 timestampDeltaInTimeUnits = stts.readInt(); 318 remainingTimestampDeltaChanges--; 319 } 320 321 offset += sizes[i]; 322 remainingSamplesInChunk--; 323 } 324 duration = timestampTimeUnits + timestampOffset; 325 326 // If the stbl's child boxes are not consistent the container is malformed, but the stream may 327 // still be playable. 328 boolean isCttsValid = true; 329 while (remainingTimestampOffsetChanges > 0) { 330 if (ctts.readUnsignedIntToInt() != 0) { 331 isCttsValid = false; 332 break; 333 } 334 ctts.readInt(); // Ignore offset. 335 remainingTimestampOffsetChanges--; 336 } 337 if (remainingSynchronizationSamples != 0 338 || remainingSamplesAtTimestampDelta != 0 339 || remainingSamplesInChunk != 0 340 || remainingTimestampDeltaChanges != 0 341 || remainingSamplesAtTimestampOffset != 0 342 || !isCttsValid) { 343 Log.w( 344 TAG, 345 "Inconsistent stbl box for track " 346 + track.id 347 + ": remainingSynchronizationSamples " 348 + remainingSynchronizationSamples 349 + ", remainingSamplesAtTimestampDelta " 350 + remainingSamplesAtTimestampDelta 351 + ", remainingSamplesInChunk " 352 + remainingSamplesInChunk 353 + ", remainingTimestampDeltaChanges " 354 + remainingTimestampDeltaChanges 355 + ", remainingSamplesAtTimestampOffset " 356 + remainingSamplesAtTimestampOffset 357 + (!isCttsValid ? ", ctts invalid" : "")); 358 } 359 } else { 360 long[] chunkOffsetsBytes = new long[chunkIterator.length]; 361 int[] chunkSampleCounts = new int[chunkIterator.length]; 362 while (chunkIterator.moveNext()) { 363 chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset; 364 chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples; 365 } 366 int fixedSampleSize = 367 Util.getPcmFrameSize(track.format.pcmEncoding, track.format.channelCount); 368 FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk( 369 fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits); 370 offsets = rechunkedResults.offsets; 371 sizes = rechunkedResults.sizes; 372 maximumSize = rechunkedResults.maximumSize; 373 timestamps = rechunkedResults.timestamps; 374 flags = rechunkedResults.flags; 375 duration = rechunkedResults.duration; 376 } 377 long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale); 378 379 if (track.editListDurations == null) { 380 Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); 381 return new TrackSampleTable( 382 track, offsets, sizes, maximumSize, timestamps, flags, durationUs); 383 } 384 385 // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a 386 // sync sample after reordering are not supported. Partial audio sample truncation is only 387 // supported in edit lists with one edit that removes less than MAX_GAPLESS_TRIM_SIZE_SAMPLES 388 // samples from the start/end of the track. This implementation handles simple 389 // discarding/delaying of samples. The extractor may place further restrictions on what edited 390 // streams are playable. 391 392 if (track.editListDurations.length == 1 393 && track.type == C.TRACK_TYPE_AUDIO 394 && timestamps.length >= 2) { 395 long editStartTime = track.editListMediaTimes[0]; 396 long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0], 397 track.timescale, track.movieTimescale); 398 if (canApplyEditWithGaplessInfo(timestamps, duration, editStartTime, editEndTime)) { 399 long paddingTimeUnits = duration - editEndTime; 400 long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0], 401 track.format.sampleRate, track.timescale); 402 long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits, 403 track.format.sampleRate, track.timescale); 404 if ((encoderDelay != 0 || encoderPadding != 0) && encoderDelay <= Integer.MAX_VALUE 405 && encoderPadding <= Integer.MAX_VALUE) { 406 gaplessInfoHolder.encoderDelay = (int) encoderDelay; 407 gaplessInfoHolder.encoderPadding = (int) encoderPadding; 408 Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); 409 long editedDurationUs = 410 Util.scaleLargeTimestamp( 411 track.editListDurations[0], C.MICROS_PER_SECOND, track.movieTimescale); 412 return new TrackSampleTable( 413 track, offsets, sizes, maximumSize, timestamps, flags, editedDurationUs); 414 } 415 } 416 } 417 418 if (track.editListDurations.length == 1 && track.editListDurations[0] == 0) { 419 // The current version of the spec leaves handling of an edit with zero segment_duration in 420 // unfragmented files open to interpretation. We handle this as a special case and include all 421 // samples in the edit. 422 long editStartTime = track.editListMediaTimes[0]; 423 for (int i = 0; i < timestamps.length; i++) { 424 timestamps[i] = 425 Util.scaleLargeTimestamp( 426 timestamps[i] - editStartTime, C.MICROS_PER_SECOND, track.timescale); 427 } 428 durationUs = 429 Util.scaleLargeTimestamp(duration - editStartTime, C.MICROS_PER_SECOND, track.timescale); 430 return new TrackSampleTable( 431 track, offsets, sizes, maximumSize, timestamps, flags, durationUs); 432 } 433 434 // Omit any sample at the end point of an edit for audio tracks. 435 boolean omitClippedSample = track.type == C.TRACK_TYPE_AUDIO; 436 437 // Count the number of samples after applying edits. 438 int editedSampleCount = 0; 439 int nextSampleIndex = 0; 440 boolean copyMetadata = false; 441 int[] startIndices = new int[track.editListDurations.length]; 442 int[] endIndices = new int[track.editListDurations.length]; 443 for (int i = 0; i < track.editListDurations.length; i++) { 444 long editMediaTime = track.editListMediaTimes[i]; 445 if (editMediaTime != -1) { 446 long editDuration = 447 Util.scaleLargeTimestamp( 448 track.editListDurations[i], track.timescale, track.movieTimescale); 449 startIndices[i] = 450 Util.binarySearchFloor( 451 timestamps, editMediaTime, /* inclusive= */ true, /* stayInBounds= */ true); 452 endIndices[i] = 453 Util.binarySearchCeil( 454 timestamps, 455 editMediaTime + editDuration, 456 /* inclusive= */ omitClippedSample, 457 /* stayInBounds= */ false); 458 while (startIndices[i] < endIndices[i] 459 && (flags[startIndices[i]] & C.BUFFER_FLAG_KEY_FRAME) == 0) { 460 // Applying the edit correctly would require prerolling from the previous sync sample. In 461 // the current implementation we advance to the next sync sample instead. Only other 462 // tracks (i.e. audio) will be rendered until the time of the first sync sample. 463 // See https://github.com/google/ExoPlayer/issues/1659. 464 startIndices[i]++; 465 } 466 editedSampleCount += endIndices[i] - startIndices[i]; 467 copyMetadata |= nextSampleIndex != startIndices[i]; 468 nextSampleIndex = endIndices[i]; 469 } 470 } 471 copyMetadata |= editedSampleCount != sampleCount; 472 473 // Calculate edited sample timestamps and update the corresponding metadata arrays. 474 long[] editedOffsets = copyMetadata ? new long[editedSampleCount] : offsets; 475 int[] editedSizes = copyMetadata ? new int[editedSampleCount] : sizes; 476 int editedMaximumSize = copyMetadata ? 0 : maximumSize; 477 int[] editedFlags = copyMetadata ? new int[editedSampleCount] : flags; 478 long[] editedTimestamps = new long[editedSampleCount]; 479 long pts = 0; 480 int sampleIndex = 0; 481 for (int i = 0; i < track.editListDurations.length; i++) { 482 long editMediaTime = track.editListMediaTimes[i]; 483 int startIndex = startIndices[i]; 484 int endIndex = endIndices[i]; 485 if (copyMetadata) { 486 int count = endIndex - startIndex; 487 System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count); 488 System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count); 489 System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count); 490 } 491 for (int j = startIndex; j < endIndex; j++) { 492 long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale); 493 long timeInSegmentUs = 494 Util.scaleLargeTimestamp( 495 Math.max(0, timestamps[j] - editMediaTime), C.MICROS_PER_SECOND, track.timescale); 496 editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs; 497 if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) { 498 editedMaximumSize = sizes[j]; 499 } 500 sampleIndex++; 501 } 502 pts += track.editListDurations[i]; 503 } 504 long editedDurationUs = 505 Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale); 506 return new TrackSampleTable( 507 track, 508 editedOffsets, 509 editedSizes, 510 editedMaximumSize, 511 editedTimestamps, 512 editedFlags, 513 editedDurationUs); 514 } 515 516 /** 517 * Parses a udta atom. 518 * 519 * @param udtaAtom The udta (user data) atom to decode. 520 * @param isQuickTime True for QuickTime media. False otherwise. 521 * @return Parsed metadata, or null. 522 */ 523 @Nullable parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime)524 public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) { 525 if (isQuickTime) { 526 // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and 527 // decode one. 528 return null; 529 } 530 ParsableByteArray udtaData = udtaAtom.data; 531 udtaData.setPosition(Atom.HEADER_SIZE); 532 while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) { 533 int atomPosition = udtaData.getPosition(); 534 int atomSize = udtaData.readInt(); 535 int atomType = udtaData.readInt(); 536 if (atomType == Atom.TYPE_meta) { 537 udtaData.setPosition(atomPosition); 538 return parseUdtaMeta(udtaData, atomPosition + atomSize); 539 } 540 udtaData.setPosition(atomPosition + atomSize); 541 } 542 return null; 543 } 544 545 /** 546 * Parses a metadata meta atom if it contains metadata with handler 'mdta'. 547 * 548 * @param meta The metadata atom to decode. 549 * @return Parsed metadata, or null. 550 */ 551 @Nullable parseMdtaFromMeta(Atom.ContainerAtom meta)552 public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) { 553 @Nullable Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr); 554 @Nullable Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys); 555 @Nullable Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst); 556 if (hdlrAtom == null 557 || keysAtom == null 558 || ilstAtom == null 559 || AtomParsers.parseHdlr(hdlrAtom.data) != TYPE_mdta) { 560 // There isn't enough information to parse the metadata, or the handler type is unexpected. 561 return null; 562 } 563 564 // Parse metadata keys. 565 ParsableByteArray keys = keysAtom.data; 566 keys.setPosition(Atom.FULL_HEADER_SIZE); 567 int entryCount = keys.readInt(); 568 String[] keyNames = new String[entryCount]; 569 for (int i = 0; i < entryCount; i++) { 570 int entrySize = keys.readInt(); 571 keys.skipBytes(4); // keyNamespace 572 int keySize = entrySize - 8; 573 keyNames[i] = keys.readString(keySize); 574 } 575 576 // Parse metadata items. 577 ParsableByteArray ilst = ilstAtom.data; 578 ilst.setPosition(Atom.HEADER_SIZE); 579 ArrayList<Metadata.Entry> entries = new ArrayList<>(); 580 while (ilst.bytesLeft() > Atom.HEADER_SIZE) { 581 int atomPosition = ilst.getPosition(); 582 int atomSize = ilst.readInt(); 583 int keyIndex = ilst.readInt() - 1; 584 if (keyIndex >= 0 && keyIndex < keyNames.length) { 585 String key = keyNames[keyIndex]; 586 @Nullable 587 Metadata.Entry entry = 588 MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key); 589 if (entry != null) { 590 entries.add(entry); 591 } 592 } else { 593 Log.w(TAG, "Skipped metadata with unknown key index: " + keyIndex); 594 } 595 ilst.setPosition(atomPosition + atomSize); 596 } 597 return entries.isEmpty() ? null : new Metadata(entries); 598 } 599 600 @Nullable parseUdtaMeta(ParsableByteArray meta, int limit)601 private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { 602 meta.skipBytes(Atom.FULL_HEADER_SIZE); 603 while (meta.getPosition() < limit) { 604 int atomPosition = meta.getPosition(); 605 int atomSize = meta.readInt(); 606 int atomType = meta.readInt(); 607 if (atomType == Atom.TYPE_ilst) { 608 meta.setPosition(atomPosition); 609 return parseIlst(meta, atomPosition + atomSize); 610 } 611 meta.setPosition(atomPosition + atomSize); 612 } 613 return null; 614 } 615 616 @Nullable parseIlst(ParsableByteArray ilst, int limit)617 private static Metadata parseIlst(ParsableByteArray ilst, int limit) { 618 ilst.skipBytes(Atom.HEADER_SIZE); 619 ArrayList<Metadata.Entry> entries = new ArrayList<>(); 620 while (ilst.getPosition() < limit) { 621 @Nullable Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst); 622 if (entry != null) { 623 entries.add(entry); 624 } 625 } 626 return entries.isEmpty() ? null : new Metadata(entries); 627 } 628 629 /** 630 * Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie. 631 * 632 * @param mvhd Contents of the mvhd atom to be parsed. 633 * @return Timescale for the movie. 634 */ parseMvhd(ParsableByteArray mvhd)635 private static long parseMvhd(ParsableByteArray mvhd) { 636 mvhd.setPosition(Atom.HEADER_SIZE); 637 int fullAtom = mvhd.readInt(); 638 int version = Atom.parseFullAtomVersion(fullAtom); 639 mvhd.skipBytes(version == 0 ? 8 : 16); 640 return mvhd.readUnsignedInt(); 641 } 642 643 /** 644 * Parses a tkhd atom (defined in 14496-12). 645 * 646 * @return An object containing the parsed data. 647 */ parseTkhd(ParsableByteArray tkhd)648 private static TkhdData parseTkhd(ParsableByteArray tkhd) { 649 tkhd.setPosition(Atom.HEADER_SIZE); 650 int fullAtom = tkhd.readInt(); 651 int version = Atom.parseFullAtomVersion(fullAtom); 652 653 tkhd.skipBytes(version == 0 ? 8 : 16); 654 int trackId = tkhd.readInt(); 655 656 tkhd.skipBytes(4); 657 boolean durationUnknown = true; 658 int durationPosition = tkhd.getPosition(); 659 int durationByteCount = version == 0 ? 4 : 8; 660 for (int i = 0; i < durationByteCount; i++) { 661 if (tkhd.data[durationPosition + i] != -1) { 662 durationUnknown = false; 663 break; 664 } 665 } 666 long duration; 667 if (durationUnknown) { 668 tkhd.skipBytes(durationByteCount); 669 duration = C.TIME_UNSET; 670 } else { 671 duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong(); 672 if (duration == 0) { 673 // 0 duration normally indicates that the file is fully fragmented (i.e. all of the media 674 // samples are in fragments). Treat as unknown. 675 duration = C.TIME_UNSET; 676 } 677 } 678 679 tkhd.skipBytes(16); 680 int a00 = tkhd.readInt(); 681 int a01 = tkhd.readInt(); 682 tkhd.skipBytes(4); 683 int a10 = tkhd.readInt(); 684 int a11 = tkhd.readInt(); 685 686 int rotationDegrees; 687 int fixedOne = 65536; 688 if (a00 == 0 && a01 == fixedOne && a10 == -fixedOne && a11 == 0) { 689 rotationDegrees = 90; 690 } else if (a00 == 0 && a01 == -fixedOne && a10 == fixedOne && a11 == 0) { 691 rotationDegrees = 270; 692 } else if (a00 == -fixedOne && a01 == 0 && a10 == 0 && a11 == -fixedOne) { 693 rotationDegrees = 180; 694 } else { 695 // Only 0, 90, 180 and 270 are supported. Treat anything else as 0. 696 rotationDegrees = 0; 697 } 698 699 return new TkhdData(trackId, duration, rotationDegrees); 700 } 701 702 /** 703 * Parses an hdlr atom. 704 * 705 * @param hdlr The hdlr atom to decode. 706 * @return The handler value. 707 */ parseHdlr(ParsableByteArray hdlr)708 private static int parseHdlr(ParsableByteArray hdlr) { 709 hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4); 710 return hdlr.readInt(); 711 } 712 713 /** Returns the track type for a given handler value. */ getTrackTypeForHdlr(int hdlr)714 private static int getTrackTypeForHdlr(int hdlr) { 715 if (hdlr == TYPE_soun) { 716 return C.TRACK_TYPE_AUDIO; 717 } else if (hdlr == TYPE_vide) { 718 return C.TRACK_TYPE_VIDEO; 719 } else if (hdlr == TYPE_text || hdlr == TYPE_sbtl || hdlr == TYPE_subt || hdlr == TYPE_clcp) { 720 return C.TRACK_TYPE_TEXT; 721 } else if (hdlr == TYPE_meta) { 722 return C.TRACK_TYPE_METADATA; 723 } else { 724 return C.TRACK_TYPE_UNKNOWN; 725 } 726 } 727 728 /** 729 * Parses an mdhd atom (defined in 14496-12). 730 * 731 * @param mdhd The mdhd atom to decode. 732 * @return A pair consisting of the media timescale defined as the number of time units that pass 733 * in one second, and the language code. 734 */ parseMdhd(ParsableByteArray mdhd)735 private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) { 736 mdhd.setPosition(Atom.HEADER_SIZE); 737 int fullAtom = mdhd.readInt(); 738 int version = Atom.parseFullAtomVersion(fullAtom); 739 mdhd.skipBytes(version == 0 ? 8 : 16); 740 long timescale = mdhd.readUnsignedInt(); 741 mdhd.skipBytes(version == 0 ? 4 : 8); 742 int languageCode = mdhd.readUnsignedShort(); 743 String language = 744 "" 745 + (char) (((languageCode >> 10) & 0x1F) + 0x60) 746 + (char) (((languageCode >> 5) & 0x1F) + 0x60) 747 + (char) ((languageCode & 0x1F) + 0x60); 748 return Pair.create(timescale, language); 749 } 750 751 /** 752 * Parses a stsd atom (defined in 14496-12). 753 * 754 * @param stsd The stsd atom to decode. 755 * @param trackId The track's identifier in its container. 756 * @param rotationDegrees The rotation of the track in degrees. 757 * @param language The language of the track. 758 * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. 759 * @param isQuickTime True for QuickTime media. False otherwise. 760 * @return An object containing the parsed data. 761 */ parseStsd( ParsableByteArray stsd, int trackId, int rotationDegrees, String language, @Nullable DrmInitData drmInitData, boolean isQuickTime)762 private static StsdData parseStsd( 763 ParsableByteArray stsd, 764 int trackId, 765 int rotationDegrees, 766 String language, 767 @Nullable DrmInitData drmInitData, 768 boolean isQuickTime) 769 throws ParserException { 770 stsd.setPosition(Atom.FULL_HEADER_SIZE); 771 int numberOfEntries = stsd.readInt(); 772 StsdData out = new StsdData(numberOfEntries); 773 for (int i = 0; i < numberOfEntries; i++) { 774 int childStartPosition = stsd.getPosition(); 775 int childAtomSize = stsd.readInt(); 776 Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); 777 int childAtomType = stsd.readInt(); 778 if (childAtomType == Atom.TYPE_avc1 779 || childAtomType == Atom.TYPE_avc3 780 || childAtomType == Atom.TYPE_encv 781 || childAtomType == Atom.TYPE_mp4v 782 || childAtomType == Atom.TYPE_hvc1 783 || childAtomType == Atom.TYPE_hev1 784 || childAtomType == Atom.TYPE_s263 785 || childAtomType == Atom.TYPE_vp08 786 || childAtomType == Atom.TYPE_vp09 787 || childAtomType == Atom.TYPE_av01 788 || childAtomType == Atom.TYPE_dvav 789 || childAtomType == Atom.TYPE_dva1 790 || childAtomType == Atom.TYPE_dvhe 791 || childAtomType == Atom.TYPE_dvh1) { 792 parseVideoSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, 793 rotationDegrees, drmInitData, out, i); 794 } else if (childAtomType == Atom.TYPE_mp4a 795 || childAtomType == Atom.TYPE_enca 796 || childAtomType == Atom.TYPE_ac_3 797 || childAtomType == Atom.TYPE_ec_3 798 || childAtomType == Atom.TYPE_ac_4 799 || childAtomType == Atom.TYPE_dtsc 800 || childAtomType == Atom.TYPE_dtse 801 || childAtomType == Atom.TYPE_dtsh 802 || childAtomType == Atom.TYPE_dtsl 803 || childAtomType == Atom.TYPE_samr 804 || childAtomType == Atom.TYPE_sawb 805 || childAtomType == Atom.TYPE_lpcm 806 || childAtomType == Atom.TYPE_sowt 807 || childAtomType == Atom.TYPE_twos 808 || childAtomType == Atom.TYPE__mp3 809 || childAtomType == Atom.TYPE_alac 810 || childAtomType == Atom.TYPE_alaw 811 || childAtomType == Atom.TYPE_ulaw 812 || childAtomType == Atom.TYPE_Opus 813 || childAtomType == Atom.TYPE_fLaC) { 814 parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, 815 language, isQuickTime, drmInitData, out, i); 816 } else if (childAtomType == Atom.TYPE_TTML || childAtomType == Atom.TYPE_tx3g 817 || childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp 818 || childAtomType == Atom.TYPE_c608) { 819 parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, 820 language, out); 821 } else if (childAtomType == Atom.TYPE_camm) { 822 out.format = 823 new Format.Builder() 824 .setId(trackId) 825 .setSampleMimeType(MimeTypes.APPLICATION_CAMERA_MOTION) 826 .build(); 827 } 828 stsd.setPosition(childStartPosition + childAtomSize); 829 } 830 return out; 831 } 832 parseTextSampleEntry( ParsableByteArray parent, int atomType, int position, int atomSize, int trackId, String language, StsdData out)833 private static void parseTextSampleEntry( 834 ParsableByteArray parent, 835 int atomType, 836 int position, 837 int atomSize, 838 int trackId, 839 String language, 840 StsdData out) { 841 parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); 842 843 // Default values. 844 @Nullable List<byte[]> initializationData = null; 845 long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE; 846 847 String mimeType; 848 if (atomType == Atom.TYPE_TTML) { 849 mimeType = MimeTypes.APPLICATION_TTML; 850 } else if (atomType == Atom.TYPE_tx3g) { 851 mimeType = MimeTypes.APPLICATION_TX3G; 852 int sampleDescriptionLength = atomSize - Atom.HEADER_SIZE - 8; 853 byte[] sampleDescriptionData = new byte[sampleDescriptionLength]; 854 parent.readBytes(sampleDescriptionData, 0, sampleDescriptionLength); 855 initializationData = Collections.singletonList(sampleDescriptionData); 856 } else if (atomType == Atom.TYPE_wvtt) { 857 mimeType = MimeTypes.APPLICATION_MP4VTT; 858 } else if (atomType == Atom.TYPE_stpp) { 859 mimeType = MimeTypes.APPLICATION_TTML; 860 subsampleOffsetUs = 0; // Subsample timing is absolute. 861 } else if (atomType == Atom.TYPE_c608) { 862 // Defined by the QuickTime File Format specification. 863 mimeType = MimeTypes.APPLICATION_MP4CEA608; 864 out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; 865 } else { 866 // Never happens. 867 throw new IllegalStateException(); 868 } 869 870 out.format = 871 new Format.Builder() 872 .setId(trackId) 873 .setSampleMimeType(mimeType) 874 .setLanguage(language) 875 .setSubsampleOffsetUs(subsampleOffsetUs) 876 .setInitializationData(initializationData) 877 .build(); 878 } 879 parseVideoSampleEntry( ParsableByteArray parent, int atomType, int position, int size, int trackId, int rotationDegrees, @Nullable DrmInitData drmInitData, StsdData out, int entryIndex)880 private static void parseVideoSampleEntry( 881 ParsableByteArray parent, 882 int atomType, 883 int position, 884 int size, 885 int trackId, 886 int rotationDegrees, 887 @Nullable DrmInitData drmInitData, 888 StsdData out, 889 int entryIndex) 890 throws ParserException { 891 parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); 892 893 parent.skipBytes(16); 894 int width = parent.readUnsignedShort(); 895 int height = parent.readUnsignedShort(); 896 boolean pixelWidthHeightRatioFromPasp = false; 897 float pixelWidthHeightRatio = 1; 898 parent.skipBytes(50); 899 900 int childPosition = parent.getPosition(); 901 if (atomType == Atom.TYPE_encv) { 902 @Nullable 903 Pair<Integer, TrackEncryptionBox> sampleEntryEncryptionData = 904 parseSampleEntryEncryptionData(parent, position, size); 905 if (sampleEntryEncryptionData != null) { 906 atomType = sampleEntryEncryptionData.first; 907 drmInitData = drmInitData == null ? null 908 : drmInitData.copyWithSchemeType(sampleEntryEncryptionData.second.schemeType); 909 out.trackEncryptionBoxes[entryIndex] = sampleEntryEncryptionData.second; 910 } 911 parent.setPosition(childPosition); 912 } 913 // TODO: Uncomment when [Internal: b/63092960] is fixed. 914 // else { 915 // drmInitData = null; 916 // } 917 918 @Nullable List<byte[]> initializationData = null; 919 @Nullable String mimeType = null; 920 @Nullable String codecs = null; 921 @Nullable byte[] projectionData = null; 922 @C.StereoMode 923 int stereoMode = Format.NO_VALUE; 924 while (childPosition - position < size) { 925 parent.setPosition(childPosition); 926 int childStartPosition = parent.getPosition(); 927 int childAtomSize = parent.readInt(); 928 if (childAtomSize == 0 && parent.getPosition() - position == size) { 929 // Handle optional terminating four zero bytes in MOV files. 930 break; 931 } 932 Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); 933 int childAtomType = parent.readInt(); 934 if (childAtomType == Atom.TYPE_avcC) { 935 Assertions.checkState(mimeType == null); 936 mimeType = MimeTypes.VIDEO_H264; 937 parent.setPosition(childStartPosition + Atom.HEADER_SIZE); 938 AvcConfig avcConfig = AvcConfig.parse(parent); 939 initializationData = avcConfig.initializationData; 940 out.nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; 941 if (!pixelWidthHeightRatioFromPasp) { 942 pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio; 943 } 944 } else if (childAtomType == Atom.TYPE_hvcC) { 945 Assertions.checkState(mimeType == null); 946 mimeType = MimeTypes.VIDEO_H265; 947 parent.setPosition(childStartPosition + Atom.HEADER_SIZE); 948 HevcConfig hevcConfig = HevcConfig.parse(parent); 949 initializationData = hevcConfig.initializationData; 950 out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; 951 } else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) { 952 @Nullable DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent); 953 if (dolbyVisionConfig != null) { 954 codecs = dolbyVisionConfig.codecs; 955 mimeType = MimeTypes.VIDEO_DOLBY_VISION; 956 } 957 } else if (childAtomType == Atom.TYPE_vpcC) { 958 Assertions.checkState(mimeType == null); 959 mimeType = (atomType == Atom.TYPE_vp08) ? MimeTypes.VIDEO_VP8 : MimeTypes.VIDEO_VP9; 960 } else if (childAtomType == Atom.TYPE_av1C) { 961 Assertions.checkState(mimeType == null); 962 mimeType = MimeTypes.VIDEO_AV1; 963 } else if (childAtomType == Atom.TYPE_d263) { 964 Assertions.checkState(mimeType == null); 965 mimeType = MimeTypes.VIDEO_H263; 966 } else if (childAtomType == Atom.TYPE_esds) { 967 Assertions.checkState(mimeType == null); 968 Pair<@NullableType String, byte @NullableType []> mimeTypeAndInitializationDataBytes = 969 parseEsdsFromParent(parent, childStartPosition); 970 mimeType = mimeTypeAndInitializationDataBytes.first; 971 @Nullable byte[] initializationDataBytes = mimeTypeAndInitializationDataBytes.second; 972 if (initializationDataBytes != null) { 973 initializationData = Collections.singletonList(initializationDataBytes); 974 } 975 } else if (childAtomType == Atom.TYPE_pasp) { 976 pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition); 977 pixelWidthHeightRatioFromPasp = true; 978 } else if (childAtomType == Atom.TYPE_sv3d) { 979 projectionData = parseProjFromParent(parent, childStartPosition, childAtomSize); 980 } else if (childAtomType == Atom.TYPE_st3d) { 981 int version = parent.readUnsignedByte(); 982 parent.skipBytes(3); // Flags. 983 if (version == 0) { 984 int layout = parent.readUnsignedByte(); 985 switch (layout) { 986 case 0: 987 stereoMode = C.STEREO_MODE_MONO; 988 break; 989 case 1: 990 stereoMode = C.STEREO_MODE_TOP_BOTTOM; 991 break; 992 case 2: 993 stereoMode = C.STEREO_MODE_LEFT_RIGHT; 994 break; 995 case 3: 996 stereoMode = C.STEREO_MODE_STEREO_MESH; 997 break; 998 default: 999 break; 1000 } 1001 } 1002 } 1003 childPosition += childAtomSize; 1004 } 1005 1006 // If the media type was not recognized, ignore the track. 1007 if (mimeType == null) { 1008 return; 1009 } 1010 1011 out.format = 1012 new Format.Builder() 1013 .setId(trackId) 1014 .setSampleMimeType(mimeType) 1015 .setCodecs(codecs) 1016 .setWidth(width) 1017 .setHeight(height) 1018 .setPixelWidthHeightRatio(pixelWidthHeightRatio) 1019 .setRotationDegrees(rotationDegrees) 1020 .setProjectionData(projectionData) 1021 .setStereoMode(stereoMode) 1022 .setInitializationData(initializationData) 1023 .setDrmInitData(drmInitData) 1024 .build(); 1025 } 1026 1027 /** 1028 * Parses the edts atom (defined in 14496-12 subsection 8.6.5). 1029 * 1030 * @param edtsAtom edts (edit box) atom to decode. 1031 * @return Pair of edit list durations and edit list media times, or {@code null} if they are not 1032 * present. 1033 */ 1034 @Nullable parseEdts(Atom.ContainerAtom edtsAtom)1035 private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) { 1036 @Nullable Atom.LeafAtom elstAtom = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst); 1037 if (elstAtom == null) { 1038 return null; 1039 } 1040 ParsableByteArray elstData = elstAtom.data; 1041 elstData.setPosition(Atom.HEADER_SIZE); 1042 int fullAtom = elstData.readInt(); 1043 int version = Atom.parseFullAtomVersion(fullAtom); 1044 int entryCount = elstData.readUnsignedIntToInt(); 1045 long[] editListDurations = new long[entryCount]; 1046 long[] editListMediaTimes = new long[entryCount]; 1047 for (int i = 0; i < entryCount; i++) { 1048 editListDurations[i] = 1049 version == 1 ? elstData.readUnsignedLongToLong() : elstData.readUnsignedInt(); 1050 editListMediaTimes[i] = version == 1 ? elstData.readLong() : elstData.readInt(); 1051 int mediaRateInteger = elstData.readShort(); 1052 if (mediaRateInteger != 1) { 1053 // The extractor does not handle dwell edits (mediaRateInteger == 0). 1054 throw new IllegalArgumentException("Unsupported media rate."); 1055 } 1056 elstData.skipBytes(2); 1057 } 1058 return Pair.create(editListDurations, editListMediaTimes); 1059 } 1060 parsePaspFromParent(ParsableByteArray parent, int position)1061 private static float parsePaspFromParent(ParsableByteArray parent, int position) { 1062 parent.setPosition(position + Atom.HEADER_SIZE); 1063 int hSpacing = parent.readUnsignedIntToInt(); 1064 int vSpacing = parent.readUnsignedIntToInt(); 1065 return (float) hSpacing / vSpacing; 1066 } 1067 parseAudioSampleEntry( ParsableByteArray parent, int atomType, int position, int size, int trackId, String language, boolean isQuickTime, @Nullable DrmInitData drmInitData, StsdData out, int entryIndex)1068 private static void parseAudioSampleEntry( 1069 ParsableByteArray parent, 1070 int atomType, 1071 int position, 1072 int size, 1073 int trackId, 1074 String language, 1075 boolean isQuickTime, 1076 @Nullable DrmInitData drmInitData, 1077 StsdData out, 1078 int entryIndex) 1079 throws ParserException { 1080 parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); 1081 1082 int quickTimeSoundDescriptionVersion = 0; 1083 if (isQuickTime) { 1084 quickTimeSoundDescriptionVersion = parent.readUnsignedShort(); 1085 parent.skipBytes(6); 1086 } else { 1087 parent.skipBytes(8); 1088 } 1089 1090 int channelCount; 1091 int sampleRate; 1092 @C.PcmEncoding int pcmEncoding = Format.NO_VALUE; 1093 @Nullable String codecs = null; 1094 1095 if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) { 1096 channelCount = parent.readUnsignedShort(); 1097 parent.skipBytes(6); // sampleSize, compressionId, packetSize. 1098 sampleRate = parent.readUnsignedFixedPoint1616(); 1099 1100 if (quickTimeSoundDescriptionVersion == 1) { 1101 parent.skipBytes(16); 1102 } 1103 } else if (quickTimeSoundDescriptionVersion == 2) { 1104 parent.skipBytes(16); // always[3,16,Minus2,0,65536], sizeOfStructOnly 1105 1106 sampleRate = (int) Math.round(parent.readDouble()); 1107 channelCount = parent.readUnsignedIntToInt(); 1108 1109 // Skip always7F000000, sampleSize, formatSpecificFlags, constBytesPerAudioPacket, 1110 // constLPCMFramesPerAudioPacket. 1111 parent.skipBytes(20); 1112 } else { 1113 // Unsupported version. 1114 return; 1115 } 1116 1117 int childPosition = parent.getPosition(); 1118 if (atomType == Atom.TYPE_enca) { 1119 @Nullable 1120 Pair<Integer, TrackEncryptionBox> sampleEntryEncryptionData = 1121 parseSampleEntryEncryptionData(parent, position, size); 1122 if (sampleEntryEncryptionData != null) { 1123 atomType = sampleEntryEncryptionData.first; 1124 drmInitData = drmInitData == null ? null 1125 : drmInitData.copyWithSchemeType(sampleEntryEncryptionData.second.schemeType); 1126 out.trackEncryptionBoxes[entryIndex] = sampleEntryEncryptionData.second; 1127 } 1128 parent.setPosition(childPosition); 1129 } 1130 // TODO: Uncomment when [Internal: b/63092960] is fixed. 1131 // else { 1132 // drmInitData = null; 1133 // } 1134 1135 // If the atom type determines a MIME type, set it immediately. 1136 @Nullable String mimeType = null; 1137 if (atomType == Atom.TYPE_ac_3) { 1138 mimeType = MimeTypes.AUDIO_AC3; 1139 } else if (atomType == Atom.TYPE_ec_3) { 1140 mimeType = MimeTypes.AUDIO_E_AC3; 1141 } else if (atomType == Atom.TYPE_ac_4) { 1142 mimeType = MimeTypes.AUDIO_AC4; 1143 } else if (atomType == Atom.TYPE_dtsc) { 1144 mimeType = MimeTypes.AUDIO_DTS; 1145 } else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) { 1146 mimeType = MimeTypes.AUDIO_DTS_HD; 1147 } else if (atomType == Atom.TYPE_dtse) { 1148 mimeType = MimeTypes.AUDIO_DTS_EXPRESS; 1149 } else if (atomType == Atom.TYPE_samr) { 1150 mimeType = MimeTypes.AUDIO_AMR_NB; 1151 } else if (atomType == Atom.TYPE_sawb) { 1152 mimeType = MimeTypes.AUDIO_AMR_WB; 1153 } else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) { 1154 mimeType = MimeTypes.AUDIO_RAW; 1155 pcmEncoding = C.ENCODING_PCM_16BIT; 1156 } else if (atomType == Atom.TYPE_twos) { 1157 mimeType = MimeTypes.AUDIO_RAW; 1158 pcmEncoding = C.ENCODING_PCM_16BIT_BIG_ENDIAN; 1159 } else if (atomType == Atom.TYPE__mp3) { 1160 mimeType = MimeTypes.AUDIO_MPEG; 1161 } else if (atomType == Atom.TYPE_alac) { 1162 mimeType = MimeTypes.AUDIO_ALAC; 1163 } else if (atomType == Atom.TYPE_alaw) { 1164 mimeType = MimeTypes.AUDIO_ALAW; 1165 } else if (atomType == Atom.TYPE_ulaw) { 1166 mimeType = MimeTypes.AUDIO_MLAW; 1167 } else if (atomType == Atom.TYPE_Opus) { 1168 mimeType = MimeTypes.AUDIO_OPUS; 1169 } else if (atomType == Atom.TYPE_fLaC) { 1170 mimeType = MimeTypes.AUDIO_FLAC; 1171 } 1172 1173 @Nullable byte[] initializationData = null; 1174 while (childPosition - position < size) { 1175 parent.setPosition(childPosition); 1176 int childAtomSize = parent.readInt(); 1177 Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); 1178 int childAtomType = parent.readInt(); 1179 if (childAtomType == Atom.TYPE_esds || (isQuickTime && childAtomType == Atom.TYPE_wave)) { 1180 int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition 1181 : findEsdsPosition(parent, childPosition, childAtomSize); 1182 if (esdsAtomPosition != C.POSITION_UNSET) { 1183 Pair<@NullableType String, byte @NullableType []> mimeTypeAndInitializationData = 1184 parseEsdsFromParent(parent, esdsAtomPosition); 1185 mimeType = mimeTypeAndInitializationData.first; 1186 initializationData = mimeTypeAndInitializationData.second; 1187 if (MimeTypes.AUDIO_AAC.equals(mimeType) && initializationData != null) { 1188 // Update sampleRate and channelCount from the AudioSpecificConfig initialization data, 1189 // which is more reliable. See [Internal: b/10903778]. 1190 AacUtil.Config aacConfig = AacUtil.parseAudioSpecificConfig(initializationData); 1191 sampleRate = aacConfig.sampleRateHz; 1192 channelCount = aacConfig.channelCount; 1193 codecs = aacConfig.codecs; 1194 } 1195 } 1196 } else if (childAtomType == Atom.TYPE_dac3) { 1197 parent.setPosition(Atom.HEADER_SIZE + childPosition); 1198 out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language, 1199 drmInitData); 1200 } else if (childAtomType == Atom.TYPE_dec3) { 1201 parent.setPosition(Atom.HEADER_SIZE + childPosition); 1202 out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language, 1203 drmInitData); 1204 } else if (childAtomType == Atom.TYPE_dac4) { 1205 parent.setPosition(Atom.HEADER_SIZE + childPosition); 1206 out.format = 1207 Ac4Util.parseAc4AnnexEFormat(parent, Integer.toString(trackId), language, drmInitData); 1208 } else if (childAtomType == Atom.TYPE_ddts) { 1209 out.format = 1210 new Format.Builder() 1211 .setId(trackId) 1212 .setSampleMimeType(mimeType) 1213 .setChannelCount(channelCount) 1214 .setSampleRate(sampleRate) 1215 .setDrmInitData(drmInitData) 1216 .setLanguage(language) 1217 .build(); 1218 } else if (childAtomType == Atom.TYPE_dOps) { 1219 // Build an Opus Identification Header (defined in RFC-7845) by concatenating the Opus Magic 1220 // Signature and the body of the dOps atom. 1221 int childAtomBodySize = childAtomSize - Atom.HEADER_SIZE; 1222 initializationData = new byte[opusMagic.length + childAtomBodySize]; 1223 System.arraycopy(opusMagic, 0, initializationData, 0, opusMagic.length); 1224 parent.setPosition(childPosition + Atom.HEADER_SIZE); 1225 parent.readBytes(initializationData, opusMagic.length, childAtomBodySize); 1226 } else if (childAtomType == Atom.TYPE_dfLa) { 1227 int childAtomBodySize = childAtomSize - Atom.FULL_HEADER_SIZE; 1228 initializationData = new byte[4 + childAtomBodySize]; 1229 initializationData[0] = 0x66; // f 1230 initializationData[1] = 0x4C; // L 1231 initializationData[2] = 0x61; // a 1232 initializationData[3] = 0x43; // C 1233 parent.setPosition(childPosition + Atom.FULL_HEADER_SIZE); 1234 parent.readBytes(initializationData, /* offset= */ 4, childAtomBodySize); 1235 } else if (childAtomType == Atom.TYPE_alac) { 1236 int childAtomBodySize = childAtomSize - Atom.FULL_HEADER_SIZE; 1237 initializationData = new byte[childAtomBodySize]; 1238 parent.setPosition(childPosition + Atom.FULL_HEADER_SIZE); 1239 parent.readBytes(initializationData, /* offset= */ 0, childAtomBodySize); 1240 // Update sampleRate and channelCount from the AudioSpecificConfig initialization data, 1241 // which is more reliable. See https://github.com/google/ExoPlayer/pull/6629. 1242 Pair<Integer, Integer> audioSpecificConfig = 1243 CodecSpecificDataUtil.parseAlacAudioSpecificConfig(initializationData); 1244 sampleRate = audioSpecificConfig.first; 1245 channelCount = audioSpecificConfig.second; 1246 } 1247 childPosition += childAtomSize; 1248 } 1249 1250 if (out.format == null && mimeType != null) { 1251 out.format = 1252 new Format.Builder() 1253 .setId(trackId) 1254 .setSampleMimeType(mimeType) 1255 .setCodecs(codecs) 1256 .setChannelCount(channelCount) 1257 .setSampleRate(sampleRate) 1258 .setPcmEncoding(pcmEncoding) 1259 .setInitializationData( 1260 initializationData == null ? null : Collections.singletonList(initializationData)) 1261 .setDrmInitData(drmInitData) 1262 .setLanguage(language) 1263 .build(); 1264 } 1265 } 1266 1267 /** 1268 * Returns the position of the esds box within a parent, or {@link C#POSITION_UNSET} if no esds 1269 * box is found 1270 */ findEsdsPosition(ParsableByteArray parent, int position, int size)1271 private static int findEsdsPosition(ParsableByteArray parent, int position, int size) { 1272 int childAtomPosition = parent.getPosition(); 1273 while (childAtomPosition - position < size) { 1274 parent.setPosition(childAtomPosition); 1275 int childAtomSize = parent.readInt(); 1276 Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); 1277 int childType = parent.readInt(); 1278 if (childType == Atom.TYPE_esds) { 1279 return childAtomPosition; 1280 } 1281 childAtomPosition += childAtomSize; 1282 } 1283 return C.POSITION_UNSET; 1284 } 1285 1286 /** Returns codec-specific initialization data contained in an esds box. */ parseEsdsFromParent( ParsableByteArray parent, int position)1287 private static Pair<@NullableType String, byte @NullableType []> parseEsdsFromParent( 1288 ParsableByteArray parent, int position) { 1289 parent.setPosition(position + Atom.HEADER_SIZE + 4); 1290 // Start of the ES_Descriptor (defined in 14496-1) 1291 parent.skipBytes(1); // ES_Descriptor tag 1292 parseExpandableClassSize(parent); 1293 parent.skipBytes(2); // ES_ID 1294 1295 int flags = parent.readUnsignedByte(); 1296 if ((flags & 0x80 /* streamDependenceFlag */) != 0) { 1297 parent.skipBytes(2); 1298 } 1299 if ((flags & 0x40 /* URL_Flag */) != 0) { 1300 parent.skipBytes(parent.readUnsignedShort()); 1301 } 1302 if ((flags & 0x20 /* OCRstreamFlag */) != 0) { 1303 parent.skipBytes(2); 1304 } 1305 1306 // Start of the DecoderConfigDescriptor (defined in 14496-1) 1307 parent.skipBytes(1); // DecoderConfigDescriptor tag 1308 parseExpandableClassSize(parent); 1309 1310 // Set the MIME type based on the object type indication (14496-1 table 5). 1311 int objectTypeIndication = parent.readUnsignedByte(); 1312 String mimeType = getMimeTypeFromMp4ObjectType(objectTypeIndication); 1313 if (MimeTypes.AUDIO_MPEG.equals(mimeType) 1314 || MimeTypes.AUDIO_DTS.equals(mimeType) 1315 || MimeTypes.AUDIO_DTS_HD.equals(mimeType)) { 1316 return Pair.create(mimeType, null); 1317 } 1318 1319 parent.skipBytes(12); 1320 1321 // Start of the DecoderSpecificInfo. 1322 parent.skipBytes(1); // DecoderSpecificInfo tag 1323 int initializationDataSize = parseExpandableClassSize(parent); 1324 byte[] initializationData = new byte[initializationDataSize]; 1325 parent.readBytes(initializationData, 0, initializationDataSize); 1326 return Pair.create(mimeType, initializationData); 1327 } 1328 1329 /** 1330 * Parses encryption data from an audio/video sample entry, returning a pair consisting of the 1331 * unencrypted atom type and a {@link TrackEncryptionBox}. Null is returned if no common 1332 * encryption sinf atom was present. 1333 */ 1334 @Nullable parseSampleEntryEncryptionData( ParsableByteArray parent, int position, int size)1335 private static Pair<Integer, TrackEncryptionBox> parseSampleEntryEncryptionData( 1336 ParsableByteArray parent, int position, int size) { 1337 int childPosition = parent.getPosition(); 1338 while (childPosition - position < size) { 1339 parent.setPosition(childPosition); 1340 int childAtomSize = parent.readInt(); 1341 Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); 1342 int childAtomType = parent.readInt(); 1343 if (childAtomType == Atom.TYPE_sinf) { 1344 Pair<Integer, TrackEncryptionBox> result = parseCommonEncryptionSinfFromParent(parent, 1345 childPosition, childAtomSize); 1346 if (result != null) { 1347 return result; 1348 } 1349 } 1350 childPosition += childAtomSize; 1351 } 1352 return null; 1353 } 1354 1355 @Nullable parseCommonEncryptionSinfFromParent( ParsableByteArray parent, int position, int size)1356 /* package */ static Pair<Integer, TrackEncryptionBox> parseCommonEncryptionSinfFromParent( 1357 ParsableByteArray parent, int position, int size) { 1358 int childPosition = position + Atom.HEADER_SIZE; 1359 int schemeInformationBoxPosition = C.POSITION_UNSET; 1360 int schemeInformationBoxSize = 0; 1361 @Nullable String schemeType = null; 1362 @Nullable Integer dataFormat = null; 1363 while (childPosition - position < size) { 1364 parent.setPosition(childPosition); 1365 int childAtomSize = parent.readInt(); 1366 int childAtomType = parent.readInt(); 1367 if (childAtomType == Atom.TYPE_frma) { 1368 dataFormat = parent.readInt(); 1369 } else if (childAtomType == Atom.TYPE_schm) { 1370 parent.skipBytes(4); 1371 // Common encryption scheme_type values are defined in ISO/IEC 23001-7:2016, section 4.1. 1372 schemeType = parent.readString(4); 1373 } else if (childAtomType == Atom.TYPE_schi) { 1374 schemeInformationBoxPosition = childPosition; 1375 schemeInformationBoxSize = childAtomSize; 1376 } 1377 childPosition += childAtomSize; 1378 } 1379 1380 if (C.CENC_TYPE_cenc.equals(schemeType) || C.CENC_TYPE_cbc1.equals(schemeType) 1381 || C.CENC_TYPE_cens.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType)) { 1382 Assertions.checkStateNotNull(dataFormat, "frma atom is mandatory"); 1383 Assertions.checkState( 1384 schemeInformationBoxPosition != C.POSITION_UNSET, "schi atom is mandatory"); 1385 TrackEncryptionBox encryptionBox = 1386 Assertions.checkStateNotNull( 1387 parseSchiFromParent( 1388 parent, schemeInformationBoxPosition, schemeInformationBoxSize, schemeType), 1389 "tenc atom is mandatory"); 1390 return Pair.create(dataFormat, encryptionBox); 1391 } else { 1392 return null; 1393 } 1394 } 1395 1396 @Nullable parseSchiFromParent( ParsableByteArray parent, int position, int size, String schemeType)1397 private static TrackEncryptionBox parseSchiFromParent( 1398 ParsableByteArray parent, int position, int size, String schemeType) { 1399 int childPosition = position + Atom.HEADER_SIZE; 1400 while (childPosition - position < size) { 1401 parent.setPosition(childPosition); 1402 int childAtomSize = parent.readInt(); 1403 int childAtomType = parent.readInt(); 1404 if (childAtomType == Atom.TYPE_tenc) { 1405 int fullAtom = parent.readInt(); 1406 int version = Atom.parseFullAtomVersion(fullAtom); 1407 parent.skipBytes(1); // reserved = 0. 1408 int defaultCryptByteBlock = 0; 1409 int defaultSkipByteBlock = 0; 1410 if (version == 0) { 1411 parent.skipBytes(1); // reserved = 0. 1412 } else /* version 1 or greater */ { 1413 int patternByte = parent.readUnsignedByte(); 1414 defaultCryptByteBlock = (patternByte & 0xF0) >> 4; 1415 defaultSkipByteBlock = patternByte & 0x0F; 1416 } 1417 boolean defaultIsProtected = parent.readUnsignedByte() == 1; 1418 int defaultPerSampleIvSize = parent.readUnsignedByte(); 1419 byte[] defaultKeyId = new byte[16]; 1420 parent.readBytes(defaultKeyId, 0, defaultKeyId.length); 1421 byte[] constantIv = null; 1422 if (defaultIsProtected && defaultPerSampleIvSize == 0) { 1423 int constantIvSize = parent.readUnsignedByte(); 1424 constantIv = new byte[constantIvSize]; 1425 parent.readBytes(constantIv, 0, constantIvSize); 1426 } 1427 return new TrackEncryptionBox(defaultIsProtected, schemeType, defaultPerSampleIvSize, 1428 defaultKeyId, defaultCryptByteBlock, defaultSkipByteBlock, constantIv); 1429 } 1430 childPosition += childAtomSize; 1431 } 1432 return null; 1433 } 1434 1435 /** Parses the proj box from sv3d box, as specified by https://github.com/google/spatial-media. */ 1436 @Nullable parseProjFromParent(ParsableByteArray parent, int position, int size)1437 private static byte[] parseProjFromParent(ParsableByteArray parent, int position, int size) { 1438 int childPosition = position + Atom.HEADER_SIZE; 1439 while (childPosition - position < size) { 1440 parent.setPosition(childPosition); 1441 int childAtomSize = parent.readInt(); 1442 int childAtomType = parent.readInt(); 1443 if (childAtomType == Atom.TYPE_proj) { 1444 return Arrays.copyOfRange(parent.data, childPosition, childPosition + childAtomSize); 1445 } 1446 childPosition += childAtomSize; 1447 } 1448 return null; 1449 } 1450 1451 /** 1452 * Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3. 1453 */ parseExpandableClassSize(ParsableByteArray data)1454 private static int parseExpandableClassSize(ParsableByteArray data) { 1455 int currentByte = data.readUnsignedByte(); 1456 int size = currentByte & 0x7F; 1457 while ((currentByte & 0x80) == 0x80) { 1458 currentByte = data.readUnsignedByte(); 1459 size = (size << 7) | (currentByte & 0x7F); 1460 } 1461 return size; 1462 } 1463 1464 /** Returns whether it's possible to apply the specified edit using gapless playback info. */ canApplyEditWithGaplessInfo( long[] timestamps, long duration, long editStartTime, long editEndTime)1465 private static boolean canApplyEditWithGaplessInfo( 1466 long[] timestamps, long duration, long editStartTime, long editEndTime) { 1467 int lastIndex = timestamps.length - 1; 1468 int latestDelayIndex = Util.constrainValue(MAX_GAPLESS_TRIM_SIZE_SAMPLES, 0, lastIndex); 1469 int earliestPaddingIndex = 1470 Util.constrainValue(timestamps.length - MAX_GAPLESS_TRIM_SIZE_SAMPLES, 0, lastIndex); 1471 return timestamps[0] <= editStartTime 1472 && editStartTime < timestamps[latestDelayIndex] 1473 && timestamps[earliestPaddingIndex] < editEndTime 1474 && editEndTime <= duration; 1475 } 1476 AtomParsers()1477 private AtomParsers() { 1478 // Prevent instantiation. 1479 } 1480 1481 private static final class ChunkIterator { 1482 1483 public final int length; 1484 1485 public int index; 1486 public int numSamples; 1487 public long offset; 1488 1489 private final boolean chunkOffsetsAreLongs; 1490 private final ParsableByteArray chunkOffsets; 1491 private final ParsableByteArray stsc; 1492 1493 private int nextSamplesPerChunkChangeIndex; 1494 private int remainingSamplesPerChunkChanges; 1495 ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, boolean chunkOffsetsAreLongs)1496 public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, 1497 boolean chunkOffsetsAreLongs) { 1498 this.stsc = stsc; 1499 this.chunkOffsets = chunkOffsets; 1500 this.chunkOffsetsAreLongs = chunkOffsetsAreLongs; 1501 chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE); 1502 length = chunkOffsets.readUnsignedIntToInt(); 1503 stsc.setPosition(Atom.FULL_HEADER_SIZE); 1504 remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt(); 1505 Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1"); 1506 index = -1; 1507 } 1508 moveNext()1509 public boolean moveNext() { 1510 if (++index == length) { 1511 return false; 1512 } 1513 offset = chunkOffsetsAreLongs ? chunkOffsets.readUnsignedLongToLong() 1514 : chunkOffsets.readUnsignedInt(); 1515 if (index == nextSamplesPerChunkChangeIndex) { 1516 numSamples = stsc.readUnsignedIntToInt(); 1517 stsc.skipBytes(4); // Skip sample_description_index 1518 nextSamplesPerChunkChangeIndex = --remainingSamplesPerChunkChanges > 0 1519 ? (stsc.readUnsignedIntToInt() - 1) : C.INDEX_UNSET; 1520 } 1521 return true; 1522 } 1523 1524 } 1525 1526 /** 1527 * Holds data parsed from a tkhd atom. 1528 */ 1529 private static final class TkhdData { 1530 1531 private final int id; 1532 private final long duration; 1533 private final int rotationDegrees; 1534 TkhdData(int id, long duration, int rotationDegrees)1535 public TkhdData(int id, long duration, int rotationDegrees) { 1536 this.id = id; 1537 this.duration = duration; 1538 this.rotationDegrees = rotationDegrees; 1539 } 1540 1541 } 1542 1543 /** 1544 * Holds data parsed from an stsd atom and its children. 1545 */ 1546 private static final class StsdData { 1547 1548 public static final int STSD_HEADER_SIZE = 8; 1549 1550 public final TrackEncryptionBox[] trackEncryptionBoxes; 1551 1552 @Nullable public Format format; 1553 public int nalUnitLengthFieldLength; 1554 @Track.Transformation public int requiredSampleTransformation; 1555 StsdData(int numberOfEntries)1556 public StsdData(int numberOfEntries) { 1557 trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries]; 1558 requiredSampleTransformation = Track.TRANSFORMATION_NONE; 1559 } 1560 1561 } 1562 1563 /** 1564 * A box containing sample sizes (e.g. stsz, stz2). 1565 */ 1566 private interface SampleSizeBox { 1567 1568 /** 1569 * Returns the number of samples. 1570 */ getSampleCount()1571 int getSampleCount(); 1572 1573 /** 1574 * Returns the size for the next sample. 1575 */ readNextSampleSize()1576 int readNextSampleSize(); 1577 1578 /** 1579 * Returns whether samples have a fixed size. 1580 */ isFixedSampleSize()1581 boolean isFixedSampleSize(); 1582 1583 } 1584 1585 /** 1586 * An stsz sample size box. 1587 */ 1588 /* package */ static final class StszSampleSizeBox implements SampleSizeBox { 1589 1590 private final int fixedSampleSize; 1591 private final int sampleCount; 1592 private final ParsableByteArray data; 1593 StszSampleSizeBox(Atom.LeafAtom stszAtom)1594 public StszSampleSizeBox(Atom.LeafAtom stszAtom) { 1595 data = stszAtom.data; 1596 data.setPosition(Atom.FULL_HEADER_SIZE); 1597 fixedSampleSize = data.readUnsignedIntToInt(); 1598 sampleCount = data.readUnsignedIntToInt(); 1599 } 1600 1601 @Override getSampleCount()1602 public int getSampleCount() { 1603 return sampleCount; 1604 } 1605 1606 @Override readNextSampleSize()1607 public int readNextSampleSize() { 1608 return fixedSampleSize == 0 ? data.readUnsignedIntToInt() : fixedSampleSize; 1609 } 1610 1611 @Override isFixedSampleSize()1612 public boolean isFixedSampleSize() { 1613 return fixedSampleSize != 0; 1614 } 1615 1616 } 1617 1618 /** 1619 * An stz2 sample size box. 1620 */ 1621 /* package */ static final class Stz2SampleSizeBox implements SampleSizeBox { 1622 1623 private final ParsableByteArray data; 1624 private final int sampleCount; 1625 private final int fieldSize; // Can be 4, 8, or 16. 1626 1627 // Used only if fieldSize == 4. 1628 private int sampleIndex; 1629 private int currentByte; 1630 Stz2SampleSizeBox(Atom.LeafAtom stz2Atom)1631 public Stz2SampleSizeBox(Atom.LeafAtom stz2Atom) { 1632 data = stz2Atom.data; 1633 data.setPosition(Atom.FULL_HEADER_SIZE); 1634 fieldSize = data.readUnsignedIntToInt() & 0x000000FF; 1635 sampleCount = data.readUnsignedIntToInt(); 1636 } 1637 1638 @Override getSampleCount()1639 public int getSampleCount() { 1640 return sampleCount; 1641 } 1642 1643 @Override readNextSampleSize()1644 public int readNextSampleSize() { 1645 if (fieldSize == 8) { 1646 return data.readUnsignedByte(); 1647 } else if (fieldSize == 16) { 1648 return data.readUnsignedShort(); 1649 } else { 1650 // fieldSize == 4. 1651 if ((sampleIndex++ % 2) == 0) { 1652 // Read the next byte into our cached byte when we are reading the upper bits. 1653 currentByte = data.readUnsignedByte(); 1654 // Read the upper bits from the byte and shift them to the lower 4 bits. 1655 return (currentByte & 0xF0) >> 4; 1656 } else { 1657 // Mask out the upper 4 bits of the last byte we read. 1658 return currentByte & 0x0F; 1659 } 1660 } 1661 } 1662 1663 @Override isFixedSampleSize()1664 public boolean isFixedSampleSize() { 1665 return false; 1666 } 1667 1668 } 1669 1670 } 1671