Move track sample index updates out of getPosition(long).
This requires knowing the seek time in Extractor.seek, so that it's possible to pick the latest synchronization sample at/before the seek time for each track (rather than the earliest synchronization sample after the seek position). Also remove the STATE_AFTER_SEEK state which should no longer be needed. Issue: #2167 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=141432598
This commit is contained in:
parent
2fce364936
commit
f45751872d
@ -155,7 +155,7 @@ public final class FlacExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
if (position == 0) {
|
||||
metadataParsed = false;
|
||||
}
|
||||
|
@ -93,9 +93,10 @@ public interface Extractor {
|
||||
* position} in the stream. Valid random access positions are the start of the stream and
|
||||
* positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}.
|
||||
*
|
||||
* @param position The seek position.
|
||||
* @param position The byte offset in the stream from which data will be provided.
|
||||
* @param timeUs The seek time in microseconds.
|
||||
*/
|
||||
void seek(long position);
|
||||
void seek(long position, long timeUs);
|
||||
|
||||
/**
|
||||
* Releases all kept resources.
|
||||
|
@ -126,7 +126,7 @@ public final class FlvExtractor implements Extractor, SeekMap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
parserState = STATE_READING_FLV_HEADER;
|
||||
bytesToNextTagHeader = 0;
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
clusterTimecodeUs = C.TIME_UNSET;
|
||||
blockState = BLOCK_STATE_START;
|
||||
reader.reset();
|
||||
|
@ -123,7 +123,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
synchronizedHeaderData = 0;
|
||||
basisTimeUs = C.TIME_UNSET;
|
||||
samplesRead = 0;
|
||||
|
@ -194,7 +194,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
int trackCount = trackBundles.size();
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
trackBundles.valueAt(i).reset();
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
@ -33,6 +34,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
@ -54,11 +57,15 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
|
||||
};
|
||||
|
||||
// Parser states.
|
||||
private static final int STATE_AFTER_SEEK = 0;
|
||||
private static final int STATE_READING_ATOM_HEADER = 1;
|
||||
private static final int STATE_READING_ATOM_PAYLOAD = 2;
|
||||
private static final int STATE_READING_SAMPLE = 3;
|
||||
/**
|
||||
* Parser states.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE})
|
||||
private @interface State {}
|
||||
private static final int STATE_READING_ATOM_HEADER = 0;
|
||||
private static final int STATE_READING_ATOM_PAYLOAD = 1;
|
||||
private static final int STATE_READING_SAMPLE = 2;
|
||||
|
||||
// Brand stored in the ftyp atom for QuickTime media.
|
||||
private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt ");
|
||||
@ -76,6 +83,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
private final ParsableByteArray atomHeader;
|
||||
private final Stack<ContainerAtom> containerAtoms;
|
||||
|
||||
@State
|
||||
private int parserState;
|
||||
private int atomType;
|
||||
private long atomSize;
|
||||
@ -96,7 +104,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
containerAtoms = new Stack<>();
|
||||
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
||||
nalLength = new ParsableByteArray(4);
|
||||
enterReadingAtomHeaderState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,12 +117,16 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
containerAtoms.clear();
|
||||
atomHeaderBytesRead = 0;
|
||||
sampleBytesWritten = 0;
|
||||
sampleCurrentNalBytesRemaining = 0;
|
||||
parserState = STATE_AFTER_SEEK;
|
||||
if (position == 0) {
|
||||
enterReadingAtomHeaderState();
|
||||
} else if (tracks != null) {
|
||||
updateSampleIndices(timeUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,13 +139,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
throws IOException, InterruptedException {
|
||||
while (true) {
|
||||
switch (parserState) {
|
||||
case STATE_AFTER_SEEK:
|
||||
if (input.getPosition() == 0) {
|
||||
enterReadingAtomHeaderState();
|
||||
} else {
|
||||
parserState = STATE_READING_SAMPLE;
|
||||
}
|
||||
break;
|
||||
case STATE_READING_ATOM_HEADER:
|
||||
if (!readAtomHeader(input)) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
@ -145,8 +149,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
return RESULT_SEEK;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case STATE_READING_SAMPLE:
|
||||
return readSample(input, seekPosition);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,8 +179,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
// Handle the case where the requested time is before the first synchronization sample.
|
||||
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
|
||||
}
|
||||
track.sampleIndex = sampleIndex;
|
||||
|
||||
long offset = sampleTable.offsets[sampleIndex];
|
||||
if (offset < earliestSamplePosition) {
|
||||
earliestSamplePosition = offset;
|
||||
@ -478,6 +482,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
return earliestSampleTrackIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates every track's sample index to point its latest sync sample before/at {@code timeUs}.
|
||||
*/
|
||||
private void updateSampleIndices(long timeUs) {
|
||||
for (Mp4Track track : tracks) {
|
||||
TrackSampleTable sampleTable = track.sampleTable;
|
||||
int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs);
|
||||
if (sampleIndex == C.INDEX_UNSET) {
|
||||
// Handle the case where the requested time is before the first synchronization sample.
|
||||
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
|
||||
}
|
||||
track.sampleIndex = sampleIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extractor should decode a leaf atom with type {@code atom}.
|
||||
*/
|
||||
|
@ -82,7 +82,7 @@ public class OggExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
streamReader.seek(position);
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ public final class RawCcExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
parserState = STATE_READING_HEADER;
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ public final class Ac3Extractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
startedPacket = false;
|
||||
reader.seek();
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
startedPacket = false;
|
||||
reader.seek();
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ public final class PsExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
timestampAdjuster.reset();
|
||||
for (int i = 0; i < psPayloadReaders.size(); i++) {
|
||||
psPayloadReaders.valueAt(i).seek();
|
||||
|
@ -149,7 +149,7 @@ public final class TsExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
timestampAdjuster.reset();
|
||||
tsPacketBuffer.reset();
|
||||
continuityCounters.clear();
|
||||
|
@ -66,7 +66,7 @@ public final class WavExtractor implements Extractor, SeekMap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
pendingBytes = 0;
|
||||
}
|
||||
|
||||
|
@ -453,7 +453,7 @@ import java.io.IOException;
|
||||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
return;
|
||||
}
|
||||
loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs));
|
||||
loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs), pendingResetPositionUs);
|
||||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
}
|
||||
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
|
||||
@ -486,7 +486,7 @@ import java.io.IOException;
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]);
|
||||
}
|
||||
loadable.setLoadPosition(0);
|
||||
loadable.setLoadPosition(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,6 +578,7 @@ import java.io.IOException;
|
||||
private volatile boolean loadCanceled;
|
||||
|
||||
private boolean pendingExtractorSeek;
|
||||
private long seekTimeUs;
|
||||
private long length;
|
||||
|
||||
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
|
||||
@ -591,8 +592,9 @@ import java.io.IOException;
|
||||
this.length = C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
public void setLoadPosition(long position) {
|
||||
public void setLoadPosition(long position, long timeUs) {
|
||||
positionHolder.position = position;
|
||||
seekTimeUs = timeUs;
|
||||
pendingExtractorSeek = true;
|
||||
}
|
||||
|
||||
@ -620,7 +622,7 @@ import java.io.IOException;
|
||||
input = new DefaultExtractorInput(dataSource, position, length);
|
||||
Extractor extractor = extractorHolder.selectExtractor(input);
|
||||
if (pendingExtractorSeek) {
|
||||
extractor.seek(position);
|
||||
extractor.seek(position, seekTimeUs);
|
||||
pendingExtractorSeek = false;
|
||||
}
|
||||
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
||||
|
@ -92,7 +92,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
||||
extractor.init(this);
|
||||
extractorInitialized = true;
|
||||
} else {
|
||||
extractor.seek(0);
|
||||
extractor.seek(0, 0);
|
||||
if (resendFormatOnInit && sentFormat != null) {
|
||||
trackOutput.format(sentFormat);
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ import java.util.regex.Pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) {
|
||||
public void seek(long position, long timeUs) {
|
||||
// This extractor is only used for the HLS use case, which should not call this method.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
@ -66,28 +66,23 @@ public class TestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static FakeExtractorOutput consumeTestData(Extractor extractor, byte[] data)
|
||||
throws IOException, InterruptedException {
|
||||
return consumeTestData(extractor, newExtractorInput(data));
|
||||
}
|
||||
|
||||
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
return consumeTestData(extractor, input, false);
|
||||
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
|
||||
long timeUs) throws IOException, InterruptedException {
|
||||
return consumeTestData(extractor, input, timeUs, false);
|
||||
}
|
||||
|
||||
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
|
||||
boolean retryFromStartIfLive) throws IOException, InterruptedException {
|
||||
long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException {
|
||||
FakeExtractorOutput output = new FakeExtractorOutput();
|
||||
extractor.init(output);
|
||||
consumeTestData(extractor, input, output, retryFromStartIfLive);
|
||||
consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static void consumeTestData(Extractor extractor, FakeExtractorInput input,
|
||||
private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs,
|
||||
FakeExtractorOutput output, boolean retryFromStartIfLive)
|
||||
throws IOException, InterruptedException {
|
||||
extractor.seek(input.getPosition());
|
||||
extractor.seek(input.getPosition(), timeUs);
|
||||
PositionHolder seekPositionHolder = new PositionHolder();
|
||||
int readResult = Extractor.RESULT_CONTINUE;
|
||||
while (readResult != Extractor.RESULT_END_OF_INPUT) {
|
||||
@ -114,7 +109,7 @@ public class TestUtil {
|
||||
for (int i = 0; i < output.numberOfTracks; i++) {
|
||||
output.trackOutputs.valueAt(i).clear();
|
||||
}
|
||||
extractor.seek(0);
|
||||
extractor.seek(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,7 +272,7 @@ public class TestUtil {
|
||||
|
||||
Assert.assertTrue(sniffTestData(extractor, input));
|
||||
input.resetPeekPosition();
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, true);
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true);
|
||||
|
||||
if (simulateUnknownLength
|
||||
&& assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) {
|
||||
@ -297,7 +292,7 @@ public class TestUtil {
|
||||
extractorOutput.trackOutputs.valueAt(i).clear();
|
||||
}
|
||||
|
||||
consumeTestData(extractor, input, extractorOutput, false);
|
||||
consumeTestData(extractor, input, timeUs, extractorOutput, false);
|
||||
extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user