Parse ServiceDescription from DASH manifest

Issue: #4904
PiperOrigin-RevId: 336838559
This commit is contained in:
christosts 2020-10-13 10:39:51 +01:00 committed by kim-vde
parent d47258381f
commit ed873322d3
9 changed files with 211 additions and 5 deletions

View File

@ -60,9 +60,10 @@ public final class C {
*/ */
public static final int POSITION_UNSET = -1; public static final int POSITION_UNSET = -1;
/** /** Represents an unset or unknown rate. */
* Represents an unset or unknown length. public static final float RATE_UNSET = -Float.MAX_VALUE;
*/
/** Represents an unset or unknown length. */
public static final int LENGTH_UNSET = -1; public static final int LENGTH_UNSET = -1;
/** Represents an unset or unknown percentage. */ /** Represents an unset or unknown percentage. */

View File

@ -82,6 +82,9 @@ public class DashManifest implements FilterableManifest<DashManifest> {
*/ */
@Nullable public final UtcTimingElement utcTiming; @Nullable public final UtcTimingElement utcTiming;
/** The {@link ServiceDescriptionElement}, or null if not present. */
@Nullable public final ServiceDescriptionElement serviceDescription;
/** The location of this manifest, or null if not present. */ /** The location of this manifest, or null if not present. */
@Nullable public final Uri location; @Nullable public final Uri location;
@ -92,7 +95,7 @@ public class DashManifest implements FilterableManifest<DashManifest> {
/** /**
* @deprecated Use {@link #DashManifest(long, long, long, boolean, long, long, long, long, * @deprecated Use {@link #DashManifest(long, long, long, boolean, long, long, long, long,
* ProgramInformation, UtcTimingElement, Uri, List)}. * ProgramInformation, UtcTimingElement, ServiceDescriptionElement, Uri, List)}.
*/ */
@Deprecated @Deprecated
public DashManifest( public DashManifest(
@ -118,6 +121,7 @@ public class DashManifest implements FilterableManifest<DashManifest> {
publishTimeMs, publishTimeMs,
/* programInformation= */ null, /* programInformation= */ null,
utcTiming, utcTiming,
/* serviceDescription= */ null,
location, location,
periods); periods);
} }
@ -133,6 +137,7 @@ public class DashManifest implements FilterableManifest<DashManifest> {
long publishTimeMs, long publishTimeMs,
@Nullable ProgramInformation programInformation, @Nullable ProgramInformation programInformation,
@Nullable UtcTimingElement utcTiming, @Nullable UtcTimingElement utcTiming,
@Nullable ServiceDescriptionElement serviceDescription,
@Nullable Uri location, @Nullable Uri location,
List<Period> periods) { List<Period> periods) {
this.availabilityStartTimeMs = availabilityStartTimeMs; this.availabilityStartTimeMs = availabilityStartTimeMs;
@ -146,6 +151,7 @@ public class DashManifest implements FilterableManifest<DashManifest> {
this.programInformation = programInformation; this.programInformation = programInformation;
this.utcTiming = utcTiming; this.utcTiming = utcTiming;
this.location = location; this.location = location;
this.serviceDescription = serviceDescription;
this.periods = periods == null ? Collections.emptyList() : periods; this.periods = periods == null ? Collections.emptyList() : periods;
} }
@ -203,6 +209,7 @@ public class DashManifest implements FilterableManifest<DashManifest> {
publishTimeMs, publishTimeMs,
programInformation, programInformation,
utcTiming, utcTiming,
serviceDescription,
location, location,
copyPeriods); copyPeriods);
} }

View File

@ -125,6 +125,7 @@ public class DashManifestParser extends DefaultHandler
ProgramInformation programInformation = null; ProgramInformation programInformation = null;
UtcTimingElement utcTiming = null; UtcTimingElement utcTiming = null;
Uri location = null; Uri location = null;
ServiceDescriptionElement serviceDescription = null;
long baseUrlAvailabilityTimeOffsetUs = dynamic ? 0 : C.TIME_UNSET; long baseUrlAvailabilityTimeOffsetUs = dynamic ? 0 : C.TIME_UNSET;
List<Period> periods = new ArrayList<>(); List<Period> periods = new ArrayList<>();
@ -146,6 +147,8 @@ public class DashManifestParser extends DefaultHandler
utcTiming = parseUtcTiming(xpp); utcTiming = parseUtcTiming(xpp);
} else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) {
location = Uri.parse(xpp.nextText()); location = Uri.parse(xpp.nextText());
} else if (XmlPullParserUtil.isStartTag(xpp, "ServiceDescription")) {
serviceDescription = parseServiceDescription(xpp);
} else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
Pair<Period, Long> periodWithDurationMs = Pair<Period, Long> periodWithDurationMs =
parsePeriod( parsePeriod(
@ -199,6 +202,7 @@ public class DashManifestParser extends DefaultHandler
publishTimeMs, publishTimeMs,
programInformation, programInformation,
utcTiming, utcTiming,
serviceDescription,
location, location,
periods); periods);
} }
@ -214,6 +218,7 @@ public class DashManifestParser extends DefaultHandler
long publishTimeMs, long publishTimeMs,
@Nullable ProgramInformation programInformation, @Nullable ProgramInformation programInformation,
@Nullable UtcTimingElement utcTiming, @Nullable UtcTimingElement utcTiming,
@Nullable ServiceDescriptionElement serviceDescription,
@Nullable Uri location, @Nullable Uri location,
List<Period> periods) { List<Period> periods) {
return new DashManifest( return new DashManifest(
@ -227,6 +232,7 @@ public class DashManifestParser extends DefaultHandler
publishTimeMs, publishTimeMs,
programInformation, programInformation,
utcTiming, utcTiming,
serviceDescription,
location, location,
periods); periods);
} }
@ -241,6 +247,23 @@ public class DashManifestParser extends DefaultHandler
return new UtcTimingElement(schemeIdUri, value); return new UtcTimingElement(schemeIdUri, value);
} }
protected ServiceDescriptionElement parseServiceDescription(XmlPullParser xpp)
throws XmlPullParserException, IOException {
long targetOffsetMs = C.TIME_UNSET;
float minPlaybackSpeed = C.RATE_UNSET;
float maxPlaybackSpeed = C.RATE_UNSET;
do {
xpp.next();
if (XmlPullParserUtil.isStartTag(xpp, "Latency")) {
targetOffsetMs = parseLong(xpp, "target", C.TIME_UNSET);
} else if (XmlPullParserUtil.isStartTag(xpp, "PlaybackRate")) {
minPlaybackSpeed = parseFloat(xpp, "min", C.RATE_UNSET);
maxPlaybackSpeed = parseFloat(xpp, "max", C.RATE_UNSET);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "ServiceDescription"));
return new ServiceDescriptionElement(targetOffsetMs, minPlaybackSpeed, maxPlaybackSpeed);
}
protected Pair<Period, Long> parsePeriod( protected Pair<Period, Long> parsePeriod(
XmlPullParser xpp, XmlPullParser xpp,
String baseUrl, String baseUrl,
@ -1715,6 +1738,11 @@ public class DashManifestParser extends DefaultHandler
return value == null ? defaultValue : Long.parseLong(value); return value == null ? defaultValue : Long.parseLong(value);
} }
protected static float parseFloat(XmlPullParser xpp, String name, float defaultValue) {
String value = xpp.getAttributeValue(null, name);
return value == null ? defaultValue : Float.parseFloat(value);
}
protected static String parseString(XmlPullParser xpp, String name, String defaultValue) { protected static String parseString(XmlPullParser xpp, String name, String defaultValue) {
String value = xpp.getAttributeValue(null, name); String value = xpp.getAttributeValue(null, name);
return value == null ? defaultValue : value; return value == null ? defaultValue : value;

View File

@ -0,0 +1,46 @@
/*
* Copyright 2020 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 com.google.android.exoplayer2.C;
/** Represents a service description element. */
public final class ServiceDescriptionElement {
/** The target live offset in milliseconds, or {@link C#TIME_UNSET} if undefined. */
public final long targetOffsetMs;
/** The minimum playback speed for live speed adjustment, or {@link C#RATE_UNSET} if undefined. */
public final float minPlaybackSpeed;
/** The maximum playback speed for live speed adjustment, or {@link C#RATE_UNSET} if undefined. */
public final float maxPlaybackSpeed;
/**
* Creates a service description element.
*
* @param targetOffsetMs The target live offset in milliseconds, or {@link C#TIME_UNSET} if
* undefined.
* @param minPlaybackSpeed The minimum playback speed for live speed adjustment, or {@link
* C#RATE_UNSET} if undefined.
* @param maxPlaybackSpeed The maximum playback speed for live speed adjustment, or {@link
* C#RATE_UNSET} if undefined.
*/
public ServiceDescriptionElement(
long targetOffsetMs, float minPlaybackSpeed, float maxPlaybackSpeed) {
this.targetOffsetMs = targetOffsetMs;
this.minPlaybackSpeed = minPlaybackSpeed;
this.maxPlaybackSpeed = maxPlaybackSpeed;
}
}

View File

@ -57,6 +57,12 @@ public class DashManifestParserTest {
"media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate"; "media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate";
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST = private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST =
"media/mpd/sample_mpd_availabilityTimeOffset_segmentList"; "media/mpd/sample_mpd_availabilityTimeOffset_segmentList";
private static final String SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY =
"media/mpd/sample_mpd_service_description_low_latency";
private static final String SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_NO_TARGET_LATENCY =
"media/mpd/sample_mpd_service_description_low_latency_no_target_latency";
private static final String SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_NO_PLAYBACK_RATES =
"media/mpd/sample_mpd_service_description_low_latency_no_playback_rates";
private static final String NEXT_TAG_NAME = "Next"; private static final String NEXT_TAG_NAME = "Next";
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>"; private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
@ -560,6 +566,71 @@ public class DashManifestParserTest {
assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(9_999_000); assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(9_999_000);
} }
@Test
public void serviceDescriptionElement_allValuesSet() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY));
assertThat(manifest.serviceDescription).isNotNull();
assertThat(manifest.serviceDescription.targetOffsetMs).isEqualTo(20_000);
assertThat(manifest.serviceDescription.minPlaybackSpeed).isEqualTo(0.1f);
assertThat(manifest.serviceDescription.maxPlaybackSpeed).isEqualTo(99f);
}
@Test
public void serviceDescriptionElement_noLatency_isUnset() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_NO_TARGET_LATENCY));
assertThat(manifest.serviceDescription).isNotNull();
assertThat(manifest.serviceDescription.targetOffsetMs).isEqualTo(C.TIME_UNSET);
assertThat(manifest.serviceDescription.minPlaybackSpeed).isEqualTo(0.1f);
assertThat(manifest.serviceDescription.maxPlaybackSpeed).isEqualTo(99f);
}
@Test
public void serviceDescriptionElement_noPlaybackRates_isUnset() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_SERVICE_DESCRIPTION_LOW_LATENCY_NO_PLAYBACK_RATES));
assertThat(manifest.serviceDescription).isNotNull();
assertThat(manifest.serviceDescription.targetOffsetMs).isEqualTo(20_000);
assertThat(manifest.serviceDescription.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
assertThat(manifest.serviceDescription.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
}
@Test
public void serviceDescriptionElement_noServiceDescription_isNullInManifest() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST));
assertThat(manifest.serviceDescription).isNull();
}
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) { private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null)); return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.dash.manifest;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
@ -40,9 +41,13 @@ public class DashManifestTest {
@Test @Test
public void copy() { public void copy() {
Representation[][][] representations = newRepresentations(3, 2, 3); Representation[][][] representations = newRepresentations(3, 2, 3);
ServiceDescriptionElement serviceDescriptionElement =
new ServiceDescriptionElement(
/* targetOffsetMs= */ 20, /* minPlaybackSpeed= */ 0.9f, /* maxPlaybackSpeed= */ 1.1f);
DashManifest sourceManifest = DashManifest sourceManifest =
newDashManifest( newDashManifest(
10, 10,
serviceDescriptionElement,
newPeriod( newPeriod(
"1", "1",
1, 1,
@ -78,6 +83,7 @@ public class DashManifestTest {
DashManifest expectedManifest = DashManifest expectedManifest =
newDashManifest( newDashManifest(
10, 10,
serviceDescriptionElement,
newPeriod( newPeriod(
"1", "1",
1, 1,
@ -102,6 +108,7 @@ public class DashManifestTest {
DashManifest sourceManifest = DashManifest sourceManifest =
newDashManifest( newDashManifest(
10, 10,
/* serviceDescription= */ null,
newPeriod("1", 1, newAdaptationSet(2, representations[0][0])), newPeriod("1", 1, newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4, newAdaptationSet(5, representations[1][0]))); newPeriod("4", 4, newAdaptationSet(5, representations[1][0])));
@ -111,6 +118,7 @@ public class DashManifestTest {
DashManifest expectedManifest = DashManifest expectedManifest =
newDashManifest( newDashManifest(
10, 10,
/* serviceDescription= */ null,
newPeriod("1", 1, newAdaptationSet(2, representations[0][0])), newPeriod("1", 1, newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4, newAdaptationSet(5, representations[1][0]))); newPeriod("4", 4, newAdaptationSet(5, representations[1][0])));
assertManifestEquals(expectedManifest, copyManifest); assertManifestEquals(expectedManifest, copyManifest);
@ -122,6 +130,7 @@ public class DashManifestTest {
DashManifest sourceManifest = DashManifest sourceManifest =
newDashManifest( newDashManifest(
10, 10,
/* serviceDescription= */ null,
newPeriod( newPeriod(
"1", "1",
1, 1,
@ -151,6 +160,7 @@ public class DashManifestTest {
DashManifest expectedManifest = DashManifest expectedManifest =
newDashManifest( newDashManifest(
7, 7,
/* serviceDescription= */ null,
newPeriod( newPeriod(
"1", "1",
1, 1,
@ -177,6 +187,7 @@ public class DashManifestTest {
assertThat(actual.utcTiming).isEqualTo(expected.utcTiming); assertThat(actual.utcTiming).isEqualTo(expected.utcTiming);
assertThat(actual.location).isEqualTo(expected.location); assertThat(actual.location).isEqualTo(expected.location);
assertThat(actual.getPeriodCount()).isEqualTo(expected.getPeriodCount()); assertThat(actual.getPeriodCount()).isEqualTo(expected.getPeriodCount());
assertThat(actual.serviceDescription).isEqualTo(expected.serviceDescription);
for (int i = 0; i < expected.getPeriodCount(); i++) { for (int i = 0; i < expected.getPeriodCount(); i++) {
Period expectedPeriod = expected.getPeriod(i); Period expectedPeriod = expected.getPeriod(i);
Period actualPeriod = actual.getPeriod(i); Period actualPeriod = actual.getPeriod(i);
@ -217,7 +228,8 @@ public class DashManifestTest {
return Representation.newInstance(/* revisionId= */ 0, FORMAT, /* baseUrl= */ "", SEGMENT_BASE); return Representation.newInstance(/* revisionId= */ 0, FORMAT, /* baseUrl= */ "", SEGMENT_BASE);
} }
private static DashManifest newDashManifest(int duration, Period... periods) { private static DashManifest newDashManifest(
int duration, @Nullable ServiceDescriptionElement serviceDescription, Period... periods) {
return new DashManifest( return new DashManifest(
/* availabilityStartTimeMs= */ 0, /* availabilityStartTimeMs= */ 0,
duration, duration,
@ -229,6 +241,7 @@ public class DashManifestTest {
/* publishTimeMs= */ 12345, /* publishTimeMs= */ 12345,
/* programInformation= */ null, /* programInformation= */ null,
UTC_TIMING, UTC_TIMING,
serviceDescription,
Uri.EMPTY, Uri.EMPTY,
Arrays.asList(periods)); Arrays.asList(periods));
} }

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD type="static" duration="1s" mediaPresentationDuration="PT1S">
<ServiceDescription>
<Latency target="20000"/>
<PlaybackRate min="0.1" max="99"/>
</ServiceDescription>
<Period>
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
<Representation id="0" codecs="mp4a.40.2" audioSamplingRate="48000" bandwidth="144000">
<BaseURL>https://test.com/0</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD type="static" duration="1s" mediaPresentationDuration="PT1S">
<ServiceDescription>
<Latency target="20000"/>
</ServiceDescription>
<Period>
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
<Representation id="0" codecs="mp4a.40.2" audioSamplingRate="48000" bandwidth="144000">
<BaseURL>https://test.com/0</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD type="static" duration="1s" mediaPresentationDuration="PT1S">
<ServiceDescription>
<PlaybackRate min="0.1" max="99"/>
</ServiceDescription>
<Period>
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
<Representation id="0" codecs="mp4a.40.2" audioSamplingRate="48000" bandwidth="144000">
<BaseURL>https://test.com/0</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>