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
private TrackGroupArray trackGroups;
private boolean trackEnabled;
private boolean pendingReset;
private Format downstreamSampleFormat;
private long downstreamPositionUs;
private long lastSeekPositionUs;
......@@ -196,7 +195,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
loadControl.register(this, bufferSizeContribution);
}
downstreamFormat = null;
downstreamSampleFormat = null;
sampleQueue.needDownstreamFormat();
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
pendingReset = false;
......@@ -265,7 +264,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
@Override
public boolean isReady() {
return loadingFinished || !sampleQueue.isEmpty();
return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty());
}
@Override
......@@ -289,44 +288,33 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
return NOTHING_READ;
}
BaseMediaChunk currentChunk = mediaChunks.getFirst();
while (mediaChunks.size() > 1
&& mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) {
mediaChunks.removeFirst();
currentChunk = mediaChunks.getFirst();
}
if (downstreamFormat == null || !downstreamFormat.equals(currentChunk.format)) {
eventDispatcher.downstreamFormatChanged(currentChunk.format, currentChunk.trigger,
BaseMediaChunk currentChunk = mediaChunks.getFirst();
Format currentFormat = currentChunk.format;
if (downstreamFormat == null || !downstreamFormat.equals(currentFormat)) {
eventDispatcher.downstreamFormatChanged(currentFormat, currentChunk.trigger,
currentChunk.startTimeUs);
downstreamFormat = currentChunk.format;
}
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;
downstreamFormat = currentFormat;
}
if (sampleQueue.readSample(buffer)) {
if (buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
onSampleRead(currentChunk, buffer);
return BUFFER_READ;
int result = sampleQueue.readData(formatHolder, buffer, loadingFinished);
switch (result) {
case FORMAT_READ:
formatHolder.drmInitData = currentChunk.getDrmInitData();
break;
case BUFFER_READ:
if (!buffer.isEndOfStream()) {
if (buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
onSampleRead(currentChunk, buffer);
}
break;
}
return NOTHING_READ;
return result;
}
// Loader.Callback implementation.
......
......@@ -212,7 +212,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private DefaultTrackOutput[] sampleQueues;
private TrackGroupArray tracks;
private long durationUs;
private boolean[] pendingMediaFormat;
private boolean[] trackEnabledStates;
private long downstreamPositionUs;
......@@ -332,7 +331,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount];
pendingMediaFormat = new boolean[trackCount];
durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) {
trackArray[i] = new TrackGroup(sampleQueues[i].getUpstreamFormat());
......@@ -384,7 +382,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++;
trackEnabledStates[track] = true;
pendingMediaFormat[track] = true;
sampleQueues[track].needDownstreamFormat();
newStreams[i] = new TrackStreamImpl(track);
}
// Cancel or start requests as necessary.
......@@ -449,8 +447,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// TrackStream methods.
/* package */ boolean isReady(int track) {
Assertions.checkState(trackEnabledStates[track]);
return sampleQueues[track].isEmpty();
return loadingFinished || (!isPendingReset() && !sampleQueues[track].isEmpty());
}
......@@ -466,27 +463,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return TrackStream.NOTHING_READ;
}
DefaultTrackOutput sampleQueue = sampleQueues[track];
if (pendingMediaFormat[track]) {
formatHolder.format = sampleQueue.getUpstreamFormat();
formatHolder.drmInitData = drmInitData;
pendingMediaFormat[track] = false;
return TrackStream.FORMAT_READ;
}
if (sampleQueue.readSample(buffer)) {
if (buffer.timeUs < lastSeekPositionUs) {
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;
int result = sampleQueues[track].readData(formatHolder, buffer, loadingFinished);
switch (result) {
case TrackStream.FORMAT_READ:
formatHolder.drmInitData = drmInitData;
break;
case TrackStream.BUFFER_READ:
if (!buffer.isEndOfStream() && buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
break;
}
return TrackStream.NOTHING_READ;
return result;
}
// Loader.Callback implementation.
......
......@@ -79,7 +79,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
private int primaryTrackGroupIndex;
// Indexed by group.
private boolean[] groupEnabledStates;
private Format[] downstreamSampleFormats;
private long downstreamPositionUs;
private long lastSeekPositionUs;
......@@ -202,7 +201,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
int group = selection.group;
int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true);
downstreamSampleFormats[group] = null;
sampleQueues[group].needDownstreamFormat();
if (group == primaryTrackGroupIndex) {
primaryTracksDeselected |= chunkSource.selectTracks(tracks);
}
......@@ -291,7 +290,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
// TrackStream implementation.
/* package */ boolean isReady(int group) {
Assertions.checkState(groupEnabledStates[group]);
return loadingFinished || (!isPendingReset() && !sampleQueues[group].isEmpty());
}
......@@ -305,6 +303,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
return TrackStream.NOTHING_READ;
}
while (mediaChunks.size() > 1 && mediaChunks.get(1).startTimeUs <= downstreamPositionUs) {
mediaChunks.removeFirst();
}
HlsMediaChunk currentChunk = mediaChunks.getFirst();
Format currentFormat = currentChunk.format;
if (downstreamFormat == null || !downstreamFormat.equals(currentFormat)) {
......@@ -313,34 +314,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
downstreamFormat = currentFormat;
}
DefaultTrackOutput sampleQueue = sampleQueues[group];
if (sampleQueue.isEmpty()) {
if (loadingFinished) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
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;
int result = sampleQueues[group].readData(formatHolder, buffer, loadingFinished);
if (result == TrackStream.BUFFER_READ && !buffer.isEndOfStream()
&& buffer.timeUs < lastSeekPositionUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
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;
return result;
}
// Loader.Callback implementation.
......@@ -463,7 +442,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
// Instantiate the necessary internal data-structures.
primaryTrackGroupIndex = -1;
groupEnabledStates = new boolean[extractorTrackCount];
downstreamSampleFormats = new Format[extractorTrackCount];
// Construct the set of exposed track groups.
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