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. media segment has been established.
* Reduce startup latency for on-demand DASH playbacks by allowing codec * Reduce startup latency for on-demand DASH playbacks by allowing codec
initialization to occur before the sidx box has been loaded. 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) ### ### 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.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -359,7 +360,11 @@ public abstract class MappingTrackSelector extends TrackSelector {
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex); TrackGroup group = trackGroups.get(groupIndex);
// Associate the group to a preferred renderer. // 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. // Evaluate the support that the renderer provides for each track in the group.
@Capabilities @Capabilities
int[] rendererFormatSupport = int[] rendererFormatSupport =
@ -431,43 +436,65 @@ public abstract class MappingTrackSelector extends TrackSelector {
/** /**
* Finds the renderer to which the provided {@link TrackGroup} should be mapped. * 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 * <p>A {@link TrackGroup} is mapped to the renderer that reports the highest of (listed in
* decreasing order of support) {@link RendererCapabilities#FORMAT_HANDLED}, * decreasing order of support) {@link RendererCapabilities#FORMAT_HANDLED}, {@link
* {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, * RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, {@link
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM} and * RendererCapabilities#FORMAT_UNSUPPORTED_DRM} and {@link
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE}. In the case that two or more renderers * RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE}.
* report the same level of support, the renderer with the lowest index is associated. *
* <p> * <p>In the case that two or more renderers report the same level of support, the assignment
* If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the * 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 * tracks in the group, then {@code renderers.length} is returned to indicate that the group was
* not mapped to any renderer. * not mapped to any renderer.
* *
* @param rendererCapabilities The {@link RendererCapabilities} of the renderers. * @param rendererCapabilities The {@link RendererCapabilities} of the renderers.
* @param group The track group to map to a renderer. * @param group The track group to map to a renderer.
* @return The index of the renderer to which the track group was mapped, or * @param rendererTrackGroupCounts The number of already mapped track groups for each renderer.
* {@code renderers.length} if it was not mapped to any 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. * @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 { throws ExoPlaybackException {
int bestRendererIndex = rendererCapabilities.length; int bestRendererIndex = rendererCapabilities.length;
@FormatSupport int bestFormatSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE; @FormatSupport int bestFormatSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
boolean bestRendererIsUnassociated = true;
for (int rendererIndex = 0; rendererIndex < rendererCapabilities.length; rendererIndex++) { for (int rendererIndex = 0; rendererIndex < rendererCapabilities.length; rendererIndex++) {
RendererCapabilities rendererCapability = rendererCapabilities[rendererIndex]; RendererCapabilities rendererCapability = rendererCapabilities[rendererIndex];
@FormatSupport int formatSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
@FormatSupport @FormatSupport
int formatSupportLevel = int trackFormatSupportLevel =
RendererCapabilities.getFormatSupport( RendererCapabilities.getFormatSupport(
rendererCapability.supportsFormat(group.getFormat(trackIndex))); rendererCapability.supportsFormat(group.getFormat(trackIndex)));
if (formatSupportLevel > bestFormatSupportLevel) { formatSupportLevel = Math.max(formatSupportLevel, trackFormatSupportLevel);
}
boolean rendererIsUnassociated = rendererTrackGroupCounts[rendererIndex] == 0;
if (formatSupportLevel > bestFormatSupportLevel
|| (formatSupportLevel == bestFormatSupportLevel
&& preferUnassociatedRenderer
&& !bestRendererIsUnassociated
&& rendererIsUnassociated)) {
bestRendererIndex = rendererIndex; bestRendererIndex = rendererIndex;
bestFormatSupportLevel = formatSupportLevel; bestFormatSupportLevel = formatSupportLevel;
if (bestFormatSupportLevel == RendererCapabilities.FORMAT_HANDLED) { bestRendererIsUnassociated = rendererIsUnassociated;
// We can't do better.
return bestRendererIndex;
}
}
} }
} }
return bestRendererIndex; return bestRendererIndex;

View File

@ -44,17 +44,41 @@ public final class MappingTrackSelectorTest {
new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO); new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO);
private static final RendererCapabilities AUDIO_CAPABILITIES = private static final RendererCapabilities AUDIO_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO); new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
private static final RendererCapabilities[] RENDERER_CAPABILITIES = new RendererCapabilities[] { private static final RendererCapabilities METADATA_CAPABILITIES =
VIDEO_CAPABILITIES, AUDIO_CAPABILITIES new FakeRendererCapabilities(C.TRACK_TYPE_METADATA);
};
private static final TrackGroup VIDEO_TRACK_GROUP = new TrackGroup( private static final TrackGroup VIDEO_TRACK_GROUP =
Format.createVideoSampleFormat("video", MimeTypes.VIDEO_H264, null, Format.NO_VALUE, new TrackGroup(
Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, null)); Format.createVideoSampleFormat(
private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup( "video",
Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, MimeTypes.VIDEO_H264,
Format.NO_VALUE, 2, 44100, null, null, 0, null)); /* codecs= */ null,
private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray( /* bitrate= */ Format.NO_VALUE,
VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP); /* 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 final Timeline TIMELINE = new FakeTimeline(/* windowCount= */ 1);
private static MediaPeriodId periodId; private static MediaPeriodId periodId;
@ -64,43 +88,68 @@ public final class MappingTrackSelectorTest {
periodId = new MediaPeriodId(TIMELINE.getUidOfPeriod(/* periodIndex= */ 0)); periodId = new MediaPeriodId(TIMELINE.getUidOfPeriod(/* periodIndex= */ 0));
} }
/**
* Tests that the video and audio track groups are mapped onto the correct renderers.
*/
@Test @Test
public void testMapping() throws ExoPlaybackException { public void selectTracks_audioAndVideo_sameOrderAsRenderers_mappedToCorectRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); RendererCapabilities[] rendererCapabilities =
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP); new RendererCapabilities[] {VIDEO_CAPABILITIES, AUDIO_CAPABILITIES};
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP); 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 @Test
public void testMappingReverseOrder() throws ExoPlaybackException { public void selectTracks_audioAndVideo_reverseOrderToRenderers_mappedToCorectRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] { TrackGroupArray trackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
AUDIO_CAPABILITIES, VIDEO_CAPABILITIES}; RendererCapabilities[] reverseOrderRendererCapabilities =
trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS, periodId, TIMELINE); new RendererCapabilities[] {AUDIO_CAPABILITIES, VIDEO_CAPABILITIES};
trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP); 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 @Test
public void testMappingMulti() throws ExoPlaybackException { public void selectTracks_multipleVideoAndAudioTracks_mappedToSameRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, TrackGroupArray trackGroups =
VIDEO_TRACK_GROUP); new TrackGroupArray(
trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups, periodId, TIMELINE); 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(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);
} }
/** /**