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 @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(true); FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
adtsOutput = fakeExtractorOutput.track(0); adtsOutput = fakeExtractorOutput.track(0);
id3Output = fakeExtractorOutput.track(1); id3Output = fakeExtractorOutput.track(1);
adtsReader = new AdtsReader(true); adtsReader = new AdtsReader(true);

View File

@ -56,7 +56,7 @@ public interface Extractor {
boolean sniff(ExtractorInput input) throws IOException, InterruptedException; 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. * @param output An {@link ExtractorOutput} to receive extracted data.
*/ */

View File

@ -21,18 +21,19 @@ package com.google.android.exoplayer2.extractor;
public interface ExtractorOutput { 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> * <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. * @param trackId A track identifier.
* @return The {@link TrackOutput} that should receive track level data belonging to the track. * @return The {@link TrackOutput} for the given track identifier.
*/ */
TrackOutput track(int trackId); TrackOutput track(int trackId);
/** /**
* Called when all tracks have been identified, meaning that {@link #track(int)} will not be * Called when all tracks have been identified, meaning no new {@code trackId} values will be
* called again. * passed to {@link #track(int)}.
*/ */
void endTracks(); 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 = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
public static final int TS_STREAM_TYPE_AAC = 0x0F; 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. // Accessed only by the loading thread.
private ExtractorOutput output; private ExtractorOutput output;
private boolean tracksEnded;
private ElementaryStreamReader id3Reader; private ElementaryStreamReader id3Reader;
public TsExtractor() { public TsExtractor() {
@ -120,8 +120,8 @@ public final class TsExtractor implements Extractor {
tsScratch = new ParsableBitArray(new byte[3]); tsScratch = new ParsableBitArray(new byte[3]);
trackIds = new SparseBooleanArray(); trackIds = new SparseBooleanArray();
tsPayloadReaders = new SparseArray<>(); tsPayloadReaders = new SparseArray<>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
continuityCounters = new SparseIntArray(); continuityCounters = new SparseIntArray();
resetPayloadReaders();
} }
// Extractor implementation. // Extractor implementation.
@ -153,11 +153,10 @@ public final class TsExtractor implements Extractor {
@Override @Override
public void seek(long position) { public void seek(long position) {
timestampAdjuster.reset(); timestampAdjuster.reset();
for (int i = 0; i < tsPayloadReaders.size(); i++) {
tsPayloadReaders.valueAt(i).seek();
}
tsPacketBuffer.reset(); tsPacketBuffer.reset();
continuityCounters.clear(); continuityCounters.clear();
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
resetPayloadReaders();
} }
@Override @Override
@ -252,6 +251,13 @@ public final class TsExtractor implements Extractor {
// Internals. // Internals.
private void resetPayloadReaders() {
trackIds.clear();
tsPayloadReaders.clear();
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
id3Reader = null;
}
/** /**
* Parses TS packet payload data. * Parses TS packet payload data.
*/ */
@ -345,7 +351,7 @@ public final class TsExtractor implements Extractor {
patScratch.skipBits(13); // network_PID (13) patScratch.skipBits(13); // network_PID (13)
} else { } else {
int pid = patScratch.readBits(13); 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 ParsableBitArray pmtScratch;
private final ParsableByteArray sectionData; private final ParsableByteArray sectionData;
private final int pid;
private int sectionLength; private int sectionLength;
private int sectionBytesRead; private int sectionBytesRead;
private int crc; private int crc;
public PmtReader() { public PmtReader(int pid) {
pmtScratch = new ParsableBitArray(new byte[5]); pmtScratch = new ParsableBitArray(new byte[5]);
sectionData = new ParsableByteArray(); sectionData = new ParsableByteArray();
this.pid = pid;
} }
@Override @Override
@ -466,8 +474,16 @@ public final class TsExtractor implements Extractor {
tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster)); tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster));
} }
} }
if (mapByType) {
output.endTracks(); 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.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.util.SparseArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; 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 com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
/** /**
* A {@link MediaPeriod} that extracts data using an {@link Extractor}. * 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 maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable; private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler; private final Handler handler;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private Callback callback; private Callback callback;
private SeekMap seekMap; private SeekMap seekMap;
@ -77,7 +78,6 @@ import java.util.Arrays;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
private boolean notifyReset; private boolean notifyReset;
private int enabledTrackCount; private int enabledTrackCount;
private DefaultTrackOutput[] sampleQueues;
private TrackGroupArray tracks; private TrackGroupArray tracks;
private long durationUs; private long durationUs;
private boolean[] trackEnabledStates; private boolean[] trackEnabledStates;
@ -131,7 +131,7 @@ import java.util.Arrays;
handler = new Handler(); handler = new Handler();
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
sampleQueues = new DefaultTrackOutput[0]; sampleQueues = new SparseArray<>();
length = C.LENGTH_UNSET; length = C.LENGTH_UNSET;
} }
@ -141,8 +141,9 @@ import java.util.Arrays;
@Override @Override
public void run() { public void run() {
extractorHolder.release(); extractorHolder.release();
for (DefaultTrackOutput sampleQueue : sampleQueues) { int trackCount = sampleQueues.size();
sampleQueue.disable(); for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).disable();
} }
} }
}); });
@ -178,7 +179,7 @@ import java.util.Arrays;
Assertions.checkState(trackEnabledStates[track]); Assertions.checkState(trackEnabledStates[track]);
enabledTrackCount--; enabledTrackCount--;
trackEnabledStates[track] = false; trackEnabledStates[track] = false;
sampleQueues[track].disable(); sampleQueues.valueAt(track).disable();
streams[i] = null; streams[i] = null;
} }
} }
@ -201,9 +202,10 @@ import java.util.Arrays;
if (!seenFirstTrackSelection) { if (!seenFirstTrackSelection) {
// At the time of the first track selection all queues will be enabled, so we need to disable // At the time of the first track selection all queues will be enabled, so we need to disable
// any that are no longer required. // 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]) { 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. // Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0; positionUs = seekMap.isSeekable() ? positionUs : 0;
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
int trackCount = sampleQueues.size();
// If we're not pending a reset, see if we can seek within the sample queues. // If we're not pending a reset, see if we can seek within the sample queues.
boolean seekInsideBuffer = !isPendingReset(); boolean seekInsideBuffer = !isPendingReset();
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) { for (int i = 0; seekInsideBuffer && i < trackCount; i++) {
if (trackEnabledStates[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. // If we failed to seek within the sample queues, we need to restart.
@ -284,8 +287,8 @@ import java.util.Arrays;
if (loader.isLoading()) { if (loader.isLoading()) {
loader.cancelLoading(); loader.cancelLoading();
} else { } else {
for (int i = 0; i < sampleQueues.length; i++) { for (int i = 0; i < trackCount; i++) {
sampleQueues[i].reset(trackEnabledStates[i]); sampleQueues.valueAt(i).reset(trackEnabledStates[i]);
} }
} }
} }
@ -296,7 +299,7 @@ import java.util.Arrays;
// SampleStream methods. // SampleStream methods.
/* package */ boolean isReady(int track) { /* package */ boolean isReady(int track) {
return loadingFinished || (!isPendingReset() && !sampleQueues[track].isEmpty()); return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(track).isEmpty());
} }
/* package */ void maybeThrowError() throws IOException { /* package */ void maybeThrowError() throws IOException {
@ -308,7 +311,8 @@ import java.util.Arrays;
return C.RESULT_NOTHING_READ; 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. // Loader.Callback implementation.
@ -332,8 +336,9 @@ import java.util.Arrays;
long loadDurationMs, boolean released) { long loadDurationMs, boolean released) {
copyLengthFromLoader(loadable); copyLengthFromLoader(loadable);
if (!released && enabledTrackCount > 0) { if (!released && enabledTrackCount > 0) {
for (int i = 0; i < sampleQueues.length; i++) { int trackCount = sampleQueues.size();
sampleQueues[i].reset(trackEnabledStates[i]); for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).reset(trackEnabledStates[i]);
} }
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
@ -358,11 +363,13 @@ import java.util.Arrays;
@Override @Override
public TrackOutput track(int id) { public TrackOutput track(int id) {
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1); DefaultTrackOutput trackOutput = sampleQueues.get(id);
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator); if (trackOutput == null) {
sampleQueue.setUpstreamFormatChangeListener(this); trackOutput = new DefaultTrackOutput(allocator);
sampleQueues[sampleQueues.length - 1] = sampleQueue; trackOutput.setUpstreamFormatChangeListener(this);
return sampleQueue; sampleQueues.put(id, trackOutput);
}
return trackOutput;
} }
@Override @Override
@ -390,18 +397,18 @@ import java.util.Arrays;
if (released || prepared || seekMap == null || !tracksBuilt) { if (released || prepared || seekMap == null || !tracksBuilt) {
return; return;
} }
for (DefaultTrackOutput sampleQueue : sampleQueues) { int trackCount = sampleQueues.size();
if (sampleQueue.getUpstreamFormat() == null) { for (int i = 0; i < trackCount; i++) {
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
return; return;
} }
} }
loadCondition.close(); loadCondition.close();
int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount]; TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
durationUs = seekMap.getDurationUs(); durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) { 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); tracks = new TrackGroupArray(trackArray);
prepared = true; prepared = true;
@ -455,8 +462,9 @@ import java.util.Arrays;
// a new load. // a new load.
lastSeekPositionUs = 0; lastSeekPositionUs = 0;
notifyReset = prepared; notifyReset = prepared;
for (int i = 0; i < sampleQueues.length; i++) { int trackCount = sampleQueues.size();
sampleQueues[i].reset(!prepared || trackEnabledStates[i]); for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]);
} }
loadable.setLoadPosition(0); loadable.setLoadPosition(0);
} }
@ -464,17 +472,19 @@ import java.util.Arrays;
private int getExtractedSamplesCount() { private int getExtractedSamplesCount() {
int extractedSamplesCount = 0; int extractedSamplesCount = 0;
for (DefaultTrackOutput sampleQueue : sampleQueues) { int trackCount = sampleQueues.size();
extractedSamplesCount += sampleQueue.getWriteIndex(); for (int i = 0; i < trackCount; i++) {
extractedSamplesCount += sampleQueues.valueAt(i).getWriteIndex();
} }
return extractedSamplesCount; return extractedSamplesCount;
} }
private long getLargestQueuedTimestampUs() { private long getLargestQueuedTimestampUs() {
long largestQueuedTimestampUs = Long.MIN_VALUE; long largestQueuedTimestampUs = Long.MIN_VALUE;
for (DefaultTrackOutput sampleQueue : sampleQueues) { int trackCount = sampleQueues.size();
for (int i = 0; i < trackCount; i++) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
sampleQueue.getLargestQueuedTimestampUs()); sampleQueues.valueAt(i).getLargestQueuedTimestampUs());
} }
return largestQueuedTimestampUs; return largestQueuedTimestampUs;
} }
@ -523,7 +533,7 @@ import java.util.Arrays;
@Override @Override
public void skipToKeyframeBefore(long timeUs) { 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. // Accessed only on the loader thread.
private boolean seenTrack; private boolean seenTrack;
private int seenTrackId;
/** /**
* @param extractor The extractor to wrap. * @param extractor The extractor to wrap.
@ -116,8 +117,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override @Override
public TrackOutput track(int id) { public TrackOutput track(int id) {
Assertions.checkState(!seenTrack); Assertions.checkState(!seenTrack || seenTrackId == id);
seenTrack = true; seenTrack = true;
seenTrackId = id;
return this; return this;
} }

View File

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

View File

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