Commit 3474c39c by ibaker Committed by Oliver Woodman

Add support for non-contiguous Ogg pages

bear_vorbis_gap.ogg is a copy of bear_vorbis.ogg with 10 garbage bytes
(DE AD BE EF DE AD BE EF DE AD) inserted before the second capture
pattern and 3 garbage bytes inserted at the end (DE AD BE).

Issue: #7230
PiperOrigin-RevId: 314715729
parent 9b5cab04
......@@ -156,6 +156,8 @@
* HLS:
* Add support for upstream discard including cancelation of ongoing load
([#6322](https://github.com/google/ExoPlayer/issues/6322)).
* Ogg: Allow non-contiguous pages
([#7230](https://github.com/google/ExoPlayer/issues/7230)).
* Extractors:
* Add `IndexSeeker` for accurate seeks in VBR MP3 streams
([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker
......
......@@ -155,7 +155,7 @@ import java.io.IOException;
}
long currentPosition = input.getPosition();
if (!skipToNextPage(input, end)) {
if (!pageHeader.skipToNextPage(input, end)) {
if (start == currentPosition) {
throw new IOException("No ogg page can be found.");
}
......@@ -200,69 +200,22 @@ import java.io.IOException;
* @throws IOException If reading from the input fails.
*/
private void skipToPageOfTargetGranule(ExtractorInput input) throws IOException {
pageHeader.populate(input, /* quiet= */ false);
while (pageHeader.granulePosition <= targetGranule) {
while (true) {
// If pageHeader.skipToNextPage fails to find a page it will advance input.position to the
// end of the file, so pageHeader.populate will throw EOFException (because quiet=false).
pageHeader.skipToNextPage(input);
pageHeader.populate(input, /* quiet= */ false);
if (pageHeader.granulePosition > targetGranule) {
break;
}
input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
start = input.getPosition();
startGranule = pageHeader.granulePosition;
pageHeader.populate(input, /* quiet= */ false);
}
input.resetPeekPosition();
}
/**
* Skips to the next page.
*
* @param input The {@code ExtractorInput} to skip to the next page.
* @throws IOException If peeking/reading from the input fails.
* @throws EOFException If the next page can't be found before the end of the input.
*/
@VisibleForTesting
void skipToNextPage(ExtractorInput input) throws IOException {
if (!skipToNextPage(input, payloadEndPosition)) {
// Not found until eof.
throw new EOFException();
}
}
/**
* Skips to the next page. Searches for the next page header.
*
* @param input The {@code ExtractorInput} to skip to the next page.
* @param limit The limit up to which the search should take place.
* @return Whether the next page was found.
* @throws IOException If peeking/reading from the input fails.
*/
private boolean skipToNextPage(ExtractorInput input, long limit) throws IOException {
limit = Math.min(limit + 3, payloadEndPosition);
byte[] buffer = new byte[2048];
int peekLength = buffer.length;
while (true) {
if (input.getPosition() + peekLength > limit) {
// Make sure to not peek beyond the end of the input.
peekLength = (int) (limit - input.getPosition());
if (peekLength < 4) {
// Not found until end.
return false;
}
}
input.peekFully(buffer, 0, peekLength, false);
for (int i = 0; i < peekLength - 3; i++) {
if (buffer[i] == 'O'
&& buffer[i + 1] == 'g'
&& buffer[i + 2] == 'g'
&& buffer[i + 3] == 'S') {
// Match! Skip to the start of the pattern.
input.skipFully(i);
return true;
}
}
// Overlap by not skipping the entire peekLength.
input.skipFully(peekLength - 3);
}
}
/**
* Skips to the last Ogg page in the stream and reads the header's granule field which is the
* total number of samples per channel.
*
......@@ -272,12 +225,16 @@ import java.io.IOException;
*/
@VisibleForTesting
long readGranuleOfLastPage(ExtractorInput input) throws IOException {
skipToNextPage(input);
pageHeader.reset();
while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < payloadEndPosition) {
if (!pageHeader.skipToNextPage(input)) {
throw new EOFException();
}
do {
pageHeader.populate(input, /* quiet= */ false);
input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
}
} while ((pageHeader.type & 0x04) != 0x04
&& pageHeader.skipToNextPage(input)
&& input.getPosition() < payloadEndPosition);
return pageHeader.granulePosition;
}
......
......@@ -67,7 +67,7 @@ import java.util.Arrays;
while (!populated) {
if (currentSegmentIndex < 0) {
// We're at the start of a page.
if (!pageHeader.populate(input, true)) {
if (!pageHeader.skipToNextPage(input) || !pageHeader.populate(input, /* quiet= */ true)) {
return false;
}
int segmentIndex = 0;
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ogg;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.EOFException;
import java.io.IOException;
......@@ -33,7 +34,8 @@ import java.io.IOException;
public static final int MAX_PAGE_SIZE = EMPTY_PAGE_HEADER_SIZE + MAX_SEGMENT_COUNT
+ MAX_PAGE_PAYLOAD;
private static final int TYPE_OGGS = 0x4f676753;
private static final int CAPTURE_PATTERN = 0x4f676753; // OggS
private static final int CAPTURE_PATTERN_SIZE = 4;
public int revision;
public int type;
......@@ -74,6 +76,51 @@ import java.io.IOException;
}
/**
* Advances through {@code input} looking for the start of the next Ogg page.
*
* <p>Equivalent to {@link #skipToNextPage(ExtractorInput, long) skipToNextPage(input, /* limit=
* *\/ C.POSITION_UNSET)}.
*/
public boolean skipToNextPage(ExtractorInput input) throws IOException {
return skipToNextPage(input, /* limit= */ C.POSITION_UNSET);
}
/**
* Advances through {@code input} looking for the start of the next Ogg page.
*
* <p>The start of a page is identified by the 4-byte capture_pattern 'OggS'.
*
* <p>Returns {@code true} if a capture pattern was found, with the read and peek positions of
* {@code input} at the start of the page, just before the capture_pattern. Otherwise returns
* {@code false}, with the read and peek positions of {@code input} at either {@code limit} (if
* set) or end-of-input.
*
* @param input The {@link ExtractorInput} to read from (must have {@code readPosition ==
* peekPosition}).
* @param limit The max position in {@code input} to peek to, or {@link C#POSITION_UNSET} to allow
* peeking to the end.
* @return True if a capture_pattern was found.
* @throws IOException If reading data fails.
*/
public boolean skipToNextPage(ExtractorInput input, long limit) throws IOException {
Assertions.checkArgument(input.getPosition() == input.getPeekPosition());
while ((limit == C.POSITION_UNSET || input.getPosition() + CAPTURE_PATTERN_SIZE < limit)
&& peekSafely(input, scratch.data, 0, CAPTURE_PATTERN_SIZE, /* quiet= */ true)) {
scratch.reset();
if (scratch.readUnsignedInt() == CAPTURE_PATTERN) {
input.resetPeekPosition();
return true;
}
// Advance one byte before looking for the capture pattern again.
input.skipFully(1);
}
// Move the read & peek positions to limit or end-of-input, whichever is closer.
while ((limit == C.POSITION_UNSET || input.getPosition() < limit)
&& input.skip(1) != C.RESULT_END_OF_INPUT) {}
return false;
}
/**
* Peeks an Ogg page header and updates this {@link OggPageHeader}.
*
* @param input The {@link ExtractorInput} to read from.
......@@ -84,23 +131,11 @@ import java.io.IOException;
* @throws IOException If reading data fails or the stream is invalid.
*/
public boolean populate(ExtractorInput input, boolean quiet) throws IOException {
scratch.reset();
reset();
boolean hasEnoughBytes = input.getLength() == C.LENGTH_UNSET
|| input.getLength() - input.getPeekPosition() >= EMPTY_PAGE_HEADER_SIZE;
if (!hasEnoughBytes || !input.peekFully(scratch.data, 0, EMPTY_PAGE_HEADER_SIZE, true)) {
if (quiet) {
return false;
} else {
throw new EOFException();
}
}
if (scratch.readUnsignedInt() != TYPE_OGGS) {
if (quiet) {
return false;
} else {
throw new ParserException("expected OggS capture pattern at begin of page");
}
scratch.reset();
if (!peekSafely(input, scratch.data, 0, EMPTY_PAGE_HEADER_SIZE, quiet)
|| scratch.readUnsignedInt() != CAPTURE_PATTERN) {
return false;
}
revision = scratch.readUnsignedByte();
......@@ -130,4 +165,31 @@ import java.io.IOException;
return true;
}
/**
* Peek data from {@code input}, respecting {@code quiet}. Return true if the peek is successful.
*
* <p>If {@code quiet=false} then encountering the end of the input (whether before or after
* reading some data) will throw {@link EOFException}.
*
* <p>If {@code quiet=true} then encountering the end of the input (even after reading some data)
* will return {@code false}.
*
* <p>This is slightly different to the behaviour of {@link ExtractorInput#peekFully(byte[], int,
* int, boolean)}, where {@code allowEndOfInput=true} only returns false (and suppresses the
* exception) if the end of the input is reached before reading any data.
*/
private static boolean peekSafely(
ExtractorInput input, byte[] output, int offset, int length, boolean quiet)
throws IOException {
try {
return input.peekFully(output, offset, length, /* allowEndOfInput= */ quiet);
} catch (EOFException e) {
if (quiet) {
return false;
} else {
throw e;
}
}
}
}
......@@ -22,11 +22,9 @@ import static org.junit.Assert.fail;
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.ExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.common.primitives.Bytes;
import java.io.EOFException;
import java.io.IOException;
import java.util.Random;
......@@ -123,53 +121,6 @@ public final class DefaultOggSeekerTest {
}
@Test
public void skipToNextPage_success() throws Exception {
FakeExtractorInput extractorInput =
createInput(
Bytes.concat(
TestUtil.buildTestData(4000, random),
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)),
/* simulateUnknownLength= */ false);
skipToNextPage(extractorInput);
assertThat(extractorInput.getPosition()).isEqualTo(4000);
}
@Test
public void skipToNextPage_withOverlappingInput_success() throws Exception {
FakeExtractorInput extractorInput =
createInput(
Bytes.concat(
TestUtil.buildTestData(2046, random),
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)),
/* simulateUnknownLength= */ false);
skipToNextPage(extractorInput);
assertThat(extractorInput.getPosition()).isEqualTo(2046);
}
@Test
public void skipToNextPage_withInputShorterThanPeekLength_success() throws Exception {
FakeExtractorInput extractorInput =
createInput(
Bytes.concat(new byte[] {'x', 'O', 'g', 'g', 'S'}), /* simulateUnknownLength= */ false);
skipToNextPage(extractorInput);
assertThat(extractorInput.getPosition()).isEqualTo(1);
}
@Test
public void skipToNextPage_withoutMatch_throwsException() throws Exception {
FakeExtractorInput extractorInput =
createInput(new byte[] {'g', 'g', 'S', 'O', 'g', 'g'}, /* simulateUnknownLength= */ false);
try {
skipToNextPage(extractorInput);
fail();
} catch (EOFException e) {
// expected
}
}
@Test
public void readGranuleOfLastPage() throws IOException {
// This test stream has three headers with granule numbers 20000, 40000 and 60000.
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/three_headers");
......@@ -200,25 +151,6 @@ public final class DefaultOggSeekerTest {
}
}
private static void skipToNextPage(ExtractorInput extractorInput) throws IOException {
DefaultOggSeeker oggSeeker =
new DefaultOggSeeker(
/* streamReader= */ new FlacReader(),
/* payloadStartPosition= */ 0,
/* payloadEndPosition= */ extractorInput.getLength(),
/* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false);
while (true) {
try {
oggSeeker.skipToNextPage(extractorInput);
break;
} catch (FakeExtractorInput.SimulatedIOException e) {
/* ignored */
}
}
}
private static void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected)
throws IOException {
DefaultOggSeeker oggSeeker =
......
......@@ -59,4 +59,11 @@ public final class OggExtractorParameterizedTest {
public void vorbis() throws Exception {
ExtractorAsserts.assertBehavior(OggExtractor::new, "ogg/bear_vorbis.ogg", simulationConfig);
}
// Ensure the extractor can handle non-contiguous pages by using a file with 10 bytes of garbage
// data before the start of the second page.
@Test
public void vorbisWithGapBeforeSecondPage() throws Exception {
ExtractorAsserts.assertBehavior(OggExtractor::new, "ogg/bear_vorbis_gap.ogg", simulationConfig);
}
}
......@@ -23,6 +23,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorInput.SimulatedIOException;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.common.primitives.Bytes;
import java.io.IOException;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -30,14 +33,69 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public final class OggPageHeaderTest {
private final Random random;
public OggPageHeaderTest() {
this.random = new Random(/* seed= */ 0);
}
@Test
public void skipToNextPage_success() throws Exception {
FakeExtractorInput input =
createInput(
Bytes.concat(
TestUtil.buildTestData(20, random),
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(20, random)),
/* simulateUnknownLength= */ false);
OggPageHeader oggHeader = new OggPageHeader();
boolean result = retrySimulatedIOException(() -> oggHeader.skipToNextPage(input));
assertThat(result).isTrue();
assertThat(input.getPosition()).isEqualTo(20);
}
@Test
public void skipToNextPage_noPage_returnsFalse() throws Exception {
FakeExtractorInput input =
createInput(
Bytes.concat(TestUtil.buildTestData(20, random)), /* simulateUnknownLength= */ false);
OggPageHeader oggHeader = new OggPageHeader();
boolean result = retrySimulatedIOException(() -> oggHeader.skipToNextPage(input));
assertThat(result).isFalse();
assertThat(input.getPosition()).isEqualTo(20);
}
@Test
public void skipToNextPage_respectsLimit() throws Exception {
FakeExtractorInput input =
createInput(
Bytes.concat(
TestUtil.buildTestData(20, random),
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(20, random)),
/* simulateUnknownLength= */ false);
OggPageHeader oggHeader = new OggPageHeader();
boolean result = retrySimulatedIOException(() -> oggHeader.skipToNextPage(input, 10));
assertThat(result).isFalse();
assertThat(input.getPosition()).isEqualTo(10);
}
@Test
public void populatePageHeader_success() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/page_header");
FakeExtractorInput input = createInput(data, /* simulateUnknownLength= */ true);
OggPageHeader header = new OggPageHeader();
populatePageHeader(input, header, /* quiet= */ false);
boolean result = retrySimulatedIOException(() -> header.populate(input, /* quiet= */ false));
assertThat(result).isTrue();
assertThat(header.type).isEqualTo(0x01);
assertThat(header.headerSize).isEqualTo(27 + 2);
assertThat(header.bodySize).isEqualTo(4);
......@@ -55,7 +113,10 @@ public final class OggPageHeaderTest {
FakeExtractorInput input =
createInput(TestUtil.createByteArray(2, 2), /* simulateUnknownLength= */ false);
OggPageHeader header = new OggPageHeader();
assertThat(populatePageHeader(input, header, /* quiet= */ true)).isFalse();
boolean result = retrySimulatedIOException(() -> header.populate(input, /* quiet= */ true));
assertThat(result).isFalse();
}
@Test
......@@ -65,7 +126,10 @@ public final class OggPageHeaderTest {
data[0] = 'o';
FakeExtractorInput input = createInput(data, /* simulateUnknownLength= */ false);
OggPageHeader header = new OggPageHeader();
assertThat(populatePageHeader(input, header, /* quiet= */ true)).isFalse();
boolean result = retrySimulatedIOException(() -> header.populate(input, /* quiet= */ true));
assertThat(result).isFalse();
}
@Test
......@@ -75,18 +139,10 @@ public final class OggPageHeaderTest {
data[4] = 0x01;
FakeExtractorInput input = createInput(data, /* simulateUnknownLength= */ false);
OggPageHeader header = new OggPageHeader();
assertThat(populatePageHeader(input, header, /* quiet= */ true)).isFalse();
}
private static boolean populatePageHeader(
FakeExtractorInput input, OggPageHeader header, boolean quiet) throws Exception {
while (true) {
try {
return header.populate(input, quiet);
} catch (SimulatedIOException e) {
// ignored
}
}
boolean result = retrySimulatedIOException(() -> header.populate(input, /* quiet= */ true));
assertThat(result).isFalse();
}
private static FakeExtractorInput createInput(byte[] data, boolean simulateUnknownLength) {
......@@ -97,5 +153,20 @@ public final class OggPageHeaderTest {
.setSimulatePartialReads(true)
.build();
}
private static <T> T retrySimulatedIOException(ThrowingSupplier<T, IOException> supplier)
throws IOException {
while (true) {
try {
return supplier.get();
} catch (SimulatedIOException e) {
// ignored
}
}
}
private interface ThrowingSupplier<S, E extends Throwable> {
S get() throws E;
}
}
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=4005]]
getPosition(1) = [[timeUs=1, position=4005]]
getPosition(1370500) = [[timeUs=1370500, position=4005]]
getPosition(2741000) = [[timeUs=2741000, position=4005]]
numberOfTracks = 1
track 0:
total output bytes = 17598
sample count = 109
format 0:
averageBitrate = 112000
sampleMimeType = audio/vorbis
channelCount = 2
sampleRate = 48000
initializationData:
data = length 30, hash 9A8FF207
data = length 3832, hash 8A406249
sample 0:
time = 896000
flags = 1
data = length 195, hash 2722159A
sample 1:
time = 917333
flags = 1
data = length 199, hash 10CEE97A
sample 2:
time = 938666
flags = 1
data = length 191, hash 2CF9FB3F
sample 3:
time = 960000
flags = 1
data = length 197, hash A725DA0
sample 4:
time = 981333
flags = 1
data = length 211, hash D4E5DB9E
sample 5:
time = 1002666
flags = 1
data = length 189, hash 1A90F496
sample 6:
time = 1024000
flags = 1
data = length 187, hash 44DB2689
sample 7:
time = 1045333
flags = 1
data = length 197, hash 6D3E5117
sample 8:
time = 1066666
flags = 1
data = length 208, hash 5B57B288
sample 9:
time = 1088000
flags = 1
data = length 198, hash D5FC05
sample 10:
time = 1109333
flags = 1
data = length 192, hash 350BBA45
sample 11:
time = 1130666
flags = 1
data = length 195, hash 5F96F2A8
sample 12:
time = 1152000
flags = 1
data = length 202, hash 61D7CC33
sample 13:
time = 1173333
flags = 1
data = length 202, hash 49D335F2
sample 14:
time = 1194666
flags = 1
data = length 192, hash 2FE9CB1A
sample 15:
time = 1216000
flags = 1
data = length 201, hash BF0763B2
sample 16:
time = 1237333
flags = 1
data = length 184, hash AD047421
sample 17:
time = 1258666
flags = 1
data = length 196, hash F9088F14
sample 18:
time = 1280000
flags = 1
data = length 190, hash AC6D38FD
sample 19:
time = 1301333
flags = 1
data = length 195, hash 8D1A66D2
sample 20:
time = 1322666
flags = 1
data = length 197, hash B46BFB6B
sample 21:
time = 1344000
flags = 1
data = length 195, hash D9761F23
sample 22:
time = 1365333
flags = 1
data = length 204, hash 3391B617
sample 23:
time = 1386666
flags = 1
data = length 42, hash 33A1FB52
sample 24:
time = 1398666
flags = 1
data = length 44, hash 408B146E
sample 25:
time = 1401333
flags = 1
data = length 44, hash 171C7E0D
sample 26:
time = 1404000
flags = 1
data = length 54, hash 6307E16C
sample 27:
time = 1406666
flags = 1
data = length 53, hash 4A319572
sample 28:
time = 1409333
flags = 1
data = length 215, hash BA9C445C
sample 29:
time = 1421333
flags = 1
data = length 201, hash 3120D234
sample 30:
time = 1442666
flags = 1
data = length 187, hash DB44993C
sample 31:
time = 1464000
flags = 1
data = length 196, hash CF2002D7
sample 32:
time = 1485333
flags = 1
data = length 185, hash E03B5D7
sample 33:
time = 1506666
flags = 1
data = length 187, hash DA399A2C
sample 34:
time = 1528000
flags = 1
data = length 191, hash 292AA0DB
sample 35:
time = 1549333
flags = 1
data = length 201, hash 221910E0
sample 36:
time = 1570666
flags = 1
data = length 194, hash F4ED7821
sample 37:
time = 1592000
flags = 1
data = length 43, hash FDDA515E
sample 38:
time = 1604000
flags = 1
data = length 42, hash F3571C0A
sample 39:
time = 1606666
flags = 1
data = length 38, hash 39F910B3
sample 40:
time = 1609333
flags = 1
data = length 41, hash 2D189531
sample 41:
time = 1612000
flags = 1
data = length 43, hash 1F7574DB
sample 42:
time = 1614666
flags = 1
data = length 43, hash 644D15E5
sample 43:
time = 1617333
flags = 1
data = length 49, hash E8A0878
sample 44:
time = 1620000
flags = 1
data = length 55, hash DFF2046D
sample 45:
time = 1622666
flags = 1
data = length 49, hash 9FB8A23
sample 46:
time = 1625333
flags = 1
data = length 41, hash E3E15E3B
sample 47:
time = 1628000
flags = 1
data = length 42, hash E5D17A32
sample 48:
time = 1630666
flags = 1
data = length 42, hash F308B653
sample 49:
time = 1633333
flags = 1
data = length 55, hash BB750D76
sample 50:
time = 1636000
flags = 1
data = length 51, hash 96772ABF
sample 51:
time = 1638666
flags = 1
data = length 197, hash E4524346
sample 52:
time = 1650666
flags = 1
data = length 188, hash AC3E1BB5
sample 53:
time = 1672000
flags = 1
data = length 195, hash F56DB8A5
sample 54:
time = 1693333
flags = 1
data = length 198, hash C8970FF7
sample 55:
time = 1714666
flags = 1
data = length 202, hash AF425C68
sample 56:
time = 1736000
flags = 1
data = length 196, hash 4215D839
sample 57:
time = 1757333
flags = 1
data = length 204, hash DB9BE8E3
sample 58:
time = 1778666
flags = 1
data = length 206, hash E5B20AB8
sample 59:
time = 1800000
flags = 1
data = length 209, hash D7F47B95
sample 60:
time = 1821333
flags = 1
data = length 193, hash FB54FB05
sample 61:
time = 1842666
flags = 1
data = length 199, hash D99C3106
sample 62:
time = 1864000
flags = 1
data = length 206, hash 253885B9
sample 63:
time = 1885333
flags = 1
data = length 191, hash FBDD8162
sample 64:
time = 1906666
flags = 1
data = length 183, hash 7290332F
sample 65:
time = 1928000
flags = 1
data = length 189, hash 1A9DC3DE
sample 66:
time = 1949333
flags = 1
data = length 201, hash 5D936764
sample 67:
time = 1970666
flags = 1
data = length 193, hash 6B03E75E
sample 68:
time = 1992000
flags = 1
data = length 199, hash 8A21BA83
sample 69:
time = 2013333
flags = 1
data = length 41, hash E6362210
sample 70:
time = 2025333
flags = 1
data = length 43, hash 36A57B44
sample 71:
time = 2028000
flags = 1
data = length 43, hash E51797D5
sample 72:
time = 2030666
flags = 1
data = length 43, hash 1F336C72
sample 73:
time = 2033333
flags = 1
data = length 42, hash 201AD367
sample 74:
time = 2036000
flags = 1
data = length 50, hash 606CCD6
sample 75:
time = 2038666
flags = 1
data = length 56, hash B15EBD7A
sample 76:
time = 2041333
flags = 1
data = length 212, hash 273B8D22
sample 77:
time = 2053333
flags = 1
data = length 194, hash 44F9CE1
sample 78:
time = 2074666
flags = 1
data = length 195, hash EDF9EBA1
sample 79:
time = 2096000
flags = 1
data = length 194, hash CE9F2D26
sample 80:
time = 2117333
flags = 1
data = length 192, hash 204F8A23
sample 81:
time = 2138666
flags = 1
data = length 206, hash DFA57E67
sample 82:
time = 2160000
flags = 1
data = length 196, hash 3CF084AB
sample 83:
time = 2181333
flags = 1
data = length 202, hash 2AF75C08
sample 84:
time = 2202666
flags = 1
data = length 203, hash 748EAF7
sample 85:
time = 2224000
flags = 1
data = length 205, hash ED82379D
sample 86:
time = 2245333
flags = 1
data = length 193, hash 61F26F22
sample 87:
time = 2266666
flags = 1
data = length 189, hash 85EF1D20
sample 88:
time = 2288000
flags = 1
data = length 187, hash 25E41FBF
sample 89:
time = 2309333
flags = 1
data = length 199, hash F365808
sample 90:
time = 2330666
flags = 1
data = length 197, hash 94205329
sample 91:
time = 2352000
flags = 1
data = length 201, hash FA2B2055
sample 92:
time = 2373333
flags = 1
data = length 194, hash AF95381F
sample 93:
time = 2394666
flags = 1
data = length 201, hash 923D3534
sample 94:
time = 2416000
flags = 1
data = length 198, hash 35F84C2E
sample 95:
time = 2437333
flags = 1
data = length 204, hash 6642CA40
sample 96:
time = 2458666
flags = 1
data = length 183, hash 3E2DC6BE
sample 97:
time = 2480000
flags = 1
data = length 197, hash B1E458CE
sample 98:
time = 2501333
flags = 1
data = length 193, hash E9218C84
sample 99:
time = 2522666
flags = 1
data = length 192, hash FEF08D4B
sample 100:
time = 2544000
flags = 1
data = length 201, hash FC411147
sample 101:
time = 2565333
flags = 1
data = length 218, hash 86893464
sample 102:
time = 2586666
flags = 1
data = length 226, hash 31C5320
sample 103:
time = 2608000
flags = 1
data = length 233, hash 9432BEE5
sample 104:
time = 2629333
flags = 1
data = length 213, hash B3FCC53E
sample 105:
time = 2650666
flags = 1
data = length 204, hash D70DD5A2
sample 106:
time = 2672000
flags = 1
data = length 212, hash A4EF1B69
sample 107:
time = 2693333
flags = 1
data = length 203, hash 8B0748B5
sample 108:
time = 2714666
flags = 1
data = length 149, hash E455335B
tracksEnded = true
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=4005]]
getPosition(1) = [[timeUs=1, position=4005]]
getPosition(1370500) = [[timeUs=1370500, position=4005]]
getPosition(2741000) = [[timeUs=2741000, position=4005]]
numberOfTracks = 1
track 0:
total output bytes = 8658
sample count = 49
format 0:
averageBitrate = 112000
sampleMimeType = audio/vorbis
channelCount = 2
sampleRate = 48000
initializationData:
data = length 30, hash 9A8FF207
data = length 3832, hash 8A406249
sample 0:
time = 1821333
flags = 1
data = length 193, hash FB54FB05
sample 1:
time = 1842666
flags = 1
data = length 199, hash D99C3106
sample 2:
time = 1864000
flags = 1
data = length 206, hash 253885B9
sample 3:
time = 1885333
flags = 1
data = length 191, hash FBDD8162
sample 4:
time = 1906666
flags = 1
data = length 183, hash 7290332F
sample 5:
time = 1928000
flags = 1
data = length 189, hash 1A9DC3DE
sample 6:
time = 1949333
flags = 1
data = length 201, hash 5D936764
sample 7:
time = 1970666
flags = 1
data = length 193, hash 6B03E75E
sample 8:
time = 1992000
flags = 1
data = length 199, hash 8A21BA83
sample 9:
time = 2013333
flags = 1
data = length 41, hash E6362210
sample 10:
time = 2025333
flags = 1
data = length 43, hash 36A57B44
sample 11:
time = 2028000
flags = 1
data = length 43, hash E51797D5
sample 12:
time = 2030666
flags = 1
data = length 43, hash 1F336C72
sample 13:
time = 2033333
flags = 1
data = length 42, hash 201AD367
sample 14:
time = 2036000
flags = 1
data = length 50, hash 606CCD6
sample 15:
time = 2038666
flags = 1
data = length 56, hash B15EBD7A
sample 16:
time = 2041333
flags = 1
data = length 212, hash 273B8D22
sample 17:
time = 2053333
flags = 1
data = length 194, hash 44F9CE1
sample 18:
time = 2074666
flags = 1
data = length 195, hash EDF9EBA1
sample 19:
time = 2096000
flags = 1
data = length 194, hash CE9F2D26
sample 20:
time = 2117333
flags = 1
data = length 192, hash 204F8A23
sample 21:
time = 2138666
flags = 1
data = length 206, hash DFA57E67
sample 22:
time = 2160000
flags = 1
data = length 196, hash 3CF084AB
sample 23:
time = 2181333
flags = 1
data = length 202, hash 2AF75C08
sample 24:
time = 2202666
flags = 1
data = length 203, hash 748EAF7
sample 25:
time = 2224000
flags = 1
data = length 205, hash ED82379D
sample 26:
time = 2245333
flags = 1
data = length 193, hash 61F26F22
sample 27:
time = 2266666
flags = 1
data = length 189, hash 85EF1D20
sample 28:
time = 2288000
flags = 1
data = length 187, hash 25E41FBF
sample 29:
time = 2309333
flags = 1
data = length 199, hash F365808
sample 30:
time = 2330666
flags = 1
data = length 197, hash 94205329
sample 31:
time = 2352000
flags = 1
data = length 201, hash FA2B2055
sample 32:
time = 2373333
flags = 1
data = length 194, hash AF95381F
sample 33:
time = 2394666
flags = 1
data = length 201, hash 923D3534
sample 34:
time = 2416000
flags = 1
data = length 198, hash 35F84C2E
sample 35:
time = 2437333
flags = 1
data = length 204, hash 6642CA40
sample 36:
time = 2458666
flags = 1
data = length 183, hash 3E2DC6BE
sample 37:
time = 2480000
flags = 1
data = length 197, hash B1E458CE
sample 38:
time = 2501333
flags = 1
data = length 193, hash E9218C84
sample 39:
time = 2522666
flags = 1
data = length 192, hash FEF08D4B
sample 40:
time = 2544000
flags = 1
data = length 201, hash FC411147
sample 41:
time = 2565333
flags = 1
data = length 218, hash 86893464
sample 42:
time = 2586666
flags = 1
data = length 226, hash 31C5320
sample 43:
time = 2608000
flags = 1
data = length 233, hash 9432BEE5
sample 44:
time = 2629333
flags = 1
data = length 213, hash B3FCC53E
sample 45:
time = 2650666
flags = 1
data = length 204, hash D70DD5A2
sample 46:
time = 2672000
flags = 1
data = length 212, hash A4EF1B69
sample 47:
time = 2693333
flags = 1
data = length 203, hash 8B0748B5
sample 48:
time = 2714666
flags = 1
data = length 149, hash E455335B
tracksEnded = true
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=4005]]
getPosition(1) = [[timeUs=1, position=4005]]
getPosition(1370500) = [[timeUs=1370500, position=4005]]
getPosition(2741000) = [[timeUs=2741000, position=4005]]
numberOfTracks = 1
track 0:
total output bytes = 0
sample count = 0
format 0:
averageBitrate = 112000
sampleMimeType = audio/vorbis
channelCount = 2
sampleRate = 48000
initializationData:
data = length 30, hash 9A8FF207
data = length 3832, hash 8A406249
tracksEnded = true
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment