diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1d6df24d99..066614ec72 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,7 @@ ### dev-v2 (not yet released) ### +* Optimize seeking in FMP4. * Match codecs starting with "mp4a" to different Audio MimeTypes ([#3779](https://github.com/google/ExoPlayer/issues/3779)). * Moved initial bitrate estimate from `AdaptiveTrackSelection` to diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 1cce86c525..d30355699c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -134,8 +134,6 @@ public final class FragmentedMp4Extractor implements Extractor { private final ParsableByteArray nalStartCode; private final ParsableByteArray nalPrefix; private final ParsableByteArray nalBuffer; - private final ParsableByteArray encryptionSignalByte; - private final ParsableByteArray defaultInitializationVector; // Adjusts sample timestamps. private final TimestampAdjuster timestampAdjuster; @@ -154,6 +152,7 @@ public final class FragmentedMp4Extractor implements Extractor { private ParsableByteArray atomData; private long endOfMdatPosition; private int pendingMetadataSampleBytes; + private long pendingSeekTimeUs; private long durationUs; private long segmentIndexEarliestPresentationTimeUs; @@ -246,13 +245,12 @@ public final class FragmentedMp4Extractor implements Extractor { nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalPrefix = new ParsableByteArray(5); nalBuffer = new ParsableByteArray(); - encryptionSignalByte = new ParsableByteArray(1); - defaultInitializationVector = new ParsableByteArray(); extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); pendingMetadataSampleInfos = new ArrayDeque<>(); trackBundles = new SparseArray<>(); durationUs = C.TIME_UNSET; + pendingSeekTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; enterReadingAtomHeaderState(); } @@ -282,6 +280,7 @@ public final class FragmentedMp4Extractor implements Extractor { } pendingMetadataSampleInfos.clear(); pendingMetadataSampleBytes = 0; + pendingSeekTimeUs = timeUs; containerAtoms.clear(); enterReadingAtomHeaderState(); } @@ -516,6 +515,14 @@ public final class FragmentedMp4Extractor implements Extractor { trackBundles.valueAt(i).updateDrmInitData(drmInitData); } } + // If we have a pending seek, advance tracks to their preceding sync frames. + if (pendingSeekTimeUs != C.TIME_UNSET) { + int trackCount = trackBundles.size(); + for (int i = 0; i < trackCount; i++) { + trackBundles.valueAt(i).seek(pendingSeekTimeUs); + } + pendingSeekTimeUs = C.TIME_UNSET; + } } private void maybeInitExtraTracks() { @@ -1097,16 +1104,18 @@ public final class FragmentedMp4Extractor implements Extractor { } /** - * Attempts to extract the next sample in the current mdat atom. - *

- * If there are no more samples in the current mdat atom then the parser state is transitioned + * Attempts to read the next sample in the current mdat atom. The read sample may be output or + * skipped. + * + *

If there are no more samples in the current mdat atom then the parser state is transitioned * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned. - *

- * It is possible for a sample to be extracted in part in the case that an exception is thrown. In - * this case the method can be called again to extract the remainder of the sample. + * + *

It is possible for a sample to be partially read in the case that an exception is thrown. In + * this case the method can be called again to read the remainder of the sample. * * @param input The {@link ExtractorInput} from which to read data. - * @return Whether a sample was extracted. + * @return Whether a sample was read. The read sample may have been output or skipped. False + * indicates that there are no samples left to read in the current mdat. * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ @@ -1138,18 +1147,26 @@ public final class FragmentedMp4Extractor implements Extractor { input.skipFully(bytesToSkip); this.currentTrackBundle = currentTrackBundle; } + sampleSize = currentTrackBundle.fragment .sampleSizeTable[currentTrackBundle.currentSampleIndex]; + + if (currentTrackBundle.currentSampleIndex < currentTrackBundle.firstSampleToOutputIndex) { + input.skipFully(sampleSize); + currentTrackBundle.skipSampleEncryptionData(); + if (!currentTrackBundle.next()) { + currentTrackBundle = null; + } + parserState = STATE_READING_SAMPLE_START; + return true; + } + if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { sampleSize -= Atom.HEADER_SIZE; input.skipFully(Atom.HEADER_SIZE); } - if (currentTrackBundle.fragment.definesEncryptionData) { - sampleBytesWritten = appendSampleEncryptionData(currentTrackBundle); - sampleSize += sampleBytesWritten; - } else { - sampleBytesWritten = 0; - } + sampleBytesWritten = currentTrackBundle.outputSampleEncryptionData(); + sampleSize += sampleBytesWritten; parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; } @@ -1237,13 +1254,7 @@ public final class FragmentedMp4Extractor implements Extractor { // After we have the sampleTimeUs, we can commit all the pending metadata samples outputPendingMetadataSamples(sampleTimeUs); - - currentTrackBundle.currentSampleIndex++; - currentTrackBundle.currentSampleInTrackRun++; - if (currentTrackBundle.currentSampleInTrackRun - == fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) { - currentTrackBundle.currentTrackRunIndex++; - currentTrackBundle.currentSampleInTrackRun = 0; + if (!currentTrackBundle.next()) { currentTrackBundle = null; } parserState = STATE_READING_SAMPLE_START; @@ -1286,57 +1297,6 @@ public final class FragmentedMp4Extractor implements Extractor { return nextTrackBundle; } - /** - * Appends the corresponding encryption data to the {@link TrackOutput} contained in the given - * {@link TrackBundle}. - * - * @param trackBundle The {@link TrackBundle} that contains the {@link Track} for which the - * Sample encryption data must be output. - * @return The number of written bytes. - */ - private int appendSampleEncryptionData(TrackBundle trackBundle) { - TrackFragment trackFragment = trackBundle.fragment; - int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex; - TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null - ? trackFragment.trackEncryptionBox - : trackBundle.track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex); - - ParsableByteArray initializationVectorData; - int vectorSize; - if (encryptionBox.initializationVectorSize != 0) { - initializationVectorData = trackFragment.sampleEncryptionData; - vectorSize = encryptionBox.initializationVectorSize; - } else { - // The default initialization vector should be used. - byte[] initVectorData = encryptionBox.defaultInitializationVector; - defaultInitializationVector.reset(initVectorData, initVectorData.length); - initializationVectorData = defaultInitializationVector; - vectorSize = initVectorData.length; - } - - boolean subsampleEncryption = trackFragment - .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex]; - - // Write the signal byte, containing the vector size and the subsample encryption flag. - encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0)); - encryptionSignalByte.setPosition(0); - TrackOutput output = trackBundle.output; - output.sampleData(encryptionSignalByte, 1); - // Write the vector. - output.sampleData(initializationVectorData, vectorSize); - // If we don't have subsample encryption data, we're done. - if (!subsampleEncryption) { - return 1 + vectorSize; - } - // Write the subsample encryption data. - ParsableByteArray subsampleEncryptionData = trackFragment.sampleEncryptionData; - int subsampleCount = subsampleEncryptionData.readUnsignedShort(); - subsampleEncryptionData.skipBytes(-2); - int subsampleDataLength = 2 + 6 * subsampleCount; - output.sampleData(subsampleEncryptionData, subsampleDataLength); - return 1 + vectorSize + subsampleDataLength; - } - /** Returns DrmInitData from leaf atoms. */ private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) { ArrayList schemeDatas = null; @@ -1397,18 +1357,24 @@ public final class FragmentedMp4Extractor implements Extractor { */ private static final class TrackBundle { - public final TrackFragment fragment; public final TrackOutput output; + public final TrackFragment fragment; public Track track; public DefaultSampleValues defaultSampleValues; public int currentSampleIndex; public int currentSampleInTrackRun; public int currentTrackRunIndex; + public int firstSampleToOutputIndex; + + private final ParsableByteArray encryptionSignalByte; + private final ParsableByteArray defaultInitializationVector; public TrackBundle(TrackOutput output) { - fragment = new TrackFragment(); this.output = output; + fragment = new TrackFragment(); + encryptionSignalByte = new ParsableByteArray(1); + defaultInitializationVector = new ParsableByteArray(); } public void init(Track track, DefaultSampleValues defaultSampleValues) { @@ -1418,13 +1384,6 @@ public final class FragmentedMp4Extractor implements Extractor { reset(); } - public void reset() { - fragment.reset(); - currentSampleIndex = 0; - currentTrackRunIndex = 0; - currentSampleInTrackRun = 0; - } - public void updateDrmInitData(DrmInitData drmInitData) { TrackEncryptionBox encryptionBox = track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); @@ -1432,6 +1391,120 @@ public final class FragmentedMp4Extractor implements Extractor { output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType))); } + /** Resets the current fragment and sample indices. */ + public void reset() { + fragment.reset(); + currentSampleIndex = 0; + currentTrackRunIndex = 0; + currentSampleInTrackRun = 0; + firstSampleToOutputIndex = 0; + } + + /** + * Advances {@link #firstSampleToOutputIndex} to point to the sync sample before the specified + * seek time in the current fragment. + * + * @param timeUs The seek time, in microseconds. + */ + public void seek(long timeUs) { + long timeMs = C.usToMs(timeUs); + int searchIndex = currentSampleIndex; + while (searchIndex < fragment.sampleCount + && fragment.getSamplePresentationTime(searchIndex) < timeMs) { + if (fragment.sampleIsSyncFrameTable[searchIndex]) { + firstSampleToOutputIndex = searchIndex; + } + searchIndex++; + } + } + + /** + * Advances the indices in the bundle to point to the next sample in the current fragment. If + * the current sample is the last one in the current fragment, then the advanced state will be + * {@code currentSampleIndex == fragment.sampleCount}, {@code currentTrackRunIndex == + * fragment.trunCount} and {@code #currentSampleInTrackRun == 0}. + * + * @return Whether the next sample is in the same track run as the previous one. + */ + public boolean next() { + currentSampleIndex++; + currentSampleInTrackRun++; + if (currentSampleInTrackRun == fragment.trunLength[currentTrackRunIndex]) { + currentTrackRunIndex++; + currentSampleInTrackRun = 0; + return false; + } + return true; + } + + /** + * Outputs the encryption data for the current sample. + * + * @return The number of written bytes. + */ + public int outputSampleEncryptionData() { + if (!fragment.definesEncryptionData) { + return 0; + } + + TrackEncryptionBox encryptionBox = getEncryptionBox(); + ParsableByteArray initializationVectorData; + int vectorSize; + if (encryptionBox.initializationVectorSize != 0) { + initializationVectorData = fragment.sampleEncryptionData; + vectorSize = encryptionBox.initializationVectorSize; + } else { + // The default initialization vector should be used. + byte[] initVectorData = encryptionBox.defaultInitializationVector; + defaultInitializationVector.reset(initVectorData, initVectorData.length); + initializationVectorData = defaultInitializationVector; + vectorSize = initVectorData.length; + } + + boolean subsampleEncryption = fragment.sampleHasSubsampleEncryptionTable[currentSampleIndex]; + + // Write the signal byte, containing the vector size and the subsample encryption flag. + encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0)); + encryptionSignalByte.setPosition(0); + output.sampleData(encryptionSignalByte, 1); + // Write the vector. + output.sampleData(initializationVectorData, vectorSize); + // If we don't have subsample encryption data, we're done. + if (!subsampleEncryption) { + return 1 + vectorSize; + } + // Write the subsample encryption data. + ParsableByteArray subsampleEncryptionData = fragment.sampleEncryptionData; + int subsampleCount = subsampleEncryptionData.readUnsignedShort(); + subsampleEncryptionData.skipBytes(-2); + int subsampleDataLength = 2 + 6 * subsampleCount; + output.sampleData(subsampleEncryptionData, subsampleDataLength); + return 1 + vectorSize + subsampleDataLength; + } + + /** Skips the encryption data for the current sample. */ + private void skipSampleEncryptionData() { + if (!fragment.definesEncryptionData) { + return; + } + + ParsableByteArray sampleEncryptionData = fragment.sampleEncryptionData; + TrackEncryptionBox encryptionBox = getEncryptionBox(); + if (encryptionBox.initializationVectorSize != 0) { + sampleEncryptionData.skipBytes(encryptionBox.initializationVectorSize); + } + if (fragment.sampleHasSubsampleEncryptionTable[currentSampleIndex]) { + sampleEncryptionData.skipBytes(6 * sampleEncryptionData.readUnsignedShort()); + } + } + + private TrackEncryptionBox getEncryptionBox() { + int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; + return fragment.trackEncryptionBox != null + ? fragment.trackEncryptionBox + : track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex); + } + } } diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4 b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4 new file mode 100644 index 0000000000..592aa725b2 Binary files /dev/null and b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4 differ diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump new file mode 100644 index 0000000000..04e2f6f0a0 --- /dev/null +++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump @@ -0,0 +1,361 @@ +seekMap: + isSeekable = true + duration = 1067733 + getPosition(0) = [[timeUs=66733, position=1325]] +numberOfTracks = 2 +track 0: + format: + bitrate = -1 + id = 1 + containerMimeType = null + sampleMimeType = video/avc + maxInputSize = -1 + width = 1080 + height = 720 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = -1 + sampleRate = -1 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + total output bytes = 85933 + sample count = 30 + sample 0: + time = 66000 + flags = 1 + data = length 38070, hash B58E1AEE + sample 1: + time = 199000 + flags = 0 + data = length 8340, hash 8AC449FF + sample 2: + time = 132000 + flags = 0 + data = length 1295, hash C0DA5090 + sample 3: + time = 100000 + flags = 0 + data = length 469, hash D6E0A200 + sample 4: + time = 166000 + flags = 0 + data = length 564, hash E5F56C5B + sample 5: + time = 332000 + flags = 0 + data = length 6075, hash 8756E49E + sample 6: + time = 266000 + flags = 0 + data = length 847, hash DCC2B618 + sample 7: + time = 233000 + flags = 0 + data = length 455, hash B9CCE047 + sample 8: + time = 299000 + flags = 0 + data = length 467, hash 69806D94 + sample 9: + time = 466000 + flags = 0 + data = length 4549, hash 3944F501 + sample 10: + time = 399000 + flags = 0 + data = length 1087, hash 491BF106 + sample 11: + time = 367000 + flags = 0 + data = length 380, hash 5FED016A + sample 12: + time = 433000 + flags = 0 + data = length 455, hash 8A0610 + sample 13: + time = 599000 + flags = 0 + data = length 5190, hash B9031D8 + sample 14: + time = 533000 + flags = 0 + data = length 1071, hash 684E7DC8 + sample 15: + time = 500000 + flags = 0 + data = length 653, hash 8494F326 + sample 16: + time = 566000 + flags = 0 + data = length 485, hash 2CCC85F4 + sample 17: + time = 733000 + flags = 0 + data = length 4884, hash D16B6A96 + sample 18: + time = 666000 + flags = 0 + data = length 997, hash 164FF210 + sample 19: + time = 633000 + flags = 0 + data = length 640, hash F664125B + sample 20: + time = 700000 + flags = 0 + data = length 491, hash B5930C7C + sample 21: + time = 866000 + flags = 0 + data = length 2989, hash 92CF4FCF + sample 22: + time = 800000 + flags = 0 + data = length 838, hash 294A3451 + sample 23: + time = 767000 + flags = 0 + data = length 544, hash FCCE2DE6 + sample 24: + time = 833000 + flags = 0 + data = length 329, hash A654FFA1 + sample 25: + time = 1000000 + flags = 0 + data = length 1517, hash 5F7EBF8B + sample 26: + time = 933000 + flags = 0 + data = length 803, hash 7A5C4C1D + sample 27: + time = 900000 + flags = 0 + data = length 415, hash B31BBC3B + sample 28: + time = 967000 + flags = 0 + data = length 415, hash 850DFEA3 + sample 29: + time = 1033000 + flags = 0 + data = length 619, hash AB5E56CA +track 1: + format: + bitrate = -1 + id = 2 + containerMimeType = null + sampleMimeType = audio/mp4a-latm + maxInputSize = -1 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 1 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = und + drmInitData = - + initializationData: + data = length 5, hash 2B7623A + total output bytes = 18257 + sample count = 46 + sample 0: + time = 0 + flags = 1 + data = length 18, hash 96519432 + sample 1: + time = 23000 + flags = 1 + data = length 4, hash EE9DF + sample 2: + time = 46000 + flags = 1 + data = length 4, hash EEDBF + sample 3: + time = 69000 + flags = 1 + data = length 157, hash E2F078F4 + sample 4: + time = 92000 + flags = 1 + data = length 371, hash B9471F94 + sample 5: + time = 116000 + flags = 1 + data = length 373, hash 2AB265CB + sample 6: + time = 139000 + flags = 1 + data = length 402, hash 1295477C + sample 7: + time = 162000 + flags = 1 + data = length 455, hash 2D8146C8 + sample 8: + time = 185000 + flags = 1 + data = length 434, hash F2C5D287 + sample 9: + time = 208000 + flags = 1 + data = length 450, hash 84143FCD + sample 10: + time = 232000 + flags = 1 + data = length 429, hash EF769D50 + sample 11: + time = 255000 + flags = 1 + data = length 450, hash EC3DE692 + sample 12: + time = 278000 + flags = 1 + data = length 447, hash 3E519E13 + sample 13: + time = 301000 + flags = 1 + data = length 457, hash 1E4F23A0 + sample 14: + time = 325000 + flags = 1 + data = length 447, hash A439EA97 + sample 15: + time = 348000 + flags = 1 + data = length 456, hash 1E9034C6 + sample 16: + time = 371000 + flags = 1 + data = length 398, hash 99DB7345 + sample 17: + time = 394000 + flags = 1 + data = length 474, hash 3F05F10A + sample 18: + time = 417000 + flags = 1 + data = length 416, hash C105EE09 + sample 19: + time = 441000 + flags = 1 + data = length 454, hash 5FDBE458 + sample 20: + time = 464000 + flags = 1 + data = length 438, hash 41A93AC3 + sample 21: + time = 487000 + flags = 1 + data = length 443, hash 10FDA652 + sample 22: + time = 510000 + flags = 1 + data = length 412, hash 1F791E25 + sample 23: + time = 534000 + flags = 1 + data = length 482, hash A6D983D + sample 24: + time = 557000 + flags = 1 + data = length 386, hash BED7392F + sample 25: + time = 580000 + flags = 1 + data = length 463, hash 5309F8C9 + sample 26: + time = 603000 + flags = 1 + data = length 394, hash 21C7321F + sample 27: + time = 626000 + flags = 1 + data = length 489, hash 71B4730D + sample 28: + time = 650000 + flags = 1 + data = length 403, hash D9C6DE89 + sample 29: + time = 673000 + flags = 1 + data = length 447, hash 9B14B73B + sample 30: + time = 696000 + flags = 1 + data = length 439, hash 4760D35B + sample 31: + time = 719000 + flags = 1 + data = length 463, hash 1601F88D + sample 32: + time = 743000 + flags = 1 + data = length 423, hash D4AE6773 + sample 33: + time = 766000 + flags = 1 + data = length 497, hash A3C674D3 + sample 34: + time = 789000 + flags = 1 + data = length 419, hash D3734A1F + sample 35: + time = 812000 + flags = 1 + data = length 474, hash DFB41F9 + sample 36: + time = 835000 + flags = 1 + data = length 413, hash 53E7CB9F + sample 37: + time = 859000 + flags = 1 + data = length 445, hash D15B0E39 + sample 38: + time = 882000 + flags = 1 + data = length 453, hash 77ED81E4 + sample 39: + time = 905000 + flags = 1 + data = length 545, hash 3321AEB9 + sample 40: + time = 928000 + flags = 1 + data = length 317, hash F557D0E + sample 41: + time = 952000 + flags = 1 + data = length 537, hash ED58CF7B + sample 42: + time = 975000 + flags = 1 + data = length 458, hash 51CDAA10 + sample 43: + time = 998000 + flags = 1 + data = length 465, hash CBA1EFD7 + sample 44: + time = 1021000 + flags = 1 + data = length 446, hash D6735B8A + sample 45: + time = 1044000 + flags = 1 + data = length 10, hash A453EEBE +tracksEnded = true diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump new file mode 100644 index 0000000000..48a7623a7d --- /dev/null +++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump @@ -0,0 +1,301 @@ +seekMap: + isSeekable = true + duration = 1067733 + getPosition(0) = [[timeUs=66733, position=1325]] +numberOfTracks = 2 +track 0: + format: + bitrate = -1 + id = 1 + containerMimeType = null + sampleMimeType = video/avc + maxInputSize = -1 + width = 1080 + height = 720 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = -1 + sampleRate = -1 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + total output bytes = 85933 + sample count = 30 + sample 0: + time = 66000 + flags = 1 + data = length 38070, hash B58E1AEE + sample 1: + time = 199000 + flags = 0 + data = length 8340, hash 8AC449FF + sample 2: + time = 132000 + flags = 0 + data = length 1295, hash C0DA5090 + sample 3: + time = 100000 + flags = 0 + data = length 469, hash D6E0A200 + sample 4: + time = 166000 + flags = 0 + data = length 564, hash E5F56C5B + sample 5: + time = 332000 + flags = 0 + data = length 6075, hash 8756E49E + sample 6: + time = 266000 + flags = 0 + data = length 847, hash DCC2B618 + sample 7: + time = 233000 + flags = 0 + data = length 455, hash B9CCE047 + sample 8: + time = 299000 + flags = 0 + data = length 467, hash 69806D94 + sample 9: + time = 466000 + flags = 0 + data = length 4549, hash 3944F501 + sample 10: + time = 399000 + flags = 0 + data = length 1087, hash 491BF106 + sample 11: + time = 367000 + flags = 0 + data = length 380, hash 5FED016A + sample 12: + time = 433000 + flags = 0 + data = length 455, hash 8A0610 + sample 13: + time = 599000 + flags = 0 + data = length 5190, hash B9031D8 + sample 14: + time = 533000 + flags = 0 + data = length 1071, hash 684E7DC8 + sample 15: + time = 500000 + flags = 0 + data = length 653, hash 8494F326 + sample 16: + time = 566000 + flags = 0 + data = length 485, hash 2CCC85F4 + sample 17: + time = 733000 + flags = 0 + data = length 4884, hash D16B6A96 + sample 18: + time = 666000 + flags = 0 + data = length 997, hash 164FF210 + sample 19: + time = 633000 + flags = 0 + data = length 640, hash F664125B + sample 20: + time = 700000 + flags = 0 + data = length 491, hash B5930C7C + sample 21: + time = 866000 + flags = 0 + data = length 2989, hash 92CF4FCF + sample 22: + time = 800000 + flags = 0 + data = length 838, hash 294A3451 + sample 23: + time = 767000 + flags = 0 + data = length 544, hash FCCE2DE6 + sample 24: + time = 833000 + flags = 0 + data = length 329, hash A654FFA1 + sample 25: + time = 1000000 + flags = 0 + data = length 1517, hash 5F7EBF8B + sample 26: + time = 933000 + flags = 0 + data = length 803, hash 7A5C4C1D + sample 27: + time = 900000 + flags = 0 + data = length 415, hash B31BBC3B + sample 28: + time = 967000 + flags = 0 + data = length 415, hash 850DFEA3 + sample 29: + time = 1033000 + flags = 0 + data = length 619, hash AB5E56CA +track 1: + format: + bitrate = -1 + id = 2 + containerMimeType = null + sampleMimeType = audio/mp4a-latm + maxInputSize = -1 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 1 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = und + drmInitData = - + initializationData: + data = length 5, hash 2B7623A + total output bytes = 13359 + sample count = 31 + sample 0: + time = 348000 + flags = 1 + data = length 456, hash 1E9034C6 + sample 1: + time = 371000 + flags = 1 + data = length 398, hash 99DB7345 + sample 2: + time = 394000 + flags = 1 + data = length 474, hash 3F05F10A + sample 3: + time = 417000 + flags = 1 + data = length 416, hash C105EE09 + sample 4: + time = 441000 + flags = 1 + data = length 454, hash 5FDBE458 + sample 5: + time = 464000 + flags = 1 + data = length 438, hash 41A93AC3 + sample 6: + time = 487000 + flags = 1 + data = length 443, hash 10FDA652 + sample 7: + time = 510000 + flags = 1 + data = length 412, hash 1F791E25 + sample 8: + time = 534000 + flags = 1 + data = length 482, hash A6D983D + sample 9: + time = 557000 + flags = 1 + data = length 386, hash BED7392F + sample 10: + time = 580000 + flags = 1 + data = length 463, hash 5309F8C9 + sample 11: + time = 603000 + flags = 1 + data = length 394, hash 21C7321F + sample 12: + time = 626000 + flags = 1 + data = length 489, hash 71B4730D + sample 13: + time = 650000 + flags = 1 + data = length 403, hash D9C6DE89 + sample 14: + time = 673000 + flags = 1 + data = length 447, hash 9B14B73B + sample 15: + time = 696000 + flags = 1 + data = length 439, hash 4760D35B + sample 16: + time = 719000 + flags = 1 + data = length 463, hash 1601F88D + sample 17: + time = 743000 + flags = 1 + data = length 423, hash D4AE6773 + sample 18: + time = 766000 + flags = 1 + data = length 497, hash A3C674D3 + sample 19: + time = 789000 + flags = 1 + data = length 419, hash D3734A1F + sample 20: + time = 812000 + flags = 1 + data = length 474, hash DFB41F9 + sample 21: + time = 835000 + flags = 1 + data = length 413, hash 53E7CB9F + sample 22: + time = 859000 + flags = 1 + data = length 445, hash D15B0E39 + sample 23: + time = 882000 + flags = 1 + data = length 453, hash 77ED81E4 + sample 24: + time = 905000 + flags = 1 + data = length 545, hash 3321AEB9 + sample 25: + time = 928000 + flags = 1 + data = length 317, hash F557D0E + sample 26: + time = 952000 + flags = 1 + data = length 537, hash ED58CF7B + sample 27: + time = 975000 + flags = 1 + data = length 458, hash 51CDAA10 + sample 28: + time = 998000 + flags = 1 + data = length 465, hash CBA1EFD7 + sample 29: + time = 1021000 + flags = 1 + data = length 446, hash D6735B8A + sample 30: + time = 1044000 + flags = 1 + data = length 10, hash A453EEBE +tracksEnded = true diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump new file mode 100644 index 0000000000..7522891e14 --- /dev/null +++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump @@ -0,0 +1,241 @@ +seekMap: + isSeekable = true + duration = 1067733 + getPosition(0) = [[timeUs=66733, position=1325]] +numberOfTracks = 2 +track 0: + format: + bitrate = -1 + id = 1 + containerMimeType = null + sampleMimeType = video/avc + maxInputSize = -1 + width = 1080 + height = 720 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = -1 + sampleRate = -1 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + total output bytes = 85933 + sample count = 30 + sample 0: + time = 66000 + flags = 1 + data = length 38070, hash B58E1AEE + sample 1: + time = 199000 + flags = 0 + data = length 8340, hash 8AC449FF + sample 2: + time = 132000 + flags = 0 + data = length 1295, hash C0DA5090 + sample 3: + time = 100000 + flags = 0 + data = length 469, hash D6E0A200 + sample 4: + time = 166000 + flags = 0 + data = length 564, hash E5F56C5B + sample 5: + time = 332000 + flags = 0 + data = length 6075, hash 8756E49E + sample 6: + time = 266000 + flags = 0 + data = length 847, hash DCC2B618 + sample 7: + time = 233000 + flags = 0 + data = length 455, hash B9CCE047 + sample 8: + time = 299000 + flags = 0 + data = length 467, hash 69806D94 + sample 9: + time = 466000 + flags = 0 + data = length 4549, hash 3944F501 + sample 10: + time = 399000 + flags = 0 + data = length 1087, hash 491BF106 + sample 11: + time = 367000 + flags = 0 + data = length 380, hash 5FED016A + sample 12: + time = 433000 + flags = 0 + data = length 455, hash 8A0610 + sample 13: + time = 599000 + flags = 0 + data = length 5190, hash B9031D8 + sample 14: + time = 533000 + flags = 0 + data = length 1071, hash 684E7DC8 + sample 15: + time = 500000 + flags = 0 + data = length 653, hash 8494F326 + sample 16: + time = 566000 + flags = 0 + data = length 485, hash 2CCC85F4 + sample 17: + time = 733000 + flags = 0 + data = length 4884, hash D16B6A96 + sample 18: + time = 666000 + flags = 0 + data = length 997, hash 164FF210 + sample 19: + time = 633000 + flags = 0 + data = length 640, hash F664125B + sample 20: + time = 700000 + flags = 0 + data = length 491, hash B5930C7C + sample 21: + time = 866000 + flags = 0 + data = length 2989, hash 92CF4FCF + sample 22: + time = 800000 + flags = 0 + data = length 838, hash 294A3451 + sample 23: + time = 767000 + flags = 0 + data = length 544, hash FCCE2DE6 + sample 24: + time = 833000 + flags = 0 + data = length 329, hash A654FFA1 + sample 25: + time = 1000000 + flags = 0 + data = length 1517, hash 5F7EBF8B + sample 26: + time = 933000 + flags = 0 + data = length 803, hash 7A5C4C1D + sample 27: + time = 900000 + flags = 0 + data = length 415, hash B31BBC3B + sample 28: + time = 967000 + flags = 0 + data = length 415, hash 850DFEA3 + sample 29: + time = 1033000 + flags = 0 + data = length 619, hash AB5E56CA +track 1: + format: + bitrate = -1 + id = 2 + containerMimeType = null + sampleMimeType = audio/mp4a-latm + maxInputSize = -1 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 1 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = und + drmInitData = - + initializationData: + data = length 5, hash 2B7623A + total output bytes = 6804 + sample count = 16 + sample 0: + time = 696000 + flags = 1 + data = length 439, hash 4760D35B + sample 1: + time = 719000 + flags = 1 + data = length 463, hash 1601F88D + sample 2: + time = 743000 + flags = 1 + data = length 423, hash D4AE6773 + sample 3: + time = 766000 + flags = 1 + data = length 497, hash A3C674D3 + sample 4: + time = 789000 + flags = 1 + data = length 419, hash D3734A1F + sample 5: + time = 812000 + flags = 1 + data = length 474, hash DFB41F9 + sample 6: + time = 835000 + flags = 1 + data = length 413, hash 53E7CB9F + sample 7: + time = 859000 + flags = 1 + data = length 445, hash D15B0E39 + sample 8: + time = 882000 + flags = 1 + data = length 453, hash 77ED81E4 + sample 9: + time = 905000 + flags = 1 + data = length 545, hash 3321AEB9 + sample 10: + time = 928000 + flags = 1 + data = length 317, hash F557D0E + sample 11: + time = 952000 + flags = 1 + data = length 537, hash ED58CF7B + sample 12: + time = 975000 + flags = 1 + data = length 458, hash 51CDAA10 + sample 13: + time = 998000 + flags = 1 + data = length 465, hash CBA1EFD7 + sample 14: + time = 1021000 + flags = 1 + data = length 446, hash D6735B8A + sample 15: + time = 1044000 + flags = 1 + data = length 10, hash A453EEBE +tracksEnded = true diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump new file mode 100644 index 0000000000..afd24e40ce --- /dev/null +++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump @@ -0,0 +1,181 @@ +seekMap: + isSeekable = true + duration = 1067733 + getPosition(0) = [[timeUs=66733, position=1325]] +numberOfTracks = 2 +track 0: + format: + bitrate = -1 + id = 1 + containerMimeType = null + sampleMimeType = video/avc + maxInputSize = -1 + width = 1080 + height = 720 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = -1 + sampleRate = -1 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B + total output bytes = 85933 + sample count = 30 + sample 0: + time = 66000 + flags = 1 + data = length 38070, hash B58E1AEE + sample 1: + time = 199000 + flags = 0 + data = length 8340, hash 8AC449FF + sample 2: + time = 132000 + flags = 0 + data = length 1295, hash C0DA5090 + sample 3: + time = 100000 + flags = 0 + data = length 469, hash D6E0A200 + sample 4: + time = 166000 + flags = 0 + data = length 564, hash E5F56C5B + sample 5: + time = 332000 + flags = 0 + data = length 6075, hash 8756E49E + sample 6: + time = 266000 + flags = 0 + data = length 847, hash DCC2B618 + sample 7: + time = 233000 + flags = 0 + data = length 455, hash B9CCE047 + sample 8: + time = 299000 + flags = 0 + data = length 467, hash 69806D94 + sample 9: + time = 466000 + flags = 0 + data = length 4549, hash 3944F501 + sample 10: + time = 399000 + flags = 0 + data = length 1087, hash 491BF106 + sample 11: + time = 367000 + flags = 0 + data = length 380, hash 5FED016A + sample 12: + time = 433000 + flags = 0 + data = length 455, hash 8A0610 + sample 13: + time = 599000 + flags = 0 + data = length 5190, hash B9031D8 + sample 14: + time = 533000 + flags = 0 + data = length 1071, hash 684E7DC8 + sample 15: + time = 500000 + flags = 0 + data = length 653, hash 8494F326 + sample 16: + time = 566000 + flags = 0 + data = length 485, hash 2CCC85F4 + sample 17: + time = 733000 + flags = 0 + data = length 4884, hash D16B6A96 + sample 18: + time = 666000 + flags = 0 + data = length 997, hash 164FF210 + sample 19: + time = 633000 + flags = 0 + data = length 640, hash F664125B + sample 20: + time = 700000 + flags = 0 + data = length 491, hash B5930C7C + sample 21: + time = 866000 + flags = 0 + data = length 2989, hash 92CF4FCF + sample 22: + time = 800000 + flags = 0 + data = length 838, hash 294A3451 + sample 23: + time = 767000 + flags = 0 + data = length 544, hash FCCE2DE6 + sample 24: + time = 833000 + flags = 0 + data = length 329, hash A654FFA1 + sample 25: + time = 1000000 + flags = 0 + data = length 1517, hash 5F7EBF8B + sample 26: + time = 933000 + flags = 0 + data = length 803, hash 7A5C4C1D + sample 27: + time = 900000 + flags = 0 + data = length 415, hash B31BBC3B + sample 28: + time = 967000 + flags = 0 + data = length 415, hash 850DFEA3 + sample 29: + time = 1033000 + flags = 0 + data = length 619, hash AB5E56CA +track 1: + format: + bitrate = -1 + id = 2 + containerMimeType = null + sampleMimeType = audio/mp4a-latm + maxInputSize = -1 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 1 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = und + drmInitData = - + initializationData: + data = length 5, hash 2B7623A + total output bytes = 10 + sample count = 1 + sample 0: + time = 1044000 + flags = 1 + data = length 10, hash A453EEBE +tracksEnded = true diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java index f5b0f48592..176211acb8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java @@ -36,6 +36,12 @@ public final class FragmentedMp4ExtractorTest { getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented.mp4"); } + @Test + public void testSampleSeekable() throws Exception { + ExtractorAsserts.assertBehavior( + getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented_seekable.mp4"); + } + @Test public void testSampleWithSeiPayloadParsing() throws Exception { // Enabling the CEA-608 track enables SEI payload parsing.