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