Enabled EMSG and CEA-608 embedded streams for DASH

Issue: #2362
Issue: #2176

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=149524412
This commit is contained in:
olly 2017-03-08 04:09:05 -08:00 committed by Oliver Woodman
parent 99e19a92af
commit 09471defd7
4 changed files with 283 additions and 113 deletions

View file

@ -35,7 +35,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOut
* @param trackTypes The track types of the individual track outputs. * @param trackTypes The track types of the individual track outputs.
* @param trackOutputs The individual track outputs. * @param trackOutputs The individual track outputs.
*/ */
public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput... trackOutputs) { public BaseMediaChunkOutput(int[] trackTypes, DefaultTrackOutput[] trackOutputs) {
this.trackTypes = trackTypes; this.trackTypes = trackTypes;
this.trackOutputs = trackOutputs; this.trackOutputs = trackOutputs;
} }

View file

@ -33,31 +33,34 @@ import java.util.List;
/** /**
* A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}. * A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}.
* May also be configured to expose additional embedded {@link SampleStream}s.
*/ */
public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, SequenceableLoader, public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, SequenceableLoader,
Loader.Callback<Chunk> { Loader.Callback<Chunk> {
private final int trackType; private final int primaryTrackType;
private final int[] embeddedTrackTypes;
private final T chunkSource; private final T chunkSource;
private final SequenceableLoader.Callback<ChunkSampleStream<T>> callback; private final SequenceableLoader.Callback<ChunkSampleStream<T>> callback;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final Loader loader;
private final ChunkHolder nextChunkHolder;
private final LinkedList<BaseMediaChunk> mediaChunks; private final LinkedList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> readOnlyMediaChunks; private final List<BaseMediaChunk> readOnlyMediaChunks;
private final DefaultTrackOutput primarySampleQueue;
private final EmbeddedSampleStream[] embeddedSampleStreams;
private final BaseMediaChunkOutput mediaChunkOutput; private final BaseMediaChunkOutput mediaChunkOutput;
private final DefaultTrackOutput sampleQueue;
private final ChunkHolder nextChunkHolder;
private final Loader loader;
private Format downstreamTrackFormat; private Format primaryDownstreamTrackFormat;
private long lastSeekPositionUs;
private long pendingResetPositionUs; private long pendingResetPositionUs;
/* package */ long lastSeekPositionUs;
private boolean loadingFinished; /* package */ boolean loadingFinished;
/** /**
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. * @param primaryTrackType The type of the primary track. One of the {@link C}
* {@code TRACK_TYPE_*} constants.
* @param embeddedTrackTypes The types of any embedded tracks, or null.
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
* @param callback An {@link Callback} for the stream. * @param callback An {@link Callback} for the stream.
* @param allocator An {@link Allocator} from which allocations can be obtained. * @param allocator An {@link Allocator} from which allocations can be obtained.
@ -66,10 +69,11 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
* before propagating an error. * before propagating an error.
* @param eventDispatcher A dispatcher to notify of events. * @param eventDispatcher A dispatcher to notify of events.
*/ */
public ChunkSampleStream(int trackType, T chunkSource, public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunkSource,
SequenceableLoader.Callback<ChunkSampleStream<T>> callback, Allocator allocator, Callback<ChunkSampleStream<T>> callback, Allocator allocator, long positionUs,
long positionUs, int minLoadableRetryCount, EventDispatcher eventDispatcher) { int minLoadableRetryCount, EventDispatcher eventDispatcher) {
this.trackType = trackType; this.primaryTrackType = primaryTrackType;
this.embeddedTrackTypes = embeddedTrackTypes;
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.callback = callback; this.callback = callback;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
@ -78,16 +82,56 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
nextChunkHolder = new ChunkHolder(); nextChunkHolder = new ChunkHolder();
mediaChunks = new LinkedList<>(); mediaChunks = new LinkedList<>();
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
sampleQueue = new DefaultTrackOutput(allocator);
mediaChunkOutput = new BaseMediaChunkOutput(new int[] {trackType}, sampleQueue); int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length;
lastSeekPositionUs = positionUs; embeddedSampleStreams = newEmbeddedSampleStreamArray(embeddedTrackCount);
int[] trackTypes = new int[1 + embeddedTrackCount];
DefaultTrackOutput[] sampleQueues = new DefaultTrackOutput[1 + embeddedTrackCount];
primarySampleQueue = new DefaultTrackOutput(allocator);
trackTypes[0] = primaryTrackType;
sampleQueues[0] = primarySampleQueue;
for (int i = 0; i < embeddedTrackCount; i++) {
trackTypes[i + 1] = embeddedTrackTypes[i];
sampleQueues[i + 1] = new DefaultTrackOutput(allocator);
embeddedSampleStreams[i] = new EmbeddedSampleStream(sampleQueues[i + 1]);
}
mediaChunkOutput = new BaseMediaChunkOutput(trackTypes, sampleQueues);
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
lastSeekPositionUs = positionUs;
}
/**
* Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}.
*/
public static boolean isPrimarySampleStream(SampleStream sampleStream) {
return sampleStream instanceof ChunkSampleStream;
}
/**
* Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}.
*/
public static boolean isEmbeddedSampleStream(SampleStream sampleStream) {
return sampleStream instanceof ChunkSampleStream.EmbeddedSampleStream;
}
/**
* Returns the {@link SampleStream} for the embedded track with the specified type.
*/
public SampleStream getEmbeddedSampleStream(int trackType) {
for (int i = 0; i < embeddedTrackTypes.length; i++) {
if (embeddedTrackTypes[i] == trackType) {
return embeddedSampleStreams[i];
}
}
// Should never happen.
throw new IllegalStateException();
} }
/** /**
* Returns the {@link ChunkSource} used by this stream. * Returns the {@link ChunkSource} used by this stream.
*
* @return The {@link ChunkSource}.
*/ */
public T getChunkSource() { public T getChunkSource() {
return chunkSource; return chunkSource;
@ -112,7 +156,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (lastCompletedMediaChunk != null) { if (lastCompletedMediaChunk != null) {
bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs);
} }
return Math.max(bufferedPositionUs, sampleQueue.getLargestQueuedTimestampUs()); return Math.max(bufferedPositionUs, primarySampleQueue.getLargestQueuedTimestampUs());
} }
} }
@ -123,15 +167,21 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
*/ */
public void seekToUs(long positionUs) { public void seekToUs(long positionUs) {
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
// If we're not pending a reset, see if we can seek within the sample queue. // If we're not pending a reset, see if we can seek within the primary sample queue.
boolean seekInsideBuffer = !isPendingReset() boolean seekInsideBuffer = !isPendingReset() && primarySampleQueue.skipToKeyframeBefore(
&& sampleQueue.skipToKeyframeBefore(positionUs, positionUs < getNextLoadPositionUs()); positionUs, positionUs < getNextLoadPositionUs());
if (seekInsideBuffer) { if (seekInsideBuffer) {
// We succeeded. All we need to do is discard any chunks that we've moved past. // We succeeded. We need to discard any chunks that we've moved past and perform the seek for
// any embedded streams as well.
while (mediaChunks.size() > 1 while (mediaChunks.size() > 1
&& mediaChunks.get(1).getFirstSampleIndex(0) <= sampleQueue.getReadIndex()) { && mediaChunks.get(1).getFirstSampleIndex(0) <= primarySampleQueue.getReadIndex()) {
mediaChunks.removeFirst(); mediaChunks.removeFirst();
} }
// TODO: For this to work correctly, the embedded streams must not discard anything from their
// sample queues beyond the current read position of the primary stream.
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.skipToKeyframeBefore(positionUs);
}
} else { } else {
// We failed, and need to restart. // We failed, and need to restart.
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
@ -140,7 +190,10 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (loader.isLoading()) { if (loader.isLoading()) {
loader.cancelLoading(); loader.cancelLoading();
} else { } else {
sampleQueue.reset(true); primarySampleQueue.reset(true);
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.reset(true);
}
} }
} }
} }
@ -151,7 +204,10 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
* This method should be called when the stream is no longer required. * This method should be called when the stream is no longer required.
*/ */
public void release() { public void release() {
sampleQueue.disable(); primarySampleQueue.disable();
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.disable();
}
loader.release(); loader.release();
} }
@ -159,7 +215,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override @Override
public boolean isReady() { public boolean isReady() {
return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty()); return loadingFinished || (!isPendingReset() && !primarySampleQueue.isEmpty());
} }
@Override @Override
@ -176,27 +232,15 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (isPendingReset()) { if (isPendingReset()) {
return C.RESULT_NOTHING_READ; return C.RESULT_NOTHING_READ;
} }
// TODO: For embedded streams that aren't being used, we need to drain their queues here.
while (mediaChunks.size() > 1 discardDownstreamMediaChunks(primarySampleQueue.getReadIndex());
&& mediaChunks.get(1).getFirstSampleIndex(0) <= sampleQueue.getReadIndex()) { return primarySampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
mediaChunks.removeFirst();
}
BaseMediaChunk currentChunk = mediaChunks.getFirst();
Format trackFormat = currentChunk.trackFormat;
if (!trackFormat.equals(downstreamTrackFormat)) {
eventDispatcher.downstreamFormatChanged(trackType, trackFormat,
currentChunk.trackSelectionReason, currentChunk.trackSelectionData,
currentChunk.startTimeUs);
}
downstreamTrackFormat = trackFormat;
return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
lastSeekPositionUs); lastSeekPositionUs);
} }
@Override @Override
public void skipToKeyframeBefore(long timeUs) { public void skipToKeyframeBefore(long timeUs) {
sampleQueue.skipToKeyframeBefore(timeUs); primarySampleQueue.skipToKeyframeBefore(timeUs);
} }
// Loader.Callback implementation. // Loader.Callback implementation.
@ -204,20 +248,25 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override @Override
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
chunkSource.onChunkLoadCompleted(loadable); chunkSource.onChunkLoadCompleted(loadable);
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, primaryTrackType,
loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs,
loadable.bytesLoaded());
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
@Override @Override
public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
boolean released) { boolean released) {
eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, primaryTrackType,
loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs,
loadable.bytesLoaded());
if (!released) { if (!released) {
sampleQueue.reset(true); primarySampleQueue.reset(true);
for (EmbeddedSampleStream embeddedStream : embeddedSampleStreams) {
embeddedStream.reset(true);
}
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
} }
@ -234,16 +283,19 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (isMediaChunk) { if (isMediaChunk) {
BaseMediaChunk removed = mediaChunks.removeLast(); BaseMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == loadable); Assertions.checkState(removed == loadable);
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0));
for (int i = 0; i < embeddedSampleStreams.length; i++) {
embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
}
if (mediaChunks.isEmpty()) { if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs; pendingResetPositionUs = lastSeekPositionUs;
} }
} }
} }
eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, eventDispatcher.loadError(loadable.dataSpec, loadable.type, primaryTrackType,
loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData,
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, error, loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded,
canceled); error, canceled);
if (canceled) { if (canceled) {
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
return Loader.DONT_RETRY; return Loader.DONT_RETRY;
@ -283,9 +335,9 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
mediaChunks.add(mediaChunk); mediaChunks.add(mediaChunk);
} }
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, primaryTrackType,
loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, loadable.trackFormat, loadable.trackSelectionReason, loadable.trackSelectionData,
loadable.endTimeUs, elapsedRealtimeMs); loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs);
return true; return true;
} }
@ -316,10 +368,25 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
return chunk instanceof BaseMediaChunk; return chunk instanceof BaseMediaChunk;
} }
private boolean isPendingReset() { /* package */ boolean isPendingReset() {
return pendingResetPositionUs != C.TIME_UNSET; return pendingResetPositionUs != C.TIME_UNSET;
} }
private void discardDownstreamMediaChunks(int primaryStreamReadIndex) {
while (mediaChunks.size() > 1
&& mediaChunks.get(1).getFirstSampleIndex(0) <= primaryStreamReadIndex) {
mediaChunks.removeFirst();
}
BaseMediaChunk currentChunk = mediaChunks.getFirst();
Format trackFormat = currentChunk.trackFormat;
if (!trackFormat.equals(primaryDownstreamTrackFormat)) {
eventDispatcher.downstreamFormatChanged(primaryTrackType, trackFormat,
currentChunk.trackSelectionReason, currentChunk.trackSelectionData,
currentChunk.startTimeUs);
}
primaryDownstreamTrackFormat = trackFormat;
}
/** /**
* Discard upstream media chunks until the queue length is equal to the length specified. * Discard upstream media chunks until the queue length is equal to the length specified.
* *
@ -332,16 +399,71 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
} }
long startTimeUs = 0; long startTimeUs = 0;
long endTimeUs = mediaChunks.getLast().endTimeUs; long endTimeUs = mediaChunks.getLast().endTimeUs;
BaseMediaChunk removed = null; BaseMediaChunk removed = null;
while (mediaChunks.size() > queueLength) { while (mediaChunks.size() > queueLength) {
removed = mediaChunks.removeLast(); removed = mediaChunks.removeLast();
startTimeUs = removed.startTimeUs; startTimeUs = removed.startTimeUs;
loadingFinished = false; loadingFinished = false;
} }
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0)); primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0));
eventDispatcher.upstreamDiscarded(trackType, startTimeUs, endTimeUs); for (int i = 0; i < embeddedSampleStreams.length; i++) {
embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
}
eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs);
return true; return true;
} }
@SuppressWarnings("unchecked")
private static <T extends ChunkSource> ChunkSampleStream<T>.EmbeddedSampleStream[]
newEmbeddedSampleStreamArray(int length) {
return new ChunkSampleStream.EmbeddedSampleStream[length];
}
private final class EmbeddedSampleStream implements SampleStream {
private final DefaultTrackOutput sampleQueue;
public EmbeddedSampleStream(DefaultTrackOutput sampleQueue) {
this.sampleQueue = sampleQueue;
}
@Override
public boolean isReady() {
return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty());
}
@Override
public void skipToKeyframeBefore(long timeUs) {
sampleQueue.skipToKeyframeBefore(timeUs);
}
@Override
public void maybeThrowError() throws IOException {
// Do nothing. Errors will be thrown from the primary stream.
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
lastSeekPositionUs);
}
public void reset(boolean enable) {
sampleQueue.reset(enable);
}
public void disable() {
sampleQueue.disable();
}
public void discardUpstreamSamples(int discardFromIndex) {
sampleQueue.discardUpstreamSamples(discardFromIndex);
}
}
} }

View file

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.dash; package com.google.android.exoplayer2.source.dash;
import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
@ -35,7 +36,8 @@ import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
@ -52,6 +54,7 @@ import java.util.List;
private final LoaderErrorThrower manifestLoaderErrorThrower; private final LoaderErrorThrower manifestLoaderErrorThrower;
private final Allocator allocator; private final Allocator allocator;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private final EmbeddedTrackInfo[] embeddedTrackInfos;
private Callback callback; private Callback callback;
private ChunkSampleStream<DashChunkSource>[] sampleStreams; private ChunkSampleStream<DashChunkSource>[] sampleStreams;
@ -76,7 +79,9 @@ import java.util.List;
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
trackGroups = buildTrackGroups(adaptationSets); Pair<TrackGroupArray, EmbeddedTrackInfo[]> result = buildTrackGroups(adaptationSets);
trackGroups = result.first;
embeddedTrackInfos = result.second;
} }
public void updateManifest(DashManifest manifest, int periodIndex) { public void updateManifest(DashManifest manifest, int periodIndex) {
@ -116,37 +121,59 @@ import java.util.List;
@Override @Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
ArrayList<ChunkSampleStream<DashChunkSource>> sampleStreamsList = new ArrayList<>(); int adaptationSetCount = adaptationSets.size();
HashMap<Integer, ChunkSampleStream<DashChunkSource>> primarySampleStreams = new HashMap<>();
// First pass for primary tracks.
for (int i = 0; i < selections.length; i++) { for (int i = 0; i < selections.length; i++) {
if (streams[i] instanceof ChunkSampleStream) { if (ChunkSampleStream.isPrimarySampleStream(streams[i])) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ChunkSampleStream<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) streams[i]; ChunkSampleStream<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) streams[i];
if (selections[i] == null || !mayRetainStreamFlags[i]) { if (selections[i] == null || !mayRetainStreamFlags[i]) {
stream.release(); stream.release();
streams[i] = null; streams[i] = null;
} else { } else {
sampleStreamsList.add(stream); int adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup());
primarySampleStreams.put(adaptationSetIndex, stream);
} }
} else if (streams[i] instanceof EmptySampleStream && selections[i] == null) { }
// TODO: Release streams for cea-608 and emsg tracks. if (streams[i] == null && selections[i] != null) {
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
if (trackGroupIndex < adaptationSetCount) {
ChunkSampleStream<DashChunkSource> stream = buildSampleStream(trackGroupIndex,
selections[i], positionUs);
primarySampleStreams.put(trackGroupIndex, stream);
streams[i] = stream;
streamResetFlags[i] = true;
}
}
}
// Second pass for embedded tracks.
for (int i = 0; i < selections.length; i++) {
if (ChunkSampleStream.isEmbeddedSampleStream(streams[i])) {
// Always clear even if the selection is unchanged, since the parent primary sample stream
// may have been replaced.
streams[i] = null; streams[i] = null;
} }
if (streams[i] == null && selections[i] != null) { if (streams[i] == null && selections[i] != null) {
int adaptationSetIndex = trackGroups.indexOf(selections[i].getTrackGroup()); int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
if (adaptationSetIndex < adaptationSets.size()) { if (trackGroupIndex >= adaptationSetCount) {
ChunkSampleStream<DashChunkSource> stream = buildSampleStream(adaptationSetIndex, EmbeddedTrackInfo embeddedTrackInfo =
selections[i], positionUs); embeddedTrackInfos[trackGroupIndex - adaptationSetCount];
sampleStreamsList.add(stream); int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex;
streams[i] = stream; ChunkSampleStream<DashChunkSource> primarySampleStream =
} else { primarySampleStreams.get(adaptationSetIndex);
// TODO: Output streams for cea-608 and emsg tracks. if (primarySampleStream != null) {
streams[i] = new EmptySampleStream(); streams[i] = primarySampleStream.getEmbeddedSampleStream(embeddedTrackInfo.trackType);
} else {
// The primary track in which this one is embedded is not selected.
streams[i] = new EmptySampleStream();
}
streamResetFlags[i] = true;
} }
streamResetFlags[i] = true;
} }
} }
sampleStreams = newSampleStreamArray(sampleStreamsList.size()); sampleStreams = newSampleStreamArray(primarySampleStreams.size());
sampleStreamsList.toArray(sampleStreams); primarySampleStreams.values().toArray(sampleStreams);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
return positionUs; return positionUs;
} }
@ -195,14 +222,14 @@ import java.util.List;
// Internal methods. // Internal methods.
private static TrackGroupArray buildTrackGroups(List<AdaptationSet> adaptationSets) { private static Pair<TrackGroupArray, EmbeddedTrackInfo[]> buildTrackGroups(
List<AdaptationSet> adaptationSets) {
int adaptationSetCount = adaptationSets.size(); int adaptationSetCount = adaptationSets.size();
int eventMessageTrackCount = getEventMessageTrackCount(adaptationSets); int embeddedTrackCount = getEmbeddedTrackCount(adaptationSets);
int cea608TrackCount = getCea608TrackCount(adaptationSets); TrackGroup[] trackGroupArray = new TrackGroup[adaptationSetCount + embeddedTrackCount];
TrackGroup[] trackGroupArray = new TrackGroup[adaptationSetCount + eventMessageTrackCount EmbeddedTrackInfo[] embeddedTrackInfos = new EmbeddedTrackInfo[embeddedTrackCount];
+ cea608TrackCount];
int eventMessageTrackIndex = 0; int embeddedTrackIndex = 0;
int cea608TrackIndex = 0;
for (int i = 0; i < adaptationSetCount; i++) { for (int i = 0; i < adaptationSetCount; i++) {
AdaptationSet adaptationSet = adaptationSets.get(i); AdaptationSet adaptationSet = adaptationSets.get(i);
List<Representation> representations = adaptationSet.representations; List<Representation> representations = adaptationSet.representations;
@ -214,38 +241,57 @@ import java.util.List;
if (hasEventMessageTrack(adaptationSet)) { if (hasEventMessageTrack(adaptationSet)) {
Format format = Format.createSampleFormat(adaptationSet.id + ":emsg", Format format = Format.createSampleFormat(adaptationSet.id + ":emsg",
MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null); MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
trackGroupArray[adaptationSetCount + eventMessageTrackIndex++] = new TrackGroup(format); trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format);
embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_METADATA);
} }
if (hasCea608Track(adaptationSet)) { if (hasCea608Track(adaptationSet)) {
Format format = Format.createTextSampleFormat(adaptationSet.id + ":cea608", Format format = Format.createTextSampleFormat(adaptationSet.id + ":cea608",
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null); MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null);
trackGroupArray[adaptationSetCount + eventMessageTrackCount + cea608TrackIndex++] = trackGroupArray[adaptationSetCount + embeddedTrackIndex] = new TrackGroup(format);
new TrackGroup(format); embeddedTrackInfos[embeddedTrackIndex++] = new EmbeddedTrackInfo(i, C.TRACK_TYPE_TEXT);
} }
} }
return new TrackGroupArray(trackGroupArray);
return Pair.create(new TrackGroupArray(trackGroupArray), embeddedTrackInfos);
} }
private ChunkSampleStream<DashChunkSource> buildSampleStream(int adaptationSetIndex, private ChunkSampleStream<DashChunkSource> buildSampleStream(int adaptationSetIndex,
TrackSelection selection, long positionUs) { TrackSelection selection, long positionUs) {
AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex);
int embeddedTrackCount = 0;
int[] embeddedTrackTypes = new int[2];
boolean enableEventMessageTrack = hasEventMessageTrack(adaptationSet); boolean enableEventMessageTrack = hasEventMessageTrack(adaptationSet);
if (enableEventMessageTrack) {
embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_METADATA;
}
boolean enableCea608Track = hasCea608Track(adaptationSet); boolean enableCea608Track = hasCea608Track(adaptationSet);
if (enableCea608Track) {
embeddedTrackTypes[embeddedTrackCount++] = C.TRACK_TYPE_TEXT;
}
if (embeddedTrackCount < embeddedTrackTypes.length) {
embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount);
}
DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource(
manifestLoaderErrorThrower, manifest, periodIndex, adaptationSetIndex, selection, manifestLoaderErrorThrower, manifest, periodIndex, adaptationSetIndex, selection,
elapsedRealtimeOffset, enableEventMessageTrack, enableCea608Track); elapsedRealtimeOffset, enableEventMessageTrack, enableCea608Track);
return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs, ChunkSampleStream<DashChunkSource> stream = new ChunkSampleStream<>(adaptationSet.type,
minLoadableRetryCount, eventDispatcher); embeddedTrackTypes, chunkSource, this, allocator, positionUs, minLoadableRetryCount,
eventDispatcher);
return stream;
} }
private static int getEventMessageTrackCount(List<AdaptationSet> adaptationSets) { private static int getEmbeddedTrackCount(List<AdaptationSet> adaptationSets) {
int inbandEventStreamTrackCount = 0; int embeddedTrackCount = 0;
for (int i = 0; i < adaptationSets.size(); i++) { for (int i = 0; i < adaptationSets.size(); i++) {
if (hasEventMessageTrack(adaptationSets.get(i))) { AdaptationSet adaptationSet = adaptationSets.get(i);
inbandEventStreamTrackCount++; if (hasEventMessageTrack(adaptationSet)) {
embeddedTrackCount++;
}
if (hasCea608Track(adaptationSet)) {
embeddedTrackCount++;
} }
} }
return inbandEventStreamTrackCount; return embeddedTrackCount;
} }
private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) { private static boolean hasEventMessageTrack(AdaptationSet adaptationSet) {
@ -259,16 +305,6 @@ import java.util.List;
return false; return false;
} }
private static int getCea608TrackCount(List<AdaptationSet> adaptationSets) {
int cea608TrackCount = 0;
for (int i = 0; i < adaptationSets.size(); i++) {
if (hasCea608Track(adaptationSets.get(i))) {
cea608TrackCount++;
}
}
return cea608TrackCount;
}
private static boolean hasCea608Track(AdaptationSet adaptationSet) { private static boolean hasCea608Track(AdaptationSet adaptationSet) {
List<SchemeValuePair> descriptors = adaptationSet.accessibilityDescriptors; List<SchemeValuePair> descriptors = adaptationSet.accessibilityDescriptors;
for (int i = 0; i < descriptors.size(); i++) { for (int i = 0; i < descriptors.size(); i++) {
@ -285,4 +321,16 @@ import java.util.List;
return new ChunkSampleStream[length]; return new ChunkSampleStream[length];
} }
private static final class EmbeddedTrackInfo {
public final int adaptationSetIndex;
public final int trackType;
public EmbeddedTrackInfo(int adaptationSetIndex, int trackType) {
this.adaptationSetIndex = adaptationSetIndex;
this.trackType = trackType;
}
}
} }

View file

@ -185,8 +185,8 @@ import java.util.ArrayList;
int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup()); int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup());
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower, SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower,
manifest, streamElementIndex, selection, trackEncryptionBoxes); manifest, streamElementIndex, selection, trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource, return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, null,
this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); chunkSource, this, allocator, positionUs, minLoadableRetryCount, eventDispatcher);
} }
private static TrackGroupArray buildTrackGroups(SsManifest manifest) { private static TrackGroupArray buildTrackGroups(SsManifest manifest) {