diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8eae3298c2d..6192f63209b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -55,6 +55,8 @@ ([#7988](https://github.com/google/ExoPlayer/issues/7988)). * Ignore negative payload size in PES packets ([#8005](https://github.com/google/ExoPlayer/issues/8005)). + * Make FLV files seekable by using the key frame index + ([#7378](https://github.com/google/ExoPlayer/issues/7378)). * HLS: * Fix crash affecting chunkful preparation of master playlists that start with an I-FRAME only variant diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/IndexSeekMap.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/IndexSeekMap.java new file mode 100644 index 00000000000..44c3ab0184e --- /dev/null +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/IndexSeekMap.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 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.checkArgument; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Util; + +/** + * A {@link SeekMap} implementation based on a mapping between times and positions in the input + * stream. + */ +public final class IndexSeekMap implements SeekMap { + + private final long[] positions; + private final long[] timesUs; + private final long durationUs; + private final boolean isSeekable; + + /** + * Creates an instance. + * + * @param positions The positions in the stream corresponding to {@code timesUs}, in bytes. + * @param timesUs The times corresponding to {@code positions}, in microseconds. + * @param durationUs The duration of the input stream, or {@link C#TIME_UNSET} if it is unknown. + */ + public IndexSeekMap(long[] positions, long[] timesUs, long durationUs) { + checkArgument(positions.length == timesUs.length); + int length = timesUs.length; + isSeekable = length > 0; + if (isSeekable && timesUs[0] > 0) { + // Add (position = 0, timeUs = 0) as first entry. + this.positions = new long[length + 1]; + this.timesUs = new long[length + 1]; + System.arraycopy(positions, 0, this.positions, 1, length); + System.arraycopy(timesUs, 0, this.timesUs, 1, length); + } else { + this.positions = positions; + this.timesUs = timesUs; + } + this.durationUs = durationUs; + } + + @Override + public boolean isSeekable() { + return isSeekable; + } + + @Override + public long getDurationUs() { + return durationUs; + } + + @Override + public SeekMap.SeekPoints getSeekPoints(long timeUs) { + if (!isSeekable) { + return new SeekMap.SeekPoints(SeekPoint.START); + } + int targetIndex = + Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true); + SeekPoint leftSeekPoint = new SeekPoint(timesUs[targetIndex], positions[targetIndex]); + if (leftSeekPoint.timeUs >= timeUs || targetIndex == timesUs.length - 1) { + return new SeekMap.SeekPoints(leftSeekPoint); + } else { + SeekPoint rightSeekPoint = + new SeekPoint(timesUs[targetIndex + 1], positions[targetIndex + 1]); + return new SeekMap.SeekPoints(leftSeekPoint, rightSeekPoint); + } + } +} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java index a90410c02dc..6f9c5b9c40a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; +import com.google.android.exoplayer2.extractor.IndexSeekMap; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.util.Assertions; @@ -135,8 +136,12 @@ public void init(ExtractorOutput output) { @Override public void seek(long position, long timeUs) { - state = STATE_READING_FLV_HEADER; - outputFirstSample = false; + if (position == 0) { + state = STATE_READING_FLV_HEADER; + outputFirstSample = false; + } else { + state = STATE_READING_TAG_HEADER; + } bytesToNextTagHeader = 0; } @@ -267,7 +272,11 @@ private boolean readTagData(ExtractorInput input) throws IOException { wasSampleOutput = metadataReader.consume(prepareTagData(input), timestampUs); long durationUs = metadataReader.getDurationUs(); if (durationUs != C.TIME_UNSET) { - extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); + extractorOutput.seekMap( + new IndexSeekMap( + metadataReader.getKeyFrameTagPositions(), + metadataReader.getKeyFrameTimesUs(), + durationUs)); outputSeekMap = true; } } else { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java index 54594ed50fc..f0b4efb1063 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -31,6 +32,9 @@ private static final String NAME_METADATA = "onMetaData"; private static final String KEY_DURATION = "duration"; + private static final String KEY_KEY_FRAMES = "keyframes"; + private static final String KEY_FILE_POSITIONS = "filepositions"; + private static final String KEY_TIMES = "times"; // AMF object types private static final int AMF_TYPE_NUMBER = 0; @@ -43,16 +47,28 @@ private static final int AMF_TYPE_DATE = 11; private long durationUs; + private long[] keyFrameTimesUs; + private long[] keyFrameTagPositions; public ScriptTagPayloadReader() { super(new DummyTrackOutput()); durationUs = C.TIME_UNSET; + keyFrameTimesUs = new long[0]; + keyFrameTagPositions = new long[0]; } public long getDurationUs() { return durationUs; } + public long[] getKeyFrameTimesUs() { + return keyFrameTimesUs; + } + + public long[] getKeyFrameTagPositions() { + return keyFrameTagPositions; + } + @Override public void seek() { // Do nothing. @@ -80,14 +96,41 @@ protected boolean parsePayload(ParsableByteArray data, long timeUs) { // We're not interested in this metadata. return false; } - // Set the duration to the value contained in the metadata, if present. Map metadata = readAmfEcmaArray(data); - if (metadata.containsKey(KEY_DURATION)) { - double durationSeconds = (double) metadata.get(KEY_DURATION); + // Set the duration to the value contained in the metadata, if present. + @Nullable Object durationSecondsObj = metadata.get(KEY_DURATION); + if (durationSecondsObj instanceof Double) { + double durationSeconds = (double) durationSecondsObj; if (durationSeconds > 0.0) { durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND); } } + // Set the key frame times and positions to the value contained in the metadata, if present. + @Nullable Object keyFramesObj = metadata.get(KEY_KEY_FRAMES); + if (keyFramesObj instanceof Map) { + Map keyFrames = (Map) keyFramesObj; + @Nullable Object positionsObj = keyFrames.get(KEY_FILE_POSITIONS); + @Nullable Object timesSecondsObj = keyFrames.get(KEY_TIMES); + if (positionsObj instanceof List && timesSecondsObj instanceof List) { + List positions = (List) positionsObj; + List timesSeconds = (List) timesSecondsObj; + int keyFrameCount = timesSeconds.size(); + keyFrameTimesUs = new long[keyFrameCount]; + keyFrameTagPositions = new long[keyFrameCount]; + for (int i = 0; i < keyFrameCount; i++) { + Object positionObj = positions.get(i); + Object timeSecondsObj = timesSeconds.get(i); + if (timeSecondsObj instanceof Double && positionObj instanceof Double) { + keyFrameTimesUs[i] = (long) (((Double) timeSecondsObj) * C.MICROS_PER_SECOND); + keyFrameTagPositions[i] = ((Double) positionObj).longValue(); + } else { + keyFrameTimesUs = new long[0]; + keyFrameTagPositions = new long[0]; + break; + } + } + } + } return false; } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java index f8c63ff8e26..4b9d2e46e81 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java @@ -70,7 +70,7 @@ public SeekPoints getSeekPoints(long timeUs) { int targetIndex = Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true); SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex)); - if (seekPoint.timeUs >= timeUs || targetIndex == timesUs.size() - 1) { + if (seekPoint.timeUs == timeUs || targetIndex == timesUs.size() - 1) { return new SeekPoints(seekPoint); } else { SeekPoint nextSeekPoint = diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorSeekTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorSeekTest.java new file mode 100644 index 00000000000..e03b7ec6d69 --- /dev/null +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorSeekTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2020 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.flv; + +import static com.google.android.exoplayer2.testutil.TestUtil.extractAllSamplesFromFile; +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.extractor.SeekMap; +import com.google.android.exoplayer2.testutil.FakeExtractorOutput; +import com.google.android.exoplayer2.testutil.FakeTrackOutput; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.upstream.DefaultDataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.util.Util; +import java.io.IOException; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Seeking tests for {@link FlvExtractor}. */ +@RunWith(AndroidJUnit4.class) +public class FlvExtractorSeekTest { + + private static final String TEST_FILE_KEY_FRAME_INDEX = + "media/flv/sample-with-key-frame-index.flv"; + private static final long DURATION_US = 3_042_000; + private static final long KEY_FRAMES_INTERVAL_US = C.MICROS_PER_SECOND; + + private FlvExtractor extractor; + private FakeExtractorOutput extractorOutput; + private DefaultDataSource dataSource; + + @Before + public void setUp() throws Exception { + extractor = new FlvExtractor(); + extractorOutput = new FakeExtractorOutput(); + dataSource = + new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext()) + .createDataSource(); + } + + @Test + public void flvExtractorReads_returnsSeekableSeekMap() throws Exception { + Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_KEY_FRAME_INDEX); + + SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri); + + assertThat(seekMap.getDurationUs()).isEqualTo(DURATION_US); + assertThat(seekMap.isSeekable()).isTrue(); + } + + @Test + public void seeking_handlesSeekToZero() throws Exception { + String fileName = TEST_FILE_KEY_FRAME_INDEX; + Uri fileUri = TestUtil.buildAssetUri(fileName); + SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri); + int trackId = extractorOutput.trackOutputs.keyAt(0); + FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(trackId); + + long targetSeekTimeUs = 0; + int extractedFrameIndex = + TestUtil.seekToTimeUs( + extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri); + + assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET); + assertFirstFrameAfterSeekIsWithinKeyFrameInterval( + fileName, trackId, trackOutput, extractedFrameIndex, targetSeekTimeUs); + } + + @Test + public void seeking_handlesSeekToEof() throws Exception { + String fileName = TEST_FILE_KEY_FRAME_INDEX; + Uri fileUri = TestUtil.buildAssetUri(fileName); + SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri); + int trackId = extractorOutput.trackOutputs.keyAt(0); + FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(trackId); + + long targetSeekTimeUs = seekMap.getDurationUs(); + int extractedFrameIndex = + TestUtil.seekToTimeUs( + extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri); + + assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET); + assertFirstFrameAfterSeekIsWithinKeyFrameInterval( + fileName, trackId, trackOutput, extractedFrameIndex, targetSeekTimeUs); + } + + @Test + public void seeking_handlesSeekingBackward() throws Exception { + String fileName = TEST_FILE_KEY_FRAME_INDEX; + Uri fileUri = TestUtil.buildAssetUri(fileName); + SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri); + int trackId = extractorOutput.trackOutputs.keyAt(0); + FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(trackId); + + long firstSeekTimeUs = seekMap.getDurationUs() * 2 / 3; + TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri); + long targetSeekTimeUs = seekMap.getDurationUs() / 3; + int extractedFrameIndex = + TestUtil.seekToTimeUs( + extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri); + + assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET); + assertFirstFrameAfterSeekIsWithinKeyFrameInterval( + fileName, trackId, trackOutput, extractedFrameIndex, targetSeekTimeUs); + } + + @Test + public void seeking_handlesSeekingForward() throws Exception { + String fileName = TEST_FILE_KEY_FRAME_INDEX; + Uri fileUri = TestUtil.buildAssetUri(fileName); + SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri); + int trackId = extractorOutput.trackOutputs.keyAt(0); + FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(trackId); + + long firstSeekTimeUs = seekMap.getDurationUs() / 3; + TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri); + long targetSeekTimeUs = seekMap.getDurationUs() * 2 / 3; + int extractedFrameIndex = + TestUtil.seekToTimeUs( + extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri); + + assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET); + assertFirstFrameAfterSeekIsWithinKeyFrameInterval( + fileName, trackId, trackOutput, extractedFrameIndex, targetSeekTimeUs); + } + + private static void assertFirstFrameAfterSeekIsWithinKeyFrameInterval( + String fileName, + int trackId, + FakeTrackOutput trackOutput, + int firstFrameIndexAfterSeek, + long targetSeekTimeUs) + throws IOException { + long foundFrameTimeUs = trackOutput.getSampleTimeUs(firstFrameIndexAfterSeek); + assertThat(targetSeekTimeUs - foundFrameTimeUs).isAtMost(KEY_FRAMES_INTERVAL_US); + + FakeTrackOutput expectedTrackOutput = getTrackOutput(fileName, trackId); + int foundFrameIndex = getFrameIndex(expectedTrackOutput, foundFrameTimeUs); + + trackOutput.assertSample( + firstFrameIndexAfterSeek, + expectedTrackOutput.getSampleData(foundFrameIndex), + expectedTrackOutput.getSampleTimeUs(foundFrameIndex), + expectedTrackOutput.getSampleFlags(foundFrameIndex), + expectedTrackOutput.getSampleCryptoData(foundFrameIndex)); + } + + private static FakeTrackOutput getTrackOutput(String fileName, int trackId) throws IOException { + return extractAllSamplesFromFile( + new FlvExtractor(), ApplicationProvider.getApplicationContext(), fileName) + .trackOutputs + .get(trackId); + } + + private static int getFrameIndex(FakeTrackOutput trackOutput, long targetSeekTimeUs) { + List frameTimes = trackOutput.getSampleTimesUs(); + return Util.binarySearchFloor( + frameTimes, targetSeekTimeUs, /* inclusive= */ true, /* stayInBounds= */ false); + } +} diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java index 06678ae912c..248e4b378db 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java @@ -38,4 +38,10 @@ public static List params() { public void sample() throws Exception { ExtractorAsserts.assertBehavior(FlvExtractor::new, "media/flv/sample.flv", simulationConfig); } + + @Test + public void sampleSeekable() throws Exception { + ExtractorAsserts.assertBehavior( + FlvExtractor::new, "media/flv/sample-with-key-frame-index.flv", simulationConfig); + } } diff --git a/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.0.dump b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.0.dump new file mode 100644 index 00000000000..387853091ef --- /dev/null +++ b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.0.dump @@ -0,0 +1,303 @@ +seekMap: + isSeekable = true + duration = 3042000 + getPosition(0) = [[timeUs=0, position=788]] + getPosition(1) = [[timeUs=0, position=788], [timeUs=1000000, position=6537]] + getPosition(1521000) = [[timeUs=1000000, position=6537], [timeUs=2000000, position=17543]] + getPosition(3042000) = [[timeUs=2000000, position=17543]] +numberOfTracks = 1 +track 9: + total output bytes = 28759 + sample count = 71 + format 0: + sampleMimeType = video/avc + width = 320 + height = 180 + initializationData: + data = length 30, hash 962338CC + data = length 10, hash 7A0D0F2B + sample 0: + time = 84000 + flags = 1 + data = length 747, hash 59AEB08 + sample 1: + time = 250000 + flags = 0 + data = length 117, hash 57A315CB + sample 2: + time = 167000 + flags = 0 + data = length 16, hash 7E8FA845 + sample 3: + time = 125000 + flags = 0 + data = length 13, hash 7AEB9BE0 + sample 4: + time = 209000 + flags = 0 + data = length 16, hash 644DA4CC + sample 5: + time = 334000 + flags = 0 + data = length 200, hash E5BF3A39 + sample 6: + time = 292000 + flags = 0 + data = length 167, hash 74FCA726 + sample 7: + time = 375000 + flags = 0 + data = length 134, hash 3B12FEC0 + sample 8: + time = 459000 + flags = 0 + data = length 264, hash 7D9323C7 + sample 9: + time = 417000 + flags = 0 + data = length 141, hash B5AAF09F + sample 10: + time = 500000 + flags = 0 + data = length 229, hash 181AD475 + sample 11: + time = 625000 + flags = 0 + data = length 279, hash 11A95D98 + sample 12: + time = 542000 + flags = 0 + data = length 39, hash 6F87AFD9 + sample 13: + time = 584000 + flags = 0 + data = length 88, hash 6E7EC1EF + sample 14: + time = 750000 + flags = 0 + data = length 481, hash 82246706 + sample 15: + time = 667000 + flags = 0 + data = length 103, hash A201C852 + sample 16: + time = 709000 + flags = 0 + data = length 85, hash 7D6F33C4 + sample 17: + time = 875000 + flags = 0 + data = length 659, hash 3BF583EF + sample 18: + time = 792000 + flags = 0 + data = length 134, hash 46C97FD9 + sample 19: + time = 834000 + flags = 0 + data = length 153, hash 5E737D26 + sample 20: + time = 1000000 + flags = 0 + data = length 652, hash E3151CCE + sample 21: + time = 917000 + flags = 0 + data = length 86, hash A1884AD8 + sample 22: + time = 959000 + flags = 0 + data = length 150, hash 6C7DEF31 + sample 23: + time = 1042000 + flags = 0 + data = length 316, hash E7867 + sample 24: + time = 1084000 + flags = 1 + data = length 1950, hash 28E6760E + sample 25: + time = 1250000 + flags = 0 + data = length 561, hash 8394BDB5 + sample 26: + time = 1167000 + flags = 0 + data = length 130, hash B50D0F26 + sample 27: + time = 1125000 + flags = 0 + data = length 185, hash 359FC134 + sample 28: + time = 1209000 + flags = 0 + data = length 130, hash C53797EC + sample 29: + time = 1417000 + flags = 0 + data = length 867, hash B87AD770 + sample 30: + time = 1334000 + flags = 0 + data = length 155, hash 73B4B0E7 + sample 31: + time = 1292000 + flags = 0 + data = length 168, hash 9C9C9994 + sample 32: + time = 1375000 + flags = 0 + data = length 145, hash 2D3F2527 + sample 33: + time = 1584000 + flags = 0 + data = length 991, hash 78143488 + sample 34: + time = 1500000 + flags = 0 + data = length 174, hash 6C778CE7 + sample 35: + time = 1459000 + flags = 0 + data = length 82, hash D605F20D + sample 36: + time = 1542000 + flags = 0 + data = length 125, hash 248E8190 + sample 37: + time = 1750000 + flags = 0 + data = length 1095, hash 21B08B6C + sample 38: + time = 1667000 + flags = 0 + data = length 238, hash AE5854DF + sample 39: + time = 1625000 + flags = 0 + data = length 151, hash DF20C082 + sample 40: + time = 1709000 + flags = 0 + data = length 45, hash 35165468 + sample 41: + time = 1875000 + flags = 0 + data = length 1425, hash D20DA4F0 + sample 42: + time = 1792000 + flags = 0 + data = length 67, hash 49E25397 + sample 43: + time = 1834000 + flags = 0 + data = length 72, hash EEDD2F83 + sample 44: + time = 2042000 + flags = 0 + data = length 1382, hash 6C35D237 + sample 45: + time = 1959000 + flags = 0 + data = length 186, hash CDE97917 + sample 46: + time = 1917000 + flags = 0 + data = length 80, hash 923EC2C + sample 47: + time = 2000000 + flags = 0 + data = length 122, hash EB3EEF54 + sample 48: + time = 2084000 + flags = 1 + data = length 4380, hash 9221E054 + sample 49: + time = 2167000 + flags = 0 + data = length 819, hash 46A722D6 + sample 50: + time = 2125000 + flags = 0 + data = length 140, hash E9AA6D8B + sample 51: + time = 2250000 + flags = 0 + data = length 711, hash C49CB26 + sample 52: + time = 2209000 + flags = 0 + data = length 111, hash A53830B7 + sample 53: + time = 2417000 + flags = 0 + data = length 1062, hash B95BF284 + sample 54: + time = 2334000 + flags = 0 + data = length 312, hash 1C667DA3 + sample 55: + time = 2292000 + flags = 0 + data = length 132, hash 59C0F906 + sample 56: + time = 2375000 + flags = 0 + data = length 149, hash B7F8F4A5 + sample 57: + time = 2584000 + flags = 0 + data = length 1040, hash 82D4CCFD + sample 58: + time = 2500000 + flags = 0 + data = length 349, hash C1236BA4 + sample 59: + time = 2459000 + flags = 0 + data = length 140, hash 67015D1B + sample 60: + time = 2542000 + flags = 0 + data = length 186, hash C0DF4AB0 + sample 61: + time = 2750000 + flags = 0 + data = length 811, hash EEA6FBFF + sample 62: + time = 2667000 + flags = 0 + data = length 356, hash A9847C12 + sample 63: + time = 2625000 + flags = 0 + data = length 156, hash 61E72801 + sample 64: + time = 2709000 + flags = 0 + data = length 212, hash F1F7EBE8 + sample 65: + time = 2917000 + flags = 0 + data = length 681, hash FA95355F + sample 66: + time = 2834000 + flags = 0 + data = length 351, hash F771D4DD + sample 67: + time = 2792000 + flags = 0 + data = length 174, hash 325A7512 + sample 68: + time = 2875000 + flags = 0 + data = length 199, hash A22FAA40 + sample 69: + time = 3000000 + flags = 0 + data = length 322, hash C494E5A7 + sample 70: + time = 2959000 + flags = 0 + data = length 171, hash 2DA9ECEC +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.1.dump b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.1.dump new file mode 100644 index 00000000000..4ed0bc703f4 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.1.dump @@ -0,0 +1,207 @@ +seekMap: + isSeekable = true + duration = 3042000 + getPosition(0) = [[timeUs=0, position=788]] + getPosition(1) = [[timeUs=0, position=788], [timeUs=1000000, position=6537]] + getPosition(1521000) = [[timeUs=1000000, position=6537], [timeUs=2000000, position=17543]] + getPosition(3042000) = [[timeUs=2000000, position=17543]] +numberOfTracks = 1 +track 9: + total output bytes = 23490 + sample count = 47 + format 0: + sampleMimeType = video/avc + width = 320 + height = 180 + initializationData: + data = length 30, hash 962338CC + data = length 10, hash 7A0D0F2B + sample 0: + time = 1084000 + flags = 1 + data = length 1950, hash 28E6760E + sample 1: + time = 1250000 + flags = 0 + data = length 561, hash 8394BDB5 + sample 2: + time = 1167000 + flags = 0 + data = length 130, hash B50D0F26 + sample 3: + time = 1125000 + flags = 0 + data = length 185, hash 359FC134 + sample 4: + time = 1209000 + flags = 0 + data = length 130, hash C53797EC + sample 5: + time = 1417000 + flags = 0 + data = length 867, hash B87AD770 + sample 6: + time = 1334000 + flags = 0 + data = length 155, hash 73B4B0E7 + sample 7: + time = 1292000 + flags = 0 + data = length 168, hash 9C9C9994 + sample 8: + time = 1375000 + flags = 0 + data = length 145, hash 2D3F2527 + sample 9: + time = 1584000 + flags = 0 + data = length 991, hash 78143488 + sample 10: + time = 1500000 + flags = 0 + data = length 174, hash 6C778CE7 + sample 11: + time = 1459000 + flags = 0 + data = length 82, hash D605F20D + sample 12: + time = 1542000 + flags = 0 + data = length 125, hash 248E8190 + sample 13: + time = 1750000 + flags = 0 + data = length 1095, hash 21B08B6C + sample 14: + time = 1667000 + flags = 0 + data = length 238, hash AE5854DF + sample 15: + time = 1625000 + flags = 0 + data = length 151, hash DF20C082 + sample 16: + time = 1709000 + flags = 0 + data = length 45, hash 35165468 + sample 17: + time = 1875000 + flags = 0 + data = length 1425, hash D20DA4F0 + sample 18: + time = 1792000 + flags = 0 + data = length 67, hash 49E25397 + sample 19: + time = 1834000 + flags = 0 + data = length 72, hash EEDD2F83 + sample 20: + time = 2042000 + flags = 0 + data = length 1382, hash 6C35D237 + sample 21: + time = 1959000 + flags = 0 + data = length 186, hash CDE97917 + sample 22: + time = 1917000 + flags = 0 + data = length 80, hash 923EC2C + sample 23: + time = 2000000 + flags = 0 + data = length 122, hash EB3EEF54 + sample 24: + time = 2084000 + flags = 1 + data = length 4380, hash 9221E054 + sample 25: + time = 2167000 + flags = 0 + data = length 819, hash 46A722D6 + sample 26: + time = 2125000 + flags = 0 + data = length 140, hash E9AA6D8B + sample 27: + time = 2250000 + flags = 0 + data = length 711, hash C49CB26 + sample 28: + time = 2209000 + flags = 0 + data = length 111, hash A53830B7 + sample 29: + time = 2417000 + flags = 0 + data = length 1062, hash B95BF284 + sample 30: + time = 2334000 + flags = 0 + data = length 312, hash 1C667DA3 + sample 31: + time = 2292000 + flags = 0 + data = length 132, hash 59C0F906 + sample 32: + time = 2375000 + flags = 0 + data = length 149, hash B7F8F4A5 + sample 33: + time = 2584000 + flags = 0 + data = length 1040, hash 82D4CCFD + sample 34: + time = 2500000 + flags = 0 + data = length 349, hash C1236BA4 + sample 35: + time = 2459000 + flags = 0 + data = length 140, hash 67015D1B + sample 36: + time = 2542000 + flags = 0 + data = length 186, hash C0DF4AB0 + sample 37: + time = 2750000 + flags = 0 + data = length 811, hash EEA6FBFF + sample 38: + time = 2667000 + flags = 0 + data = length 356, hash A9847C12 + sample 39: + time = 2625000 + flags = 0 + data = length 156, hash 61E72801 + sample 40: + time = 2709000 + flags = 0 + data = length 212, hash F1F7EBE8 + sample 41: + time = 2917000 + flags = 0 + data = length 681, hash FA95355F + sample 42: + time = 2834000 + flags = 0 + data = length 351, hash F771D4DD + sample 43: + time = 2792000 + flags = 0 + data = length 174, hash 325A7512 + sample 44: + time = 2875000 + flags = 0 + data = length 199, hash A22FAA40 + sample 45: + time = 3000000 + flags = 0 + data = length 322, hash C494E5A7 + sample 46: + time = 2959000 + flags = 0 + data = length 171, hash 2DA9ECEC +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.2.dump b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.2.dump new file mode 100644 index 00000000000..eced87adfc4 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.2.dump @@ -0,0 +1,111 @@ +seekMap: + isSeekable = true + duration = 3042000 + getPosition(0) = [[timeUs=0, position=788]] + getPosition(1) = [[timeUs=0, position=788], [timeUs=1000000, position=6537]] + getPosition(1521000) = [[timeUs=1000000, position=6537], [timeUs=2000000, position=17543]] + getPosition(3042000) = [[timeUs=2000000, position=17543]] +numberOfTracks = 1 +track 9: + total output bytes = 12964 + sample count = 23 + format 0: + sampleMimeType = video/avc + width = 320 + height = 180 + initializationData: + data = length 30, hash 962338CC + data = length 10, hash 7A0D0F2B + sample 0: + time = 2084000 + flags = 1 + data = length 4380, hash 9221E054 + sample 1: + time = 2167000 + flags = 0 + data = length 819, hash 46A722D6 + sample 2: + time = 2125000 + flags = 0 + data = length 140, hash E9AA6D8B + sample 3: + time = 2250000 + flags = 0 + data = length 711, hash C49CB26 + sample 4: + time = 2209000 + flags = 0 + data = length 111, hash A53830B7 + sample 5: + time = 2417000 + flags = 0 + data = length 1062, hash B95BF284 + sample 6: + time = 2334000 + flags = 0 + data = length 312, hash 1C667DA3 + sample 7: + time = 2292000 + flags = 0 + data = length 132, hash 59C0F906 + sample 8: + time = 2375000 + flags = 0 + data = length 149, hash B7F8F4A5 + sample 9: + time = 2584000 + flags = 0 + data = length 1040, hash 82D4CCFD + sample 10: + time = 2500000 + flags = 0 + data = length 349, hash C1236BA4 + sample 11: + time = 2459000 + flags = 0 + data = length 140, hash 67015D1B + sample 12: + time = 2542000 + flags = 0 + data = length 186, hash C0DF4AB0 + sample 13: + time = 2750000 + flags = 0 + data = length 811, hash EEA6FBFF + sample 14: + time = 2667000 + flags = 0 + data = length 356, hash A9847C12 + sample 15: + time = 2625000 + flags = 0 + data = length 156, hash 61E72801 + sample 16: + time = 2709000 + flags = 0 + data = length 212, hash F1F7EBE8 + sample 17: + time = 2917000 + flags = 0 + data = length 681, hash FA95355F + sample 18: + time = 2834000 + flags = 0 + data = length 351, hash F771D4DD + sample 19: + time = 2792000 + flags = 0 + data = length 174, hash 325A7512 + sample 20: + time = 2875000 + flags = 0 + data = length 199, hash A22FAA40 + sample 21: + time = 3000000 + flags = 0 + data = length 322, hash C494E5A7 + sample 22: + time = 2959000 + flags = 0 + data = length 171, hash 2DA9ECEC +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.3.dump b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.3.dump new file mode 100644 index 00000000000..eced87adfc4 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.3.dump @@ -0,0 +1,111 @@ +seekMap: + isSeekable = true + duration = 3042000 + getPosition(0) = [[timeUs=0, position=788]] + getPosition(1) = [[timeUs=0, position=788], [timeUs=1000000, position=6537]] + getPosition(1521000) = [[timeUs=1000000, position=6537], [timeUs=2000000, position=17543]] + getPosition(3042000) = [[timeUs=2000000, position=17543]] +numberOfTracks = 1 +track 9: + total output bytes = 12964 + sample count = 23 + format 0: + sampleMimeType = video/avc + width = 320 + height = 180 + initializationData: + data = length 30, hash 962338CC + data = length 10, hash 7A0D0F2B + sample 0: + time = 2084000 + flags = 1 + data = length 4380, hash 9221E054 + sample 1: + time = 2167000 + flags = 0 + data = length 819, hash 46A722D6 + sample 2: + time = 2125000 + flags = 0 + data = length 140, hash E9AA6D8B + sample 3: + time = 2250000 + flags = 0 + data = length 711, hash C49CB26 + sample 4: + time = 2209000 + flags = 0 + data = length 111, hash A53830B7 + sample 5: + time = 2417000 + flags = 0 + data = length 1062, hash B95BF284 + sample 6: + time = 2334000 + flags = 0 + data = length 312, hash 1C667DA3 + sample 7: + time = 2292000 + flags = 0 + data = length 132, hash 59C0F906 + sample 8: + time = 2375000 + flags = 0 + data = length 149, hash B7F8F4A5 + sample 9: + time = 2584000 + flags = 0 + data = length 1040, hash 82D4CCFD + sample 10: + time = 2500000 + flags = 0 + data = length 349, hash C1236BA4 + sample 11: + time = 2459000 + flags = 0 + data = length 140, hash 67015D1B + sample 12: + time = 2542000 + flags = 0 + data = length 186, hash C0DF4AB0 + sample 13: + time = 2750000 + flags = 0 + data = length 811, hash EEA6FBFF + sample 14: + time = 2667000 + flags = 0 + data = length 356, hash A9847C12 + sample 15: + time = 2625000 + flags = 0 + data = length 156, hash 61E72801 + sample 16: + time = 2709000 + flags = 0 + data = length 212, hash F1F7EBE8 + sample 17: + time = 2917000 + flags = 0 + data = length 681, hash FA95355F + sample 18: + time = 2834000 + flags = 0 + data = length 351, hash F771D4DD + sample 19: + time = 2792000 + flags = 0 + data = length 174, hash 325A7512 + sample 20: + time = 2875000 + flags = 0 + data = length 199, hash A22FAA40 + sample 21: + time = 3000000 + flags = 0 + data = length 322, hash C494E5A7 + sample 22: + time = 2959000 + flags = 0 + data = length 171, hash 2DA9ECEC +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.unknown_length.dump b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.unknown_length.dump new file mode 100644 index 00000000000..387853091ef --- /dev/null +++ b/testdata/src/test/assets/extractordumps/flv/sample-with-key-frame-index.flv.unknown_length.dump @@ -0,0 +1,303 @@ +seekMap: + isSeekable = true + duration = 3042000 + getPosition(0) = [[timeUs=0, position=788]] + getPosition(1) = [[timeUs=0, position=788], [timeUs=1000000, position=6537]] + getPosition(1521000) = [[timeUs=1000000, position=6537], [timeUs=2000000, position=17543]] + getPosition(3042000) = [[timeUs=2000000, position=17543]] +numberOfTracks = 1 +track 9: + total output bytes = 28759 + sample count = 71 + format 0: + sampleMimeType = video/avc + width = 320 + height = 180 + initializationData: + data = length 30, hash 962338CC + data = length 10, hash 7A0D0F2B + sample 0: + time = 84000 + flags = 1 + data = length 747, hash 59AEB08 + sample 1: + time = 250000 + flags = 0 + data = length 117, hash 57A315CB + sample 2: + time = 167000 + flags = 0 + data = length 16, hash 7E8FA845 + sample 3: + time = 125000 + flags = 0 + data = length 13, hash 7AEB9BE0 + sample 4: + time = 209000 + flags = 0 + data = length 16, hash 644DA4CC + sample 5: + time = 334000 + flags = 0 + data = length 200, hash E5BF3A39 + sample 6: + time = 292000 + flags = 0 + data = length 167, hash 74FCA726 + sample 7: + time = 375000 + flags = 0 + data = length 134, hash 3B12FEC0 + sample 8: + time = 459000 + flags = 0 + data = length 264, hash 7D9323C7 + sample 9: + time = 417000 + flags = 0 + data = length 141, hash B5AAF09F + sample 10: + time = 500000 + flags = 0 + data = length 229, hash 181AD475 + sample 11: + time = 625000 + flags = 0 + data = length 279, hash 11A95D98 + sample 12: + time = 542000 + flags = 0 + data = length 39, hash 6F87AFD9 + sample 13: + time = 584000 + flags = 0 + data = length 88, hash 6E7EC1EF + sample 14: + time = 750000 + flags = 0 + data = length 481, hash 82246706 + sample 15: + time = 667000 + flags = 0 + data = length 103, hash A201C852 + sample 16: + time = 709000 + flags = 0 + data = length 85, hash 7D6F33C4 + sample 17: + time = 875000 + flags = 0 + data = length 659, hash 3BF583EF + sample 18: + time = 792000 + flags = 0 + data = length 134, hash 46C97FD9 + sample 19: + time = 834000 + flags = 0 + data = length 153, hash 5E737D26 + sample 20: + time = 1000000 + flags = 0 + data = length 652, hash E3151CCE + sample 21: + time = 917000 + flags = 0 + data = length 86, hash A1884AD8 + sample 22: + time = 959000 + flags = 0 + data = length 150, hash 6C7DEF31 + sample 23: + time = 1042000 + flags = 0 + data = length 316, hash E7867 + sample 24: + time = 1084000 + flags = 1 + data = length 1950, hash 28E6760E + sample 25: + time = 1250000 + flags = 0 + data = length 561, hash 8394BDB5 + sample 26: + time = 1167000 + flags = 0 + data = length 130, hash B50D0F26 + sample 27: + time = 1125000 + flags = 0 + data = length 185, hash 359FC134 + sample 28: + time = 1209000 + flags = 0 + data = length 130, hash C53797EC + sample 29: + time = 1417000 + flags = 0 + data = length 867, hash B87AD770 + sample 30: + time = 1334000 + flags = 0 + data = length 155, hash 73B4B0E7 + sample 31: + time = 1292000 + flags = 0 + data = length 168, hash 9C9C9994 + sample 32: + time = 1375000 + flags = 0 + data = length 145, hash 2D3F2527 + sample 33: + time = 1584000 + flags = 0 + data = length 991, hash 78143488 + sample 34: + time = 1500000 + flags = 0 + data = length 174, hash 6C778CE7 + sample 35: + time = 1459000 + flags = 0 + data = length 82, hash D605F20D + sample 36: + time = 1542000 + flags = 0 + data = length 125, hash 248E8190 + sample 37: + time = 1750000 + flags = 0 + data = length 1095, hash 21B08B6C + sample 38: + time = 1667000 + flags = 0 + data = length 238, hash AE5854DF + sample 39: + time = 1625000 + flags = 0 + data = length 151, hash DF20C082 + sample 40: + time = 1709000 + flags = 0 + data = length 45, hash 35165468 + sample 41: + time = 1875000 + flags = 0 + data = length 1425, hash D20DA4F0 + sample 42: + time = 1792000 + flags = 0 + data = length 67, hash 49E25397 + sample 43: + time = 1834000 + flags = 0 + data = length 72, hash EEDD2F83 + sample 44: + time = 2042000 + flags = 0 + data = length 1382, hash 6C35D237 + sample 45: + time = 1959000 + flags = 0 + data = length 186, hash CDE97917 + sample 46: + time = 1917000 + flags = 0 + data = length 80, hash 923EC2C + sample 47: + time = 2000000 + flags = 0 + data = length 122, hash EB3EEF54 + sample 48: + time = 2084000 + flags = 1 + data = length 4380, hash 9221E054 + sample 49: + time = 2167000 + flags = 0 + data = length 819, hash 46A722D6 + sample 50: + time = 2125000 + flags = 0 + data = length 140, hash E9AA6D8B + sample 51: + time = 2250000 + flags = 0 + data = length 711, hash C49CB26 + sample 52: + time = 2209000 + flags = 0 + data = length 111, hash A53830B7 + sample 53: + time = 2417000 + flags = 0 + data = length 1062, hash B95BF284 + sample 54: + time = 2334000 + flags = 0 + data = length 312, hash 1C667DA3 + sample 55: + time = 2292000 + flags = 0 + data = length 132, hash 59C0F906 + sample 56: + time = 2375000 + flags = 0 + data = length 149, hash B7F8F4A5 + sample 57: + time = 2584000 + flags = 0 + data = length 1040, hash 82D4CCFD + sample 58: + time = 2500000 + flags = 0 + data = length 349, hash C1236BA4 + sample 59: + time = 2459000 + flags = 0 + data = length 140, hash 67015D1B + sample 60: + time = 2542000 + flags = 0 + data = length 186, hash C0DF4AB0 + sample 61: + time = 2750000 + flags = 0 + data = length 811, hash EEA6FBFF + sample 62: + time = 2667000 + flags = 0 + data = length 356, hash A9847C12 + sample 63: + time = 2625000 + flags = 0 + data = length 156, hash 61E72801 + sample 64: + time = 2709000 + flags = 0 + data = length 212, hash F1F7EBE8 + sample 65: + time = 2917000 + flags = 0 + data = length 681, hash FA95355F + sample 66: + time = 2834000 + flags = 0 + data = length 351, hash F771D4DD + sample 67: + time = 2792000 + flags = 0 + data = length 174, hash 325A7512 + sample 68: + time = 2875000 + flags = 0 + data = length 199, hash A22FAA40 + sample 69: + time = 3000000 + flags = 0 + data = length 322, hash C494E5A7 + sample 70: + time = 2959000 + flags = 0 + data = length 171, hash 2DA9ECEC +tracksEnded = true diff --git a/testdata/src/test/assets/media/flv/sample-with-key-frame-index.flv b/testdata/src/test/assets/media/flv/sample-with-key-frame-index.flv new file mode 100644 index 00000000000..fdb223aa0ce Binary files /dev/null and b/testdata/src/test/assets/media/flv/sample-with-key-frame-index.flv differ