Skip to content

Commit

Permalink
Support multiple track outputs from BaseMediaChunk
Browse files Browse the repository at this point in the history
Issue: #2362
Issue: #2176

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148764237
  • Loading branch information
ojw28 committed Feb 28, 2017
1 parent d58008e commit ab8fd14
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,7 @@ public byte[] download(HttpDataSource dataSource, DashManifest dashManifest)
Representation representation = adaptationSet.representations.get(0);
DrmInitData drmInitData = representation.format.drmInitData;
if (drmInitData == null) {
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation,
adaptationSet.type);
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation);
if (sampleFormat != null) {
drmInitData = sampleFormat.drmInitData;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
import com.google.android.exoplayer2.upstream.DataSpec;

/**
* A base implementation of {@link MediaChunk}, for chunks that contain a single track.
* <p>
* Loaded samples are output to a {@link DefaultTrackOutput}.
* A base implementation of {@link MediaChunk} that outputs to a {@link BaseMediaChunkOutput}.
*/
public abstract class BaseMediaChunk extends MediaChunk {

private DefaultTrackOutput trackOutput;
private int firstSampleIndex;
private BaseMediaChunkOutput output;
private int[] firstSampleIndices;

/**
* @param dataSource The source from which the data should be loaded.
Expand All @@ -48,29 +46,29 @@ public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackForm
}

/**
* Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive
* Initializes the chunk for loading, setting the {@link BaseMediaChunkOutput} that will receive
* samples as they are loaded.
*
* @param trackOutput The output that will receive the loaded samples.
* @param output The output that will receive the loaded media samples.
*/
public void init(DefaultTrackOutput trackOutput) {
this.trackOutput = trackOutput;
this.firstSampleIndex = trackOutput.getWriteIndex();
public void init(BaseMediaChunkOutput output) {
this.output = output;
firstSampleIndices = output.getWriteIndices();
}

/**
* Returns the index of the first sample in the output that was passed to
* {@link #init(DefaultTrackOutput)} that will originate from this chunk.
* Returns the index of the first sample in the specified track of the output that will originate
* from this chunk.
*/
public final int getFirstSampleIndex() {
return firstSampleIndex;
public final int getFirstSampleIndex(int trackIndex) {
return firstSampleIndices[trackIndex];
}

/**
* Returns the track output most recently passed to {@link #init(DefaultTrackOutput)}.
* Returns the output most recently passed to {@link #init(BaseMediaChunkOutput)}.
*/
protected final DefaultTrackOutput getTrackOutput() {
return trackOutput;
protected final BaseMediaChunkOutput getOutput() {
return output;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source.chunk;

import android.util.Log;
import com.google.android.exoplayer2.extractor.DefaultTrackOutput;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider;

/**
* An output for {@link BaseMediaChunk}s.
*/
/* package */ final class BaseMediaChunkOutput implements TrackOutputProvider {

private static final String TAG = "BaseMediaChunkOutput";

private final int[] trackTypes;
private final DefaultTrackOutput[] trackOutputs;

/**
* @param trackTypes The track types of the individual track outputs.
* @param trackOutputs The individual track outputs.
*/
public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput... trackOutputs) {
this.trackTypes = trackTypes;
this.trackOutputs = trackOutputs;
}

@Override
public TrackOutput track(int id, int type) {
for (int i = 0; i < trackTypes.length; i++) {
if (type == trackTypes[i]) {
return trackOutputs[i];
}
}
Log.e(TAG, "Unmatched track of type: " + type);
return new DummyTrackOutput();
}

/**
* Returns the current absolute write indices of the individual track outputs.
*/
public int[] getWriteIndices() {
int[] writeIndices = new int[trackOutputs.length];
for (int i = 0; i < trackOutputs.length; i++) {
if (trackOutputs[i] != null) {
writeIndices[i] = trackOutputs[i].getWriteIndex();
}
}
return writeIndices;
}

/**
* Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples
* subsequently written to the track outputs.
*/
public void setSampleOffsetUs(long sampleOffsetUs) {
for (DefaultTrackOutput trackOutput : trackOutputs) {
if (trackOutput != null) {
trackOutput.setSampleOffsetUs(sampleOffsetUs);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.chunk;

import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
Expand All @@ -32,33 +33,46 @@
* <p>
* The wrapper allows switching of the {@link TrackOutput} that receives parsed data.
*/
public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput {
public final class ChunkExtractorWrapper implements ExtractorOutput {

/**
* Provides {@link TrackOutput} instances to be written to by the wrapper.
*/
public interface TrackOutputProvider {

/**
* Called to get the {@link TrackOutput} for a specific track.
* <p>
* The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
*
* @param id A track identifier.
* @param type The type of the track. Typically one of the
* {@link com.google.android.exoplayer2.C} {@code TRACK_TYPE_*} constants.
* @return The {@link TrackOutput} for the given track identifier.
*/
TrackOutput track(int id, int type);

}

public final Extractor extractor;

private final Format manifestFormat;
private final int primaryTrackType;
private final SparseArray<BindingTrackOutput> bindingTrackOutputs;

private boolean extractorInitialized;
private TrackOutput trackOutput;
private TrackOutputProvider trackOutputProvider;
private SeekMap seekMap;
private Format sampleFormat;

// Accessed only on the loader thread.
private boolean seenTrack;
private int seenTrackId;
private Format[] sampleFormats;

/**
* @param extractor The extractor to wrap.
* @param manifestFormat A manifest defined {@link Format} whose data should be merged into any
* sample {@link Format} output from the {@link Extractor}.
* @param primaryTrackType The type of the primary track. Typically one of the {@link C}
* {@code TRACK_TYPE_*} constants.
*/
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat, int primaryTrackType) {
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat) {
this.extractor = extractor;
this.manifestFormat = manifestFormat;
this.primaryTrackType = primaryTrackType;
bindingTrackOutputs = new SparseArray<>();
}

/**
Expand All @@ -69,27 +83,27 @@ public SeekMap getSeekMap() {
}

/**
* Returns the sample {@link Format} most recently output by the extractor, or null.
* Returns the sample {@link Format}s most recently output by the extractor, or null.
*/
public Format getSampleFormat() {
return sampleFormat;
public Format[] getSampleFormats() {
return sampleFormats;
}

/**
* Initializes the extractor to output to the provided {@link TrackOutput}, and configures it to
* receive data from a new chunk.
*
* @param trackOutput The {@link TrackOutput} that will receive sample data.
* @param trackOutputProvider The provider of {@link TrackOutput}s that will receive sample data.
*/
public void init(TrackOutput trackOutput) {
this.trackOutput = trackOutput;
public void init(TrackOutputProvider trackOutputProvider) {
this.trackOutputProvider = trackOutputProvider;
if (!extractorInitialized) {
extractor.init(this);
extractorInitialized = true;
} else {
extractor.seek(0, 0);
if (sampleFormat != null && trackOutput != null) {
trackOutput.format(sampleFormat);
for (int i = 0; i < bindingTrackOutputs.size(); i++) {
bindingTrackOutputs.valueAt(i).bind(trackOutputProvider);
}
}
}
Expand All @@ -98,50 +112,84 @@ public void init(TrackOutput trackOutput) {

@Override
public TrackOutput track(int id, int type) {
if (primaryTrackType != C.TRACK_TYPE_UNKNOWN && primaryTrackType != type) {
return new DummyTrackOutput();
BindingTrackOutput bindingTrackOutput = bindingTrackOutputs.get(id);
if (bindingTrackOutput == null) {
// Assert that if we're seeing a new track we have not seen endTracks.
Assertions.checkState(sampleFormats == null);
bindingTrackOutput = new BindingTrackOutput(id, type, manifestFormat);
bindingTrackOutput.bind(trackOutputProvider);
bindingTrackOutputs.put(id, bindingTrackOutput);
}
Assertions.checkState(!seenTrack || seenTrackId == id);
seenTrack = true;
seenTrackId = id;
return this;
return bindingTrackOutput;
}

@Override
public void endTracks() {
Assertions.checkState(seenTrack);
Format[] sampleFormats = new Format[bindingTrackOutputs.size()];
for (int i = 0; i < bindingTrackOutputs.size(); i++) {
sampleFormats[i] = bindingTrackOutputs.valueAt(i).sampleFormat;
}
this.sampleFormats = sampleFormats;
}

@Override
public void seekMap(SeekMap seekMap) {
this.seekMap = seekMap;
}

// TrackOutput implementation.
// Internal logic.

@Override
public void format(Format format) {
sampleFormat = format.copyWithManifestFormatInfo(manifestFormat);
if (trackOutput != null) {
private static final class BindingTrackOutput implements TrackOutput {

private final int id;
private final int type;
private final Format manifestFormat;

public Format sampleFormat;
private TrackOutput trackOutput;

public BindingTrackOutput(int id, int type, Format manifestFormat) {
this.id = id;
this.type = type;
this.manifestFormat = manifestFormat;
}

public void bind(TrackOutputProvider trackOutputProvider) {
if (trackOutputProvider == null) {
trackOutput = new DummyTrackOutput();
return;
}
trackOutput = trackOutputProvider.track(id, type);
if (trackOutput != null) {
trackOutput.format(sampleFormat);
}
}

@Override
public void format(Format format) {
// TODO: This should only happen for the primary track. Additional metadata/text tracks need
// to be copied with different manifest derived formats.
sampleFormat = format.copyWithManifestFormatInfo(manifestFormat);
trackOutput.format(sampleFormat);
}
}

@Override
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
return trackOutput.sampleData(input, length, allowEndOfInput);
}
@Override
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
return trackOutput.sampleData(input, length, allowEndOfInput);
}

@Override
public void sampleData(ParsableByteArray data, int length) {
trackOutput.sampleData(data, length);
}
@Override
public void sampleData(ParsableByteArray data, int length) {
trackOutput.sampleData(data, length);
}

@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
}

@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
}

}
Loading

0 comments on commit ab8fd14

Please sign in to comment.