Merge pull request #6678 from phhusson:feature/enable-multi-metadata-tracks

PiperOrigin-RevId: 290032841
This commit is contained in:
Oliver Woodman 2020-01-16 13:52:08 +00:00
commit 17d1343375
3 changed files with 137 additions and 59 deletions

View File

@ -71,6 +71,8 @@
media segment has been established.
* Reduce startup latency for on-demand DASH playbacks by allowing codec
initialization to occur before the sidx box has been loaded.
* Select multiple metadata tracks if multiple metadata renderers are available
([#6676](https://github.com/google/ExoPlayer/issues/6676)).
### 2.11.1 (2019-12-20) ###

View File

@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -359,7 +360,11 @@ public abstract class MappingTrackSelector extends TrackSelector {
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex);
// Associate the group to a preferred renderer.
int rendererIndex = findRenderer(rendererCapabilities, group);
boolean preferUnassociatedRenderer =
MimeTypes.getTrackType(group.getFormat(0).sampleMimeType) == C.TRACK_TYPE_METADATA;
int rendererIndex =
findRenderer(
rendererCapabilities, group, rendererTrackGroupCounts, preferUnassociatedRenderer);
// Evaluate the support that the renderer provides for each track in the group.
@Capabilities
int[] rendererFormatSupport =
@ -431,43 +436,65 @@ public abstract class MappingTrackSelector extends TrackSelector {
/**
* Finds the renderer to which the provided {@link TrackGroup} should be mapped.
* <p>
* A {@link TrackGroup} is mapped to the renderer that reports the highest of (listed in
* decreasing order of support) {@link RendererCapabilities#FORMAT_HANDLED},
* {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES},
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM} and
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE}. In the case that two or more renderers
* report the same level of support, the renderer with the lowest index is associated.
* <p>
* If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the
*
* <p>A {@link TrackGroup} is mapped to the renderer that reports the highest of (listed in
* decreasing order of support) {@link RendererCapabilities#FORMAT_HANDLED}, {@link
* RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, {@link
* RendererCapabilities#FORMAT_UNSUPPORTED_DRM} and {@link
* RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE}.
*
* <p>In the case that two or more renderers report the same level of support, the assignment
* depends on {@code preferUnassociatedRenderer}.
*
* <ul>
* <li>If {@code preferUnassociatedRenderer} is false, the renderer with the lowest index is
* chosen regardless of how many other track groups are already mapped to this renderer.
* <li>If {@code preferUnassociatedRenderer} is true, the renderer with the lowest index and no
* other mapped track group is chosen, or the renderer with the lowest index if all
* available renderers have already mapped track groups.
* </ul>
*
* <p>If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the
* tracks in the group, then {@code renderers.length} is returned to indicate that the group was
* not mapped to any renderer.
*
* @param rendererCapabilities The {@link RendererCapabilities} of the renderers.
* @param group The track group to map to a renderer.
* @return The index of the renderer to which the track group was mapped, or
* {@code renderers.length} if it was not mapped to any renderer.
* @param rendererTrackGroupCounts The number of already mapped track groups for each renderer.
* @param preferUnassociatedRenderer Whether renderers unassociated to any track group should be
* preferred.
* @return The index of the renderer to which the track group was mapped, or {@code
* renderers.length} if it was not mapped to any renderer.
* @throws ExoPlaybackException If an error occurs finding a renderer.
*/
private static int findRenderer(RendererCapabilities[] rendererCapabilities, TrackGroup group)
private static int findRenderer(
RendererCapabilities[] rendererCapabilities,
TrackGroup group,
int[] rendererTrackGroupCounts,
boolean preferUnassociatedRenderer)
throws ExoPlaybackException {
int bestRendererIndex = rendererCapabilities.length;
@FormatSupport int bestFormatSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
boolean bestRendererIsUnassociated = true;
for (int rendererIndex = 0; rendererIndex < rendererCapabilities.length; rendererIndex++) {
RendererCapabilities rendererCapability = rendererCapabilities[rendererIndex];
@FormatSupport int formatSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
@FormatSupport
int formatSupportLevel =
int trackFormatSupportLevel =
RendererCapabilities.getFormatSupport(
rendererCapability.supportsFormat(group.getFormat(trackIndex)));
if (formatSupportLevel > bestFormatSupportLevel) {
bestRendererIndex = rendererIndex;
bestFormatSupportLevel = formatSupportLevel;
if (bestFormatSupportLevel == RendererCapabilities.FORMAT_HANDLED) {
// We can't do better.
return bestRendererIndex;
}
}
formatSupportLevel = Math.max(formatSupportLevel, trackFormatSupportLevel);
}
boolean rendererIsUnassociated = rendererTrackGroupCounts[rendererIndex] == 0;
if (formatSupportLevel > bestFormatSupportLevel
|| (formatSupportLevel == bestFormatSupportLevel
&& preferUnassociatedRenderer
&& !bestRendererIsUnassociated
&& rendererIsUnassociated)) {
bestRendererIndex = rendererIndex;
bestFormatSupportLevel = formatSupportLevel;
bestRendererIsUnassociated = rendererIsUnassociated;
}
}
return bestRendererIndex;

View File

@ -44,17 +44,41 @@ public final class MappingTrackSelectorTest {
new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO);
private static final RendererCapabilities AUDIO_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
private static final RendererCapabilities[] RENDERER_CAPABILITIES = new RendererCapabilities[] {
VIDEO_CAPABILITIES, AUDIO_CAPABILITIES
};
private static final TrackGroup VIDEO_TRACK_GROUP = new TrackGroup(
Format.createVideoSampleFormat("video", MimeTypes.VIDEO_H264, null, Format.NO_VALUE,
Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, null));
private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup(
Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
Format.NO_VALUE, 2, 44100, null, null, 0, null));
private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray(
VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
private static final RendererCapabilities METADATA_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_METADATA);
private static final TrackGroup VIDEO_TRACK_GROUP =
new TrackGroup(
Format.createVideoSampleFormat(
"video",
MimeTypes.VIDEO_H264,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* maxInputSize= */ Format.NO_VALUE,
/* width= */ 1024,
/* height= */ 768,
/* frameRate= */ Format.NO_VALUE,
/* initializationData= */ null,
/* drmInitData= */ null));
private static final TrackGroup AUDIO_TRACK_GROUP =
new TrackGroup(
Format.createAudioSampleFormat(
"audio",
MimeTypes.AUDIO_AAC,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* maxInputSize= */ Format.NO_VALUE,
/* channelCount= */ 2,
/* sampleRate= */ 44100,
/* initializationData= */ null,
/* drmInitData= */ null,
/* selectionFlags= */ 0,
/* language= */ null));
private static final TrackGroup METADATA_TRACK_GROUP =
new TrackGroup(
Format.createSampleFormat(
"metadata", MimeTypes.APPLICATION_ID3, /* subsampleOffsetUs= */ 0));
private static final Timeline TIMELINE = new FakeTimeline(/* windowCount= */ 1);
private static MediaPeriodId periodId;
@ -64,43 +88,68 @@ public final class MappingTrackSelectorTest {
periodId = new MediaPeriodId(TIMELINE.getUidOfPeriod(/* periodIndex= */ 0));
}
/**
* Tests that the video and audio track groups are mapped onto the correct renderers.
*/
@Test
public void testMapping() throws ExoPlaybackException {
public void selectTracks_audioAndVideo_sameOrderAsRenderers_mappedToCorectRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
RendererCapabilities[] rendererCapabilities =
new RendererCapabilities[] {VIDEO_CAPABILITIES, AUDIO_CAPABILITIES};
TrackGroupArray trackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
trackSelector.assertMappedTrackGroups(/* rendererIndex= */ 0, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(/* rendererIndex= */ 1, AUDIO_TRACK_GROUP);
}
/**
* Tests that the video and audio track groups are mapped onto the correct renderers when the
* renderer ordering is reversed.
*/
@Test
public void testMappingReverseOrder() throws ExoPlaybackException {
public void selectTracks_audioAndVideo_reverseOrderToRenderers_mappedToCorectRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] {
AUDIO_CAPABILITIES, VIDEO_CAPABILITIES};
trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS, periodId, TIMELINE);
trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP);
TrackGroupArray trackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
RendererCapabilities[] reverseOrderRendererCapabilities =
new RendererCapabilities[] {AUDIO_CAPABILITIES, VIDEO_CAPABILITIES};
trackSelector.selectTracks(reverseOrderRendererCapabilities, trackGroups, periodId, TIMELINE);
trackSelector.assertMappedTrackGroups(/* rendererIndex= */ 0, AUDIO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(/* rendererIndex= */ 1, VIDEO_TRACK_GROUP);
}
/**
* Tests video and audio track groups are mapped onto the correct renderers when there are
* multiple track groups of the same type.
*/
@Test
public void testMappingMulti() throws ExoPlaybackException {
public void selectTracks_multipleVideoAndAudioTracks_mappedToSameRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP,
VIDEO_TRACK_GROUP);
trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups, periodId, TIMELINE);
TrackGroupArray trackGroups =
new TrackGroupArray(
VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP);
RendererCapabilities[] rendererCapabilities =
new RendererCapabilities[] {
VIDEO_CAPABILITIES, AUDIO_CAPABILITIES, VIDEO_CAPABILITIES, AUDIO_CAPABILITIES
};
trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP, AUDIO_TRACK_GROUP);
}
@Test
public void selectTracks_multipleMetadataTracks_mappedToDifferentRenderers()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
TrackGroupArray trackGroups =
new TrackGroupArray(VIDEO_TRACK_GROUP, METADATA_TRACK_GROUP, METADATA_TRACK_GROUP);
RendererCapabilities[] rendererCapabilities =
new RendererCapabilities[] {
VIDEO_CAPABILITIES, METADATA_CAPABILITIES, METADATA_CAPABILITIES
};
trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, METADATA_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(2, METADATA_TRACK_GROUP);
}
/**