Simplify chunk package ahead of EMSG/608 piping

Issue: #2362
Issue: #2176

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=146243681
This commit is contained in:
olly 2017-02-01 08:00:48 -08:00 committed by Oliver Woodman
parent 025a67cae9
commit ee3c5f875f
9 changed files with 75 additions and 152 deletions

View File

@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
import java.util.HashMap;
import org.mockito.Mock;
@ -217,7 +218,11 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
}
private static Representation newRepresentations(DrmInitData drmInitData) {
Format format = Format.createVideoSampleFormat("", "", "", 0, 0, 0, 0, 0, null, drmInitData);
Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4,
MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0);
if (drmInitData != null) {
format = format.copyWithDrmInitData(drmInitData);
}
return Representation.newInstance("", 0, format, "", new SingleSegmentBase());
}

View File

@ -210,11 +210,13 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
Representation representation = adaptationSet.representations.get(0);
DrmInitData drmInitData = representation.format.drmInitData;
if (drmInitData == null) {
InitializationChunk initializationChunk = loadInitializationChunk(dataSource, representation);
ChunkExtractorWrapper extractorWrapper = newWrappedExtractor(representation.format);
InitializationChunk initializationChunk = loadInitializationChunk(dataSource, representation,
extractorWrapper);
if (initializationChunk == null) {
return null;
}
Format sampleFormat = initializationChunk.getSampleFormat();
Format sampleFormat = extractorWrapper.getSampleFormat();
if (sampleFormat != null) {
drmInitData = sampleFormat.drmInitData;
}
@ -288,8 +290,9 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
return session;
}
private static InitializationChunk loadInitializationChunk(final DataSource dataSource,
final Representation representation) throws IOException, InterruptedException {
private static InitializationChunk loadInitializationChunk(DataSource dataSource,
Representation representation, ChunkExtractorWrapper extractorWrapper)
throws IOException, InterruptedException {
RangedUri rangedUri = representation.getInitializationUri();
if (rangedUri == null) {
return null;
@ -298,7 +301,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
rangedUri.length, representation.getCacheKey());
InitializationChunk initializationChunk = new InitializationChunk(dataSource, dataSpec,
representation.format, C.SELECTION_REASON_UNKNOWN, null /* trackSelectionData */,
newWrappedExtractor(representation.format));
extractorWrapper);
initializationChunk.load();
return initializationChunk;
}
@ -308,8 +311,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
final boolean isWebm = mimeType.startsWith(MimeTypes.VIDEO_WEBM)
|| mimeType.startsWith(MimeTypes.AUDIO_WEBM);
final Extractor extractor = isWebm ? new MatroskaExtractor() : new FragmentedMp4Extractor();
return new ChunkExtractorWrapper(extractor, format, false /* preferManifestDrmInitData */,
false /* resendFormatOnInit */);
return new ChunkExtractorWrapper(extractor, format, false /* preferManifestDrmInitData */);
}
}

View File

@ -70,6 +70,8 @@ public final class DefaultTrackOutput implements TrackOutput {
private Format downstreamFormat;
// Accessed only by the loading thread (or the consuming thread when there is no loading thread).
private boolean pendingFormatAdjustment;
private Format lastUnadjustedFormat;
private long sampleOffsetUs;
private long totalBytesWritten;
private Allocation lastAllocation;
@ -445,23 +447,24 @@ public final class DefaultTrackOutput implements TrackOutput {
}
/**
* 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)}.
* Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples
* subsequently queued to the buffer.
*
* @param format The format.
* @param sampleOffsetUs The timestamp offset in microseconds.
*/
public void formatWithOffset(Format format, long sampleOffsetUs) {
public void setSampleOffsetUs(long sampleOffsetUs) {
if (this.sampleOffsetUs != sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
format(format);
pendingFormatAdjustment = true;
}
}
@Override
public void format(Format format) {
Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
boolean formatChanged = infoQueue.format(adjustedFormat);
lastUnadjustedFormat = format;
pendingFormatAdjustment = false;
if (upstreamFormatChangeListener != null && formatChanged) {
upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat);
}
@ -518,6 +521,9 @@ public final class DefaultTrackOutput implements TrackOutput {
@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
if (pendingFormatAdjustment) {
format(lastUnadjustedFormat);
}
if (!startWriteOperation()) {
infoQueue.commitSampleTimestamp(timeUs);
return;

View File

@ -30,33 +30,19 @@ import java.io.IOException;
/**
* An {@link Extractor} wrapper for loading chunks containing a single track.
* <p>
* The wrapper allows switching of the {@link SeekMapOutput} and {@link TrackOutput} that receive
* parsed data.
* The wrapper allows switching of the {@link TrackOutput} that receives parsed data.
*/
public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput {
/**
* Receives {@link SeekMap}s extracted by the wrapped {@link Extractor}.
*/
public interface SeekMapOutput {
/**
* @see ExtractorOutput#seekMap(SeekMap)
*/
void seekMap(SeekMap seekMap);
}
public final Extractor extractor;
private final Format manifestFormat;
private final boolean preferManifestDrmInitData;
private final boolean resendFormatOnInit;
private boolean extractorInitialized;
private SeekMapOutput seekMapOutput;
private TrackOutput trackOutput;
private Format sentFormat;
private SeekMap seekMap;
private Format sampleFormat;
// Accessed only on the loader thread.
private boolean seenTrack;
@ -68,34 +54,43 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
* sample {@link Format} output from the {@link Extractor}.
* @param preferManifestDrmInitData Whether {@link DrmInitData} defined in {@code manifestFormat}
* should be preferred when the sample and manifest {@link Format}s are merged.
* @param resendFormatOnInit Whether the extractor should resend the previous {@link Format} when
* it is initialized via {@link #init(SeekMapOutput, TrackOutput)}.
*/
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat,
boolean preferManifestDrmInitData, boolean resendFormatOnInit) {
boolean preferManifestDrmInitData) {
this.extractor = extractor;
this.manifestFormat = manifestFormat;
this.preferManifestDrmInitData = preferManifestDrmInitData;
this.resendFormatOnInit = resendFormatOnInit;
}
/**
* Initializes the extractor to output to the provided {@link SeekMapOutput} and
* {@link TrackOutput} instances, and configures it to receive data from a new chunk.
* Returns the {@link SeekMap} most recently output by the extractor, or null.
*/
public SeekMap getSeekMap() {
return seekMap;
}
/**
* Returns the sample {@link Format} most recently output by the extractor, or null.
*/
public Format getSampleFormat() {
return sampleFormat;
}
/**
* Initializes the extractor to output to the provided {@link TrackOutput}, and configures it to
* receive data from a new chunk.
*
* @param seekMapOutput The {@link SeekMapOutput} that will receive extracted {@link SeekMap}s.
* @param trackOutput The {@link TrackOutput} that will receive sample data.
*/
public void init(SeekMapOutput seekMapOutput, TrackOutput trackOutput) {
this.seekMapOutput = seekMapOutput;
public void init(TrackOutput trackOutput) {
this.trackOutput = trackOutput;
if (!extractorInitialized) {
extractor.init(this);
extractorInitialized = true;
} else {
extractor.seek(0, 0);
if (resendFormatOnInit && sentFormat != null) {
trackOutput.format(sentFormat);
if (sampleFormat != null) {
trackOutput.format(sampleFormat);
}
}
}
@ -117,15 +112,17 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override
public void seekMap(SeekMap seekMap) {
seekMapOutput.seekMap(seekMap);
this.seekMap = seekMap;
}
// TrackOutput implementation.
@Override
public void format(Format format) {
sentFormat = format.copyWithManifestFormatInfo(manifestFormat, preferManifestDrmInitData);
trackOutput.format(sentFormat);
sampleFormat = format.copyWithManifestFormatInfo(manifestFormat, preferManifestDrmInitData);
if (trackOutput != null) {
trackOutput.format(sampleFormat);
}
}
@Override

View File

@ -20,8 +20,6 @@ import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.DefaultTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.SeekMapOutput;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
@ -31,12 +29,11 @@ import java.io.IOException;
/**
* A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data.
*/
public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput {
public class ContainerMediaChunk extends BaseMediaChunk {
private final int chunkCount;
private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper;
private final Format sampleFormat;
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
@ -56,19 +53,15 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
* underlying media are being merged into a single load.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
* @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
* the data is known to define its own sample format.
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
Format sampleFormat) {
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex);
this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs;
this.extractorWrapper = extractorWrapper;
this.sampleFormat = sampleFormat;
}
@Override
@ -86,13 +79,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
return bytesLoaded;
}
// SeekMapOutput implementation.
@Override
public final void seekMap(SeekMap seekMap) {
// Do nothing.
}
// Loadable implementation.
@Override
@ -116,8 +102,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
if (bytesLoaded == 0) {
// Set the target to ourselves.
DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs);
extractorWrapper.init(this, trackOutput);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
extractorWrapper.init(trackOutput);
}
// Load and decode the sample data.
try {

View File

@ -20,30 +20,19 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.SeekMapOutput;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* A {@link Chunk} that uses an {@link Extractor} to decode initialization data for single track.
*/
public final class InitializationChunk extends Chunk implements SeekMapOutput,
TrackOutput {
public final class InitializationChunk extends Chunk {
private final ChunkExtractorWrapper extractorWrapper;
// Initialization results. Set by the loader thread and read by any thread that knows loading
// has completed. These variables do not need to be volatile, since a memory barrier must occur
// for the reading thread to know that loading has completed.
private Format sampleFormat;
private SeekMap seekMap;
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
@ -68,55 +57,6 @@ public final class InitializationChunk extends Chunk implements SeekMapOutput,
return bytesLoaded;
}
/**
* Returns a {@link Format} parsed from the chunk, or null.
* <p>
* Should be called after loading has completed.
*/
public Format getSampleFormat() {
return sampleFormat;
}
/**
* Returns a {@link SeekMap} parsed from the chunk, or null.
* <p>
* Should be called after loading has completed.
*/
public SeekMap getSeekMap() {
return seekMap;
}
// SeekMapOutput implementation.
@Override
public void seekMap(SeekMap seekMap) {
this.seekMap = seekMap;
}
// TrackOutput implementation.
@Override
public void format(Format format) {
this.sampleFormat = format;
}
@Override
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
throw new IllegalStateException("Unexpected sample data in initialization chunk");
}
@Override
public void sampleData(ParsableByteArray data, int length) {
throw new IllegalStateException("Unexpected sample data in initialization chunk");
}
@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
throw new IllegalStateException("Unexpected sample data in initialization chunk");
}
// Loadable implementation.
@Override
@ -138,8 +78,7 @@ public final class InitializationChunk extends Chunk implements SeekMapOutput,
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (bytesLoaded == 0) {
// Set the target to ourselves.
extractorWrapper.init(this, this);
extractorWrapper.init(null);
}
// Load and decode the initialization data.
try {

View File

@ -88,7 +88,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
}
ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length);
DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, 0);
trackOutput.setSampleOffsetUs(0);
trackOutput.format(sampleFormat);
// Load the sample data.
int result = 0;
while (result != C.RESULT_END_OF_INPUT) {

View File

@ -176,8 +176,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null;
Format sampleFormat = representationHolder.sampleFormat;
if (sampleFormat == null) {
if (representationHolder.extractorWrapper.getSampleFormat() == null) {
pendingInitializationUri = selectedRepresentation.getInitializationUri();
}
if (segmentIndex == null) {
@ -233,8 +232,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
out.chunk = newMediaChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(),
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), sampleFormat,
segmentNum, maxSegmentCount);
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segmentNum,
maxSegmentCount);
}
@Override
@ -243,15 +242,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
InitializationChunk initializationChunk = (InitializationChunk) chunk;
RepresentationHolder representationHolder =
representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)];
Format sampleFormat = initializationChunk.getSampleFormat();
if (sampleFormat != null) {
representationHolder.setSampleFormat(sampleFormat);
}
// 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) {
SeekMap seekMap = initializationChunk.getSeekMap();
SeekMap seekMap = representationHolder.extractorWrapper.getSeekMap();
if (seekMap != null) {
representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap);
}
@ -318,7 +313,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
private static Chunk newMediaChunk(RepresentationHolder representationHolder,
DataSource dataSource, Format trackFormat, int trackSelectionReason,
Object trackSelectionData, Format sampleFormat, int firstSegmentNum, int maxSegmentCount) {
Object trackSelectionData, int firstSegmentNum, int maxSegmentCount) {
Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
@ -347,7 +342,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount,
sampleOffsetUs, representationHolder.extractorWrapper, sampleFormat);
sampleOffsetUs, representationHolder.extractorWrapper);
}
}
@ -359,7 +354,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
public Representation representation;
public DashSegmentIndex segmentIndex;
public Format sampleFormat;
private long periodDurationUs;
private int segmentNumShift;
@ -371,11 +365,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (mimeTypeIsRawText(containerMimeType)) {
extractorWrapper = null;
} else {
boolean resendFormatOnInit = false;
Extractor extractor;
if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
extractor = new RawCcExtractor(representation.format);
resendFormatOnInit = true;
} else if (mimeTypeIsWebm(containerMimeType)) {
extractor = new MatroskaExtractor();
} else {
@ -383,17 +375,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
// Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
// as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
extractorWrapper = new ChunkExtractorWrapper(extractor,
representation.format, true /* preferManifestDrmInitData */,
resendFormatOnInit);
extractorWrapper = new ChunkExtractorWrapper(extractor, representation.format,
true /* preferManifestDrmInitData */);
}
segmentIndex = representation.getIndex();
}
public void setSampleFormat(Format sampleFormat) {
this.sampleFormat = sampleFormat;
}
public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation)
throws BehindLiveWindowException{
DashSegmentIndex oldIndex = representation.getIndex();

View File

@ -102,7 +102,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track, null);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false, false);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false);
}
}
@ -219,7 +219,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason,
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs,
extractorWrapper, format);
extractorWrapper);
}
}