diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9660bd4714..20565d0078 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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) ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 9c6b2409c3..f6ba1f259e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -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. - *
- * 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. - *
- * If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the + * + *
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 assignment + * depends on {@code preferUnassociatedRenderer}. + * + *
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; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index f7bfc24881..ca6cae34b5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -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); } /**