Add RepresentationKey and DashManifest copy method

RepresentationKey defines a representation location in a DashManifest.
DashManifest copy method creates a copy of the manifest which includes
only the representations pointed by the given RepresentationKeys.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=150195990
This commit is contained in:
eguven 2017-03-15 08:47:48 -07:00 committed by Oliver Woodman
parent f092c4446f
commit 76c9968211
3 changed files with 341 additions and 0 deletions

View File

@ -0,0 +1,197 @@
/*
* 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.dash.manifest;
import android.net.Uri;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import junit.framework.TestCase;
/**
* Unit tests for {@link DashManifest}.
*/
public class DashManifestTest extends TestCase {
private static final UtcTimingElement DUMMY_UTC_TIMING = new UtcTimingElement("", "");
private static final List<SchemeValuePair> DUMMY_ACCESSIBILITY_DESCRIPTORS =
Collections.emptyList();
private static final SingleSegmentBase DUMMY_SEGMENT_BASE = new SingleSegmentBase();
private static final Format DUMMY_FORMAT = Format.createSampleFormat("", "", 0);
public void testCopy() throws Exception {
Representation[][][] representations = newRepresentations(3, 2, 3);
DashManifest sourceManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0]),
newAdaptationSet(3, representations[0][1])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0]),
newAdaptationSet(6, representations[1][1])),
newPeriod("7", 7,
newAdaptationSet(8, representations[2][0]),
newAdaptationSet(9, representations[2][1])));
List<RepresentationKey> keys = Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(0, 0, 1),
new RepresentationKey(0, 1, 2),
new RepresentationKey(1, 0, 1),
new RepresentationKey(1, 1, 0),
new RepresentationKey(1, 1, 2),
new RepresentationKey(2, 0, 1),
new RepresentationKey(2, 0, 2),
new RepresentationKey(2, 1, 0));
// Keys don't need to be in any particular order
Collections.shuffle(keys, new Random(0));
DashManifest copyManifest = sourceManifest.copy(keys);
DashManifest expectedManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0][0], representations[0][0][1]),
newAdaptationSet(3, representations[0][1][2])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0][1]),
newAdaptationSet(6, representations[1][1][0], representations[1][1][2])),
newPeriod("7", 7,
newAdaptationSet(8, representations[2][0][1], representations[2][0][2]),
newAdaptationSet(9, representations[2][1][0])));
assertManifestEquals(expectedManifest, copyManifest);
}
public void testCopySameAdaptationIndexButDifferentPeriod() throws Exception {
Representation[][][] representations = newRepresentations(2, 1, 1);
DashManifest sourceManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0])));
DashManifest copyManifest = sourceManifest.copy(Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(1, 0, 0)));
DashManifest expectedManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0])));
assertManifestEquals(expectedManifest, copyManifest);
}
public void testCopySkipPeriod() throws Exception {
Representation[][][] representations = newRepresentations(3, 2, 3);
DashManifest sourceManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0]),
newAdaptationSet(3, representations[0][1])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0]),
newAdaptationSet(6, representations[1][1])),
newPeriod("7", 7,
newAdaptationSet(8, representations[2][0]),
newAdaptationSet(9, representations[2][1])));
DashManifest copyManifest = sourceManifest.copy(Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(0, 0, 1),
new RepresentationKey(0, 1, 2),
new RepresentationKey(2, 0, 1),
new RepresentationKey(2, 0, 2),
new RepresentationKey(2, 1, 0)));
DashManifest expectedManifest = newDashManifest(7,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0][0], representations[0][0][1]),
newAdaptationSet(3, representations[0][1][2])),
newPeriod("7", 4,
newAdaptationSet(8, representations[2][0][1], representations[2][0][2]),
newAdaptationSet(9, representations[2][1][0])));
assertManifestEquals(expectedManifest, copyManifest);
}
private static void assertManifestEquals(DashManifest expected, DashManifest actual) {
assertEquals(expected.availabilityStartTime, actual.availabilityStartTime);
assertEquals(expected.duration, actual.duration);
assertEquals(expected.minBufferTime, actual.minBufferTime);
assertEquals(expected.dynamic, actual.dynamic);
assertEquals(expected.minUpdatePeriod, actual.minUpdatePeriod);
assertEquals(expected.timeShiftBufferDepth, actual.timeShiftBufferDepth);
assertEquals(expected.suggestedPresentationDelay, actual.suggestedPresentationDelay);
assertEquals(expected.utcTiming, actual.utcTiming);
assertEquals(expected.location, actual.location);
assertEquals(expected.getPeriodCount(), actual.getPeriodCount());
for (int i = 0; i < expected.getPeriodCount(); i++) {
Period expectedPeriod = expected.getPeriod(i);
Period actualPeriod = actual.getPeriod(i);
assertEquals(expectedPeriod.id, actualPeriod.id);
assertEquals(expectedPeriod.startMs, actualPeriod.startMs);
List<AdaptationSet> expectedAdaptationSets = expectedPeriod.adaptationSets;
List<AdaptationSet> actualAdaptationSets = actualPeriod.adaptationSets;
assertEquals(expectedAdaptationSets.size(), actualAdaptationSets.size());
for (int j = 0; j < expectedAdaptationSets.size(); j++) {
AdaptationSet expectedAdaptationSet = expectedAdaptationSets.get(j);
AdaptationSet actualAdaptationSet = actualAdaptationSets.get(j);
assertEquals(expectedAdaptationSet.id, actualAdaptationSet.id);
assertEquals(expectedAdaptationSet.type, actualAdaptationSet.type);
assertEquals(expectedAdaptationSet.accessibilityDescriptors,
actualAdaptationSet.accessibilityDescriptors);
assertEquals(expectedAdaptationSet.representations, actualAdaptationSet.representations);
}
}
}
private static Representation[][][] newRepresentations(int periodCount, int adaptationSetCounts,
int representationCounts) {
Representation[][][] representations = new Representation[periodCount][][];
for (int i = 0; i < periodCount; i++) {
representations[i] = new Representation[adaptationSetCounts][];
for (int j = 0; j < adaptationSetCounts; j++) {
representations[i][j] = new Representation[representationCounts];
for (int k = 0; k < representationCounts; k++) {
representations[i][j][k] = newRepresentation();
}
}
}
return representations;
}
private static Representation newRepresentation() {
return Representation.newInstance("", 0, DUMMY_FORMAT, "", DUMMY_SEGMENT_BASE);
}
private static DashManifest newDashManifest(int duration, Period... periods) {
return new DashManifest(0, duration, 1, false, 2, 3, 4, DUMMY_UTC_TIMING, Uri.EMPTY,
Arrays.asList(periods));
}
private static Period newPeriod(String id, int startMs, AdaptationSet... adaptationSets) {
return new Period(id, startMs, Arrays.asList(adaptationSets));
}
private static AdaptationSet newAdaptationSet(int seed, Representation... representations) {
return new AdaptationSet(++seed, ++seed, Arrays.asList(representations),
DUMMY_ACCESSIBILITY_DESCRIPTORS);
}
}

View File

@ -17,7 +17,9 @@ package com.google.android.exoplayer2.source.dash.manifest;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
@ -79,4 +81,64 @@ public class DashManifest {
return C.msToUs(getPeriodDurationMs(index));
}
/**
* Creates a copy of this manifest which includes only the representations identified by the given
* keys.
*
* @param representationKeys List of keys for the representations to be included in the copy.
* @return A copy of this manifest with the selected representations.
* @throws IndexOutOfBoundsException If a key has an invalid index.
*/
public final DashManifest copy(List<RepresentationKey> representationKeys) {
LinkedList<RepresentationKey> keys = new LinkedList<>(representationKeys);
Collections.sort(keys);
keys.add(new RepresentationKey(-1, -1, -1)); // Add a stopper key to the end
ArrayList<Period> copyPeriods = new ArrayList<>();
long shiftMs = 0;
for (int periodIndex = 0; periodIndex < getPeriodCount(); periodIndex++) {
if (keys.peek().periodIndex != periodIndex) {
// No representations selected in this period.
long periodDurationMs = getPeriodDurationMs(periodIndex);
if (periodDurationMs != C.TIME_UNSET) {
shiftMs += periodDurationMs;
}
} else {
Period period = getPeriod(periodIndex);
ArrayList<AdaptationSet> copyAdaptationSets =
copyAdaptationSets(period.adaptationSets, keys);
copyPeriods.add(new Period(period.id, period.startMs - shiftMs, copyAdaptationSets));
}
}
long newDuration = duration != C.TIME_UNSET ? duration - shiftMs : C.TIME_UNSET;
return new DashManifest(availabilityStartTime, newDuration, minBufferTime, dynamic,
minUpdatePeriod, timeShiftBufferDepth, suggestedPresentationDelay, utcTiming, location,
copyPeriods);
}
private static ArrayList<AdaptationSet> copyAdaptationSets(
List<AdaptationSet> adaptationSets, LinkedList<RepresentationKey> keys) {
RepresentationKey key = keys.poll();
int periodIndex = key.periodIndex;
ArrayList<AdaptationSet> copyAdaptationSets = new ArrayList<>();
do {
int adaptationSetIndex = key.adaptationSetIndex;
AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex);
List<Representation> representations = adaptationSet.representations;
ArrayList<Representation> copyRepresentations = new ArrayList<>();
do {
Representation representation = representations.get(key.representationIndex);
copyRepresentations.add(representation);
key = keys.poll();
} while(key.periodIndex == periodIndex && key.adaptationSetIndex == adaptationSetIndex);
copyAdaptationSets.add(new AdaptationSet(adaptationSet.id, adaptationSet.type,
copyRepresentations, adaptationSet.accessibilityDescriptors));
} while(key.periodIndex == periodIndex);
// Add back the last key which doesn't belong to the period being processed
keys.addFirst(key);
return copyAdaptationSets;
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.dash.manifest;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Uniquely identifies a {@link Representation} in a {@link DashManifest}.
*/
public final class RepresentationKey implements Parcelable, Comparable<RepresentationKey> {
public final int periodIndex;
public final int adaptationSetIndex;
public final int representationIndex;
public RepresentationKey(int periodIndex, int adaptationSetIndex, int representationIndex) {
this.periodIndex = periodIndex;
this.adaptationSetIndex = adaptationSetIndex;
this.representationIndex = representationIndex;
}
@Override
public String toString() {
return periodIndex + "." + adaptationSetIndex + "." + representationIndex;
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(periodIndex);
dest.writeInt(adaptationSetIndex);
dest.writeInt(representationIndex);
}
public static final Creator<RepresentationKey> CREATOR =
new Creator<RepresentationKey>() {
@Override
public RepresentationKey createFromParcel(Parcel in) {
return new RepresentationKey(in.readInt(), in.readInt(), in.readInt());
}
@Override
public RepresentationKey[] newArray(int size) {
return new RepresentationKey[size];
}
};
// Comparable implementation.
@Override
public int compareTo(RepresentationKey o) {
int result = periodIndex - o.periodIndex;
if (result == 0) {
result = adaptationSetIndex - o.adaptationSetIndex;
if (result == 0) {
result = representationIndex - o.representationIndex;
}
}
return result;
}
}