Commit ecd48da1 by Oliver Woodman

Multi-track support for DASH.

- With this change, you can select from the individual video formats in
the demo app, as well as the regular "auto" (adaptive) track.
- DashRendererBuilder no longer needs to create MultiTrackChunkSource
instances for the multiple tracks to be exposed.
parent 6cf261ae
......@@ -342,7 +342,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
player.release();
}
public int getPlaybackState() {
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
return STATE_PREPARING;
......
......@@ -22,7 +22,6 @@ import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
......@@ -109,7 +108,7 @@ public class DashRendererBuilder implements ManifestCallback<MediaPresentationDe
if (mimeType.equals(MimeTypes.VIDEO_WEBM)) {
videoChunkSource = new DashChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), manifest.getPeriodDuration(0),
videoRepresentations);
AdaptationSet.TYPE_VIDEO, videoRepresentations);
} else {
throw new IllegalStateException("Unexpected mime type: " + mimeType);
}
......@@ -125,9 +124,8 @@ public class DashRendererBuilder implements ManifestCallback<MediaPresentationDe
audioRenderer = null;
} else {
DataSource audioDataSource = new DefaultUriDataSource(player, bandwidthMeter, userAgent);
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
DashChunkSource audioChunkSource = new DashChunkSource(audioDataSource, audioEvaluator,
manifest.getPeriodDuration(0), audioRepresentation);
DashChunkSource audioChunkSource = new DashChunkSource(audioDataSource, null,
manifest.getPeriodDuration(0), AdaptationSet.TYPE_AUDIO, audioRepresentation);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE);
if ("opus".equals(audioRepresentation.format.codecs)) {
......
......@@ -21,8 +21,6 @@ import static org.mockito.Mockito.when;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.FixedEvaluator;
import com.google.android.exoplayer.chunk.InitializationChunk;
import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
......@@ -53,8 +51,6 @@ import java.util.List;
*/
public class DashChunkSourceTest extends InstrumentationTestCase {
private static final FormatEvaluator EVALUATOR = new FixedEvaluator();
private static final long VOD_DURATION_MS = 30000;
private static final long LIVE_SEGMENT_COUNT = 5;
......@@ -85,8 +81,9 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
}
public void testGetAvailableRangeOnVod() {
DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(), AdaptationSet.TYPE_VIDEO,
null, null, mock(FormatEvaluator.class));
DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(),
DefaultDashTrackSelector.newVideoInstance(null, false, false), null, null);
chunkSource.prepare();
chunkSource.enable(0);
TimeRange availableRange = chunkSource.getAvailableRange();
......@@ -106,7 +103,8 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
public void testGetAvailableRangeOnMultiPeriodVod() {
DashChunkSource chunkSource = new DashChunkSource(buildMultiPeriodVodMpd(),
AdaptationSet.TYPE_VIDEO, null, null, EVALUATOR);
DefaultDashTrackSelector.newVideoInstance(null, false, false), null, null);
chunkSource.prepare();
chunkSource.enable(0);
TimeRange availableRange = chunkSource.getAvailableRange();
checkAvailableRange(availableRange, 0, MULTI_PERIOD_VOD_DURATION_MS * 1000);
......@@ -120,8 +118,10 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
}
public void testSegmentIndexInitializationOnVod() {
DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(), AdaptationSet.TYPE_VIDEO,
null, mock(DataSource.class), EVALUATOR);
DashChunkSource chunkSource = new DashChunkSource(buildVodMpd(),
DefaultDashTrackSelector.newVideoInstance(null, false, false), mock(DataSource.class),
null);
chunkSource.prepare();
chunkSource.enable(0);
List<MediaChunk> queue = new ArrayList<>();
......@@ -232,7 +232,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
private static MediaPresentationDescription buildMpd(long durationMs,
List<Representation> representations, boolean live, boolean limitTimeshiftBuffer) {
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations);
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO, representations);
Period period = new Period(null, 0, Collections.singletonList(adaptationSet));
return new MediaPresentationDescription(AVAILABILITY_START_TIME_MS, durationMs, -1, live, -1,
(limitTimeshiftBuffer) ? LIVE_TIMESHIFT_BUFFER_DEPTH_MS : -1, null, null,
......@@ -259,7 +259,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
long periodDurationMs = VOD_DURATION_MS;
for (int i = 0; i < 2; i++) {
Representation representation = buildVodRepresentation(REGULAR_VIDEO);
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN,
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO,
Collections.singletonList(representation));
Period period = new Period(null, timeMs, Collections.singletonList(adaptationSet));
periods.add(period);
......@@ -288,7 +288,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
long periodDurationMs = LIVE_DURATION_MS;
for (int i = 0; i < MULTI_PERIOD_COUNT; i++) {
Representation representation = buildSegmentTimelineRepresentation(LIVE_DURATION_MS, 0);
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN,
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO,
Collections.singletonList(representation));
Period period = new Period(null, periodStartTimeMs, Collections.singletonList(adaptationSet));
periods.add(period);
......@@ -303,7 +303,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
long periodDurationMs = LIVE_DURATION_MS;
for (int i = 0; i < MULTI_PERIOD_COUNT; i++) {
Representation representation = buildSegmentTemplateRepresentation();
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN,
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_VIDEO,
Collections.singletonList(representation));
Period period = new Period(null, periodStartTimeMs, Collections.singletonList(adaptationSet));
periods.add(period);
......@@ -322,9 +322,10 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
ManifestFetcher<MediaPresentationDescription> manifestFetcher = mock(ManifestFetcher.class);
when(manifestFetcher.getManifest()).thenReturn(mpd);
DashChunkSource chunkSource = new DashChunkSource(manifestFetcher, mpd,
AdaptationSet.TYPE_VIDEO, null, mock(DataSource.class), EVALUATOR,
DefaultDashTrackSelector.newVideoInstance(null, false, false), mock(DataSource.class), null,
new FakeClock(mpd.availabilityStartTime + mpd.duration - ELAPSED_REALTIME_OFFSET_MS),
liveEdgeLatencyMs * 1000, ELAPSED_REALTIME_OFFSET_MS * 1000, startAtLiveEdge, null, null);
chunkSource.prepare();
chunkSource.enable(0);
return chunkSource;
}
......
......@@ -18,6 +18,8 @@ package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer.ExoPlayerComponent;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
import com.google.android.exoplayer.util.Assertions;
import java.io.IOException;
......@@ -26,9 +28,12 @@ import java.util.List;
/**
* A {@link ChunkSource} providing the ability to switch between multiple other {@link ChunkSource}
* instances.
*
* @deprecated {@link DashChunkSource} and {@link SmoothStreamingChunkSource} both support multiple
* tracks directly, so use of this class should not be required. It will be deleted once legacy
* uses have been removed.
*/
// TODO: Expose multiple tracks directly in DashChunkSource and SmoothStreamingChunkSource, and
// delete this class.
@Deprecated
public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
/**
......
/*
* Copyright (C) 2014 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.exoplayer.dash;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.Period;
import java.io.IOException;
/**
* Specifies a track selection from a {@link Period} of a media presentation description.
*/
public interface DashTrackSelector {
/**
* Defines a selector output.
*/
interface Output {
/**
* Outputs an adaptive track, covering the specified representations in the specified
* adaptation set.
*
* @param manifest The media presentation description being processed.
* @param periodIndex The index of the period being processed.
* @param adaptationSetIndex The index of the adaptation set within which the representations
* are located.
* @param representationIndices The indices of the track within the element.
*/
void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex,
int adaptationSetIndex, int[] representationIndices);
/**
* Outputs an fixed track corresponding to the specified representation in the specified
* adaptation set.
*
* @param manifest The media presentation description being processed.
* @param periodIndex The index of the period being processed.
* @param adaptationSetIndex The index of the adaptation set within which the track is located.
* @param representationIndex The index of the representation within the adaptation set.
*/
void fixedTrack(MediaPresentationDescription manifest, int periodIndex, int adaptationSetIndex,
int representationIndex);
}
/**
* Outputs a track selection for a given period.
*
* @param manifest the media presentation description to process.
* @param periodIndex The index of the period to process.
* @param output The output to receive tracks.
* @throws IOException If an error occurs processing the period.
*/
void selectTracks(MediaPresentationDescription manifest, int periodIndex, Output output)
throws IOException;
}
/*
* Copyright (C) 2014 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.exoplayer.dash;
import com.google.android.exoplayer.chunk.VideoFormatSelectorUtil;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.util.Util;
import android.content.Context;
import java.io.IOException;
/**
* A default {@link DashTrackSelector} implementation.
*/
// TODO: Add more configuration options (e.g. ability to disable adaptive track output).
public final class DefaultDashTrackSelector implements DashTrackSelector {
private final int adaptationSetType;
private final Context context;
private final boolean filterVideoRepresentations;
private final boolean filterProtectedHdContent;
/**
* @param context A context. May be null if {@code filterVideoRepresentations == false}.
* @param filterVideoRepresentations Whether video representations should be filtered according to
* the capabilities of the device. It is strongly recommended to set this to {@code true},
* unless the application has already verified that all representations are playable.
* @param filterProtectedHdContent Whether video representations that are both drm protected and
* high definition should be filtered when tracks are built. If
* {@code filterVideoRepresentations == false} then this parameter is ignored.
*/
public static DefaultDashTrackSelector newVideoInstance(Context context,
boolean filterVideoRepresentations, boolean filterProtectedHdContent) {
return new DefaultDashTrackSelector(AdaptationSet.TYPE_VIDEO, context,
filterVideoRepresentations, filterProtectedHdContent);
}
public static DefaultDashTrackSelector newAudioInstance() {
return new DefaultDashTrackSelector(AdaptationSet.TYPE_AUDIO, null, false, false);
}
public static DefaultDashTrackSelector newTextInstance() {
return new DefaultDashTrackSelector(AdaptationSet.TYPE_TEXT, null, false, false);
}
private DefaultDashTrackSelector(int adaptationSetType, Context context,
boolean filterVideoRepresentations, boolean filterProtectedHdContent) {
this.adaptationSetType = adaptationSetType;
this.context = context;
this.filterVideoRepresentations = filterVideoRepresentations;
this.filterProtectedHdContent = filterProtectedHdContent;
}
@Override
public void selectTracks(MediaPresentationDescription manifest, int periodIndex, Output output)
throws IOException {
Period period = manifest.getPeriod(periodIndex);
for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i);
if (adaptationSet.type == adaptationSetType) {
if (adaptationSetType == AdaptationSet.TYPE_VIDEO) {
int[] representations;
if (filterVideoRepresentations) {
representations = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, adaptationSet.representations, null,
filterProtectedHdContent && adaptationSet.hasContentProtection());
} else {
representations = Util.firstIntegersArray(adaptationSet.representations.size());
}
output.adaptiveTrack(manifest, periodIndex, i, representations);
for (int j = 0; j < representations.length; j++) {
output.fixedTrack(manifest, periodIndex, i, representations[j]);
}
} else {
for (int j = 0; j < adaptationSet.representations.size(); j++) {
output.fixedTrack(manifest, periodIndex, i, j);
}
}
}
}
}
}
......@@ -312,6 +312,20 @@ public final class Util {
}
/**
* Creates an integer array containing the integers from 0 to {@code length - 1}.
*
* @param length The length of the array.
* @return The array.
*/
public static int[] firstIntegersArray(int length) {
int[] firstIntegers = new int[length];
for (int i = 0; i < length; i++) {
firstIntegers[i] = i;
}
return firstIntegers;
}
/**
* Parses an xs:duration attribute value, returning the parsed duration in milliseconds.
*
* @param value The attribute value to parse.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment