Make interface implementation consistent among ExtractorOutputs

The method track(int id) currently has different behaviours across
implementations. This CL maps ids to track outputs, which means
that successive calls with the same id will return the same
TrackOutput instance. Also fixes TsExtractor inconsistent behavior
after a seek.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=136026721
This commit is contained in:
aquilescanta 2016-10-13 04:54:13 -07:00 committed by Oliver Woodman
parent ff712aead5
commit e685edc179
8 changed files with 82 additions and 62 deletions

View File

@ -68,7 +68,7 @@ public class AdtsReaderTest extends TestCase {
@Override
protected void setUp() throws Exception {
FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(true);
FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
adtsOutput = fakeExtractorOutput.track(0);
id3Output = fakeExtractorOutput.track(1);
adtsReader = new AdtsReader(true);

View File

@ -56,7 +56,7 @@ public interface Extractor {
boolean sniff(ExtractorInput input) throws IOException, InterruptedException;
/**
* Initializes the extractor with an {@link ExtractorOutput}.
* Initializes the extractor with an {@link ExtractorOutput}. Called at most once.
*
* @param output An {@link ExtractorOutput} to receive extracted data.
*/

View File

@ -21,18 +21,19 @@ package com.google.android.exoplayer2.extractor;
public interface ExtractorOutput {
/**
* Called when the {@link Extractor} identifies the existence of a track in the stream.
* Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
* <p>
* Returns a {@link TrackOutput} that will receive track level data belonging to the track.
* The same {@link TrackOutput} is returned if multiple calls are made with the same
* {@code trackId}.
*
* @param trackId A unique track identifier.
* @return The {@link TrackOutput} that should receive track level data belonging to the track.
* @param trackId A track identifier.
* @return The {@link TrackOutput} for the given track identifier.
*/
TrackOutput track(int trackId);
/**
* Called when all tracks have been identified, meaning that {@link #track(int)} will not be
* called again.
* Called when all tracks have been identified, meaning no new {@code trackId} values will be
* passed to {@link #track(int)}.
*/
void endTracks();

View File

@ -54,7 +54,6 @@ public final class TsExtractor implements Extractor {
};
public static final int TS_STREAM_TYPE_MPA = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
public static final int TS_STREAM_TYPE_AAC = 0x0F;
@ -92,6 +91,7 @@ public final class TsExtractor implements Extractor {
// Accessed only by the loading thread.
private ExtractorOutput output;
private boolean tracksEnded;
private ElementaryStreamReader id3Reader;
public TsExtractor() {
@ -120,8 +120,8 @@ public final class TsExtractor implements Extractor {
tsScratch = new ParsableBitArray(new byte[3]);
trackIds = new SparseBooleanArray();
tsPayloadReaders = new SparseArray<>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
continuityCounters = new SparseIntArray();
resetPayloadReaders();
}
// Extractor implementation.
@ -153,11 +153,10 @@ public final class TsExtractor implements Extractor {
@Override
public void seek(long position) {
timestampAdjuster.reset();
for (int i = 0; i < tsPayloadReaders.size(); i++) {
tsPayloadReaders.valueAt(i).seek();
}
tsPacketBuffer.reset();
continuityCounters.clear();
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
resetPayloadReaders();
}
@Override
@ -252,6 +251,13 @@ public final class TsExtractor implements Extractor {
// Internals.
private void resetPayloadReaders() {
trackIds.clear();
tsPayloadReaders.clear();
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
id3Reader = null;
}
/**
* Parses TS packet payload data.
*/
@ -345,7 +351,7 @@ public final class TsExtractor implements Extractor {
patScratch.skipBits(13); // network_PID (13)
} else {
int pid = patScratch.readBits(13);
tsPayloadReaders.put(pid, new PmtReader());
tsPayloadReaders.put(pid, new PmtReader(pid));
}
}
}
@ -365,14 +371,16 @@ public final class TsExtractor implements Extractor {
private final ParsableBitArray pmtScratch;
private final ParsableByteArray sectionData;
private final int pid;
private int sectionLength;
private int sectionBytesRead;
private int crc;
public PmtReader() {
public PmtReader(int pid) {
pmtScratch = new ParsableBitArray(new byte[5]);
sectionData = new ParsableByteArray();
this.pid = pid;
}
@Override
@ -466,8 +474,16 @@ public final class TsExtractor implements Extractor {
tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster));
}
}
output.endTracks();
if (mapByType) {
if (!tracksEnded) {
output.endTracks();
}
} else {
tsPayloadReaders.remove(TS_PAT_PID);
tsPayloadReaders.remove(pid);
output.endTracks();
}
tracksEnded = true;
}
/**

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source;
import android.net.Uri;
import android.os.Handler;
import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
@ -41,7 +42,6 @@ import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;
/**
* A {@link MediaPeriod} that extracts data using an {@link Extractor}.
@ -68,6 +68,7 @@ import java.util.Arrays;
private final Runnable maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private Callback callback;
private SeekMap seekMap;
@ -77,7 +78,6 @@ import java.util.Arrays;
private boolean seenFirstTrackSelection;
private boolean notifyReset;
private int enabledTrackCount;
private DefaultTrackOutput[] sampleQueues;
private TrackGroupArray tracks;
private long durationUs;
private boolean[] trackEnabledStates;
@ -131,7 +131,7 @@ import java.util.Arrays;
handler = new Handler();
pendingResetPositionUs = C.TIME_UNSET;
sampleQueues = new DefaultTrackOutput[0];
sampleQueues = new SparseArray<>();
length = C.LENGTH_UNSET;
}
@ -141,8 +141,9 @@ import java.util.Arrays;
@Override
public void run() {
extractorHolder.release();
for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.disable();
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).disable();
}
}
});
@ -178,7 +179,7 @@ import java.util.Arrays;
Assertions.checkState(trackEnabledStates[track]);
enabledTrackCount--;
trackEnabledStates[track] = false;
sampleQueues[track].disable();
sampleQueues.valueAt(track).disable();
streams[i] = null;
}
}
@ -201,9 +202,10 @@ import java.util.Arrays;
if (!seenFirstTrackSelection) {
// At the time of the first track selection all queues will be enabled, so we need to disable
// any that are no longer required.
for (int i = 0; i < sampleQueues.length; i++) {
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
if (!trackEnabledStates[i]) {
sampleQueues[i].disable();
sampleQueues.valueAt(i).disable();
}
}
}
@ -270,11 +272,12 @@ import java.util.Arrays;
// Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0;
lastSeekPositionUs = positionUs;
int trackCount = sampleQueues.size();
// If we're not pending a reset, see if we can seek within the sample queues.
boolean seekInsideBuffer = !isPendingReset();
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
for (int i = 0; seekInsideBuffer && i < trackCount; i++) {
if (trackEnabledStates[i]) {
seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs);
seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs);
}
}
// If we failed to seek within the sample queues, we need to restart.
@ -284,8 +287,8 @@ import java.util.Arrays;
if (loader.isLoading()) {
loader.cancelLoading();
} else {
for (int i = 0; i < sampleQueues.length; i++) {
sampleQueues[i].reset(trackEnabledStates[i]);
for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).reset(trackEnabledStates[i]);
}
}
}
@ -296,7 +299,7 @@ import java.util.Arrays;
// SampleStream methods.
/* package */ boolean isReady(int track) {
return loadingFinished || (!isPendingReset() && !sampleQueues[track].isEmpty());
return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(track).isEmpty());
}
/* package */ void maybeThrowError() throws IOException {
@ -308,7 +311,8 @@ import java.util.Arrays;
return C.RESULT_NOTHING_READ;
}
return sampleQueues[track].readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
return sampleQueues.valueAt(track).readData(formatHolder, buffer, loadingFinished,
lastSeekPositionUs);
}
// Loader.Callback implementation.
@ -332,8 +336,9 @@ import java.util.Arrays;
long loadDurationMs, boolean released) {
copyLengthFromLoader(loadable);
if (!released && enabledTrackCount > 0) {
for (int i = 0; i < sampleQueues.length; i++) {
sampleQueues[i].reset(trackEnabledStates[i]);
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).reset(trackEnabledStates[i]);
}
callback.onContinueLoadingRequested(this);
}
@ -358,11 +363,13 @@ import java.util.Arrays;
@Override
public TrackOutput track(int id) {
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
sampleQueue.setUpstreamFormatChangeListener(this);
sampleQueues[sampleQueues.length - 1] = sampleQueue;
return sampleQueue;
DefaultTrackOutput trackOutput = sampleQueues.get(id);
if (trackOutput == null) {
trackOutput = new DefaultTrackOutput(allocator);
trackOutput.setUpstreamFormatChangeListener(this);
sampleQueues.put(id, trackOutput);
}
return trackOutput;
}
@Override
@ -390,18 +397,18 @@ import java.util.Arrays;
if (released || prepared || seekMap == null || !tracksBuilt) {
return;
}
for (DefaultTrackOutput sampleQueue : sampleQueues) {
if (sampleQueue.getUpstreamFormat() == null) {
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
return;
}
}
loadCondition.close();
int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount];
durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) {
trackArray[i] = new TrackGroup(sampleQueues[i].getUpstreamFormat());
trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getUpstreamFormat());
}
tracks = new TrackGroupArray(trackArray);
prepared = true;
@ -455,8 +462,9 @@ import java.util.Arrays;
// a new load.
lastSeekPositionUs = 0;
notifyReset = prepared;
for (int i = 0; i < sampleQueues.length; i++) {
sampleQueues[i].reset(!prepared || trackEnabledStates[i]);
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]);
}
loadable.setLoadPosition(0);
}
@ -464,17 +472,19 @@ import java.util.Arrays;
private int getExtractedSamplesCount() {
int extractedSamplesCount = 0;
for (DefaultTrackOutput sampleQueue : sampleQueues) {
extractedSamplesCount += sampleQueue.getWriteIndex();
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
extractedSamplesCount += sampleQueues.valueAt(i).getWriteIndex();
}
return extractedSamplesCount;
}
private long getLargestQueuedTimestampUs() {
long largestQueuedTimestampUs = Long.MIN_VALUE;
for (DefaultTrackOutput sampleQueue : sampleQueues) {
int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
sampleQueue.getLargestQueuedTimestampUs());
sampleQueues.valueAt(i).getLargestQueuedTimestampUs());
}
return largestQueuedTimestampUs;
}
@ -523,7 +533,7 @@ import java.util.Arrays;
@Override
public void skipToKeyframeBefore(long timeUs) {
sampleQueues[track].skipToKeyframeBefore(timeUs);
sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs);
}
}

View File

@ -59,6 +59,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
// Accessed only on the loader thread.
private boolean seenTrack;
private int seenTrackId;
/**
* @param extractor The extractor to wrap.
@ -116,8 +117,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override
public TrackOutput track(int id) {
Assertions.checkState(!seenTrack);
Assertions.checkState(!seenTrack || seenTrackId == id);
seenTrack = true;
seenTrackId = id;
return this;
}

View File

@ -23,7 +23,6 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import junit.framework.Assert;
import junit.framework.TestCase;
/**
* A fake {@link ExtractorOutput}.
@ -37,8 +36,6 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
*/
private static final boolean WRITE_DUMP = false;
private final boolean allowDuplicateTrackIds;
public final SparseArray<FakeTrackOutput> trackOutputs;
public int numberOfTracks;
@ -46,11 +43,6 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
public SeekMap seekMap;
public FakeExtractorOutput() {
this(false);
}
public FakeExtractorOutput(boolean allowDuplicateTrackIds) {
this.allowDuplicateTrackIds = allowDuplicateTrackIds;
trackOutputs = new SparseArray<>();
}
@ -58,11 +50,10 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
public FakeTrackOutput track(int trackId) {
FakeTrackOutput output = trackOutputs.get(trackId);
if (output == null) {
Assert.assertFalse(tracksEnded);
numberOfTracks++;
output = new FakeTrackOutput();
trackOutputs.put(trackId, output);
} else {
TestCase.assertTrue("Duplicate track id: " + trackId, allowDuplicateTrackIds);
}
return output;
}

View File

@ -267,8 +267,8 @@ public class TestUtil {
*/
public static FakeExtractorOutput assertOutput(Extractor extractor, String sampleFile,
byte[] fileData, Instrumentation instrumentation, boolean simulateIOErrors,
boolean simulateUnknownLength,
boolean simulatePartialReads) throws IOException, InterruptedException {
boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException,
InterruptedException {
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData)
.setSimulateIOErrors(simulateIOErrors)
.setSimulateUnknownLength(simulateUnknownLength)