Propagate format information through RollingSampleBuffer.

At this point the only reason preventing the chunk package
from using RollingSampleBuffer directly, rather than
DefaultTrackOutput, is that the latter maintains the largest
parsed timestamp. This will be pushed inside the former in
the next CL. Following that, splicing logic will also be
pushed inside of RollingSampleBuffer, and HLS will be moved
over to using a single RollingSampleBuffer per track, with
the splicing done inline.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120206658
This commit is contained in:
olly 2016-04-19 01:48:18 -07:00 committed by Oliver Woodman
parent febf86c54e
commit 5ce210e374
11 changed files with 98 additions and 112 deletions

View file

@ -28,13 +28,6 @@ import com.google.android.exoplayer.upstream.DataSpec;
*/
public abstract class BaseMediaChunk extends MediaChunk {
/**
* Whether {@link #getSampleFormat()} and {@link #getDrmInitData()} can be called at any time to
* obtain the chunk's sample format and drm initialization data. If false, these methods are only
* guaranteed to return correct data after the first sample data has been output from the chunk.
*/
public final boolean isSampleFormatFinal;
private DefaultTrackOutput trackOutput;
private int firstSampleIndex;
@ -46,15 +39,10 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk.
* @param isSampleFormatFinal True if {@link #getSampleFormat()} and {@link #getDrmInitData()} can
* be called at any time to obtain the sample format and drm initialization data. False if
* these methods are only guaranteed to return correct data after the first sample data has
* been output from the chunk.
*/
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal) {
long startTimeUs, long endTimeUs, int chunkIndex) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
this.isSampleFormatFinal = isSampleFormatFinal;
}
/**
@ -76,21 +64,8 @@ public abstract class BaseMediaChunk extends MediaChunk {
return firstSampleIndex;
}
/**
* Gets the {@link Format} of the samples in the chunk.
* <p>
* See {@link #isSampleFormatFinal} for information about when this method is guaranteed to return
* correct data.
*
* @return The {@link Format} of the samples in the chunk.
*/
public abstract Format getSampleFormat();
/**
* Gets the {@link DrmInitData} corresponding to the chunk.
* <p>
* See {@link #isSampleFormatFinal} for information about when this method is guaranteed to return
* correct data.
*
* @return The {@link DrmInitData} corresponding to this chunk.
*/

View file

@ -50,11 +50,6 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
*/
void drmInitData(DrmInitData drmInitData);
/**
* @see TrackOutput#format(Format)
*/
void format(Format format);
}
private final Extractor extractor;
@ -132,8 +127,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override
public void format(Format format) {
// Redirect the format to the metadata output. The track output doesn't need it.
metadataOutput.format(format);
trackOutput.format(format);
}
@Override

View file

@ -300,14 +300,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
downstreamFormat = currentChunk.format;
}
if (haveSamples || currentChunk.isSampleFormatFinal) {
Format sampleFormat = currentChunk.getSampleFormat();
if (!sampleFormat.equals(downstreamSampleFormat)) {
formatHolder.format = sampleFormat;
formatHolder.drmInitData = currentChunk.getDrmInitData();
downstreamSampleFormat = sampleFormat;
return FORMAT_READ;
}
Format sampleFormat = sampleQueue.getDownstreamFormat();
if (sampleFormat != null && !sampleFormat.equals(downstreamSampleFormat)) {
formatHolder.format = sampleFormat;
formatHolder.drmInitData = currentChunk.getDrmInitData();
downstreamSampleFormat = sampleFormat;
return FORMAT_READ;
}
if (!haveSamples) {

View file

@ -60,11 +60,10 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
sampleFormat != null);
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs;
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);
this.sampleFormat = sampleFormat;
this.drmInitData = drmInitData;
}
@ -73,11 +72,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
return bytesLoaded;
}
@Override
public final Format getSampleFormat() {
return sampleFormat;
}
@Override
public final DrmInitData getDrmInitData() {
return drmInitData;
@ -95,11 +89,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
this.drmInitData = drmInitData;
}
@Override
public final void format(Format format) {
this.sampleFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
}
// Loadable implementation.
@Override
@ -123,7 +112,7 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
if (bytesLoaded == 0) {
// Set the target to ourselves.
DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.setSampleOffsetUs(sampleOffsetUs);
trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs);
extractorWrapper.init(this, trackOutput);
}
// Load and parse the sample data.
@ -140,16 +129,4 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
}
}
// Private methods.
private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) {
if (format == null) {
return null;
}
if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
}
return format;
}
}

View file

@ -86,15 +86,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
return drmInitData;
}
/**
* True if a {@link SeekMap} was parsed from the chunk. False otherwise.
* <p>
* Should be called after loading has completed.
*/
public boolean hasSeekMap() {
return seekMap != null;
}
/**
* Returns a {@link SeekMap} parsed from the chunk, or null.
* <p>
@ -116,13 +107,13 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
this.drmInitData = drmInitData;
}
// TrackOutput implementation.
@Override
public void format(Format format) {
this.sampleFormat = format;
}
// TrackOutput implementation.
@Override
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {

View file

@ -19,8 +19,8 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Util;
@ -53,7 +53,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
DrmInitData sampleDrmInitData) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true);
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
this.sampleFormat = sampleFormat;
this.sampleDrmInitData = sampleDrmInitData;
}
@ -63,11 +63,6 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
return bytesLoaded;
}
@Override
public Format getSampleFormat() {
return sampleFormat;
}
@Override
public DrmInitData getDrmInitData() {
return sampleDrmInitData;
@ -96,7 +91,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
length += bytesLoaded;
}
ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length);
TrackOutput trackOutput = getTrackOutput();
DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, 0);
// Load the sample data.
int result = 0;
while (result != C.RESULT_END_OF_INPUT) {

View file

@ -38,6 +38,7 @@ import com.google.android.exoplayer.dash.mpd.RangedUri;
import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.ChunkIndex;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.upstream.DataSource;
@ -296,10 +297,12 @@ public class DashChunkSource implements ChunkSource {
// The null check avoids overwriting an index obtained from the manifest with one obtained
// from the stream. If the manifest defines an index then the stream shouldn't, but in cases
// where it does we should ignore it.
if (representationHolder.segmentIndex == null && initializationChunk.hasSeekMap()) {
representationHolder.segmentIndex = new DashWrappingSegmentIndex(
(ChunkIndex) initializationChunk.getSeekMap(),
initializationChunk.dataSpec.uri.toString());
if (representationHolder.segmentIndex == null) {
SeekMap seekMap = initializationChunk.getSeekMap();
if (seekMap != null) {
representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap,
initializationChunk.dataSpec.uri.toString());
}
}
// The null check avoids overwriting drmInitData obtained from the manifest with drmInitData
// obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.

View file

@ -92,12 +92,19 @@ public final class DefaultTrackOutput implements TrackOutput {
}
/**
* The format most recently received by the output, or null if a format has yet to be received.
* Returns the current upstream {@link Format}.
*/
public Format getFormat() {
public Format getUpstreamFormat() {
return rollingBuffer.getUpstreamFormat();
}
/**
* Returns the current downstream {@link Format}.
*/
public Format getDownstreamFormat() {
return rollingBuffer.getDownstreamFormat();
}
/**
* The largest timestamp of any sample received by the output, or {@link Long#MIN_VALUE} if a
* sample has yet to be received.
@ -210,13 +217,16 @@ public final class DefaultTrackOutput implements TrackOutput {
// Called by the loading thread.
/**
* Sets an offset that will be added to the timestamps passed to
* {@link #sampleMetadata(long, int, int, int, byte[])}.
* Like {@link #format(Format)}, but with an offset that will be added to the timestamps of
* samples subsequently queued to the buffer. The offset is also used to adjust
* {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently
* passed to {@link #format(Format)}.
*
* @param format The format.
* @param sampleOffsetUs The offset in microseconds.
*/
public void setSampleOffsetUs(long sampleOffsetUs) {
rollingBuffer.setSampleOffsetUs(sampleOffsetUs);
public void formatWithOffset(Format format, long sampleOffsetUs) {
rollingBuffer.formatWithOffset(format, sampleOffsetUs);
}
@Override

View file

@ -343,7 +343,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
pendingMediaFormat = new boolean[trackCount];
durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) {
trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat());
trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getUpstreamFormat());
}
tracks = new TrackGroupArray(trackArray);
if (minLoadableRetryCount == MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA && !seekMap.isSeekable()
@ -476,7 +476,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track);
if (pendingMediaFormat[track]) {
formatHolder.format = sampleQueue.getFormat();
formatHolder.format = sampleQueue.getUpstreamFormat();
formatHolder.drmInitData = drmInitData;
pendingMediaFormat[track] = false;
return TrackStream.FORMAT_READ;
@ -649,7 +649,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private boolean haveFormatsForAllTracks() {
for (int i = 0; i < sampleQueues.size(); i++) {
if (sampleQueues.valueAt(i).getFormat() == null) {
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
return false;
}
}

View file

@ -137,12 +137,20 @@ import java.util.concurrent.LinkedBlockingDeque;
}
/**
* Returns the current upstream format.
* Returns the current upstream {@link Format}.
*/
public Format getUpstreamFormat() {
return upstreamFormat;
}
/**
* Returns the current downstream {@link Format}.
*/
public Format getDownstreamFormat() {
Format nextSampleFormat = infoQueue.peekFormat();
return nextSampleFormat != null ? nextSampleFormat : upstreamFormat;
}
/**
* Fills {@code buffer} with information about the current sample, but does not write its data.
* <p>
@ -357,17 +365,22 @@ import java.util.concurrent.LinkedBlockingDeque;
// Called by the loading thread.
/**
* Sets an offset that will be added to the timestamps of subsequently queued samples.
* Like {@link #format(Format)}, but with an offset that will be added to the timestamps of
* samples subsequently queued to the buffer. The offset is also used to adjust
* {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently
* passed to {@link #format(Format)}.
*
* @param format The format.
* @param sampleOffsetUs The timestamp offset in microseconds.
*/
public void setSampleOffsetUs(long sampleOffsetUs) {
public void formatWithOffset(Format format, long sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
upstreamFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
}
@Override
public void format(Format format) {
upstreamFormat = format;
upstreamFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
}
@Override
@ -403,7 +416,7 @@ import java.util.concurrent.LinkedBlockingDeque;
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
timeUs += sampleOffsetUs;
long absoluteOffset = totalBytesWritten - size - offset;
infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey);
infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey, upstreamFormat);
}
/**
@ -419,6 +432,23 @@ import java.util.concurrent.LinkedBlockingDeque;
return Math.min(length, allocationLength - lastAllocationOffset);
}
/**
* Adjusts a {@link Format} to incorporate a sample offset into {@link Format#subsampleOffsetUs}.
*
* @param format The {@link Format} to adjust.
* @param sampleOffsetUs The offset to apply.
* @return The adjusted {@link Format}.
*/
private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) {
if (format == null) {
return null;
}
if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
}
return format;
}
/**
* Holds information about the samples in the rolling buffer.
*/
@ -433,6 +463,7 @@ import java.util.concurrent.LinkedBlockingDeque;
private int[] flags;
private long[] timesUs;
private byte[][] encryptionKeys;
private Format[] formats;
private int queueSize;
private int absoluteReadIndex;
@ -446,6 +477,7 @@ import java.util.concurrent.LinkedBlockingDeque;
flags = new int[capacity];
sizes = new int[capacity];
encryptionKeys = new byte[capacity][];
formats = new Format[capacity];
}
// Called by the consuming thread, but only when there is no loading thread.
@ -500,6 +532,16 @@ import java.util.concurrent.LinkedBlockingDeque;
return absoluteReadIndex;
}
/**
* Returns the {@link Format} of the next sample, or null of the queue is empty.
*/
public synchronized Format peekFormat() {
if (queueSize == 0) {
return null;
}
return formats[relativeReadIndex];
}
/**
* Fills {@code buffer} with information about the current sample, but does not write its data.
* The absolute position of the sample's data in the rolling buffer is stored in
@ -609,12 +651,13 @@ import java.util.concurrent.LinkedBlockingDeque;
// Called by the loading thread.
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
byte[] encryptionKey) {
byte[] encryptionKey, Format format) {
timesUs[relativeWriteIndex] = timeUs;
offsets[relativeWriteIndex] = offset;
sizes[relativeWriteIndex] = size;
flags[relativeWriteIndex] = sampleFlags;
encryptionKeys[relativeWriteIndex] = encryptionKey;
formats[relativeWriteIndex] = format;
// Increment the write index.
queueSize++;
if (queueSize == capacity) {
@ -625,23 +668,27 @@ import java.util.concurrent.LinkedBlockingDeque;
int[] newFlags = new int[newCapacity];
int[] newSizes = new int[newCapacity];
byte[][] newEncryptionKeys = new byte[newCapacity][];
Format[] newFormats = new Format[newCapacity];
int beforeWrap = capacity - relativeReadIndex;
System.arraycopy(offsets, relativeReadIndex, newOffsets, 0, beforeWrap);
System.arraycopy(timesUs, relativeReadIndex, newTimesUs, 0, beforeWrap);
System.arraycopy(flags, relativeReadIndex, newFlags, 0, beforeWrap);
System.arraycopy(sizes, relativeReadIndex, newSizes, 0, beforeWrap);
System.arraycopy(encryptionKeys, relativeReadIndex, newEncryptionKeys, 0, beforeWrap);
System.arraycopy(formats, relativeReadIndex, newFormats, 0, beforeWrap);
int afterWrap = relativeReadIndex;
System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap);
System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap);
System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap);
System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap);
System.arraycopy(encryptionKeys, 0, newEncryptionKeys, beforeWrap, afterWrap);
System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap);
offsets = newOffsets;
timesUs = newTimesUs;
flags = newFlags;
sizes = newSizes;
encryptionKeys = newEncryptionKeys;
formats = newFormats;
relativeReadIndex = 0;
relativeWriteIndex = capacity;
queueSize = capacity;

View file

@ -44,7 +44,6 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final boolean shouldSpliceIn;
private Format[] sampleFormats;
private Allocator allocator;
private volatile boolean tracksBuilt;
@ -81,15 +80,11 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
public boolean isPrepared() {
if (!prepared && tracksBuilt) {
for (int i = 0; i < sampleQueues.size(); i++) {
if (sampleQueues.valueAt(i).getFormat() == null) {
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
return false;
}
}
prepared = true;
sampleFormats = new Format[sampleQueues.size()];
for (int i = 0; i < sampleFormats.length; i++) {
sampleFormats[i] = sampleQueues.valueAt(i).getFormat();
}
}
return prepared;
}
@ -173,7 +168,7 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
*/
public Format getSampleFormat(int track) {
Assertions.checkState(isPrepared());
return sampleFormats[track];
return sampleQueues.valueAt(track).getUpstreamFormat();
}
/**