From 28e926602fe4e92abf0a605dc1b070a8ba8e30cb Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 14 Apr 2016 08:32:26 -0700 Subject: [PATCH] Remove layer of indirection when piping sample data. For DASH + SS, sample data is currently plumbed through: Extractor ->ChunkExtractorWrapper ->ContainerMediaChunk ->DefaultTrackOutput ->RollingSampleNBuffer This change bypasses the ContainerMediaChunk layer. It should be possible to completely delete DefaultTrackOutput in the future, but such a change may well be tied to changes in HLS format splicing + how we buffer (or don't buffer) disabled tracks. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=119853857 --- .../exoplayer/chunk/BaseMediaChunk.java | 16 ++++---- .../chunk/ChunkExtractorWrapper.java | 40 ++++++++++++------- .../exoplayer/chunk/ContainerMediaChunk.java | 29 ++++---------- .../exoplayer/chunk/InitializationChunk.java | 12 ++++-- .../chunk/SingleSampleMediaChunk.java | 6 ++- .../extractor/DefaultTrackOutput.java | 16 ++++++++ 6 files changed, 68 insertions(+), 51 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java index 16568652f7..609eb9cb4e 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java @@ -35,7 +35,7 @@ public abstract class BaseMediaChunk extends MediaChunk { */ public final boolean isSampleFormatFinal; - private DefaultTrackOutput output; + private DefaultTrackOutput trackOutput; private int firstSampleIndex; /** @@ -61,11 +61,11 @@ public abstract class BaseMediaChunk extends MediaChunk { * Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive * samples as they are loaded. * - * @param output The output that will receive the loaded samples. + * @param trackOutput The output that will receive the loaded samples. */ - public void init(DefaultTrackOutput output) { - this.output = output; - this.firstSampleIndex = output.getWriteIndex(); + public void init(DefaultTrackOutput trackOutput) { + this.trackOutput = trackOutput; + this.firstSampleIndex = trackOutput.getWriteIndex(); } /** @@ -97,10 +97,10 @@ public abstract class BaseMediaChunk extends MediaChunk { public abstract DrmInitData getDrmInitData(); /** - * Returns the output most recently passed to {@link #init(DefaultTrackOutput)}. + * Returns the track output most recently passed to {@link #init(DefaultTrackOutput)}. */ - protected final DefaultTrackOutput getOutput() { - return output; + protected final DefaultTrackOutput getTrackOutput() { + return trackOutput; } } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java index 123321b30e..ee2ec5b0c9 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java @@ -30,14 +30,15 @@ import java.io.IOException; /** * An {@link Extractor} wrapper for loading chunks containing a single track. *

- * The wrapper allows switching of the {@link SingleTrackOutput} that receives parsed data. + * The wrapper allows switching of the {@link SingleTrackMetadataOutput} and {@link TrackOutput} + * which receive parsed data. */ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput { /** - * Receives stream level data extracted by the wrapped {@link Extractor}. + * Receives metadata associated with the track as extracted by the wrapped {@link Extractor}. */ - public interface SingleTrackOutput extends TrackOutput { + public interface SingleTrackMetadataOutput { /** * @see ExtractorOutput#seekMap(SeekMap) @@ -49,11 +50,17 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput */ void drmInitData(DrmInitData drmInitData); + /** + * @see TrackOutput#format(Format) + */ + void format(Format format); + } private final Extractor extractor; private boolean extractorInitialized; - private SingleTrackOutput output; + private SingleTrackMetadataOutput metadataOutput; + private TrackOutput trackOutput; // Accessed only on the loader thread. private boolean seenTrack; @@ -66,13 +73,15 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput } /** - * Initializes the extractor to output to the provided {@link SingleTrackOutput}, and configures - * it to receive data from a new chunk. + * Initializes the extractor to output to the provided {@link SingleTrackMetadataOutput} and + * {@link TrackOutput} instances, and configures it to receive data from a new chunk. * - * @param output The {@link SingleTrackOutput} that will receive the parsed data. + * @param metadataOutput The {@link SingleTrackMetadataOutput} that will receive metadata. + * @param trackOutput The {@link TrackOutput} that will receive sample data. */ - public void init(SingleTrackOutput output) { - this.output = output; + public void init(SingleTrackMetadataOutput metadataOutput, TrackOutput trackOutput) { + this.metadataOutput = metadataOutput; + this.trackOutput = trackOutput; if (!extractorInitialized) { extractor.init(this); extractorInitialized = true; @@ -111,35 +120,36 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput @Override public void seekMap(SeekMap seekMap) { - output.seekMap(seekMap); + metadataOutput.seekMap(seekMap); } @Override public void drmInitData(DrmInitData drmInitData) { - output.drmInitData(drmInitData); + metadataOutput.drmInitData(drmInitData); } // TrackOutput implementation. @Override public void format(Format format) { - output.format(format); + // Redirect the format to the metadata output. The track output doesn't need it. + metadataOutput.format(format); } @Override public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) throws IOException, InterruptedException { - return output.sampleData(input, length, allowEndOfInput); + return trackOutput.sampleData(input, length, allowEndOfInput); } @Override public void sampleData(ParsableByteArray data, int length) { - output.sampleData(data, length); + trackOutput.sampleData(data, length); } @Override public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - output.sampleMetadata(timeUs, flags, size, offset, encryptionKey); + trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); } } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java index 3e98191fe1..4693dc2949 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java @@ -16,15 +16,15 @@ package com.google.android.exoplayer.chunk; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput; +import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput; 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.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; -import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.Util; import java.io.IOException; @@ -32,7 +32,7 @@ import java.io.IOException; /** * A {@link BaseMediaChunk} that uses an {@link Extractor} to parse sample data. */ -public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOutput { +public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMetadataOutput { private final ChunkExtractorWrapper extractorWrapper; private final long sampleOffsetUs; @@ -88,7 +88,7 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu return drmInitData; } - // SingleTrackOutput implementation. + // SingleTrackMetadataOutput implementation. @Override public final void seekMap(SeekMap seekMap) { @@ -105,23 +105,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu this.sampleFormat = getAdjustedSampleFormat(format, sampleOffsetUs); } - @Override - public final int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - return getOutput().sampleData(input, length, allowEndOfInput); - } - - @Override - public final void sampleData(ParsableByteArray data, int length) { - getOutput().sampleData(data, length); - } - - @Override - public final void sampleMetadata(long timeUs, int flags, int size, int offset, - byte[] encryptionKey) { - getOutput().sampleMetadata(timeUs + sampleOffsetUs, flags, size, offset, encryptionKey); - } - // Loadable implementation. @Override @@ -144,7 +127,9 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); if (bytesLoaded == 0) { // Set the target to ourselves. - extractorWrapper.init(this); + DefaultTrackOutput trackOutput = getTrackOutput(); + trackOutput.setSampleOffsetUs(sampleOffsetUs); + extractorWrapper.init(this, trackOutput); } // Load and parse the initialization data. try { diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java index cc468cd084..b5d5991c85 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java @@ -16,12 +16,13 @@ package com.google.android.exoplayer.chunk; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput; +import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput; import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.extractor.DefaultExtractorInput; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.SeekMap; +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.ParsableByteArray; @@ -32,7 +33,8 @@ import java.io.IOException; /** * A {@link Chunk} that uses an {@link Extractor} to parse initialization data for single track. */ -public final class InitializationChunk extends Chunk implements SingleTrackOutput { +public final class InitializationChunk extends Chunk implements SingleTrackMetadataOutput, + TrackOutput { private final ChunkExtractorWrapper extractorWrapper; @@ -120,7 +122,7 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu return seekMap; } - // SingleTrackOutput implementation. + // SingleTrackMetadataOutput implementation. @Override public void seekMap(SeekMap seekMap) { @@ -137,6 +139,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu this.sampleFormat = format; } + // TrackOutput implementation. + @Override public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) throws IOException, InterruptedException { @@ -175,7 +179,7 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); if (bytesLoaded == 0) { // Set the target to ourselves. - extractorWrapper.init(this); + extractorWrapper.init(this, this); } // Load and parse the initialization data. try { diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java index 1520442dbe..c93eecb722 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java @@ -20,6 +20,7 @@ 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.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; @@ -95,14 +96,15 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk { length += bytesLoaded; } ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length); + TrackOutput trackOutput = getTrackOutput(); // Load the sample data. int result = 0; while (result != C.RESULT_END_OF_INPUT) { bytesLoaded += result; - result = getOutput().sampleData(extractorInput, Integer.MAX_VALUE, true); + result = trackOutput.sampleData(extractorInput, Integer.MAX_VALUE, true); } int sampleSize = bytesLoaded; - getOutput().sampleMetadata(startTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + trackOutput.sampleMetadata(startTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); } finally { dataSource.close(); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java index fd10b59e71..5b0c6a42e2 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java @@ -31,6 +31,9 @@ public class DefaultTrackOutput implements TrackOutput { private final RollingSampleBuffer rollingBuffer; private final DecoderInputBuffer sampleBuffer; + // Accessed only by the loading thread. + private long sampleOffsetUs; + // Accessed only by the consuming thread. private boolean needKeyframe; private long lastReadTimeUs; @@ -222,6 +225,18 @@ public class DefaultTrackOutput implements TrackOutput { return true; } + // Called by the loading thread. + + /** + * Sets an offset that will be added to the timestamps passed to + * {@link #sampleMetadata(long, int, int, int, byte[])}. + * + * @param sampleOffsetUs The offset in microseconds. + */ + public void setSampleOffsetUs(long sampleOffsetUs) { + this.sampleOffsetUs = sampleOffsetUs; + } + // TrackOutput implementation. Called by the loading thread. @Override @@ -242,6 +257,7 @@ public class DefaultTrackOutput implements TrackOutput { @Override public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + timeUs += sampleOffsetUs; largestParsedTimestampUs = Math.max(largestParsedTimestampUs, timeUs); rollingBuffer.commitSample(timeUs, flags, size, offset, encryptionKey); }