Commit 9a893e30 by olly Committed by Oliver Woodman

Fix thread-safety issue using DefaultTrackOutput.

Reading the format and/or a sample needs to be done as a
single operation. Else you can have a situation where the
queue is initially empty, and this happens:

1) Read downstream format X
2) Read downstream format X (unchanged)
3) Write format Y
4) Write first sample
5) Read first sample

The first sample then appears to be format X rather than Y.

Note that readData in the SampleSource implementations always
looks roughly the same. readReset is identical in all cases.
isReady is identical in all cases now I've fixed them to be
that way. So it should be pretty easy to get DefaultTrackOutput
to implement TrackStream directly, at which point a whole load
of duplication will disappear.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120546377
parent 90b70818
...@@ -67,7 +67,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -67,7 +67,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private boolean trackEnabled; private boolean trackEnabled;
private boolean pendingReset; private boolean pendingReset;
private Format downstreamSampleFormat;
private long downstreamPositionUs; private long downstreamPositionUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
...@@ -196,7 +195,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -196,7 +195,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
loadControl.register(this, bufferSizeContribution); loadControl.register(this, bufferSizeContribution);
} }
downstreamFormat = null; downstreamFormat = null;
downstreamSampleFormat = null; sampleQueue.needDownstreamFormat();
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
pendingReset = false; pendingReset = false;
...@@ -265,7 +264,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -265,7 +264,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
@Override @Override
public boolean isReady() { public boolean isReady() {
return loadingFinished || !sampleQueue.isEmpty(); return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty());
} }
@Override @Override
...@@ -289,44 +288,33 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -289,44 +288,33 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
return NOTHING_READ; return NOTHING_READ;
} }
BaseMediaChunk currentChunk = mediaChunks.getFirst();
while (mediaChunks.size() > 1 while (mediaChunks.size() > 1
&& mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) {
mediaChunks.removeFirst(); mediaChunks.removeFirst();
currentChunk = mediaChunks.getFirst();
} }
BaseMediaChunk currentChunk = mediaChunks.getFirst();
if (downstreamFormat == null || !downstreamFormat.equals(currentChunk.format)) { Format currentFormat = currentChunk.format;
eventDispatcher.downstreamFormatChanged(currentChunk.format, currentChunk.trigger, if (downstreamFormat == null || !downstreamFormat.equals(currentFormat)) {
eventDispatcher.downstreamFormatChanged(currentFormat, currentChunk.trigger,
currentChunk.startTimeUs); currentChunk.startTimeUs);
downstreamFormat = currentChunk.format; downstreamFormat = currentFormat;
}
if (sampleQueue.isEmpty()) {
if (loadingFinished) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return BUFFER_READ;
}
return NOTHING_READ;
}
Format sampleFormat = sampleQueue.getDownstreamFormat();
if (!sampleFormat.equals(downstreamSampleFormat)) {
formatHolder.format = sampleFormat;
formatHolder.drmInitData = currentChunk.getDrmInitData();
downstreamSampleFormat = sampleFormat;
return FORMAT_READ;
} }
if (sampleQueue.readSample(buffer)) { int result = sampleQueue.readData(formatHolder, buffer, loadingFinished);
if (buffer.timeUs < lastSeekPositionUs) { switch (result) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); case FORMAT_READ:
} formatHolder.drmInitData = currentChunk.getDrmInitData();
onSampleRead(currentChunk, buffer); break;
return BUFFER_READ; case BUFFER_READ:
if (!buffer.isEndOfStream()) {
if (buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
onSampleRead(currentChunk, buffer);
}
break;
} }
return result;
return NOTHING_READ;
} }
// Loader.Callback implementation. // Loader.Callback implementation.
......
...@@ -212,7 +212,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -212,7 +212,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private DefaultTrackOutput[] sampleQueues; private DefaultTrackOutput[] sampleQueues;
private TrackGroupArray tracks; private TrackGroupArray tracks;
private long durationUs; private long durationUs;
private boolean[] pendingMediaFormat;
private boolean[] trackEnabledStates; private boolean[] trackEnabledStates;
private long downstreamPositionUs; private long downstreamPositionUs;
...@@ -332,7 +331,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -332,7 +331,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount]; TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
pendingMediaFormat = 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[i].getUpstreamFormat());
...@@ -384,7 +382,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -384,7 +382,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
Assertions.checkState(!trackEnabledStates[track]); Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++; enabledTrackCount++;
trackEnabledStates[track] = true; trackEnabledStates[track] = true;
pendingMediaFormat[track] = true; sampleQueues[track].needDownstreamFormat();
newStreams[i] = new TrackStreamImpl(track); newStreams[i] = new TrackStreamImpl(track);
} }
// Cancel or start requests as necessary. // Cancel or start requests as necessary.
...@@ -449,8 +447,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -449,8 +447,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// TrackStream methods. // TrackStream methods.
/* package */ boolean isReady(int track) { /* package */ boolean isReady(int track) {
Assertions.checkState(trackEnabledStates[track]); return loadingFinished || (!isPendingReset() && !sampleQueues[track].isEmpty());
return sampleQueues[track].isEmpty();
} }
...@@ -466,27 +463,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -466,27 +463,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return TrackStream.NOTHING_READ; return TrackStream.NOTHING_READ;
} }
DefaultTrackOutput sampleQueue = sampleQueues[track]; int result = sampleQueues[track].readData(formatHolder, buffer, loadingFinished);
if (pendingMediaFormat[track]) { switch (result) {
formatHolder.format = sampleQueue.getUpstreamFormat(); case TrackStream.FORMAT_READ:
formatHolder.drmInitData = drmInitData; formatHolder.drmInitData = drmInitData;
pendingMediaFormat[track] = false; break;
return TrackStream.FORMAT_READ; case TrackStream.BUFFER_READ:
} if (!buffer.isEndOfStream() && buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
if (sampleQueue.readSample(buffer)) { }
if (buffer.timeUs < lastSeekPositionUs) { break;
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
return TrackStream.BUFFER_READ;
}
if (loadingFinished) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return TrackStream.BUFFER_READ;
} }
return result;
return TrackStream.NOTHING_READ;
} }
// Loader.Callback implementation. // Loader.Callback implementation.
......
...@@ -79,7 +79,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -79,7 +79,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
private int primaryTrackGroupIndex; private int primaryTrackGroupIndex;
// Indexed by group. // Indexed by group.
private boolean[] groupEnabledStates; private boolean[] groupEnabledStates;
private Format[] downstreamSampleFormats;
private long downstreamPositionUs; private long downstreamPositionUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
...@@ -202,7 +201,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -202,7 +201,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
int group = selection.group; int group = selection.group;
int[] tracks = selection.getTracks(); int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true); setTrackGroupEnabledState(group, true);
downstreamSampleFormats[group] = null; sampleQueues[group].needDownstreamFormat();
if (group == primaryTrackGroupIndex) { if (group == primaryTrackGroupIndex) {
primaryTracksDeselected |= chunkSource.selectTracks(tracks); primaryTracksDeselected |= chunkSource.selectTracks(tracks);
} }
...@@ -291,7 +290,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -291,7 +290,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
// TrackStream implementation. // TrackStream implementation.
/* package */ boolean isReady(int group) { /* package */ boolean isReady(int group) {
Assertions.checkState(groupEnabledStates[group]);
return loadingFinished || (!isPendingReset() && !sampleQueues[group].isEmpty()); return loadingFinished || (!isPendingReset() && !sampleQueues[group].isEmpty());
} }
...@@ -305,6 +303,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -305,6 +303,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
return TrackStream.NOTHING_READ; return TrackStream.NOTHING_READ;
} }
while (mediaChunks.size() > 1 && mediaChunks.get(1).startTimeUs <= downstreamPositionUs) {
mediaChunks.removeFirst();
}
HlsMediaChunk currentChunk = mediaChunks.getFirst(); HlsMediaChunk currentChunk = mediaChunks.getFirst();
Format currentFormat = currentChunk.format; Format currentFormat = currentChunk.format;
if (downstreamFormat == null || !downstreamFormat.equals(currentFormat)) { if (downstreamFormat == null || !downstreamFormat.equals(currentFormat)) {
...@@ -313,34 +314,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -313,34 +314,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
downstreamFormat = currentFormat; downstreamFormat = currentFormat;
} }
DefaultTrackOutput sampleQueue = sampleQueues[group]; int result = sampleQueues[group].readData(formatHolder, buffer, loadingFinished);
if (sampleQueue.isEmpty()) { if (result == TrackStream.BUFFER_READ && !buffer.isEndOfStream()
if (loadingFinished) { && buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
return TrackStream.BUFFER_READ;
}
return TrackStream.NOTHING_READ;
}
Format sampleFormat = sampleQueue.getDownstreamFormat();
if (!sampleFormat.equals(downstreamSampleFormats[group])) {
formatHolder.format = sampleFormat;
downstreamSampleFormats[group] = sampleFormat;
return TrackStream.FORMAT_READ;
} }
return result;
if (sampleQueue.readSample(buffer)) {
long sampleTimeUs = buffer.timeUs;
while (mediaChunks.size() > 1 && mediaChunks.get(1).startTimeUs <= sampleTimeUs) {
mediaChunks.removeFirst();
}
if (sampleTimeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
return TrackStream.BUFFER_READ;
}
return TrackStream.NOTHING_READ;
} }
// Loader.Callback implementation. // Loader.Callback implementation.
...@@ -463,7 +442,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -463,7 +442,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
// Instantiate the necessary internal data-structures. // Instantiate the necessary internal data-structures.
primaryTrackGroupIndex = -1; primaryTrackGroupIndex = -1;
groupEnabledStates = new boolean[extractorTrackCount]; groupEnabledStates = new boolean[extractorTrackCount];
downstreamSampleFormats = new Format[extractorTrackCount];
// Construct the set of exposed track groups. // Construct the set of exposed track groups.
TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
......
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