diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6e5f508aba..c43ef9fa5d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,8 +8,8 @@ `DefaultRenderersFactory` to force enable or force disable asynchronous queueing ([6348](https://github.com/google/ExoPlayer/issues/6348)). * Add 12 public method headers to `ExoPlayer` that exist in - `SimpleExoPlayer`, such that all public methods in `SimpleExoPlayer` - are overrides. + `SimpleExoPlayer`, such that all public methods in `SimpleExoPlayer` are + overrides. * Move `com.google.android.exoplayer2.device.DeviceInfo` to `com.google.android.exoplayer2.DeviceInfo`. * Move `com.google.android.exoplayer2.drm.DecryptionException` to @@ -51,6 +51,8 @@ ([#9452](https://github.com/google/ExoPlayer/issues/9452)). * Extractors: * MP4: Correctly handle HEVC tracks with pixel aspect ratios other than 1. + * MP4: Add support for Dolby TrueHD (only for unfragmented streams) + ([#9496](https://github.com/google/ExoPlayer/issues/9496)). * TS: Correctly handle HEVC tracks with pixel aspect ratios other than 1. * TS: Map stream type 0x80 to H262 ([#9472](https://github.com/google/ExoPlayer/issues/9472)). diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrueHdSampleRechunker.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrueHdSampleRechunker.java new file mode 100644 index 0000000000..6bae591956 --- /dev/null +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrueHdSampleRechunker.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.exoplayer2.extractor; + +import static com.google.android.exoplayer2.util.Assertions.checkState; + +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import java.io.IOException; + +/** + * Rechunks TrueHD sample data into groups of {@link Ac3Util#TRUEHD_RECHUNK_SAMPLE_COUNT} samples. + */ +public final class TrueHdSampleRechunker { + + private final byte[] syncframePrefix; + + private boolean foundSyncframe; + private int chunkSampleCount; + private long chunkTimeUs; + @C.BufferFlags private int chunkFlags; + private int chunkSize; + private int chunkOffset; + + public TrueHdSampleRechunker() { + syncframePrefix = new byte[Ac3Util.TRUEHD_SYNCFRAME_PREFIX_LENGTH]; + } + + public void reset() { + foundSyncframe = false; + chunkSampleCount = 0; + } + + public void startSample(ExtractorInput input) throws IOException { + if (foundSyncframe) { + return; + } + input.peekFully(syncframePrefix, 0, Ac3Util.TRUEHD_SYNCFRAME_PREFIX_LENGTH); + input.resetPeekPosition(); + if (Ac3Util.parseTrueHdSyncframeAudioSampleCount(syncframePrefix) == 0) { + return; + } + foundSyncframe = true; + } + + public void sampleMetadata( + TrackOutput trackOutput, + long timeUs, + @C.BufferFlags int flags, + int size, + int offset, + @Nullable TrackOutput.CryptoData cryptoData) { + checkState( + chunkOffset <= size + offset, + "TrueHD chunk samples must be contiguous in the sample queue."); + if (!foundSyncframe) { + return; + } + if (chunkSampleCount++ == 0) { + // This is the first sample in the chunk. + chunkTimeUs = timeUs; + chunkFlags = flags; + chunkSize = 0; + } + chunkSize += size; + chunkOffset = offset; // The offset is to the end of the sample. + if (chunkSampleCount >= Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT) { + outputPendingSampleMetadata(trackOutput, cryptoData); + } + } + + public void outputPendingSampleMetadata( + TrackOutput trackOutput, @Nullable TrackOutput.CryptoData cryptoData) { + if (chunkSampleCount > 0) { + trackOutput.sampleMetadata(chunkTimeUs, chunkFlags, chunkSize, chunkOffset, cryptoData); + chunkSampleCount = 0; + } + } +} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index e1c68726b3..3a0091e28a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -31,7 +31,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.AacUtil; -import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; @@ -43,6 +42,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.TrueHdSampleRechunker; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.MimeTypes; @@ -1334,7 +1334,8 @@ public class MatroskaExtractor implements Extractor { private void commitSampleToOutput( Track track, long timeUs, @C.BufferFlags int flags, int size, int offset) { if (track.trueHdSampleRechunker != null) { - track.trueHdSampleRechunker.sampleMetadata(track, timeUs, flags, size, offset); + track.trueHdSampleRechunker.sampleMetadata( + track.output, timeUs, flags, size, offset, track.cryptoData); } else { if (CODEC_ID_SUBRIP.equals(track.codecId) || CODEC_ID_ASS.equals(track.codecId)) { if (blockSampleCount > 1) { @@ -1899,70 +1900,6 @@ public class MatroskaExtractor implements Extractor { } } - /** - * Rechunks TrueHD sample data into groups of {@link Ac3Util#TRUEHD_RECHUNK_SAMPLE_COUNT} samples. - */ - private static final class TrueHdSampleRechunker { - - private final byte[] syncframePrefix; - - private boolean foundSyncframe; - private int chunkSampleCount; - private long chunkTimeUs; - private @C.BufferFlags int chunkFlags; - private int chunkSize; - private int chunkOffset; - - public TrueHdSampleRechunker() { - syncframePrefix = new byte[Ac3Util.TRUEHD_SYNCFRAME_PREFIX_LENGTH]; - } - - public void reset() { - foundSyncframe = false; - chunkSampleCount = 0; - } - - public void startSample(ExtractorInput input) throws IOException { - if (foundSyncframe) { - return; - } - input.peekFully(syncframePrefix, 0, Ac3Util.TRUEHD_SYNCFRAME_PREFIX_LENGTH); - input.resetPeekPosition(); - if (Ac3Util.parseTrueHdSyncframeAudioSampleCount(syncframePrefix) == 0) { - return; - } - foundSyncframe = true; - } - - @RequiresNonNull("#1.output") - public void sampleMetadata( - Track track, long timeUs, @C.BufferFlags int flags, int size, int offset) { - if (!foundSyncframe) { - return; - } - if (chunkSampleCount++ == 0) { - // This is the first sample in the chunk. - chunkTimeUs = timeUs; - chunkFlags = flags; - chunkSize = 0; - } - chunkSize += size; - chunkOffset = offset; // The offset is to the end of the sample. - if (chunkSampleCount >= Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT) { - outputPendingSampleMetadata(track); - } - } - - @RequiresNonNull("#1.output") - public void outputPendingSampleMetadata(Track track) { - if (chunkSampleCount > 0) { - track.output.sampleMetadata( - chunkTimeUs, chunkFlags, chunkSize, chunkOffset, track.cryptoData); - chunkSampleCount = 0; - } - } - } - private static final class Track { private static final int DISPLAY_UNIT_PIXELS = 0; @@ -2335,7 +2272,7 @@ public class MatroskaExtractor implements Extractor { @RequiresNonNull("output") public void outputPendingSampleMetadata() { if (trueHdSampleRechunker != null) { - trueHdSampleRechunker.outputPendingSampleMetadata(this); + trueHdSampleRechunker.outputPendingSampleMetadata(output, cryptoData); } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index a85d928f04..bc8633acc8 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -152,6 +152,12 @@ import java.util.List; @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_dac4 = 0x64616334; + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_mlpa = 0x6d6c7061; + + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_dmlp = 0x646d6c70; + @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_dtsc = 0x64747363; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 1bdcb32d1d..ee8719fe5c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -962,6 +962,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; || childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3 || childAtomType == Atom.TYPE_ac_4 + || childAtomType == Atom.TYPE_mlpa || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse || childAtomType == Atom.TYPE_dtsh @@ -1317,13 +1318,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int channelCount; int sampleRate; + int sampleRateMlp = 0; @C.PcmEncoding int pcmEncoding = Format.NO_VALUE; @Nullable String codecs = null; if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) { channelCount = parent.readUnsignedShort(); parent.skipBytes(6); // sampleSize, compressionId, packetSize. + sampleRate = parent.readUnsignedFixedPoint1616(); + parent.skipBytes(-4); + // The sample rate has been redefined as a 32-bit value for Dolby TrueHD (MLP) streams. + sampleRateMlp = parent.readInt(); if (quickTimeSoundDescriptionVersion == 1) { parent.skipBytes(16); @@ -1404,6 +1410,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; mimeType = MimeTypes.AUDIO_OPUS; } else if (atomType == Atom.TYPE_fLaC) { mimeType = MimeTypes.AUDIO_FLAC; + } else if (atomType == Atom.TYPE_mlpa) { + mimeType = MimeTypes.AUDIO_TRUEHD; } @Nullable List initializationData = null; @@ -1457,6 +1465,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; parent.setPosition(Atom.HEADER_SIZE + childPosition); out.format = Ac4Util.parseAc4AnnexEFormat(parent, Integer.toString(trackId), language, drmInitData); + } else if (childAtomType == Atom.TYPE_dmlp) { + if (sampleRateMlp <= 0) { + throw ParserException.createForMalformedContainer( + "Invalid sample rate for Dolby TrueHD MLP stream: " + sampleRateMlp, + /* cause= */ null); + } + sampleRate = sampleRateMlp; + // The channel count from the sample entry must be ignored for Dolby TrueHD (MLP) streams + // because these streams can carry simultaneously multiple representations of the same + // audio. Use stereo by default. + channelCount = 2; } else if (childAtomType == Atom.TYPE_ddts) { out.format = new Format.Builder() diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index d542fa8545..736b392cba 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -29,6 +29,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.audio.Ac4Util; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; @@ -39,6 +40,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekPoint; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.TrueHdSampleRechunker; import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata; @@ -56,7 +58,6 @@ import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** Extracts data from the MP4 container format. */ public final class Mp4Extractor implements Extractor, SeekMap { @@ -220,7 +221,12 @@ public final class Mp4Extractor implements Extractor, SeekMap { slowMotionMetadataEntries.clear(); } } else if (tracks != null) { - updateSampleIndices(timeUs); + for (Mp4Track track : tracks) { + updateSampleIndex(track, timeUs); + if (track.trueHdSampleRechunker != null) { + track.trueHdSampleRechunker.reset(); + } + } } } @@ -503,9 +509,16 @@ public final class Mp4Extractor implements Extractor, SeekMap { Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i, track.type)); - // Each sample has up to three bytes of overhead for the start code that replaces its length. - // Allow ten source samples per output sample, like the platform extractor. - int maxInputSize = trackSampleTable.maximumSize + 3 * 10; + int maxInputSize; + if (MimeTypes.AUDIO_TRUEHD.equals(track.format.sampleMimeType)) { + // TrueHD groups samples per chunks of TRUEHD_RECHUNK_SAMPLE_COUNT samples. + maxInputSize = trackSampleTable.maximumSize * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT; + } else { + // Each sample has up to three bytes of overhead for the start code that replaces its + // length. Allow ten source samples per output sample, like the platform extractor. + maxInputSize = trackSampleTable.maximumSize + 3 * 10; + } + Format.Builder formatBuilder = track.format.buildUpon(); formatBuilder.setMaxInputSize(maxInputSize); if (track.type == C.TRACK_TYPE_VIDEO @@ -567,6 +580,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { int sampleIndex = track.sampleIndex; long position = track.sampleTable.offsets[sampleIndex]; int sampleSize = track.sampleTable.sizes[sampleIndex]; + @Nullable TrueHdSampleRechunker trueHdSampleRechunker = track.trueHdSampleRechunker; long skipAmount = position - inputPosition + sampleBytesRead; if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) { positionHolder.position = position; @@ -624,7 +638,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE; } sampleSize += Ac4Util.SAMPLE_HEADER_SIZE; + } else if (trueHdSampleRechunker != null) { + trueHdSampleRechunker.startSample(input); } + while (sampleBytesWritten < sampleSize) { int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesRead += writtenBytes; @@ -632,12 +649,20 @@ public final class Mp4Extractor implements Extractor, SeekMap { sampleCurrentNalBytesRemaining -= writtenBytes; } } - trackOutput.sampleMetadata( - track.sampleTable.timestampsUs[sampleIndex], - track.sampleTable.flags[sampleIndex], - sampleSize, - 0, - null); + + long timeUs = track.sampleTable.timestampsUs[sampleIndex]; + @C.BufferFlags int flags = track.sampleTable.flags[sampleIndex]; + if (trueHdSampleRechunker != null) { + trueHdSampleRechunker.sampleMetadata( + trackOutput, timeUs, flags, sampleSize, /* offset= */ 0, /* cryptoData= */ null); + if (sampleIndex + 1 == track.sampleTable.sampleCount) { + trueHdSampleRechunker.outputPendingSampleMetadata(trackOutput, /* cryptoData= */ null); + } + } else { + trackOutput.sampleMetadata( + timeUs, flags, sampleSize, /* offset= */ 0, /* cryptoData= */ null); + } + track.sampleIndex++; sampleTrackIndex = C.INDEX_UNSET; sampleBytesRead = 0; @@ -697,20 +722,15 @@ public final class Mp4Extractor implements Extractor, SeekMap { : minAccumulatedBytesTrackIndex; } - /** - * Updates every track's sample index to point its latest sync sample before/at {@code timeUs}. - */ - @RequiresNonNull("tracks") - private void updateSampleIndices(long timeUs) { - for (Mp4Track track : tracks) { - TrackSampleTable sampleTable = track.sampleTable; - int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); - if (sampleIndex == C.INDEX_UNSET) { - // Handle the case where the requested time is before the first synchronization sample. - sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); - } - track.sampleIndex = sampleIndex; + /** Updates a track's sample index to point its latest sync sample before/at {@code timeUs}. */ + private void updateSampleIndex(Mp4Track track, long timeUs) { + TrackSampleTable sampleTable = track.sampleTable; + int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); + if (sampleIndex == C.INDEX_UNSET) { + // Handle the case where the requested time is before the first synchronization sample. + sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); } + track.sampleIndex = sampleIndex; } /** Processes the end of stream in case there is not atom left to read. */ @@ -902,6 +922,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { public final Track track; public final TrackSampleTable sampleTable; public final TrackOutput trackOutput; + @Nullable public final TrueHdSampleRechunker trueHdSampleRechunker; public int sampleIndex; @@ -909,6 +930,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { this.track = track; this.sampleTable = sampleTable; this.trackOutput = trackOutput; + trueHdSampleRechunker = + MimeTypes.AUDIO_TRUEHD.equals(track.format.sampleMimeType) + ? new TrueHdSampleRechunker() + : null; } } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index 4408ffab83..9c41d361ad 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -102,4 +102,10 @@ public final class Mp4ExtractorTest { ExtractorAsserts.assertBehavior( Mp4Extractor::new, "media/mp4/sample_with_color_info.mp4", simulationConfig); } + + @Test + public void mp4SampleWithDolbyTrueHDTrack() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig); + } } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.0.dump new file mode 100644 index 0000000000..d05b05b2cc --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.0.dump @@ -0,0 +1,147 @@ +seekMap: + isSeekable = true + duration = 418333 + getPosition(0) = [[timeUs=0, position=3447]] + getPosition(1) = [[timeUs=1, position=3447]] + getPosition(209166) = [[timeUs=209166, position=27035]] + getPosition(418333) = [[timeUs=418333, position=75365]] +numberOfTracks = 1 +track 0: + total output bytes = 94656 + sample count = 32 + format 0: + id = 1 + sampleMimeType = audio/true-hd + maxInputSize = 12480 + channelCount = 2 + sampleRate = 48000 + language = und + sample 0: + time = 0 + flags = 1 + data = length 3512, hash B77F1117 + sample 1: + time = 13333 + flags = 0 + data = length 2830, hash 4B19B7D5 + sample 2: + time = 26666 + flags = 0 + data = length 2868, hash BC04A38E + sample 3: + time = 40000 + flags = 0 + data = length 2834, hash D2AF8AF9 + sample 4: + time = 53333 + flags = 0 + data = length 2898, hash 5C9B3119 + sample 5: + time = 66666 + flags = 0 + data = length 2800, hash 31B9C93F + sample 6: + time = 80000 + flags = 0 + data = length 2866, hash 7FCABDBC + sample 7: + time = 93333 + flags = 0 + data = length 2980, hash FC2CCBDA + sample 8: + time = 106666 + flags = 1 + data = length 3432, hash 17F43166 + sample 9: + time = 120000 + flags = 0 + data = length 2974, hash 69EDFD38 + sample 10: + time = 133333 + flags = 0 + data = length 2898, hash 60E09542 + sample 11: + time = 146666 + flags = 0 + data = length 2896, hash 94A43D4A + sample 12: + time = 160000 + flags = 0 + data = length 3008, hash 82D706BB + sample 13: + time = 173333 + flags = 0 + data = length 2918, hash 22DE72A8 + sample 14: + time = 186666 + flags = 0 + data = length 2990, hash E478A008 + sample 15: + time = 200000 + flags = 0 + data = length 2860, hash B5C3DE40 + sample 16: + time = 213333 + flags = 1 + data = length 3638, hash 3FCD885B + sample 17: + time = 226666 + flags = 0 + data = length 2968, hash A3692382 + sample 18: + time = 240000 + flags = 0 + data = length 2940, hash 72A71C81 + sample 19: + time = 253333 + flags = 0 + data = length 3010, hash A826B2C3 + sample 20: + time = 266666 + flags = 0 + data = length 2952, hash BCEA8C02 + sample 21: + time = 280000 + flags = 0 + data = length 3018, hash C313A53F + sample 22: + time = 293333 + flags = 0 + data = length 2930, hash 4AAB358 + sample 23: + time = 306666 + flags = 0 + data = length 2898, hash C2C22662 + sample 24: + time = 320000 + flags = 1 + data = length 3680, hash 354DF989 + sample 25: + time = 333333 + flags = 0 + data = length 2970, hash 3191F764 + sample 26: + time = 346666 + flags = 0 + data = length 3044, hash 9E115802 + sample 27: + time = 360000 + flags = 0 + data = length 2946, hash B1341399 + sample 28: + time = 373333 + flags = 0 + data = length 2992, hash 4DA27845 + sample 29: + time = 386666 + flags = 0 + data = length 2930, hash 140DC44C + sample 30: + time = 400000 + flags = 0 + data = length 2960, hash 5287EBF8 + sample 31: + time = 413333 + flags = 0 + data = length 1216, hash B83FE151 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.1.dump new file mode 100644 index 0000000000..47ac8e4b97 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.1.dump @@ -0,0 +1,115 @@ +seekMap: + isSeekable = true + duration = 418333 + getPosition(0) = [[timeUs=0, position=3447]] + getPosition(1) = [[timeUs=1, position=3447]] + getPosition(209166) = [[timeUs=209166, position=27035]] + getPosition(418333) = [[timeUs=418333, position=75365]] +numberOfTracks = 1 +track 0: + total output bytes = 71068 + sample count = 24 + format 0: + id = 1 + sampleMimeType = audio/true-hd + maxInputSize = 12480 + channelCount = 2 + sampleRate = 48000 + language = und + sample 0: + time = 106666 + flags = 1 + data = length 3432, hash 17F43166 + sample 1: + time = 120000 + flags = 0 + data = length 2974, hash 69EDFD38 + sample 2: + time = 133333 + flags = 0 + data = length 2898, hash 60E09542 + sample 3: + time = 146666 + flags = 0 + data = length 2896, hash 94A43D4A + sample 4: + time = 160000 + flags = 0 + data = length 3008, hash 82D706BB + sample 5: + time = 173333 + flags = 0 + data = length 2918, hash 22DE72A8 + sample 6: + time = 186666 + flags = 0 + data = length 2990, hash E478A008 + sample 7: + time = 200000 + flags = 0 + data = length 2860, hash B5C3DE40 + sample 8: + time = 213333 + flags = 1 + data = length 3638, hash 3FCD885B + sample 9: + time = 226666 + flags = 0 + data = length 2968, hash A3692382 + sample 10: + time = 240000 + flags = 0 + data = length 2940, hash 72A71C81 + sample 11: + time = 253333 + flags = 0 + data = length 3010, hash A826B2C3 + sample 12: + time = 266666 + flags = 0 + data = length 2952, hash BCEA8C02 + sample 13: + time = 280000 + flags = 0 + data = length 3018, hash C313A53F + sample 14: + time = 293333 + flags = 0 + data = length 2930, hash 4AAB358 + sample 15: + time = 306666 + flags = 0 + data = length 2898, hash C2C22662 + sample 16: + time = 320000 + flags = 1 + data = length 3680, hash 354DF989 + sample 17: + time = 333333 + flags = 0 + data = length 2970, hash 3191F764 + sample 18: + time = 346666 + flags = 0 + data = length 3044, hash 9E115802 + sample 19: + time = 360000 + flags = 0 + data = length 2946, hash B1341399 + sample 20: + time = 373333 + flags = 0 + data = length 2992, hash 4DA27845 + sample 21: + time = 386666 + flags = 0 + data = length 2930, hash 140DC44C + sample 22: + time = 400000 + flags = 0 + data = length 2960, hash 5287EBF8 + sample 23: + time = 413333 + flags = 0 + data = length 1216, hash B83FE151 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.2.dump new file mode 100644 index 0000000000..c52bcbffed --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.2.dump @@ -0,0 +1,83 @@ +seekMap: + isSeekable = true + duration = 418333 + getPosition(0) = [[timeUs=0, position=3447]] + getPosition(1) = [[timeUs=1, position=3447]] + getPosition(209166) = [[timeUs=209166, position=27035]] + getPosition(418333) = [[timeUs=418333, position=75365]] +numberOfTracks = 1 +track 0: + total output bytes = 47092 + sample count = 16 + format 0: + id = 1 + sampleMimeType = audio/true-hd + maxInputSize = 12480 + channelCount = 2 + sampleRate = 48000 + language = und + sample 0: + time = 213333 + flags = 1 + data = length 3638, hash 3FCD885B + sample 1: + time = 226666 + flags = 0 + data = length 2968, hash A3692382 + sample 2: + time = 240000 + flags = 0 + data = length 2940, hash 72A71C81 + sample 3: + time = 253333 + flags = 0 + data = length 3010, hash A826B2C3 + sample 4: + time = 266666 + flags = 0 + data = length 2952, hash BCEA8C02 + sample 5: + time = 280000 + flags = 0 + data = length 3018, hash C313A53F + sample 6: + time = 293333 + flags = 0 + data = length 2930, hash 4AAB358 + sample 7: + time = 306666 + flags = 0 + data = length 2898, hash C2C22662 + sample 8: + time = 320000 + flags = 1 + data = length 3680, hash 354DF989 + sample 9: + time = 333333 + flags = 0 + data = length 2970, hash 3191F764 + sample 10: + time = 346666 + flags = 0 + data = length 3044, hash 9E115802 + sample 11: + time = 360000 + flags = 0 + data = length 2946, hash B1341399 + sample 12: + time = 373333 + flags = 0 + data = length 2992, hash 4DA27845 + sample 13: + time = 386666 + flags = 0 + data = length 2930, hash 140DC44C + sample 14: + time = 400000 + flags = 0 + data = length 2960, hash 5287EBF8 + sample 15: + time = 413333 + flags = 0 + data = length 1216, hash B83FE151 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.3.dump new file mode 100644 index 0000000000..1d226c257a --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.3.dump @@ -0,0 +1,51 @@ +seekMap: + isSeekable = true + duration = 418333 + getPosition(0) = [[timeUs=0, position=3447]] + getPosition(1) = [[timeUs=1, position=3447]] + getPosition(209166) = [[timeUs=209166, position=27035]] + getPosition(418333) = [[timeUs=418333, position=75365]] +numberOfTracks = 1 +track 0: + total output bytes = 22738 + sample count = 8 + format 0: + id = 1 + sampleMimeType = audio/true-hd + maxInputSize = 12480 + channelCount = 2 + sampleRate = 48000 + language = und + sample 0: + time = 320000 + flags = 1 + data = length 3680, hash 354DF989 + sample 1: + time = 333333 + flags = 0 + data = length 2970, hash 3191F764 + sample 2: + time = 346666 + flags = 0 + data = length 3044, hash 9E115802 + sample 3: + time = 360000 + flags = 0 + data = length 2946, hash B1341399 + sample 4: + time = 373333 + flags = 0 + data = length 2992, hash 4DA27845 + sample 5: + time = 386666 + flags = 0 + data = length 2930, hash 140DC44C + sample 6: + time = 400000 + flags = 0 + data = length 2960, hash 5287EBF8 + sample 7: + time = 413333 + flags = 0 + data = length 1216, hash B83FE151 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.unknown_length.dump new file mode 100644 index 0000000000..d05b05b2cc --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_dthd.mp4.unknown_length.dump @@ -0,0 +1,147 @@ +seekMap: + isSeekable = true + duration = 418333 + getPosition(0) = [[timeUs=0, position=3447]] + getPosition(1) = [[timeUs=1, position=3447]] + getPosition(209166) = [[timeUs=209166, position=27035]] + getPosition(418333) = [[timeUs=418333, position=75365]] +numberOfTracks = 1 +track 0: + total output bytes = 94656 + sample count = 32 + format 0: + id = 1 + sampleMimeType = audio/true-hd + maxInputSize = 12480 + channelCount = 2 + sampleRate = 48000 + language = und + sample 0: + time = 0 + flags = 1 + data = length 3512, hash B77F1117 + sample 1: + time = 13333 + flags = 0 + data = length 2830, hash 4B19B7D5 + sample 2: + time = 26666 + flags = 0 + data = length 2868, hash BC04A38E + sample 3: + time = 40000 + flags = 0 + data = length 2834, hash D2AF8AF9 + sample 4: + time = 53333 + flags = 0 + data = length 2898, hash 5C9B3119 + sample 5: + time = 66666 + flags = 0 + data = length 2800, hash 31B9C93F + sample 6: + time = 80000 + flags = 0 + data = length 2866, hash 7FCABDBC + sample 7: + time = 93333 + flags = 0 + data = length 2980, hash FC2CCBDA + sample 8: + time = 106666 + flags = 1 + data = length 3432, hash 17F43166 + sample 9: + time = 120000 + flags = 0 + data = length 2974, hash 69EDFD38 + sample 10: + time = 133333 + flags = 0 + data = length 2898, hash 60E09542 + sample 11: + time = 146666 + flags = 0 + data = length 2896, hash 94A43D4A + sample 12: + time = 160000 + flags = 0 + data = length 3008, hash 82D706BB + sample 13: + time = 173333 + flags = 0 + data = length 2918, hash 22DE72A8 + sample 14: + time = 186666 + flags = 0 + data = length 2990, hash E478A008 + sample 15: + time = 200000 + flags = 0 + data = length 2860, hash B5C3DE40 + sample 16: + time = 213333 + flags = 1 + data = length 3638, hash 3FCD885B + sample 17: + time = 226666 + flags = 0 + data = length 2968, hash A3692382 + sample 18: + time = 240000 + flags = 0 + data = length 2940, hash 72A71C81 + sample 19: + time = 253333 + flags = 0 + data = length 3010, hash A826B2C3 + sample 20: + time = 266666 + flags = 0 + data = length 2952, hash BCEA8C02 + sample 21: + time = 280000 + flags = 0 + data = length 3018, hash C313A53F + sample 22: + time = 293333 + flags = 0 + data = length 2930, hash 4AAB358 + sample 23: + time = 306666 + flags = 0 + data = length 2898, hash C2C22662 + sample 24: + time = 320000 + flags = 1 + data = length 3680, hash 354DF989 + sample 25: + time = 333333 + flags = 0 + data = length 2970, hash 3191F764 + sample 26: + time = 346666 + flags = 0 + data = length 3044, hash 9E115802 + sample 27: + time = 360000 + flags = 0 + data = length 2946, hash B1341399 + sample 28: + time = 373333 + flags = 0 + data = length 2992, hash 4DA27845 + sample 29: + time = 386666 + flags = 0 + data = length 2930, hash 140DC44C + sample 30: + time = 400000 + flags = 0 + data = length 2960, hash 5287EBF8 + sample 31: + time = 413333 + flags = 0 + data = length 1216, hash B83FE151 +tracksEnded = true diff --git a/testdata/src/test/assets/media/mp4/sample_dthd.mp4 b/testdata/src/test/assets/media/mp4/sample_dthd.mp4 new file mode 100644 index 0000000000..c5d3eb2a39 Binary files /dev/null and b/testdata/src/test/assets/media/mp4/sample_dthd.mp4 differ