Check language/role flags before merging adaptation sets

The spec technically allows to mark adaptation sets with the switching
property if they allow seamless switching with non-overlapping
segments. This is typically only used for compatible content (e.g.
different codecs), but the spec allows it to be used for other
content as well (e.g. different languages, roles). ExoPlayer's concept
of a TrackGroup only allows formats with the same language and role
flags to be merged, so we should check that before merging.

Issue: androidx/media#2222

#cherrypick

PiperOrigin-RevId: 736564055
This commit is contained in:
tonihei 2025-03-13 10:50:13 -07:00 committed by Copybara-Service
parent 412ba2e201
commit d37f05238a
4 changed files with 88 additions and 1 deletions

View File

@ -51,6 +51,9 @@
* RTMP extension: * RTMP extension:
* HLS extension: * HLS extension:
* DASH extension: * DASH extension:
* Fix issue where adaptation sets marked with `adaptation-set-switching`
but different languages or role flags are merged together
([#2222](https://github.com/androidx/media/issues/2222)).
* Smooth Streaming extension: * Smooth Streaming extension:
* RTSP extension: * RTSP extension:
* Decoder extensions (FFmpeg, VP9, AV1, etc.): * Decoder extensions (FFmpeg, VP9, AV1, etc.):

View File

@ -72,6 +72,7 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -624,7 +625,9 @@ import java.util.regex.Pattern;
@Nullable @Nullable
Integer otherAdaptationSetIndex = Integer otherAdaptationSetIndex =
adaptationSetIdToIndex.get(Long.parseLong(adaptationSetId)); adaptationSetIdToIndex.get(Long.parseLong(adaptationSetId));
if (otherAdaptationSetIndex != null) { if (otherAdaptationSetIndex != null
&& canMergeAdaptationSets(
adaptationSet, adaptationSets.get(otherAdaptationSetIndex))) {
mergedGroupIndex = min(mergedGroupIndex, otherAdaptationSetIndex); mergedGroupIndex = min(mergedGroupIndex, otherAdaptationSetIndex);
} }
} }
@ -650,6 +653,20 @@ import java.util.regex.Pattern;
return groupedAdaptationSetIndices; return groupedAdaptationSetIndices;
} }
private static boolean canMergeAdaptationSets(
AdaptationSet adaptationSet1, AdaptationSet adaptationSet2) {
if (adaptationSet1.type != adaptationSet2.type) {
return false;
}
if (adaptationSet1.representations.isEmpty() || adaptationSet2.representations.isEmpty()) {
return true;
}
Format format1 = adaptationSet1.representations.get(0).format;
Format format2 = adaptationSet2.representations.get(0).format;
return Objects.equals(format1.language, format2.language)
&& format1.roleFlags == format2.roleFlags;
}
/** /**
* Iterates through list of primary track groups and identifies embedded tracks. * Iterates through list of primary track groups and identifies embedded tracks.
* *

View File

@ -94,6 +94,31 @@ public final class DashMediaPeriodTest {
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups); MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
} }
@Test
public void
adaptationSetSwitchingProperty_withIncompatibleFormats_mergesOnlyCompatibleTrackGroups()
throws IOException {
DashManifest manifest = parseManifest("media/mpd/sample_mpd_switching_property_incompatible");
DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, /* periodIndex= */ 0);
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
// Only the two matching pairs of adaptation sets should be merged.
TrackGroupArray expectedTrackGroups =
new TrackGroupArray(
new TrackGroup(/* id= */ "0", adaptationSets.get(0).representations.get(0).format),
new TrackGroup(
/* id= */ "1",
adaptationSets.get(1).representations.get(0).format,
adaptationSets.get(3).representations.get(0).format),
new TrackGroup(
/* id= */ "2",
adaptationSets.get(2).representations.get(0).format,
adaptationSets.get(4).representations.get(0).format),
new TrackGroup(/* id= */ "5", adaptationSets.get(5).representations.get(0).format));
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
@Test @Test
public void trickPlayProperty_mergesTrackGroups() throws IOException { public void trickPlayProperty_mergesTrackGroups() throws IOException {
DashManifest manifest = parseManifest("media/mpd/sample_mpd_trick_play_property"); DashManifest manifest = parseManifest("media/mpd/sample_mpd_trick_play_property");

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD availabilityStartTime="2014-06-19T23:07:42" type="dynamic">
<Period start="PT7462826.784S" id="0">
<AdaptationSet id="0" lang="fr" contentType="video">
<Role schemeIdUri="urn:mpeg:DASH:role:2011" value="commentary"/>
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="1,2,3,4,5"/>
<Representation id="0"/>
</AdaptationSet>
<AdaptationSet id="1" lang="fr" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,2,3,4,5"/>
<Representation id="1"/>
</AdaptationSet>
<AdaptationSet id="2" lang="en" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,3,4,5"/>
<Representation id="2"/>
</AdaptationSet>
<AdaptationSet id="3" lang="fr" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,2,4,5"/>
<Representation id="3"/>
</AdaptationSet>
<AdaptationSet id="4" lang="en" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,2,3,5"/>
<Representation id="4"/>
</AdaptationSet>
<AdaptationSet id="5" lang="en" contentType="audio">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,2,3,4"/>
<Representation id="4"/>
</AdaptationSet>
</Period>
</MPD>